Skip to content
This repository has been archived by the owner on Nov 15, 2024. It is now read-only.

Commit

Permalink
allow for custom repr function + bump version (#13)
Browse files Browse the repository at this point in the history
* allow for custom repr function + bump version

* remove '_get_descriptors'

* make private classmethods functions

* improve readability
  • Loading branch information
PythonFZ authored Dec 14, 2022
1 parent 14685d0 commit b8f1a00
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 90 deletions.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "zninit"
version = "0.1.7"
version = "0.1.8"
description = "Descriptor based dataclass implementation"
authors = ["zincwarecode <[email protected]>"]
license = "Apache-2.0"
Expand Down
25 changes: 23 additions & 2 deletions tests/test_u_core.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""'ZnInit' unit tests."""
from zninit import Descriptor, ZnInit
from zninit.core import get_args_type_error, get_init_type_error
from zninit.core import _get_auto_init_kwargs, get_args_type_error, get_init_type_error


class ClsDefaultMixed(ZnInit):
Expand All @@ -22,7 +22,7 @@ class DoNotUseRepr(ZnInit):

def test_get_auto_init_kwargs():
"""Test auto init kwargs."""
kwargs_no_default, kwargs_with_default = ClsDefaultMixed._get_auto_init_kwargs()
kwargs_no_default, kwargs_with_default = _get_auto_init_kwargs(ClsDefaultMixed)
assert kwargs_no_default == ["param1"]
assert kwargs_with_default == {"param2": "World"}

Expand Down Expand Up @@ -97,3 +97,24 @@ def test_repr():
assert repr(DoNotUseRepr(param1="Hello", param2="World")).startswith(
"<test_u_core.DoNotUseRepr object at"
)


class CustomRepr(ZnInit):
"""Class with disabled repr."""

param1 = Descriptor(repr_func=lambda x: rf"'Custom: {x}'")
method = Descriptor(repr_func=lambda x: f"<function {x.__module__}.{x.__name__}>")


def test_custom_repr():
"""Test the __repr__."""

def do_something():
pass

instance = CustomRepr(param1="Hello", method=do_something)
assert (
repr(instance)
== "CustomRepr(method=<function test_u_core.do_something>, param1='Custom:"
" Hello')"
)
2 changes: 1 addition & 1 deletion tests/test_zninit.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@

def test_version():
"""Test the installed version."""
assert zninit.__version__ == "0.1.7"
assert zninit.__version__ == "0.1.8"
176 changes: 90 additions & 86 deletions zninit/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,91 @@ def auto_init(self, *args, **kwargs):
return auto_init


def _update_init(cls, super_init):
"""Set the automatic __init__.
Parameters
----------
cls:
the cls to be updated
super_init:
run a super call if required
Returns
-------
the updated cls instance
"""
kwargs_no_default, kwargs_with_default = _get_auto_init_kwargs(cls)
signature_params = _get_auto_init_signature(cls)

# Add new __init__ to the subclass
setattr(
cls,
"__init__",
get_auto_init(kwargs_no_default, kwargs_with_default, super_init=super_init),
)

# Add new __signature__ to the subclass
signature = Signature(parameters=signature_params)
setattr(cls, "__signature__", signature)

return cls


def _get_auto_init_kwargs(cls) -> (list, dict):
"""Get the keywords for the __init__.
Collect keywords with and without default values for the init
"""
kwargs_no_default = []
kwargs_with_default = {}

for descriptor in get_descriptors(
descriptor=object.__new__(cls)._init_descriptors_, # pylint: disable=W0212
cls=cls,
):
# For the new __init__
if descriptor.default is Empty:
kwargs_no_default.append(descriptor.name)
else:
kwargs_with_default[descriptor.name] = deepcopy(descriptor.default)

return kwargs_no_default, kwargs_with_default


def _get_auto_init_signature(cls) -> (list, dict, list):
"""Iterate over ZnTrackOptions in the __dict__ and save the option name.
and create a signature Parameter
Returns
-------
kwargs_no_default: list
a list of names that will be converted to kwargs
kwargs_with_default: dict
a dict of {name: default} that will be converted to kwargs
signature_params: inspect.Parameter
"""
signature_params = []
cls_annotations = cls.__annotations__ # pylint: disable=no-member
# fix for https://bugs.python.org/issue46930
for descriptor in get_descriptors(
descriptor=object.__new__(cls)._init_descriptors_, # pylint: disable=W0212
cls=cls,
):
# For the new __signature__
signature_params.append(
Parameter(
# default=...
name=descriptor.name,
kind=Parameter.POSITIONAL_OR_KEYWORD,
annotation=cls_annotations.get(descriptor.name),
)
)
return signature_params


class ZnInit: # pylint: disable=R0903
"""Parent class for automatic __init__ generation based on descriptors.
Expand Down Expand Up @@ -177,101 +262,20 @@ def __init_subclass__(cls, **kwargs):
log.debug(
f"Found {_init_subclass_basecls_} instance - adding dataclass-like __init__"
)
return cls._update_init(super_init=_init_subclass_basecls_.__init__)

@classmethod
def _get_descriptors(cls):
return get_descriptors(descriptor=object.__new__(cls)._init_descriptors_, cls=cls)

@classmethod
def _update_init(cls, super_init):
"""Set the automatic __init__.
Parameters
----------
cls:
the cls to be updated
super_init:
run a super call if required
Returns
-------
the updated cls instance
"""
kwargs_no_default, kwargs_with_default = cls._get_auto_init_kwargs()
signature_params = cls._get_auto_init_signature()

# Add new __init__ to the subclass
setattr(
cls,
"__init__",
get_auto_init(kwargs_no_default, kwargs_with_default, super_init=super_init),
)

# Add new __signature__ to the subclass
signature = Signature(parameters=signature_params)
setattr(cls, "__signature__", signature)

return cls

@classmethod
def _get_auto_init_kwargs(cls) -> (list, dict):
"""Get the keywords for the __init__.
Collect keywords with and without default values for the init
"""
kwargs_no_default = []
kwargs_with_default = {}

for descriptor in cls._get_descriptors():
# For the new __init__
if descriptor.default is Empty:
kwargs_no_default.append(descriptor.name)
else:
kwargs_with_default[descriptor.name] = deepcopy(descriptor.default)

return kwargs_no_default, kwargs_with_default

@classmethod
def _get_auto_init_signature(cls) -> (list, dict, list):
"""Iterate over ZnTrackOptions in the __dict__ and save the option name.
and create a signature Parameter
Returns
-------
kwargs_no_default: list
a list of names that will be converted to kwargs
kwargs_with_default: dict
a dict of {name: default} that will be converted to kwargs
signature_params: inspect.Parameter
"""
signature_params = []
cls_annotations = cls.__annotations__ # pylint: disable=no-member
# fix for https://bugs.python.org/issue46930
for descriptor in cls._get_descriptors():
# For the new __signature__
signature_params.append(
Parameter(
# default=...
name=descriptor.name,
kind=Parameter.POSITIONAL_OR_KEYWORD,
annotation=cls_annotations.get(descriptor.name),
)
)
return signature_params
return _update_init(cls=cls, super_init=_init_subclass_basecls_.__init__)

def __repr__(self):
"""Get a dataclass like representation of the ZnInit class."""
if not self._use_repr_:
return super().__repr__()
repr_str = f"{self.__class__.__name__}("
fields = []
for descriptor in self._get_descriptors():
for descriptor in get_descriptors(descriptor=self._init_descriptors_, self=self):
if not descriptor.use_repr:
continue
fields.append(f"{descriptor.name}={repr(getattr(self, descriptor.name))}")
representation = descriptor.get_repr(getattr(self, descriptor.name))

fields.append(f"{descriptor.name}={representation}")
repr_str += ", ".join(fields)
repr_str += ")"
return repr_str
Expand Down
4 changes: 4 additions & 0 deletions zninit/descriptor/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ def __init__(
instance=None,
name="",
use_repr: bool = True,
repr_func: typing.Callable = repr,
check_types: bool = False,
metadata: dict = None,
frozen: bool = False,
Expand All @@ -78,6 +79,8 @@ def __init__(
Freeze the attribute after the first __set__ call.
metadata: dict, default=None
additional metadata for the descriptor.
repr_func: Callable, default=repr
A callable that will be used to compute the _repr_ of the descriptor.
"""
self._default = default
self._owner = owner
Expand All @@ -87,6 +90,7 @@ def __init__(
self.check_types = check_types
self.metadata = metadata or {}
self.frozen = frozen
self.get_repr = repr_func
self._frozen = weakref.WeakKeyDictionary()
if check_types and ("typeguard" not in sys.modules):
raise ImportError(
Expand Down

0 comments on commit b8f1a00

Please sign in to comment.