Skip to content

Commit

Permalink
Release 0.6.0 (#19)
Browse files Browse the repository at this point in the history
  • Loading branch information
lycantropos committed Jul 5, 2021
1 parent 11a05fe commit e145b4a
Show file tree
Hide file tree
Showing 9 changed files with 177 additions and 76 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.5.0
current_version = 0.6.0
commit = True
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(-(?P<release>.*))?
serialize =
Expand Down
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ This can be done like
... self.variadic_positional = variadic_positional
... self.keyword_only = keyword_only
... self.variadic_keyword = variadic_keyword
...
... __repr__ = generate_repr(__init__)

```
Expand All @@ -82,6 +83,7 @@ or for a class with avoidance of built-in names clash
... self.id = id_
... self._name = name
... self._zip = zip_
...
... __repr__ = generate_repr(__init__,
... field_seeker=seekers.complex_)

Expand All @@ -93,6 +95,31 @@ State(1, 'Alabama', 36016)

```

We can also tell to skip unspecified optional parameters
```python
>>> from reprit.base import generate_repr
>>> class Employee:
... def __init__(self, name, email=None, manager=None):
... self.name = name
... self.email = email
... self.manager = manager
...
... __repr__ = generate_repr(__init__,
... skip_defaults=True)

```
After that
```python
>>> Employee('John Doe')
Employee('John Doe')
>>> Employee('John Doe',
... manager=Employee('Jane Doe'))
Employee('John Doe', manager=Employee('Jane Doe'))
>>> Employee('John Doe', 'johndoe@company.com', Employee('Jane Doe'))
Employee('John Doe', 'johndoe@company.com', Employee('Jane Doe'))

```

*Note*: this method doesn't automatically handle changes during runtime
(e.g. if someone deletes instance field
or replaces `__init__`/`__new__` method implementation),
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.5.0
image: lycantropos/reprit-cpython:0.6.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.5.0
image: lycantropos/reprit-pypy:0.6.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.5.0'
__version__ = '0.6.0'
57 changes: 36 additions & 21 deletions reprit/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ def generate_repr(method: _Union[_Constructor, _Initializer],
= _serializers.simple,
field_seeker: _FieldSeeker = _seekers.simple,
prefer_keyword: bool = False,
skip_defaults: bool = False,
with_module_name: bool = False) -> _Map[_Domain, str]:
"""
Generates ``__repr__`` method based on constructor/initializer parameters.
Expand All @@ -41,6 +42,9 @@ def generate_repr(method: _Union[_Constructor, _Initializer],
flag that specifies
if positional-or-keyword parameters should be outputted
as keyword ones when possible.
:param skip_defaults:
flag that specifies
if optional parameters with default arguments should be skipped.
:param with_module_name:
flag that specifies if module name should be added.
Expand Down Expand Up @@ -134,33 +138,44 @@ def __repr__(self: _Domain) -> str:
for parameter in parameters.values()
if parameter.kind is _ParameterKind.VAR_POSITIONAL),
None)
to_keyword_argument_string = '{}={}'.format
to_keyword_string = '{}={}'.format

def to_arguments_strings(object_: _Domain) -> _Iterable[str]:
variadic_positional_unset = (
variadic_positional is None
or not field_seeker(object_, variadic_positional.name))
for parameter_name, parameter in parameters.items():
field = field_seeker(object_, parameter_name)
positional_or_keyword_is_keyword = (prefer_keyword
and variadic_positional_unset)
for name, parameter in parameters.items():
field = field_seeker(object_, name)
if isinstance(field, _MethodType) and field.__self__ is object_:
field = field()
if parameter.kind is _ParameterKind.POSITIONAL_ONLY:
yield argument_serializer(field)
elif parameter.kind is _ParameterKind.POSITIONAL_OR_KEYWORD:
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 ((argument_serializer(field),)
# we don't want to exhaust iterator
if isinstance(field, abc.Iterator)
else map(argument_serializer, field))
elif parameter.kind is _ParameterKind.KEYWORD_ONLY:
yield to_keyword_argument_string(parameter_name,
argument_serializer(field))
else:
yield from map(to_keyword_argument_string, field.keys(),
map(argument_serializer, field.values()))
kind = parameter.kind
show_parameter = (
not skip_defaults or field is not parameter.default
or (not variadic_positional_unset
and (kind is _ParameterKind.POSITIONAL_ONLY
or kind is _ParameterKind.POSITIONAL_OR_KEYWORD)))
if show_parameter:
if kind is _ParameterKind.POSITIONAL_ONLY:
yield argument_serializer(field)
elif kind is _ParameterKind.POSITIONAL_OR_KEYWORD:
yield (to_keyword_string(name, argument_serializer(field))
if positional_or_keyword_is_keyword
else argument_serializer(field))
elif kind is _ParameterKind.VAR_POSITIONAL:
yield from ((argument_serializer(field),)
# we don't want to exhaust iterator
if isinstance(field, abc.Iterator)
else map(argument_serializer, field))
elif kind is _ParameterKind.KEYWORD_ONLY:
yield to_keyword_string(name, argument_serializer(field))
else:
yield from map(to_keyword_string, field.keys(),
map(argument_serializer, field.values()))
elif (not positional_or_keyword_is_keyword
and (kind is _ParameterKind.POSITIONAL_ONLY
or kind is _ParameterKind.POSITIONAL_OR_KEYWORD)):
positional_or_keyword_is_keyword = True

return __repr__
22 changes: 17 additions & 5 deletions tests/base_tests/test_complex.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,33 +17,37 @@


@given(strategies.complex_classes_methods, strategies.argument_serializers,
strategies.booleans, strategies.booleans)
strategies.booleans, strategies.booleans, strategies.booleans)
def test_basic(method: Method,
argument_serializer: ArgumentSerializer,
prefer_keyword: bool,
skip_defaults: bool,
with_module_name: bool) -> None:
result = generate_repr(method,
argument_serializer=argument_serializer,
field_seeker=seekers.complex_,
prefer_keyword=prefer_keyword,
skip_defaults=skip_defaults,
with_module_name=with_module_name)

assert callable(result)


@given(strategies.complex_classes_with_methods_and_instances,
strategies.argument_serializers, strategies.booleans,
strategies.booleans)
strategies.booleans, strategies.booleans)
def test_call(class_method_instance: ClassMethodInstance,
argument_serializer: ArgumentSerializer,
prefer_keyword: bool,
skip_defaults: 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,
skip_defaults=skip_defaults,
with_module_name=with_module_name)

result = repr_(instance)
Expand All @@ -55,16 +59,18 @@ 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, strategies.booleans)
def test_evaluation(class_with_method_and_instance: ClassMethodInstance,
prefer_keyword: bool,
skip_defaults: 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,
skip_defaults=skip_defaults,
with_module_name=with_module_name)
instance_repr = repr_(instance)

Expand All @@ -79,32 +85,38 @@ def test_evaluation(class_with_method_and_instance: ClassMethodInstance,


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

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

with pytest.raises(AttributeError):
repr_(instance)


@given(strategies.complex_classes_with_methods_and_instances,
strategies.argument_serializers, strategies.booleans)
strategies.argument_serializers, strategies.booleans,
strategies.booleans)
def test_with_module_name(class_with_method_and_instance: ClassMethodInstance,
argument_serializer: ArgumentSerializer,
skip_defaults: bool,
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,
skip_defaults=skip_defaults,
with_module_name=True)

result = repr_(instance)
Expand Down
19 changes: 14 additions & 5 deletions tests/base_tests/test_simple.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,31 +16,35 @@


@given(strategies.simple_classes_methods, strategies.argument_serializers,
strategies.booleans, strategies.booleans)
strategies.booleans, strategies.booleans, strategies.booleans)
def test_basic(method: Method,
argument_serializer: ArgumentSerializer,
prefer_keyword: bool,
skip_defaults: bool,
with_module_name: bool) -> None:
result = generate_repr(method,
argument_serializer=argument_serializer,
prefer_keyword=prefer_keyword,
skip_defaults=skip_defaults,
with_module_name=with_module_name)

assert callable(result)


@given(strategies.simple_classes_with_methods_and_instances,
strategies.argument_serializers, strategies.booleans,
strategies.booleans)
strategies.booleans, strategies.booleans)
def test_call(class_with_method_and_instance: ClassMethodInstance,
argument_serializer: ArgumentSerializer,
prefer_keyword: bool,
skip_defaults: bool,
with_module_name: bool) -> None:
_, method, instance = class_with_method_and_instance

repr_ = generate_repr(method,
argument_serializer=argument_serializer,
prefer_keyword=prefer_keyword,
skip_defaults=skip_defaults,
with_module_name=with_module_name)

result = repr_(instance)
Expand All @@ -52,15 +56,17 @@ def test_call(class_with_method_and_instance: ClassMethodInstance,
and sys.version_info > (3, 5, 3),
reason='Unreproducible failures on PyPy3.5.3')
@given(strategies.simple_classes_with_methods_and_instances,
strategies.booleans, strategies.booleans)
strategies.booleans, strategies.booleans, strategies.booleans)
def test_evaluation(class_with_method_and_instance: ClassMethodInstance,
prefer_keyword: bool,
skip_defaults: bool,
with_module_name: bool) -> None:
cls, method, instance = class_with_method_and_instance

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

Expand All @@ -75,15 +81,18 @@ def test_evaluation(class_with_method_and_instance: ClassMethodInstance,


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

repr_ = generate_repr(method,
argument_serializer=argument_serializer,
prefer_keyword=prefer_keyword,
skip_defaults=skip_defaults,
with_module_name=True)

result = repr_(instance)
Expand Down

0 comments on commit e145b4a

Please sign in to comment.