From a64e6c8e82ea1f8bc9be035bff7a80585750d065 Mon Sep 17 00:00:00 2001 From: Fabian Zills <46721498+PythonFZ@users.noreply.github.com> Date: Wed, 7 Dec 2022 17:36:01 +0100 Subject: [PATCH] fix issues in #7 with metaclasses (#8) * fix issues in #7 with metaclasses * remove unused code --- pyproject.toml | 2 +- tests/test_i_zninit.py | 23 +++++++++++++ tests/test_zninit.py | 2 +- zninit/core/__init__.py | 63 +++++++++++++---------------------- zninit/descriptor/__init__.py | 2 -- 5 files changed, 48 insertions(+), 44 deletions(-) create mode 100644 tests/test_i_zninit.py diff --git a/pyproject.toml b/pyproject.toml index 1872654..b413695 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "zninit" -version = "0.1.4" +version = "0.1.5" description = "Descriptor based dataclass implementation" authors = ["zincwarecode "] license = "Apache-2.0" diff --git a/tests/test_i_zninit.py b/tests/test_i_zninit.py new file mode 100644 index 0000000..48a50ca --- /dev/null +++ b/tests/test_i_zninit.py @@ -0,0 +1,23 @@ +"""General 'ZnInit' integration tests.""" +import zninit + + +class GetItemMeta(type): + """Metaclass for general testing.""" + + def __getitem__(cls, item): + """General purpose metaclass.""" + return item + + +class ExampleCls(zninit.ZnInit, metaclass=GetItemMeta): + """Example 'ZnInit' with metaclass.""" + + parameter = zninit.Descriptor() + + +def test_ExampleCls(): + """Test 'ZnInit' with a metaclass.""" + example = ExampleCls(parameter=25) + assert example.parameter == 25 + assert ExampleCls[42] == 42 diff --git a/tests/test_zninit.py b/tests/test_zninit.py index c127c47..d626987 100644 --- a/tests/test_zninit.py +++ b/tests/test_zninit.py @@ -5,4 +5,4 @@ def test_version(): """Test the installed version.""" - assert zninit.__version__ == "0.1.4" + assert zninit.__version__ == "0.1.5" diff --git a/zninit/core/__init__.py b/zninit/core/__init__.py index 73b01e8..db9c861 100644 --- a/zninit/core/__init__.py +++ b/zninit/core/__init__.py @@ -114,31 +114,7 @@ def auto_init(self, *args, **kwargs): return auto_init -def update_attribute_names(cls): - """Update changed attribute names. - - E.g. 'init_descriptors' was renamed to '_init_descriptors_' but should be - backwards compatible. This was done according to PEP8 style guide where - '_single_leading_underscore' are meant for weak internal usage. - """ - if cls.init_descriptors is not None: - cls._init_descriptors_ = cls.init_descriptors # pylint: disable=W0212 - if cls.use_repr is not None: - cls._use_repr_ = cls.use_repr # pylint: disable=W0212 - if cls.init_subclass_basecls is not None: - cls._init_subclass_basecls_ = cls.init_subclass_basecls # pylint: disable=W0212 - - -class Meta(type): - """Metaclass to 'update_attribute_names'.""" - - def __new__(cls, *args, **kwargs): - meta_cls = super().__new__(cls, *args, **kwargs) - update_attribute_names(meta_cls) - return meta_cls - - -class ZnInit(metaclass=Meta): +class ZnInit: """Parent class for automatic __init__ generation based on descriptors. Attributes @@ -155,14 +131,22 @@ class ZnInit(metaclass=Meta): __init__ of the basecls will be called via super. """ - _init_descriptors_: typing.List[Descriptor] = [Descriptor] - _use_repr_: bool = True - _init_subclass_basecls_ = None - - init_descriptors: typing.List[Descriptor] = None - use_repr: bool = None + init_descriptors: typing.List[Descriptor] = [Descriptor] + use_repr: bool = True init_subclass_basecls = None + @property + def _init_descriptors_(self) -> typing.List[Descriptor]: + return self.init_descriptors + + @property + def _use_repr_(self) -> bool: + return self.use_repr + + @property + def _init_subclass_basecls_(self) -> typing.Type: + return self.init_subclass_basecls + def __init__(self): """Required for Error messages. @@ -175,14 +159,15 @@ def __init__(self): def __init_subclass__(cls, **kwargs): super().__init_subclass__(**kwargs) - update_attribute_names(cls) - if cls._init_subclass_basecls_ is None: - cls._init_subclass_basecls_ = ZnInit + _init_subclass_basecls_ = object.__new__(cls)._init_subclass_basecls_ + + if _init_subclass_basecls_ is None: + _init_subclass_basecls_ = ZnInit for inherited in cls.__mro__: # Go through the mro until you find the init_subclass_basecls. # If found an init before that class it will implement super # if not add the fields to the __init__ automatically. - if inherited == cls._init_subclass_basecls_: + if inherited == _init_subclass_basecls_: break if inherited.__dict__.get("__init__") is not None: @@ -190,15 +175,13 @@ def __init_subclass__(cls, **kwargs): return cls log.debug( - f"Found {cls._init_subclass_basecls_} instance - adding dataclass-like" - " __init__" + f"Found {_init_subclass_basecls_} instance - adding dataclass-like __init__" ) - return cls._update_init(super_init=cls._init_subclass_basecls_.__init__) + return cls._update_init(super_init=_init_subclass_basecls_.__init__) @classmethod def _get_descriptors(cls): - update_attribute_names(cls) - return get_descriptors(descriptor=cls._init_descriptors_, cls=cls) + return get_descriptors(descriptor=object.__new__(cls)._init_descriptors_, cls=cls) @classmethod def _update_init(cls, super_init): diff --git a/zninit/descriptor/__init__.py b/zninit/descriptor/__init__.py index 64e9868..187d05e 100644 --- a/zninit/descriptor/__init__.py +++ b/zninit/descriptor/__init__.py @@ -188,8 +188,6 @@ def get_descriptors( a list of the found descriptor objects """ - if descriptor is None: - return [] if self is None and cls is None: raise ValueError("Either self or cls must not be None") if self is not None and cls is not None: