Skip to content

Commit

Permalink
Allow positional arguments (#21)
Browse files Browse the repository at this point in the history
* introduce keyword

* allow args

* more tests

* bump version

* poetry update

* change default args to true
  • Loading branch information
PythonFZ authored Apr 14, 2023
1 parent 5ef9b21 commit e7e3acf
Show file tree
Hide file tree
Showing 6 changed files with 362 additions and 257 deletions.
7 changes: 7 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"python.testing.pytestArgs": [
"tests"
],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true
}
501 changes: 254 additions & 247 deletions poetry.lock

Large diffs are not rendered by default.

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.9"
version = "0.1.10"
description = "Descriptor based dataclass implementation"
authors = ["zincwarecode <zincwarecode@gmail.com>"]
license = "Apache-2.0"
Expand Down
42 changes: 41 additions & 1 deletion tests/test_AutomaticInit.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class ParentClsPlain(ZnInit):
"""A parent class without any changes."""


class ChildPlainCls(ParentClsPlain):
class ChildPlainCls(ParentClsPlain, allow_args=False):
"""ZnInit child."""

parameter: int = Params()
Expand Down Expand Up @@ -239,3 +239,43 @@ def __post_init__(self):
def test_post_init_called(cls):
"""Test different versions of '_post_init_'."""
assert cls(parameter="Test").called


class AllowArgs(zninit.ZnInit, allow_args=True):
"""Class with 'allow_args'."""

a = zninit.Descriptor()
b = zninit.Descriptor()
c = zninit.Descriptor(default=42)


def test_allow_args():
"""Allow args test."""
instance = AllowArgs(1, 2, 3)
assert instance.a == 1
assert instance.b == 2
assert instance.c == 3

instance = AllowArgs(1, 2)
assert instance.a == 1
assert instance.b == 2
assert instance.c == 42

instance = AllowArgs(1, 2, c=3)
assert instance.a == 1
assert instance.b == 2
assert instance.c == 3

instance = AllowArgs(a=1, b=2, c=3)
assert instance.a == 1
assert instance.b == 2
assert instance.c == 3

with pytest.raises(TypeError):
instance = AllowArgs(1)

with pytest.raises(TypeError):
instance = AllowArgs(1, 2, 3, 4)

with pytest.raises(TypeError):
instance = AllowArgs(1, 2, a=1)
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.9"
assert zninit.__version__ == "0.1.10"
65 changes: 58 additions & 7 deletions zninit/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,45 @@ def get_init_type_error(
)


def _handle_args(args, kwargs, kwarg_names, cls_name):
"""Add args to kwargs dict.
Parameters
----------
args: list
list of positional arguments
kwargs: dict
dict of keyword arguments
kwarg_names: list
list of keyword names
cls_name: str
name of the class
Raises
------
TypeError
if more positional arguments are given than keyword arguments
if a keyword argument is given multiple times
"""
if len(args) > len(kwarg_names):
raise TypeError(
f"{cls_name}.__init__() takes {len(kwarg_names)} positional arguments"
f" but {len(args)} were given"
)

if args:
for idx, arg_value in enumerate(args):
if kwarg_names[idx] in kwargs:
raise TypeError(f"got multiple values for argument '{kwarg_names[idx]}'")
kwargs[kwarg_names[idx]] = arg_value


def get_auto_init(
kwargs_no_default: typing.List[str],
kwargs_with_default: dict,
super_init: typing.Callable,
allow_args: bool,
):
"""Automatically create an __init__ based on fields.
Expand All @@ -73,6 +108,8 @@ def get_auto_init(
__init__(self, foo=None, bar=10)
super_init: Callable
typically this is Node.__init__
allow_args: bool
allow args in the __init__. Otherwise only kwargs are allowed
"""
kwargs_no_default = [] if kwargs_no_default is None else kwargs_no_default
kwargs_with_default = {} if kwargs_with_default is None else kwargs_with_default
Expand All @@ -83,9 +120,13 @@ def auto_init(self, *args, **kwargs):
required_keys = []
uses_auto_init = getattr(self.__init__, "uses_auto_init", False)
cls_name = self.__class__.__name__
if args:
if args and not allow_args:
raise get_args_type_error(args, cls_name, uses_auto_init)
log.debug(f"The '__init__' uses auto_init: {uses_auto_init}")

kwarg_names = kwargs_no_default + list(kwargs_with_default)
_handle_args(args, kwargs, kwarg_names, cls_name)

for kwarg_name in kwargs_no_default:
try: # pylint: disable=R8203
init_kwargs[kwarg_name] = kwargs.pop(kwarg_name)
Expand Down Expand Up @@ -114,7 +155,7 @@ def auto_init(self, *args, **kwargs):
return auto_init


def _update_init(cls, super_init):
def _update_init(cls, super_init, allow_args):
"""Set the automatic __init__.
Parameters
Expand All @@ -123,6 +164,9 @@ def _update_init(cls, super_init):
the cls to be updated
super_init:
run a super call if required
allow_args:
allow args in the __init__.
Otherwise only kwargs are allowed
Returns
-------
Expand All @@ -136,7 +180,12 @@ def _update_init(cls, super_init):
setattr(
cls,
"__init__",
get_auto_init(kwargs_no_default, kwargs_with_default, super_init=super_init),
get_auto_init(
kwargs_no_default,
kwargs_with_default,
super_init=super_init,
allow_args=allow_args,
),
)

# Add new __signature__ to the subclass
Expand All @@ -146,7 +195,7 @@ def _update_init(cls, super_init):
return cls


def _get_auto_init_kwargs(cls) -> (list, dict):
def _get_auto_init_kwargs(cls) -> typing.Tuple[list, dict]:
"""Get the keywords for the __init__.
Collect keywords with and without default values for the init
Expand All @@ -167,7 +216,7 @@ def _get_auto_init_kwargs(cls) -> (list, dict):
return kwargs_no_default, kwargs_with_default


def _get_auto_init_signature(cls) -> (list, dict, list):
def _get_auto_init_signature(cls) -> typing.Tuple[list, dict, list]:
"""Iterate over ZnTrackOptions in the __dict__ and save the option name.
and create a signature Parameter
Expand Down Expand Up @@ -242,7 +291,7 @@ def __init__(self):
TypeError: ZnInit.__init__() got an unexpected keyword argument ...
"""

def __init_subclass__(cls, **kwargs):
def __init_subclass__(cls, allow_args: bool = True, **kwargs):
"""Magic method which is called upon class inheritance."""
super().__init_subclass__(**kwargs)
_init_subclass_basecls_ = object.__new__(cls)._init_subclass_basecls_
Expand All @@ -263,7 +312,9 @@ def __init_subclass__(cls, **kwargs):
log.debug(
f"Found {_init_subclass_basecls_} instance - adding dataclass-like __init__"
)
return _update_init(cls=cls, super_init=_init_subclass_basecls_.__init__)
return _update_init(
cls=cls, super_init=_init_subclass_basecls_.__init__, allow_args=allow_args
)

def __repr__(self):
"""Get a dataclass like representation of the ZnInit class."""
Expand Down

0 comments on commit e7e3acf

Please sign in to comment.