From 60b21cc87cdd36b1ebbc992e49a22359dd4e85ea Mon Sep 17 00:00:00 2001 From: Anatoly Scherbakov Date: Sun, 26 Jan 2020 20:58:55 +0700 Subject: [PATCH 1/2] #1 Obsolete code removed, __class_getitem__ implemented, all unit tests pass --- .idea/misc.xml | 2 +- .idea/platonic.iml | 5 +- README.md | 1 - platonic-amazon-s3/Makefile | 2 - platonic-amazon-s3/README.md | 1 - .../platonic_amazon_s3/__init__.py | 1 - .../platonic_amazon_s3/iterators.py | 49 ------------- platonic-amazon-s3/setup.py | 32 --------- platonic-amazon-s3/tests/test_init.py | 20 ------ platonic-redis/Makefile | 2 - platonic-redis/README.md | 1 - platonic-redis/platonic_redis/__init__.py | 3 - platonic-redis/platonic_redis/base.py | 17 ----- platonic-redis/platonic_redis/redis_box.py | 29 -------- .../platonic_redis/redis_mapping.py | 22 ------ .../platonic_redis/redis_mutable_mapping.py | 11 --- platonic-redis/setup.py | 31 --------- platonic-redis/tests/test_box.py | 22 ------ platonic-redis/tests/test_initialize.py | 16 ----- platonic/Makefile | 2 - platonic/README.md | 1 - platonic/__init__.py | 8 ++- platonic/{platonic => }/iterable/__init__.py | 0 platonic/{platonic => }/iterable/iterable.py | 0 platonic/{platonic => }/mapping/__init__.py | 0 platonic/mapping/mapping.py | 52 ++++++++++++++ platonic/model.py | 10 +++ .../mutable_mapping/__init__.py | 0 .../mutable_mapping/dict_mapping.py | 0 .../mutable_mapping/mutable_mapping.py | 0 platonic/platonic/__init__.py | 10 --- platonic/platonic/box/__init__.py | 3 - platonic/platonic/box/abstract.py | 24 ------- platonic/platonic/box/implementation.py | 17 ----- platonic/platonic/box/model.py | 16 ----- platonic/platonic/mapping/mapping.py | 35 ---------- platonic/platonic/model.py | 69 ------------------- platonic/platonic/register.py | 14 ---- platonic/setup.py | 33 --------- platonic/tests/__init__.py | 0 platonic/tests/test_abstract.py | 20 ------ platonic/tests/test_box/__init__.py | 0 .../tests/test_box/test_implementation.py | 21 ------ platonic/tests/test_dict_mapping.py | 25 ------- platonic/tests/test_model/__init__.py | 0 platonic/tests/test_model/test_type_args.py | 43 ------------ platonic/tests/test_new.py | 42 ----------- platonic/tests/test_register.py | 46 ------------- .../tests => tests}/__init__.py | 0 .../tests => tests/test_model}/__init__.py | 0 tests/test_model/test_type_args.py | 33 +++++++++ 51 files changed, 105 insertions(+), 686 deletions(-) delete mode 100644 platonic-amazon-s3/Makefile delete mode 100644 platonic-amazon-s3/README.md delete mode 100644 platonic-amazon-s3/platonic_amazon_s3/__init__.py delete mode 100644 platonic-amazon-s3/platonic_amazon_s3/iterators.py delete mode 100644 platonic-amazon-s3/setup.py delete mode 100644 platonic-amazon-s3/tests/test_init.py delete mode 100644 platonic-redis/Makefile delete mode 100644 platonic-redis/README.md delete mode 100644 platonic-redis/platonic_redis/__init__.py delete mode 100644 platonic-redis/platonic_redis/base.py delete mode 100644 platonic-redis/platonic_redis/redis_box.py delete mode 100644 platonic-redis/platonic_redis/redis_mapping.py delete mode 100644 platonic-redis/platonic_redis/redis_mutable_mapping.py delete mode 100644 platonic-redis/setup.py delete mode 100644 platonic-redis/tests/test_box.py delete mode 100644 platonic-redis/tests/test_initialize.py delete mode 100644 platonic/Makefile delete mode 100644 platonic/README.md rename platonic/{platonic => }/iterable/__init__.py (100%) rename platonic/{platonic => }/iterable/iterable.py (100%) rename platonic/{platonic => }/mapping/__init__.py (100%) create mode 100644 platonic/mapping/mapping.py create mode 100644 platonic/model.py rename platonic/{platonic => }/mutable_mapping/__init__.py (100%) rename platonic/{platonic => }/mutable_mapping/dict_mapping.py (100%) rename platonic/{platonic => }/mutable_mapping/mutable_mapping.py (100%) delete mode 100644 platonic/platonic/__init__.py delete mode 100644 platonic/platonic/box/__init__.py delete mode 100644 platonic/platonic/box/abstract.py delete mode 100644 platonic/platonic/box/implementation.py delete mode 100644 platonic/platonic/box/model.py delete mode 100644 platonic/platonic/mapping/mapping.py delete mode 100644 platonic/platonic/model.py delete mode 100644 platonic/platonic/register.py delete mode 100644 platonic/setup.py delete mode 100644 platonic/tests/__init__.py delete mode 100644 platonic/tests/test_abstract.py delete mode 100644 platonic/tests/test_box/__init__.py delete mode 100644 platonic/tests/test_box/test_implementation.py delete mode 100644 platonic/tests/test_dict_mapping.py delete mode 100644 platonic/tests/test_model/__init__.py delete mode 100644 platonic/tests/test_model/test_type_args.py delete mode 100644 platonic/tests/test_new.py delete mode 100644 platonic/tests/test_register.py rename {platonic-amazon-s3/tests => tests}/__init__.py (100%) rename {platonic-redis/tests => tests/test_model}/__init__.py (100%) create mode 100644 tests/test_model/test_type_args.py diff --git a/.idea/misc.xml b/.idea/misc.xml index fcb32da..f9beed9 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -3,5 +3,5 @@ - + \ No newline at end of file diff --git a/.idea/platonic.iml b/.idea/platonic.iml index 1b7aea9..dd04a93 100644 --- a/.idea/platonic.iml +++ b/.idea/platonic.iml @@ -2,19 +2,18 @@ - + - + - \ No newline at end of file diff --git a/README.md b/README.md index 86e485e..efacde2 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,5 @@ # platonic -[![wemake.services](https://img.shields.io/badge/%20-wemake.services-green.svg?label=%20&logo=data%3Aimage%2Fpng%3Bbase64%2CiVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABGdBTUEAALGPC%2FxhBQAAAAFzUkdCAK7OHOkAAAAbUExURQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP%2F%2F%2F5TvxDIAAAAIdFJOUwAjRA8xXANAL%2Bv0SAAAADNJREFUGNNjYCAIOJjRBdBFWMkVQeGzcHAwksJnAPPZGOGAASzPzAEHEGVsLExQwE7YswCb7AFZSF3bbAAAAABJRU5ErkJggg%3D%3D)](https://wemake.services) [![Build Status](https://travis-ci.com/python-platonic/platonic.svg?branch=master)](https://travis-ci.com/python-platonic/platonic) [![Coverage](https://coveralls.io/repos/github/python-platonic/platonic/badge.svg?branch=master)](https://coveralls.io/github/python-platonic/platonic?branch=master) [![Python Version](https://img.shields.io/pypi/pyversions/platonic.svg)](https://pypi.org/project/platonic/) diff --git a/platonic-amazon-s3/Makefile b/platonic-amazon-s3/Makefile deleted file mode 100644 index 7db08e4..0000000 --- a/platonic-amazon-s3/Makefile +++ /dev/null @@ -1,2 +0,0 @@ -develop: - ../venv/bin/pip install -e .[dev] diff --git a/platonic-amazon-s3/README.md b/platonic-amazon-s3/README.md deleted file mode 100644 index db395be..0000000 --- a/platonic-amazon-s3/README.md +++ /dev/null @@ -1 +0,0 @@ -# S3 backed data structures \ No newline at end of file diff --git a/platonic-amazon-s3/platonic_amazon_s3/__init__.py b/platonic-amazon-s3/platonic_amazon_s3/__init__.py deleted file mode 100644 index 81d8b93..0000000 --- a/platonic-amazon-s3/platonic_amazon_s3/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .iterators import S3RecursiveKeyStream diff --git a/platonic-amazon-s3/platonic_amazon_s3/iterators.py b/platonic-amazon-s3/platonic_amazon_s3/iterators.py deleted file mode 100644 index dcc2dee..0000000 --- a/platonic-amazon-s3/platonic_amazon_s3/iterators.py +++ /dev/null @@ -1,49 +0,0 @@ -from urllib.parse import urlparse - -import boto3 -from boto3_type_annotations import s3 -from botocore.paginate import PageIterator -from typing import Iterator, Optional - -from sorted.sorted import Sorted -from platonic import Iterable - - -def is_not_a_directory(url: str): - return not url.endswith('/') - - -class S3RecursiveKeyStream(Sorted, Iterable[str]): - url: Optional[str] = None - - def __init__(self, url: Optional[str] = None): - if url is not None: - self.url = url - - if self.url is None: - raise ValueError(f'{self} does not have `url` defined.') - - def _recurse(self) -> Iterator[str]: - """Stream of all file URLs on Data Lake S3 bucket.""" - - client: s3.Client = boto3.client('s3') - - decoded_url = urlparse(self.url) - bucket_name = decoded_url.netloc - - paginator = client.get_paginator('list_objects_v2') - - page_iterator: PageIterator = paginator.paginate( - Bucket=bucket_name, - Prefix=decoded_url.path.lstrip('/'), - ) - - for page in page_iterator: - records = page.get('Contents', []) - - for record in records: - key = record['Key'] - yield f's3://{bucket_name}/{key}' - - def __iter__(self): - return self._recurse() diff --git a/platonic-amazon-s3/setup.py b/platonic-amazon-s3/setup.py deleted file mode 100644 index 5bdcffc..0000000 --- a/platonic-amazon-s3/setup.py +++ /dev/null @@ -1,32 +0,0 @@ -import setuptools - -with open("README.md", "r") as fh: - long_description = fh.read() - -setuptools.setup( - name="platonic-amazon-s3", - version="0.0.1", - author="Anatoly Scherbakov", - author_email="altaisoft@gmail.com", - description=( - "S3 backend for some of platonic data structures." - ), - long_description=long_description, - long_description_content_type="text/markdown", - url='https://github.com/anatoly-scherbakov/platonic', - packages=setuptools.find_packages(), - install_requires=[ - - ], - extras_require={ - 'dev': [ - 'pytest', - 'boto3' - ] - }, - classifiers=[ - "Programming Language :: Python :: 3", - "License :: OSI Approved :: MIT License", - "Operating System :: OS Independent", - ], -) diff --git a/platonic-amazon-s3/tests/test_init.py b/platonic-amazon-s3/tests/test_init.py deleted file mode 100644 index 69b5ac3..0000000 --- a/platonic-amazon-s3/tests/test_init.py +++ /dev/null @@ -1,20 +0,0 @@ -import itertools - -import pytest - -from platonic_amazon_s3 import S3RecursiveKeyStream - - -class TestKeyStream(S3RecursiveKeyStream): - url = 's3://homo-yetiensis' - - -@pytest.mark.skip('Integration test') -def test_initialize(): - piece = itertools.islice( - TestKeyStream(), - 10 - ) - - for item in piece: - print(item) diff --git a/platonic-redis/Makefile b/platonic-redis/Makefile deleted file mode 100644 index 7db08e4..0000000 --- a/platonic-redis/Makefile +++ /dev/null @@ -1,2 +0,0 @@ -develop: - ../venv/bin/pip install -e .[dev] diff --git a/platonic-redis/README.md b/platonic-redis/README.md deleted file mode 100644 index d74303d..0000000 --- a/platonic-redis/README.md +++ /dev/null @@ -1 +0,0 @@ -# Redis backend for Mapping \ No newline at end of file diff --git a/platonic-redis/platonic_redis/__init__.py b/platonic-redis/platonic_redis/__init__.py deleted file mode 100644 index 22e9abb..0000000 --- a/platonic-redis/platonic_redis/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .redis_box import RedisBox -from .redis_mapping import RedisMapping -from .redis_mutable_mapping import RedisMutableMapping diff --git a/platonic-redis/platonic_redis/base.py b/platonic-redis/platonic_redis/base.py deleted file mode 100644 index bb5e3ab..0000000 --- a/platonic-redis/platonic_redis/base.py +++ /dev/null @@ -1,17 +0,0 @@ -from redis import Redis - - -class RedisMixin: - url = 'localhost' - - _connection: Redis = None - - def create_connection(self): - return Redis(self.url) - - @property - def redis(self): - if self._connection is None: - self._connection = self.create_connection() - - return self._connection diff --git a/platonic-redis/platonic_redis/redis_box.py b/platonic-redis/platonic_redis/redis_box.py deleted file mode 100644 index fc33ab0..0000000 --- a/platonic-redis/platonic_redis/redis_box.py +++ /dev/null @@ -1,29 +0,0 @@ -from typing import TypeVar, Optional - -from platonic.box import AbstractBox -from .base import RedisMixin - -T = TypeVar('T') - - -class RedisBox(RedisMixin, AbstractBox[T]): - name: str - encoding = 'utf-8' - - def deserialize(self, raw_value: Optional[bytes]) -> T: - if raw_value is None: - return self.ValueType() - else: - return raw_value.decode(self.encoding) - - def serialize(self, value: T) -> bytes: - return bytes(value, encoding=self.encoding) - - @property - def value(self) -> T: - return self.deserialize(self.redis.get(self.name)) - - @value.setter - def value(self, value: T): - # FIXME str() is not necessarily the best serialization to hardcode - self.redis.set(self.name, self.serialize(value)) diff --git a/platonic-redis/platonic_redis/redis_mapping.py b/platonic-redis/platonic_redis/redis_mapping.py deleted file mode 100644 index a0376d8..0000000 --- a/platonic-redis/platonic_redis/redis_mapping.py +++ /dev/null @@ -1,22 +0,0 @@ -import typing - -from redis import Redis -from .base import RedisMixin - - -class RedisMapping(RedisMixin, typing.Mapping): - name = 'test' - - def __getitem__(self, k): - value = self.redis.hget(self.name, k) - if value is None: - raise KeyError(k) - - else: - return value - - def __iter__(self): - return self.redis.hscan_iter(self.name) - - def __len__(self): - return self.redis.hlen(self.name) diff --git a/platonic-redis/platonic_redis/redis_mutable_mapping.py b/platonic-redis/platonic_redis/redis_mutable_mapping.py deleted file mode 100644 index eeb3d28..0000000 --- a/platonic-redis/platonic_redis/redis_mutable_mapping.py +++ /dev/null @@ -1,11 +0,0 @@ -import typing - -from .redis_mapping import RedisMapping - - -class RedisMutableMapping(RedisMapping, typing.MutableMapping): - def __delitem__(self, key) -> None: - self.redis.hdel(self.name, key) - - def __setitem__(self, k, v) -> None: - self.redis.hset(self.name, k, v) diff --git a/platonic-redis/setup.py b/platonic-redis/setup.py deleted file mode 100644 index 84f5054..0000000 --- a/platonic-redis/setup.py +++ /dev/null @@ -1,31 +0,0 @@ -import setuptools - -with open("README.md", "r") as fh: - long_description = fh.read() - -setuptools.setup( - name="platonic-redis", - version="0.0.1", - author="Anatoly Scherbakov", - author_email="altaisoft@gmail.com", - description=( - "Redis backend for Mapping." - ), - long_description=long_description, - long_description_content_type="text/markdown", - url='https://github.com/anatoly-scherbakov/platonic', - packages=setuptools.find_packages(), - install_requires=[ - 'redis' - ], - extras_require={ - 'dev': [ - 'pytest', - ] - }, - classifiers=[ - "Programming Language :: Python :: 3", - "License :: OSI Approved :: MIT License", - "Operating System :: OS Independent", - ], -) diff --git a/platonic-redis/tests/test_box.py b/platonic-redis/tests/test_box.py deleted file mode 100644 index d9fd07e..0000000 --- a/platonic-redis/tests/test_box.py +++ /dev/null @@ -1,22 +0,0 @@ -from platonic import Box, register -from platonic_redis import RedisBox - - -class SiteNameBox(Box[str]): - """Storing our site name""" - - -@register(SiteNameBox) -class RedisSiteNameBox(RedisBox): - name = 'site-name' - - -def test_empty(): - assert SiteNameBox().value is '' - - -def test_set(): - box = SiteNameBox() - box.value = 'John Connor' - - assert box.value == 'John Connor' diff --git a/platonic-redis/tests/test_initialize.py b/platonic-redis/tests/test_initialize.py deleted file mode 100644 index 7c53173..0000000 --- a/platonic-redis/tests/test_initialize.py +++ /dev/null @@ -1,16 +0,0 @@ -from platonic import MutableMapping, register -from platonic_redis import RedisMutableMapping -import typing_inspect - - -class Cats(MutableMapping[str, str]): - pass - - -@register(Cats) -class RedisCats(RedisMutableMapping): - pass - - -def test_init(): - Cats() diff --git a/platonic/Makefile b/platonic/Makefile deleted file mode 100644 index 7db08e4..0000000 --- a/platonic/Makefile +++ /dev/null @@ -1,2 +0,0 @@ -develop: - ../venv/bin/pip install -e .[dev] diff --git a/platonic/README.md b/platonic/README.md deleted file mode 100644 index a82a1ff..0000000 --- a/platonic/README.md +++ /dev/null @@ -1 +0,0 @@ -# platonic diff --git a/platonic/__init__.py b/platonic/__init__.py index 40a96af..6466d39 100644 --- a/platonic/__init__.py +++ b/platonic/__init__.py @@ -1 +1,7 @@ -# -*- coding: utf-8 -*- +from .model import Model + +# Data structures +from .mapping import Mapping +from .mutable_mapping import MutableMapping + +from .iterable import Iterable diff --git a/platonic/platonic/iterable/__init__.py b/platonic/iterable/__init__.py similarity index 100% rename from platonic/platonic/iterable/__init__.py rename to platonic/iterable/__init__.py diff --git a/platonic/platonic/iterable/iterable.py b/platonic/iterable/iterable.py similarity index 100% rename from platonic/platonic/iterable/iterable.py rename to platonic/iterable/iterable.py diff --git a/platonic/platonic/mapping/__init__.py b/platonic/mapping/__init__.py similarity index 100% rename from platonic/platonic/mapping/__init__.py rename to platonic/mapping/__init__.py diff --git a/platonic/mapping/mapping.py b/platonic/mapping/mapping.py new file mode 100644 index 0000000..64d805a --- /dev/null +++ b/platonic/mapping/mapping.py @@ -0,0 +1,52 @@ +import typing +from abc import ABC + +from platonic import Model + + +KeyType = typing.TypeVar('KeyType') +ValueType = typing.TypeVar('ValueType') + + +class Mapping(Model, typing.Mapping[KeyType, ValueType], ABC): + KeyType: typing.Type = typing.Any + ValueType: typing.Type = typing.Any + + def __class_getitem__(cls, args: tuple) -> type: + if ( + args is None + or not isinstance(args, tuple) + or len(args) != 2 + ): + raise TypeError( + f'Class {cls.__name__} requires exactly two type arguments, ' + f'Key type and Value type. For example:' + f'\n' + f' {cls.__name__}[str, int]\n' + f'\n' + f'means a mapping from strings to integers. Instead, the type ' + f'arguments are: {args}.' + ) + + key_type, value_type = args + + if not isinstance(key_type, type): + raise ValueError( + f'Key type {key_type} is a {type(key_type)} value; ' + f'a type expected.' + ) + + if not isinstance(value_type, type): + raise ValueError( + f'Value type {value_type} is a {type(value_type)} value; ' + f'a type expected.' + ) + + return type( + f'{cls.__name__}[{key_type.__name__}, {value_type.__name__}]', + (cls, ), + { + 'KeyType': key_type, + 'ValueType': value_type + } + ) diff --git a/platonic/model.py b/platonic/model.py new file mode 100644 index 0000000..6448114 --- /dev/null +++ b/platonic/model.py @@ -0,0 +1,10 @@ +from abc import ABC + + +PROXY_CLASS_ATTRIBUTE = '__is_proxy_class' + + +class Model(ABC): + proxy_class: type = None + __backend__: type = None + __type_args__ = None diff --git a/platonic/platonic/mutable_mapping/__init__.py b/platonic/mutable_mapping/__init__.py similarity index 100% rename from platonic/platonic/mutable_mapping/__init__.py rename to platonic/mutable_mapping/__init__.py diff --git a/platonic/platonic/mutable_mapping/dict_mapping.py b/platonic/mutable_mapping/dict_mapping.py similarity index 100% rename from platonic/platonic/mutable_mapping/dict_mapping.py rename to platonic/mutable_mapping/dict_mapping.py diff --git a/platonic/platonic/mutable_mapping/mutable_mapping.py b/platonic/mutable_mapping/mutable_mapping.py similarity index 100% rename from platonic/platonic/mutable_mapping/mutable_mapping.py rename to platonic/mutable_mapping/mutable_mapping.py diff --git a/platonic/platonic/__init__.py b/platonic/platonic/__init__.py deleted file mode 100644 index 73fef06..0000000 --- a/platonic/platonic/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -from .model import Model -from .register import register - -# Data structures -from .box import Box - -from .mapping import Mapping -from .mutable_mapping import MutableMapping - -from .iterable import Iterable diff --git a/platonic/platonic/box/__init__.py b/platonic/platonic/box/__init__.py deleted file mode 100644 index d493a41..0000000 --- a/platonic/platonic/box/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .abstract import AbstractBox -from .model import Box -from .implementation import ValueBox diff --git a/platonic/platonic/box/abstract.py b/platonic/platonic/box/abstract.py deleted file mode 100644 index a79ce9f..0000000 --- a/platonic/platonic/box/abstract.py +++ /dev/null @@ -1,24 +0,0 @@ -from abc import abstractmethod -from typing import TypeVar, Generic, Type, Any - -T = TypeVar('T') - - -class AbstractBox(Generic[T]): - ValueType: Type[T] = Any - - __marker = object() - - def __init__(self, value: T = __marker): - if not (value is self.__marker): - self.value = value - - @property - @abstractmethod - def value(self) -> T: - raise NotImplementedError() - - @value.setter - @abstractmethod - def value(self, value: T): - raise NotImplementedError() diff --git a/platonic/platonic/box/implementation.py b/platonic/platonic/box/implementation.py deleted file mode 100644 index 40804fd..0000000 --- a/platonic/platonic/box/implementation.py +++ /dev/null @@ -1,17 +0,0 @@ -from typing import TypeVar - -from .abstract import AbstractBox - -T = TypeVar('T') - - -class ValueBox(AbstractBox[T]): - _value: T - - @property - def value(self) -> T: - return self._value - - @value.setter - def value(self, value: T): - self._value = value diff --git a/platonic/platonic/box/model.py b/platonic/platonic/box/model.py deleted file mode 100644 index 82e8ea6..0000000 --- a/platonic/platonic/box/model.py +++ /dev/null @@ -1,16 +0,0 @@ -from typing import TypeVar - -from platonic import Model -from .abstract import AbstractBox - - -T = TypeVar('T') - - -class Box(Model, AbstractBox[T]): - @classmethod - def __validate_type_args__(cls, args) -> dict: - value_type, = args - return { - 'ValueType': value_type - } diff --git a/platonic/platonic/mapping/mapping.py b/platonic/platonic/mapping/mapping.py deleted file mode 100644 index 7ef3923..0000000 --- a/platonic/platonic/mapping/mapping.py +++ /dev/null @@ -1,35 +0,0 @@ -import typing -from abc import ABC - -from platonic import Model - - -KeyType = typing.TypeVar('KeyType') -ValueType = typing.TypeVar('ValueType') - - -class Mapping(Model, typing.Mapping[KeyType, ValueType], ABC): - KeyType: typing.Type = typing.Any - ValueType: typing.Type = typing.Any - - @classmethod - def __validate_type_args__(cls, args) -> dict: - if args is None: - raise TypeError(f'Type args missing for {cls}.') - - elif not isinstance(args, tuple): - raise ValueError( - f'Type args for {cls} should be a tuple, {args} found instead.' - ) - - elif len(args) != 2: - raise ValueError( - f'Exactly 2 type args expected for {cls}, {args} found instead.' - ) - - key_type, value_type = args - - return { - 'KeyType': key_type, - 'ValueType': value_type - } diff --git a/platonic/platonic/model.py b/platonic/platonic/model.py deleted file mode 100644 index c5fa21c..0000000 --- a/platonic/platonic/model.py +++ /dev/null @@ -1,69 +0,0 @@ -from abc import ABC -from typing_inspect import is_typevar - - -PROXY_CLASS_ATTRIBUTE = '__is_proxy_class' - - -def create_proxy_class(cls): - concrete_class = cls.__backend__ - abstract_class = cls - - if concrete_class is None: - raise TypeError( - f'{abstract_class} does not have a backend defined. Please use ' - f'platonic.register() decorator to assign one.' - ) - - bases = ( - concrete_class, - abstract_class - ) - - class_name = f'{abstract_class.__name__} via {concrete_class.__name__}' - - # noinspection PyTypeChecker - return type(class_name, bases, { - PROXY_CLASS_ATTRIBUTE: True, - # FIXME this means type argument assignment only happens when an - # instance is instantiated. That is not necessarily the best course - # of action and an issue should probably be filed. - **abstract_class.__validate_type_args__(abstract_class.__type_args__) - }) - - -class Model(ABC): - proxy_class: type = None - __backend__: type = None - __type_args__ = None - - @classmethod - def __validate_type_args__(cls, args) -> dict: - return {} - - # noinspection PyUnresolvedReferences - def __class_getitem__disabled(cls, params): - if not isinstance(params, tuple): - params = (params, ) - - if all(map(is_typevar, params)): - pass - - return type( - f'{cls.__name__}[{", ".join(param.__name__ for param in params)}]', - (cls, ), - { - '__type_args__': params, - } - ) - - def __new__disabled(cls, *args, **kwargs): - if getattr(cls, PROXY_CLASS_ATTRIBUTE, False): - return super().__new__(cls, *args, **kwargs) - - if cls.proxy_class is None: - cls.proxy_class = create_proxy_class(cls) - - concrete_class = cls.__backend__ - - return concrete_class.__new__(cls.proxy_class, *args, **kwargs) diff --git a/platonic/platonic/register.py b/platonic/platonic/register.py deleted file mode 100644 index a9dc758..0000000 --- a/platonic/platonic/register.py +++ /dev/null @@ -1,14 +0,0 @@ -def register(abstract_class): - def _registerer(concrete_class): - if getattr(abstract_class, '__backend__', None): - raise ValueError( - f'{abstract_class.__backend__} is already registered ' - f'as a backend for {abstract_class}; cannot register ' - f'{concrete_class} as another one.' - ) - else: - abstract_class.__backend__ = concrete_class - - return concrete_class - - return _registerer diff --git a/platonic/setup.py b/platonic/setup.py deleted file mode 100644 index 8293d75..0000000 --- a/platonic/setup.py +++ /dev/null @@ -1,33 +0,0 @@ -import setuptools - -with open("README.md", "r") as fh: - long_description = fh.read() - -setuptools.setup( - name="platonic", - version="2.0.0", - author="Anatoly Scherbakov", - author_email="altaisoft@gmail.com", - description=( - "A library of common data structures with implementable backends." - ), - long_description=long_description, - long_description_content_type="text/markdown", - url='https://github.com/anatoly-scherbakov/platonic', - packages=setuptools.find_packages(), - install_requires=[ - 'typing_inspect' - ], - extras_require={ - 'dev': [ - 'pytest', - 'boto3', - 'boto3_type_annotations' - ] - }, - classifiers=[ - "Programming Language :: Python :: 3", - "License :: OSI Approved :: MIT License", - "Operating System :: OS Independent", - ], -) diff --git a/platonic/tests/__init__.py b/platonic/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/platonic/tests/test_abstract.py b/platonic/tests/test_abstract.py deleted file mode 100644 index db6559b..0000000 --- a/platonic/tests/test_abstract.py +++ /dev/null @@ -1,20 +0,0 @@ -from abc import abstractmethod - -from platonic import Model, register - - -class Cat(Model): - @abstractmethod - def meow(self): - raise NotImplementedError() - - -# noinspection PyMethodMayBeStatic -@register(Cat) -class CatBackend: - def meow(self): - return 'meow' - - -def test_inherit(): - assert Cat().meow() == 'meow' diff --git a/platonic/tests/test_box/__init__.py b/platonic/tests/test_box/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/platonic/tests/test_box/test_implementation.py b/platonic/tests/test_box/test_implementation.py deleted file mode 100644 index e74ffb1..0000000 --- a/platonic/tests/test_box/test_implementation.py +++ /dev/null @@ -1,21 +0,0 @@ -from platonic import register -from platonic.box import Box, ValueBox - - -class IntBox(Box[int]): - pass - - -@register(IntBox) -class ValueIntBox(ValueBox): - pass - - -def test_init(): - assert IntBox(5).value == 5 - - -def test_assign(): - b = IntBox(5) - b.value = 8 - assert b.value == 8 diff --git a/platonic/tests/test_dict_mapping.py b/platonic/tests/test_dict_mapping.py deleted file mode 100644 index 02ae02c..0000000 --- a/platonic/tests/test_dict_mapping.py +++ /dev/null @@ -1,25 +0,0 @@ -from abc import ABC - -from platonic import register, MutableMapping -from platonic.mutable_mapping import DictMapping - - -class MyMapping(MutableMapping[str, str]): - pass - - -@register(MyMapping) -class MyDictMapping(DictMapping): - pass - - -def test_dict_mapping(): - m = MyMapping() - - assert isinstance(m, MyDictMapping) - assert m.__class__.__name__ == 'MyMapping via MyDictMapping' - - m['a'] = 'b' - - assert len(m) == 1 - assert m.pop('a') == 'b' diff --git a/platonic/tests/test_model/__init__.py b/platonic/tests/test_model/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/platonic/tests/test_model/test_type_args.py b/platonic/tests/test_model/test_type_args.py deleted file mode 100644 index 4f95657..0000000 --- a/platonic/tests/test_model/test_type_args.py +++ /dev/null @@ -1,43 +0,0 @@ -import typing - -import pytest - -from platonic import Mapping, register, Model - - -class Marks(Mapping[str, int]): - pass - - -@register(Marks) -class DictMarks(dict): - pass - - -class Majors(Mapping): - pass - - -@register(Majors) -class DictMajors(dict): - pass - - -def test_class_getitem(): - subclass = Mapping[str, str] - assert subclass.__name__ == 'Mapping[str, str]' - - -def test_overlap(): - assert Majors.__type_args__ is None - assert Marks.__type_args__ == (str, int) - - marks = Marks() - assert marks.KeyType is str - assert marks.ValueType is int - - with pytest.raises(TypeError): - majors = Majors() - - assert Majors.KeyType is typing.Any - assert Majors.ValueType is typing.Any diff --git a/platonic/tests/test_new.py b/platonic/tests/test_new.py deleted file mode 100644 index d064e28..0000000 --- a/platonic/tests/test_new.py +++ /dev/null @@ -1,42 +0,0 @@ -import typing -import platonic -import dataclasses - - -@dataclasses.dataclass(frozen=True) -class Cat: - color: str - age: int - - -class Cats(platonic.MutableMapping[str, Cat]): - pass - - -@platonic.register(Cats) -class CatsBackend(typing.MutableMapping): - def __delitem__(self, v) -> None: - pass - - def __getitem__(self, k): - pass - - def __len__(self) -> int: - pass - - def __iter__(self): - pass - - def __setitem__(self, k, v) -> None: - pass - - -def test_initialize(): - cats = Cats() - assert isinstance(cats, Cats) - assert isinstance(cats, CatsBackend) - assert cats.__class__.__name__ == 'Cats via CatsBackend' - assert cats.__type_args__ == (str, Cat) - - cats2 = Cats() - assert cats.__class__ == cats2.__class__ diff --git a/platonic/tests/test_register.py b/platonic/tests/test_register.py deleted file mode 100644 index 4c7de13..0000000 --- a/platonic/tests/test_register.py +++ /dev/null @@ -1,46 +0,0 @@ -import pytest - -from platonic import register, Model - - -class Cat(Model): - pass - - -@register(Cat) -class CatBackend: - pass - - -class Dog(Model): - pass - - -def test_register_twice(): - try: - @register(Cat) - class PussyBackend: - pass - - except ValueError: - pass - - else: - assert False, "Exception not raised" - - -def test_backend(): - assert Cat.__backend__ == CatBackend - - -def test_isinstance(): - assert isinstance(Cat(), Cat) - - -def test_no_backend(): - with pytest.raises(TypeError): - Dog() - - -def test_type_equality(): - assert type(Cat()) == type(Cat()) diff --git a/platonic-amazon-s3/tests/__init__.py b/tests/__init__.py similarity index 100% rename from platonic-amazon-s3/tests/__init__.py rename to tests/__init__.py diff --git a/platonic-redis/tests/__init__.py b/tests/test_model/__init__.py similarity index 100% rename from platonic-redis/tests/__init__.py rename to tests/test_model/__init__.py diff --git a/tests/test_model/test_type_args.py b/tests/test_model/test_type_args.py new file mode 100644 index 0000000..4e83714 --- /dev/null +++ b/tests/test_model/test_type_args.py @@ -0,0 +1,33 @@ +import typing + +import pytest + +from platonic import Mapping + + +class Marks(Mapping[str, int], dict): + pass + + +def test_none_parameters(): + with pytest.raises(TypeError): + Mapping[None] + + +def test_one_parameter(): + with pytest.raises(TypeError): + Mapping[int] + + +def test_key_not_type(): + with pytest.raises(ValueError): + Mapping[5, str] + + +def test_value_not_type(): + with pytest.raises(ValueError): + Mapping[int, 5] + + +def test_name(): + assert Mapping[str, str].__name__ == 'Mapping[str, str]' From 1b5192cf3369780e48dd52fbe9fb13bcf6f6963f Mon Sep 17 00:00:00 2001 From: Anatoly Scherbakov Date: Sun, 26 Jan 2020 21:56:05 +0700 Subject: [PATCH 2/2] #1 mypy is happy --- platonic/__init__.py | 6 ++---- platonic/iterable/__init__.py | 1 - platonic/iterable/iterable.py | 9 --------- platonic/mapping/mapping.py | 13 +++++++------ platonic/model.py | 4 +--- platonic/mutable_mapping/__init__.py | 1 - platonic/mutable_mapping/dict_mapping.py | 2 -- platonic/mutable_mapping/mutable_mapping.py | 6 ++++-- setup.cfg | 2 +- tests/test_model/test_type_args.py | 10 +++++----- 10 files changed, 20 insertions(+), 34 deletions(-) delete mode 100644 platonic/iterable/__init__.py delete mode 100644 platonic/iterable/iterable.py delete mode 100644 platonic/mutable_mapping/dict_mapping.py diff --git a/platonic/__init__.py b/platonic/__init__.py index 6466d39..1f63f31 100644 --- a/platonic/__init__.py +++ b/platonic/__init__.py @@ -1,7 +1,5 @@ from .model import Model # Data structures -from .mapping import Mapping -from .mutable_mapping import MutableMapping - -from .iterable import Iterable +from .mapping.mapping import Mapping as Mapping +from .mutable_mapping.mutable_mapping import MutableMapping diff --git a/platonic/iterable/__init__.py b/platonic/iterable/__init__.py deleted file mode 100644 index 4840066..0000000 --- a/platonic/iterable/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .iterable import Iterable diff --git a/platonic/iterable/iterable.py b/platonic/iterable/iterable.py deleted file mode 100644 index 04c6dff..0000000 --- a/platonic/iterable/iterable.py +++ /dev/null @@ -1,9 +0,0 @@ -import typing - -from platonic import Model - -ValueType = typing.TypeVar('ValueType') - - -class Iterable(typing.Iterable[ValueType], Model): - pass diff --git a/platonic/mapping/mapping.py b/platonic/mapping/mapping.py index 64d805a..b137da3 100644 --- a/platonic/mapping/mapping.py +++ b/platonic/mapping/mapping.py @@ -1,7 +1,7 @@ import typing from abc import ABC -from platonic import Model +from ..model import Model KeyType = typing.TypeVar('KeyType') @@ -9,13 +9,14 @@ class Mapping(Model, typing.Mapping[KeyType, ValueType], ABC): - KeyType: typing.Type = typing.Any - ValueType: typing.Type = typing.Any + KeyType: type + ValueType: type - def __class_getitem__(cls, args: tuple) -> type: + def __class_getitem__(cls, args: typing.Tuple[type, type]) -> type: if ( - args is None - or not isinstance(args, tuple) + # We have to check the types of arguments here to ensure + # the structure is used properly. mypy does not condone that. + not isinstance(args, tuple) # type: ignore or len(args) != 2 ): raise TypeError( diff --git a/platonic/model.py b/platonic/model.py index 6448114..c9891bd 100644 --- a/platonic/model.py +++ b/platonic/model.py @@ -5,6 +5,4 @@ class Model(ABC): - proxy_class: type = None - __backend__: type = None - __type_args__ = None + pass diff --git a/platonic/mutable_mapping/__init__.py b/platonic/mutable_mapping/__init__.py index 3259236..4e78f2b 100644 --- a/platonic/mutable_mapping/__init__.py +++ b/platonic/mutable_mapping/__init__.py @@ -1,2 +1 @@ from .mutable_mapping import MutableMapping -from .dict_mapping import DictMapping diff --git a/platonic/mutable_mapping/dict_mapping.py b/platonic/mutable_mapping/dict_mapping.py deleted file mode 100644 index ebb67c3..0000000 --- a/platonic/mutable_mapping/dict_mapping.py +++ /dev/null @@ -1,2 +0,0 @@ -class DictMapping(dict): - """Implementation of a Mapping based on a standard dict.""" diff --git a/platonic/mutable_mapping/mutable_mapping.py b/platonic/mutable_mapping/mutable_mapping.py index a167260..1bf0289 100644 --- a/platonic/mutable_mapping/mutable_mapping.py +++ b/platonic/mutable_mapping/mutable_mapping.py @@ -1,14 +1,16 @@ import typing from abc import ABC -from platonic.mapping import Mapping +from platonic import Mapping KeyType = typing.TypeVar('KeyType') ValueType = typing.TypeVar('ValueType') +# I am not entirely sure how to inherit from a generic type; mypy is very +# unhappy class MutableMapping( - Mapping, + Mapping, # type: ignore typing.MutableMapping[KeyType, ValueType], ABC ): diff --git a/setup.cfg b/setup.cfg index fb3fcd5..3e23026 100644 --- a/setup.cfg +++ b/setup.cfg @@ -24,7 +24,7 @@ i-control-code = False # Disable some pydocstyle checks: # Exclude some pydoctest checks globally: -ignore = D100, D104, D106, D401, W504, X100, RST303, RST304, DAR103, DAR203 +ignore = D100, D104, D106, D401, W504, X100, RST303, RST304, DAR103, DAR203, C101 # Excluding some directories: exclude = diff --git a/tests/test_model/test_type_args.py b/tests/test_model/test_type_args.py index 4e83714..b381683 100644 --- a/tests/test_model/test_type_args.py +++ b/tests/test_model/test_type_args.py @@ -5,28 +5,28 @@ from platonic import Mapping -class Marks(Mapping[str, int], dict): +class Marks(Mapping[str, int], typing.Dict[str, int]): pass def test_none_parameters(): with pytest.raises(TypeError): - Mapping[None] + Mapping[None] # type: ignore def test_one_parameter(): with pytest.raises(TypeError): - Mapping[int] + Mapping[int] # type: ignore def test_key_not_type(): with pytest.raises(ValueError): - Mapping[5, str] + Mapping[5, str] # type: ignore def test_value_not_type(): with pytest.raises(ValueError): - Mapping[int, 5] + Mapping[int, 5] # type: ignore def test_name():