Skip to content

Commit

Permalink
Release 0.5.0 (#18)
Browse files Browse the repository at this point in the history
  • Loading branch information
lycantropos committed Jul 4, 2021
1 parent 7c58e64 commit 11a05fe
Show file tree
Hide file tree
Showing 14 changed files with 221 additions and 51 deletions.
2 changes: 1 addition & 1 deletion .bumpversion.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 0.4.0
current_version = 0.5.0
commit = True
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(-(?P<release>.*))?
serialize =
Expand Down
2 changes: 1 addition & 1 deletion docker-compose.cpython.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ services:
args:
- IMAGE_NAME=${CPYTHON_IMAGE_NAME}
- IMAGE_VERSION=${CPYTHON_IMAGE_VERSION}
image: lycantropos/reprit-cpython:0.4.0
image: lycantropos/reprit-cpython:0.5.0
volumes:
- ./pytest.ini:/opt/reprit/pytest.ini
- ./README.md:/opt/reprit/README.md
Expand Down
2 changes: 1 addition & 1 deletion docker-compose.pypy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ services:
args:
- IMAGE_NAME=${PYPY_IMAGE_NAME}
- IMAGE_VERSION=${PYPY_IMAGE_VERSION}
image: lycantropos/reprit-pypy:0.4.0
image: lycantropos/reprit-pypy:0.5.0
volumes:
- ./pytest.ini:/opt/reprit/pytest.ini
- ./README.md:/opt/reprit/README.md
Expand Down
2 changes: 1 addition & 1 deletion reprit/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""Auto __repr__ method generation."""

__version__ = '0.4.0'
__version__ = '0.5.0'
33 changes: 19 additions & 14 deletions reprit/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,20 @@
from typing import (Iterable as _Iterable,
Union as _Union)

from . import seekers as _seekers
from . import (seekers as _seekers,
serializers as _serializers)
from .core.hints import (Constructor as _Constructor,
Domain as _Domain,
Initializer as _Initializer,
Map as _Map)
from .hints import FieldSeeker as _FieldSeeker
from .hints import (ArgumentSerializer as _ArgumentSerializer,
FieldSeeker as _FieldSeeker)


def generate_repr(method: _Union[_Constructor, _Initializer],
*,
argument_serializer: _ArgumentSerializer
= _serializers.simple,
field_seeker: _FieldSeeker = _seekers.simple,
prefer_keyword: bool = False,
with_module_name: bool = False) -> _Map[_Domain, str]:
Expand All @@ -29,6 +33,7 @@ def generate_repr(method: _Union[_Constructor, _Initializer],
:param method:
constructor/initializer method
which parameters will be used in resulting representation.
:param argument_serializer: function that serializes argument to string.
:param field_seeker:
function that re-creates parameter value
based on class instance and name.
Expand Down Expand Up @@ -129,8 +134,7 @@ def __repr__(self: _Domain) -> str:
for parameter in parameters.values()
if parameter.kind is _ParameterKind.VAR_POSITIONAL),
None)
to_positional_argument_string = repr
to_keyword_argument_string = '{}={!r}'.format
to_keyword_argument_string = '{}={}'.format

def to_arguments_strings(object_: _Domain) -> _Iterable[str]:
variadic_positional_unset = (
Expand All @@ -141,21 +145,22 @@ def to_arguments_strings(object_: _Domain) -> _Iterable[str]:
if isinstance(field, _MethodType) and field.__self__ is object_:
field = field()
if parameter.kind is _ParameterKind.POSITIONAL_ONLY:
yield to_positional_argument_string(field)
yield argument_serializer(field)
elif parameter.kind is _ParameterKind.POSITIONAL_OR_KEYWORD:
if prefer_keyword and variadic_positional_unset:
yield to_keyword_argument_string(parameter_name, field)
else:
yield to_positional_argument_string(field)
yield (to_keyword_argument_string(parameter_name,
argument_serializer(field))
if prefer_keyword and variadic_positional_unset
else argument_serializer(field))
elif parameter.kind is _ParameterKind.VAR_POSITIONAL:
yield from ((to_positional_argument_string(field),)
yield from ((argument_serializer(field),)
# we don't want to exhaust iterator
if isinstance(field, abc.Iterator)
else map(to_positional_argument_string, field))
else map(argument_serializer, field))
elif parameter.kind is _ParameterKind.KEYWORD_ONLY:
yield to_keyword_argument_string(parameter_name, field)
yield to_keyword_argument_string(parameter_name,
argument_serializer(field))
else:
yield from map(to_keyword_argument_string,
field.keys(), field.values())
yield from map(to_keyword_argument_string, field.keys(),
map(argument_serializer, field.values()))

return __repr__
7 changes: 4 additions & 3 deletions reprit/hints.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from typing import (Any,
Callable)
from typing import (Any as _Any,
Callable as _Callable)

from .core.hints import Domain as _Domain

FieldSeeker = Callable[[_Domain, str], Any]
ArgumentSerializer = _Callable[[_Any], str]
FieldSeeker = _Callable[[_Domain, str], _Any]
109 changes: 109 additions & 0 deletions reprit/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
from functools import singledispatch as _singledispatch
from types import (BuiltinFunctionType as _BuiltinFunctionType,
BuiltinMethodType as _BuiltinMethodType,
FunctionType as _FunctionType,
GetSetDescriptorType as _GetSetDescriptorType,
MemberDescriptorType as _MemberDescriptorType,
MethodType as _MethodType,
ModuleType as _ModuleType)
from typing import (Any as _Any,
Union as _Union)

try:
from types import ClassMethodDescriptorType as _ClassMethodDescriptorType
except ImportError:
_ClassMethodDescriptorType = type(dict.__dict__['fromkeys'])
try:
from types import MethodDescriptorType as _MethodDescriptorType
except ImportError:
_MethodDescriptorType = type(str.join)
try:
from types import MethodWrapperType as _MethodWrapperType
except ImportError:
_MethodWrapperType = type(object().__str__)
try:
from types import WrapperDescriptorType as _WrapperDescriptorType
except ImportError:
_WrapperDescriptorType = type(object.__init__)

simple = repr


@_singledispatch
def complex_(object_: _Any) -> str:
return repr(object_)


@complex_.register(_BuiltinFunctionType)
@complex_.register(_FunctionType)
@complex_.register(type)
def _(object_: _Union[_BuiltinFunctionType, _FunctionType, type]) -> str:
return object_.__module__ + '.' + object_.__qualname__


@complex_.register(_BuiltinMethodType)
@complex_.register(_MethodType)
def _(object_: _Union[_BuiltinMethodType, _MethodType]) -> str:
return complex_(object_.__self__) + '.' + object_.__name__


@complex_.register(_ClassMethodDescriptorType)
@complex_.register(_GetSetDescriptorType)
@complex_.register(_MemberDescriptorType)
@complex_.register(_MethodDescriptorType)
@complex_.register(_MethodWrapperType)
@complex_.register(_WrapperDescriptorType)
def _(object_: _Union[_ClassMethodDescriptorType, _GetSetDescriptorType,
_MemberDescriptorType, _MethodDescriptorType,
_MethodWrapperType, _WrapperDescriptorType]) -> str:
return complex_(object_.__objclass__) + '.' + object_.__name__


@complex_.register(_ModuleType)
def _(object_: _ModuleType) -> str:
return object_.__name__


@complex_.register(classmethod)
@complex_.register(staticmethod)
def _(object_: _Union[classmethod, staticmethod]) -> str:
return '{}({})'.format(complex_(type(object_)), complex_(object_.__func__))


@complex_.register(dict)
def _(object_: dict) -> str:
return '{' + ', '.join(map('{}: {}'.format,
map(complex_, object_.keys()),
map(complex_, object_.values()))) + '}'


@complex_.register(frozenset)
def _(object_: frozenset) -> str:
return (complex_(type(object_)) + '('
+ ('{' + ', '.join(map(complex_, object_)) + '}'
if object_
else '')
+ ')')


@complex_.register(list)
def _(object_: list) -> str:
return '[' + ', '.join(map(complex_, object_)) + ']'


@complex_.register(memoryview)
def _(object_: memoryview) -> str:
return complex_(type(object_)) + '(' + complex_(object_.obj) + ')'


@complex_.register(set)
def _(object_: set) -> str:
return ('{' + ', '.join(map(complex_, object_)) + '}'
if object_
else complex_(type(object_)) + '()')


@complex_.register(tuple)
def _(object_: tuple) -> str:
return ('(' + ', '.join(map(complex_, object_))
+ (',' if len(object_) == 1 else '') + ')')
35 changes: 24 additions & 11 deletions tests/base_tests/test_complex.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,29 @@
import builtins
import platform
import sys

import pytest
from hypothesis import given

from reprit import seekers
from reprit import (seekers,
serializers)
from reprit.base import generate_repr
from reprit.hints import ArgumentSerializer
from tests import strategies
from tests.utils import (ClassMethodInstance,
Method,
are_objects_equivalent,
to_namespace)


@given(strategies.complex_classes_methods,
@given(strategies.complex_classes_methods, strategies.argument_serializers,
strategies.booleans, strategies.booleans)
def test_basic(method: Method,
argument_serializer: ArgumentSerializer,
prefer_keyword: bool,
with_module_name: bool) -> None:
result = generate_repr(method,
argument_serializer=argument_serializer,
field_seeker=seekers.complex_,
prefer_keyword=prefer_keyword,
with_module_name=with_module_name)
Expand All @@ -27,13 +32,16 @@ def test_basic(method: Method,


@given(strategies.complex_classes_with_methods_and_instances,
strategies.booleans, strategies.booleans)
strategies.argument_serializers, strategies.booleans,
strategies.booleans)
def test_call(class_method_instance: ClassMethodInstance,
argument_serializer: ArgumentSerializer,
prefer_keyword: bool,
with_module_name: bool) -> None:
cls, method, instance = class_method_instance

repr_ = generate_repr(method,
argument_serializer=argument_serializer,
field_seeker=seekers.complex_,
prefer_keyword=prefer_keyword,
with_module_name=with_module_name)
Expand All @@ -47,35 +55,38 @@ def test_call(class_method_instance: ClassMethodInstance,
and sys.version_info > (3, 5, 3),
reason='Unreproducible failures on PyPy3.5.3')
@given(strategies.complex_classes_with_methods_and_instances,
strategies.booleans,
strategies.booleans)
strategies.booleans, strategies.booleans)
def test_evaluation(class_with_method_and_instance: ClassMethodInstance,
prefer_keyword: bool,
with_module_name: bool) -> None:
cls, method, instance = class_with_method_and_instance

repr_ = generate_repr(method,
argument_serializer=serializers.complex_,
field_seeker=seekers.complex_,
prefer_keyword=prefer_keyword,
with_module_name=with_module_name)
instance_repr = repr_(instance)

result = eval(instance_repr,
to_namespace(cls.__module__ + '.' + cls.__qualname__
if with_module_name
else cls.__qualname__,
cls))
{**to_namespace(cls.__module__ + '.' + cls.__qualname__
if with_module_name
else cls.__qualname__,
cls),
builtins.__name__: builtins})

assert are_objects_equivalent(instance, result)


@given(strategies.unsupported_complex_classes_with_methods_and_instances,
strategies.booleans)
strategies.argument_serializers, strategies.booleans)
def test_unsupported(class_with_method_and_instance: ClassMethodInstance,
argument_serializer: ArgumentSerializer,
prefer_keyword: bool) -> None:
cls, method, instance = class_with_method_and_instance

repr_ = generate_repr(method,
argument_serializer=argument_serializer,
field_seeker=seekers.complex_,
prefer_keyword=prefer_keyword)

Expand All @@ -84,12 +95,14 @@ def test_unsupported(class_with_method_and_instance: ClassMethodInstance,


@given(strategies.complex_classes_with_methods_and_instances,
strategies.booleans)
strategies.argument_serializers, strategies.booleans)
def test_with_module_name(class_with_method_and_instance: ClassMethodInstance,
argument_serializer: ArgumentSerializer,
prefer_keyword: bool) -> None:
cls, method, instance = class_with_method_and_instance

repr_ = generate_repr(method,
argument_serializer=argument_serializer,
field_seeker=seekers.complex_,
prefer_keyword=prefer_keyword,
with_module_name=True)
Expand Down

0 comments on commit 11a05fe

Please sign in to comment.