ducktools-classbuilder 0.2.0__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of ducktools-classbuilder might be problematic. Click here for more details.
- {ducktools_classbuilder-0.2.0/src/ducktools_classbuilder.egg-info → ducktools_classbuilder-0.3.0}/PKG-INFO +1 -1
- {ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/src/ducktools/classbuilder/__init__.py +27 -41
- {ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/src/ducktools/classbuilder/__init__.pyi +2 -3
- {ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/src/ducktools/classbuilder/prefab.py +7 -7
- {ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/src/ducktools/classbuilder/prefab.pyi +4 -4
- {ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0/src/ducktools_classbuilder.egg-info}/PKG-INFO +1 -1
- {ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/src/ducktools_classbuilder.egg-info/SOURCES.txt +1 -0
- {ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/tests/prefab/shared/test_funcs.py +3 -3
- ducktools_classbuilder-0.3.0/tests/test_core.py +319 -0
- {ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/LICENSE.md +0 -0
- {ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/MANIFEST.in +0 -0
- {ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/README.md +0 -0
- {ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/docs/Makefile +0 -0
- {ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/docs/api.md +0 -0
- {ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/docs/approach_vs_tool.md +0 -0
- {ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/docs/conf.py +0 -0
- {ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/docs/extension_examples.md +0 -0
- {ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/docs/index.md +0 -0
- {ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/docs/make.bat +0 -0
- {ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/docs/perf/performance_tests.md +0 -0
- {ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/docs/prefab/index.md +0 -0
- {ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/pyproject.toml +0 -0
- {ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/setup.cfg +0 -0
- {ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/src/ducktools/classbuilder/py.typed +0 -0
- {ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/src/ducktools_classbuilder.egg-info/dependency_links.txt +0 -0
- {ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/src/ducktools_classbuilder.egg-info/requires.txt +0 -0
- {ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/src/ducktools_classbuilder.egg-info/top_level.txt +0 -0
- {ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/tests/prefab/dynamic/test_compare_attrib.py +0 -0
- {ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/tests/prefab/dynamic/test_construction.py +0 -0
- {ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/tests/prefab/dynamic/test_internals.py +0 -0
- {ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/tests/prefab/dynamic/test_pre_post_init.py +0 -0
- {ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/tests/prefab/dynamic/test_slots_novalues.py +0 -0
- {ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/tests/prefab/dynamic/test_slotted_class.py +0 -0
- {ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/tests/prefab/shared/conftest.py +0 -0
- {ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/tests/prefab/shared/examples/creation.py +0 -0
- {ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/tests/prefab/shared/examples/creation_empty.py +0 -0
- {ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/tests/prefab/shared/examples/dunders.py +0 -0
- {ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/tests/prefab/shared/examples/fails/creation_1.py +0 -0
- {ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/tests/prefab/shared/examples/fails/creation_2.py +0 -0
- {ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/tests/prefab/shared/examples/fails/creation_3.py +0 -0
- {ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/tests/prefab/shared/examples/fails/creation_5.py +0 -0
- {ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/tests/prefab/shared/examples/fails/inheritance_1.py +0 -0
- {ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/tests/prefab/shared/examples/fails/inheritance_2.py +0 -0
- {ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/tests/prefab/shared/examples/frozen_prefabs.py +0 -0
- {ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/tests/prefab/shared/examples/funcs_prefabs.py +0 -0
- {ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/tests/prefab/shared/examples/hint_syntax.py +0 -0
- {ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/tests/prefab/shared/examples/inheritance.py +0 -0
- {ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/tests/prefab/shared/examples/init_ex.py +0 -0
- {ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/tests/prefab/shared/examples/kw_only.py +0 -0
- {ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/tests/prefab/shared/examples/repr_func.py +0 -0
- {ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/tests/prefab/shared/test_creation.py +0 -0
- {ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/tests/prefab/shared/test_dunders.py +0 -0
- {ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/tests/prefab/shared/test_frozen.py +0 -0
- {ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/tests/prefab/shared/test_hint_syntax.py +0 -0
- {ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/tests/prefab/shared/test_inheritance.py +0 -0
- {ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/tests/prefab/shared/test_init.py +0 -0
- {ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/tests/prefab/shared/test_kw_only.py +0 -0
- {ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/tests/prefab/shared/test_repr.py +0 -0
{ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/src/ducktools/classbuilder/__init__.py
RENAMED
|
@@ -19,34 +19,12 @@
|
|
|
19
19
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
20
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
21
|
# SOFTWARE.
|
|
22
|
-
__version__ = "v0.
|
|
22
|
+
__version__ = "v0.3.0"
|
|
23
23
|
|
|
24
24
|
# Change this name if you make heavy modifications
|
|
25
25
|
INTERNALS_DICT = "__classbuilder_internals__"
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
def get_internals(cls):
|
|
29
|
-
"""
|
|
30
|
-
Utility function to get the internals dictionary
|
|
31
|
-
or return None.
|
|
32
|
-
|
|
33
|
-
As generated classes will always have 'fields'
|
|
34
|
-
and 'local_fields' attributes this will always
|
|
35
|
-
evaluate as 'truthy' if this is a generated class.
|
|
36
|
-
|
|
37
|
-
Generally you should use the helper get_flags and
|
|
38
|
-
get_fields methods.
|
|
39
|
-
|
|
40
|
-
Usage:
|
|
41
|
-
if internals := get_internals(cls):
|
|
42
|
-
...
|
|
43
|
-
|
|
44
|
-
:param cls: generated class
|
|
45
|
-
:return: internals dictionary of the class or None
|
|
46
|
-
"""
|
|
47
|
-
return getattr(cls, INTERNALS_DICT, None)
|
|
48
|
-
|
|
49
|
-
|
|
50
28
|
def get_fields(cls, *, local=False):
|
|
51
29
|
"""
|
|
52
30
|
Utility function to gather the fields dictionary
|
|
@@ -71,7 +49,9 @@ def get_flags(cls):
|
|
|
71
49
|
return getattr(cls, INTERNALS_DICT)["flags"]
|
|
72
50
|
|
|
73
51
|
|
|
74
|
-
def
|
|
52
|
+
def _get_inst_fields(inst):
|
|
53
|
+
# This is an internal helper for constructing new
|
|
54
|
+
# 'Field' instances from existing ones.
|
|
75
55
|
return {
|
|
76
56
|
k: getattr(inst, k)
|
|
77
57
|
for k in get_fields(type(inst))
|
|
@@ -105,7 +85,7 @@ class MethodMaker:
|
|
|
105
85
|
self.code_generator = code_generator
|
|
106
86
|
|
|
107
87
|
def __repr__(self):
|
|
108
|
-
return f"<MethodMaker for {self.funcname} method>"
|
|
88
|
+
return f"<MethodMaker for {self.funcname!r} method>"
|
|
109
89
|
|
|
110
90
|
def __get__(self, instance, cls):
|
|
111
91
|
local_vars = {}
|
|
@@ -122,7 +102,7 @@ class MethodMaker:
|
|
|
122
102
|
return method.__get__(instance, cls)
|
|
123
103
|
|
|
124
104
|
|
|
125
|
-
def init_maker(cls, *, null=NOTHING):
|
|
105
|
+
def init_maker(cls, *, null=NOTHING, extra_code=None):
|
|
126
106
|
fields = get_fields(cls)
|
|
127
107
|
flags = get_flags(cls)
|
|
128
108
|
|
|
@@ -150,8 +130,17 @@ def init_maker(cls, *, null=NOTHING):
|
|
|
150
130
|
assignments.append(assignment)
|
|
151
131
|
|
|
152
132
|
args = ", ".join(arglist)
|
|
153
|
-
assigns = "\n ".join(assignments)
|
|
154
|
-
code =
|
|
133
|
+
assigns = "\n ".join(assignments) if assignments else "pass\n"
|
|
134
|
+
code = (
|
|
135
|
+
f"def __init__(self, {args}):\n"
|
|
136
|
+
f" {assigns}\n"
|
|
137
|
+
)
|
|
138
|
+
# Handle additional function calls
|
|
139
|
+
# Used for validate_field on fieldclasses
|
|
140
|
+
if extra_code:
|
|
141
|
+
for line in extra_code:
|
|
142
|
+
code += f" {line}\n"
|
|
143
|
+
|
|
155
144
|
return code, globs
|
|
156
145
|
|
|
157
146
|
|
|
@@ -300,7 +289,7 @@ class Field:
|
|
|
300
289
|
:param kwargs: Additional keyword arguments for subclasses
|
|
301
290
|
:return: new field subclass instance
|
|
302
291
|
"""
|
|
303
|
-
argument_dict = {**
|
|
292
|
+
argument_dict = {**_get_inst_fields(fld), **kwargs}
|
|
304
293
|
|
|
305
294
|
return cls(**argument_dict)
|
|
306
295
|
|
|
@@ -407,6 +396,14 @@ def slotclass(cls=None, /, *, methods=default_methods, syntax_check=True):
|
|
|
407
396
|
return cls
|
|
408
397
|
|
|
409
398
|
|
|
399
|
+
def _field_init_func(cls):
|
|
400
|
+
# Fields need a different Nothing for their __init__ generation
|
|
401
|
+
# And an extra call to validate_field
|
|
402
|
+
field_nothing = _NothingType()
|
|
403
|
+
extra_calls = ["self.validate_field()"]
|
|
404
|
+
return init_maker(cls, null=field_nothing, extra_code=extra_calls)
|
|
405
|
+
|
|
406
|
+
|
|
410
407
|
def fieldclass(cls):
|
|
411
408
|
"""
|
|
412
409
|
This is a special decorator for making Field subclasses using __slots__.
|
|
@@ -417,18 +414,7 @@ def fieldclass(cls):
|
|
|
417
414
|
:return: Modified subclass
|
|
418
415
|
"""
|
|
419
416
|
|
|
420
|
-
|
|
421
|
-
# So append it to the code from __init__.
|
|
422
|
-
def field_init_func(cls_):
|
|
423
|
-
code, globs = init_maker(cls_, null=field_nothing)
|
|
424
|
-
code += " self.validate_field()\n"
|
|
425
|
-
return code, globs
|
|
426
|
-
|
|
427
|
-
field_nothing = _NothingType()
|
|
428
|
-
field_init_desc = MethodMaker(
|
|
429
|
-
"__init__",
|
|
430
|
-
field_init_func,
|
|
431
|
-
)
|
|
417
|
+
field_init_desc = MethodMaker("__init__", _field_init_func)
|
|
432
418
|
field_methods = frozenset({field_init_desc, repr_desc, eq_desc})
|
|
433
419
|
|
|
434
420
|
cls = builder(
|
|
@@ -6,13 +6,11 @@ _py_type = type # Alias for type where it is used as a name
|
|
|
6
6
|
__version__: str
|
|
7
7
|
INTERNALS_DICT: str
|
|
8
8
|
|
|
9
|
-
def get_internals(cls) -> dict[str, typing.Any] | None: ...
|
|
10
|
-
|
|
11
9
|
def get_fields(cls: type, *, local: bool = False) -> dict[str, Field]: ...
|
|
12
10
|
|
|
13
11
|
def get_flags(cls:type) -> dict[str, bool]: ...
|
|
14
12
|
|
|
15
|
-
def
|
|
13
|
+
def _get_inst_fields(inst: typing.Any) -> dict[str, typing.Any]: ...
|
|
16
14
|
|
|
17
15
|
class _NothingType:
|
|
18
16
|
...
|
|
@@ -32,6 +30,7 @@ def init_maker(
|
|
|
32
30
|
cls: type,
|
|
33
31
|
*,
|
|
34
32
|
null: _NothingType = NOTHING,
|
|
33
|
+
extra_code: None | list[str] = None
|
|
35
34
|
) -> tuple[str, dict[str, typing.Any]]: ...
|
|
36
35
|
def repr_maker(cls: type) -> tuple[str, dict[str, typing.Any]]: ...
|
|
37
36
|
def eq_maker(cls: type) -> tuple[str, dict[str, typing.Any]]: ...
|
{ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/src/ducktools/classbuilder/prefab.py
RENAMED
|
@@ -398,7 +398,7 @@ def get_asdict_maker():
|
|
|
398
398
|
vals = ", ".join(
|
|
399
399
|
f"'{name}': self.{name}"
|
|
400
400
|
for name, attrib in fields.items()
|
|
401
|
-
if attrib.
|
|
401
|
+
if attrib.serialize and not attrib.exclude_field
|
|
402
402
|
)
|
|
403
403
|
out_dict = f"{{{vals}}}"
|
|
404
404
|
code = f"def as_dict(self): return {out_dict}"
|
|
@@ -428,7 +428,7 @@ class Attribute(Field):
|
|
|
428
428
|
compare=True,
|
|
429
429
|
iter=True,
|
|
430
430
|
kw_only=False,
|
|
431
|
-
|
|
431
|
+
serialize=True,
|
|
432
432
|
exclude_field=False,
|
|
433
433
|
)
|
|
434
434
|
|
|
@@ -450,7 +450,7 @@ def attribute(
|
|
|
450
450
|
compare=True,
|
|
451
451
|
iter=True,
|
|
452
452
|
kw_only=False,
|
|
453
|
-
|
|
453
|
+
serialize=True,
|
|
454
454
|
exclude_field=False,
|
|
455
455
|
doc=None,
|
|
456
456
|
type=NOTHING,
|
|
@@ -466,7 +466,7 @@ def attribute(
|
|
|
466
466
|
:param compare: Include this attribute in the class __eq__
|
|
467
467
|
:param iter: Include this attribute in the class __iter__ if generated
|
|
468
468
|
:param kw_only: Make this argument keyword only in init
|
|
469
|
-
:param
|
|
469
|
+
:param serialize: Include this attribute in methods that serialize to dict
|
|
470
470
|
:param exclude_field: Exclude this field from all magic method generation
|
|
471
471
|
apart from __init__ signature
|
|
472
472
|
and do not include it in PREFAB_FIELDS
|
|
@@ -484,7 +484,7 @@ def attribute(
|
|
|
484
484
|
compare=compare,
|
|
485
485
|
iter=iter,
|
|
486
486
|
kw_only=kw_only,
|
|
487
|
-
|
|
487
|
+
serialize=serialize,
|
|
488
488
|
exclude_field=exclude_field,
|
|
489
489
|
doc=doc,
|
|
490
490
|
type=type,
|
|
@@ -906,7 +906,7 @@ def is_prefab_instance(o):
|
|
|
906
906
|
|
|
907
907
|
def as_dict(o):
|
|
908
908
|
"""
|
|
909
|
-
Get the valid fields from a prefab respecting the
|
|
909
|
+
Get the valid fields from a prefab respecting the serialize
|
|
910
910
|
values of attributes
|
|
911
911
|
|
|
912
912
|
:param o: instance of a prefab class
|
|
@@ -927,5 +927,5 @@ def as_dict(o):
|
|
|
927
927
|
return {
|
|
928
928
|
name: getattr(o, name)
|
|
929
929
|
for name, attrib in flds.items()
|
|
930
|
-
if attrib.
|
|
930
|
+
if attrib.serialize and not attrib.exclude_field
|
|
931
931
|
}
|
{ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/src/ducktools/classbuilder/prefab.pyi
RENAMED
|
@@ -6,7 +6,7 @@ from collections.abc import Callable
|
|
|
6
6
|
from . import (
|
|
7
7
|
INTERNALS_DICT, NOTHING,
|
|
8
8
|
Field, MethodMaker, SlotFields as SlotFields,
|
|
9
|
-
builder, fieldclass,
|
|
9
|
+
builder, fieldclass, get_flags, get_fields, slot_gatherer
|
|
10
10
|
)
|
|
11
11
|
|
|
12
12
|
# noinspection PyUnresolvedReferences
|
|
@@ -63,7 +63,7 @@ class Attribute(Field):
|
|
|
63
63
|
compare: bool
|
|
64
64
|
iter: bool
|
|
65
65
|
kw_only: bool
|
|
66
|
-
|
|
66
|
+
serialize: bool
|
|
67
67
|
exclude_field: bool
|
|
68
68
|
|
|
69
69
|
def __init__(
|
|
@@ -78,7 +78,7 @@ class Attribute(Field):
|
|
|
78
78
|
compare: bool = True,
|
|
79
79
|
iter: bool = True,
|
|
80
80
|
kw_only: bool = False,
|
|
81
|
-
|
|
81
|
+
serialize: bool = True,
|
|
82
82
|
exclude_field: bool = False,
|
|
83
83
|
) -> None: ...
|
|
84
84
|
|
|
@@ -97,7 +97,7 @@ def attribute(
|
|
|
97
97
|
compare: bool = True,
|
|
98
98
|
iter: bool = True,
|
|
99
99
|
kw_only: bool = False,
|
|
100
|
-
|
|
100
|
+
serialize: bool = True,
|
|
101
101
|
exclude_field: bool = False,
|
|
102
102
|
) -> Attribute: ...
|
|
103
103
|
|
|
@@ -21,6 +21,7 @@ src/ducktools_classbuilder.egg-info/SOURCES.txt
|
|
|
21
21
|
src/ducktools_classbuilder.egg-info/dependency_links.txt
|
|
22
22
|
src/ducktools_classbuilder.egg-info/requires.txt
|
|
23
23
|
src/ducktools_classbuilder.egg-info/top_level.txt
|
|
24
|
+
tests/test_core.py
|
|
24
25
|
tests/prefab/dynamic/test_compare_attrib.py
|
|
25
26
|
tests/prefab/dynamic/test_construction.py
|
|
26
27
|
tests/prefab/dynamic/test_internals.py
|
{ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/tests/prefab/shared/test_funcs.py
RENAMED
|
@@ -45,18 +45,18 @@ def test_as_dict_excludes():
|
|
|
45
45
|
@prefab
|
|
46
46
|
class ExcludesUncached:
|
|
47
47
|
name: str
|
|
48
|
-
password: str = attribute(
|
|
48
|
+
password: str = attribute(serialize=False)
|
|
49
49
|
|
|
50
50
|
@prefab(dict_method=True)
|
|
51
51
|
class ExcludesCached:
|
|
52
52
|
name: str
|
|
53
|
-
password: str = attribute(
|
|
53
|
+
password: str = attribute(serialize=False)
|
|
54
54
|
|
|
55
55
|
@prefab(dict_method=True)
|
|
56
56
|
class ExcludesSlots:
|
|
57
57
|
__slots__ = SlotFields(
|
|
58
58
|
name=attribute(type=str),
|
|
59
|
-
password=attribute(
|
|
59
|
+
password=attribute(serialize=False, type=str)
|
|
60
60
|
)
|
|
61
61
|
|
|
62
62
|
@prefab(dict_method=True)
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
# Tests for the core 'builder'
|
|
2
|
+
import pytest
|
|
3
|
+
|
|
4
|
+
from ducktools.classbuilder import (
|
|
5
|
+
INTERNALS_DICT,
|
|
6
|
+
NOTHING,
|
|
7
|
+
get_fields,
|
|
8
|
+
get_flags,
|
|
9
|
+
MethodMaker,
|
|
10
|
+
init_desc,
|
|
11
|
+
builder,
|
|
12
|
+
Field,
|
|
13
|
+
SlotFields,
|
|
14
|
+
slot_gatherer,
|
|
15
|
+
slotclass,
|
|
16
|
+
fieldclass,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_get_fields_flags():
|
|
21
|
+
local_fields = {"Example": Field()}
|
|
22
|
+
resolved_fields = {"ParentField": Field(), "Example": Field()}
|
|
23
|
+
flags = {"slotted": False}
|
|
24
|
+
|
|
25
|
+
internals_dict = {
|
|
26
|
+
"fields": resolved_fields,
|
|
27
|
+
"local_fields": local_fields,
|
|
28
|
+
"flags": flags,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
class ExampleFields:
|
|
32
|
+
...
|
|
33
|
+
|
|
34
|
+
setattr(ExampleFields, INTERNALS_DICT, internals_dict)
|
|
35
|
+
|
|
36
|
+
assert get_fields(ExampleFields) == resolved_fields
|
|
37
|
+
assert get_fields(ExampleFields, local=True) == local_fields
|
|
38
|
+
assert get_flags(ExampleFields) == flags
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_method_maker():
|
|
42
|
+
def generator(cls):
|
|
43
|
+
code = "def demo(self): return self.x"
|
|
44
|
+
globs = {}
|
|
45
|
+
return code, globs
|
|
46
|
+
|
|
47
|
+
method_desc = MethodMaker("demo", generator)
|
|
48
|
+
|
|
49
|
+
assert repr(method_desc) == "<MethodMaker for 'demo' method>"
|
|
50
|
+
|
|
51
|
+
class ValueX:
|
|
52
|
+
demo = method_desc
|
|
53
|
+
|
|
54
|
+
def __init__(self):
|
|
55
|
+
self.x = "Example Value"
|
|
56
|
+
|
|
57
|
+
ex = ValueX()
|
|
58
|
+
|
|
59
|
+
assert ValueX.__dict__["demo"] == method_desc
|
|
60
|
+
|
|
61
|
+
assert ex.x == "Example Value"
|
|
62
|
+
assert ex.demo() == "Example Value"
|
|
63
|
+
|
|
64
|
+
# Should no longer be equal as demo was called
|
|
65
|
+
assert ValueX.__dict__["demo"] != method_desc
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def test_construct_field():
|
|
69
|
+
f = Field()
|
|
70
|
+
assert f.default is NOTHING
|
|
71
|
+
assert f.default_factory is NOTHING
|
|
72
|
+
assert f.type is NOTHING
|
|
73
|
+
assert f.doc is None
|
|
74
|
+
|
|
75
|
+
with pytest.raises(AttributeError):
|
|
76
|
+
Field(default=None, default_factory=list)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def test_eq_field():
|
|
80
|
+
f1 = Field(default=True)
|
|
81
|
+
f2 = Field(default=False)
|
|
82
|
+
f3 = Field(default_factory=list)
|
|
83
|
+
f4 = Field(default=True, type=bool)
|
|
84
|
+
f5 = Field(default=True, doc="True or False")
|
|
85
|
+
|
|
86
|
+
assert f1 != f2
|
|
87
|
+
assert f1 != f3
|
|
88
|
+
assert f1 != f4
|
|
89
|
+
assert f1 != f5
|
|
90
|
+
|
|
91
|
+
f1r = Field(default=True)
|
|
92
|
+
assert f1 == f1r
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def test_from_field():
|
|
96
|
+
f1 = Field(default=True)
|
|
97
|
+
f2 = Field(default=False)
|
|
98
|
+
f3 = Field(default_factory=list)
|
|
99
|
+
f4 = Field(default=True, type=bool)
|
|
100
|
+
f5 = Field(default=True, doc="True or False")
|
|
101
|
+
|
|
102
|
+
for fld in [f1, f2, f3, f4, f5]:
|
|
103
|
+
assert fld == Field.from_field(fld)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def test_repr_field():
|
|
107
|
+
f1 = Field(default=True)
|
|
108
|
+
f2 = Field(default=False)
|
|
109
|
+
f3 = Field(default_factory=list)
|
|
110
|
+
f4 = Field(default=True, type=bool)
|
|
111
|
+
f5 = Field(default=True, doc="True or False")
|
|
112
|
+
|
|
113
|
+
nothing_repr = repr(NOTHING)
|
|
114
|
+
|
|
115
|
+
f1_repr = f"Field(default=True, default_factory={nothing_repr}, type={nothing_repr}, doc=None)"
|
|
116
|
+
f2_repr = f"Field(default=False, default_factory={nothing_repr}, type={nothing_repr}, doc=None)"
|
|
117
|
+
f3_repr = f"Field(default={nothing_repr}, default_factory=<class 'list'>, type={nothing_repr}, doc=None)"
|
|
118
|
+
f4_repr = f"Field(default=True, default_factory={nothing_repr}, type=<class 'bool'>, doc=None)"
|
|
119
|
+
f5_repr = f"Field(default=True, default_factory={nothing_repr}, type={nothing_repr}, doc='True or False')"
|
|
120
|
+
|
|
121
|
+
assert repr(f1) == f1_repr
|
|
122
|
+
assert repr(f2) == f2_repr
|
|
123
|
+
assert repr(f3) == f3_repr
|
|
124
|
+
assert repr(f4) == f4_repr
|
|
125
|
+
assert repr(f5) == f5_repr
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def test_slot_gatherer_success():
|
|
129
|
+
|
|
130
|
+
fields = {
|
|
131
|
+
"a": Field(default=1),
|
|
132
|
+
"b": Field(default=2),
|
|
133
|
+
"c": Field(default_factory=list, doc="a list"),
|
|
134
|
+
"d": Field(type=str)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
class SlotsExample:
|
|
138
|
+
__slots__ = SlotFields(
|
|
139
|
+
a=1,
|
|
140
|
+
b=Field(default=2),
|
|
141
|
+
c=Field(default_factory=list, doc="a list"),
|
|
142
|
+
d=Field(type=str),
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
slots = slot_gatherer(SlotsExample)
|
|
146
|
+
|
|
147
|
+
assert slots == fields
|
|
148
|
+
assert SlotsExample.__slots__ == {"a": None, "b": None, "c": "a list", "d": None}
|
|
149
|
+
assert SlotsExample.__annotations__ == {"d": str}
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def test_slot_gatherer_failure():
|
|
153
|
+
class NoSlots:
|
|
154
|
+
...
|
|
155
|
+
|
|
156
|
+
with pytest.raises(TypeError):
|
|
157
|
+
slot_gatherer(NoSlots)
|
|
158
|
+
|
|
159
|
+
class WrongSlots:
|
|
160
|
+
__slots__ = ["a", "b", "c"]
|
|
161
|
+
|
|
162
|
+
with pytest.raises(TypeError):
|
|
163
|
+
slot_gatherer(WrongSlots)
|
|
164
|
+
|
|
165
|
+
class DictSlots:
|
|
166
|
+
__slots__ = {"a": "documentation"}
|
|
167
|
+
|
|
168
|
+
with pytest.raises(TypeError):
|
|
169
|
+
slot_gatherer(DictSlots)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def test_slotclass_empty():
|
|
173
|
+
@slotclass
|
|
174
|
+
class SlotClass:
|
|
175
|
+
__slots__ = SlotFields()
|
|
176
|
+
|
|
177
|
+
ex = SlotClass()
|
|
178
|
+
ex2 = SlotClass()
|
|
179
|
+
|
|
180
|
+
assert repr(ex) == "test_slotclass_empty.<locals>.SlotClass()"
|
|
181
|
+
assert ex == ex2
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def test_slotclass_methods():
|
|
185
|
+
|
|
186
|
+
class SlotClass:
|
|
187
|
+
__slots__ = SlotFields()
|
|
188
|
+
|
|
189
|
+
assert "__init__" not in SlotClass.__dict__
|
|
190
|
+
assert "__repr__" not in SlotClass.__dict__
|
|
191
|
+
assert "__eq__" not in SlotClass.__dict__
|
|
192
|
+
|
|
193
|
+
SlotClass = slotclass(SlotClass)
|
|
194
|
+
|
|
195
|
+
assert "__init__" in SlotClass.__dict__
|
|
196
|
+
assert "__repr__" in SlotClass.__dict__
|
|
197
|
+
assert "__eq__" in SlotClass.__dict__
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def test_slotclass_attributes():
|
|
201
|
+
@slotclass
|
|
202
|
+
class SlotClass:
|
|
203
|
+
__slots__ = SlotFields(
|
|
204
|
+
a=1,
|
|
205
|
+
b=Field(default=2, type=int),
|
|
206
|
+
c=Field(default_factory=list, doc="a list"),
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
prefix = "test_slotclass_attributes.<locals>."
|
|
210
|
+
|
|
211
|
+
ex = SlotClass()
|
|
212
|
+
ex2 = SlotClass()
|
|
213
|
+
ex3 = SlotClass(c=[1, 2, 3])
|
|
214
|
+
ex4 = SlotClass(4, 5, [1, 2, 3])
|
|
215
|
+
|
|
216
|
+
assert ex.a == 1
|
|
217
|
+
assert ex.b == 2
|
|
218
|
+
assert ex.c == []
|
|
219
|
+
|
|
220
|
+
assert ex3.c == [1, 2, 3]
|
|
221
|
+
|
|
222
|
+
assert ex4.a == 4
|
|
223
|
+
assert ex4.b == 5
|
|
224
|
+
assert ex4.c == [1, 2, 3]
|
|
225
|
+
|
|
226
|
+
assert ex == ex2
|
|
227
|
+
assert ex != ex3
|
|
228
|
+
|
|
229
|
+
assert repr(ex) == f"{prefix}SlotClass(a=1, b=2, c=[])"
|
|
230
|
+
assert repr(ex3) == f"{prefix}SlotClass(a=1, b=2, c=[1, 2, 3])"
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def test_slotclass_nodefault():
|
|
234
|
+
@slotclass
|
|
235
|
+
class SlotClass:
|
|
236
|
+
__slots__ = SlotFields(
|
|
237
|
+
a=Field(),
|
|
238
|
+
b=2,
|
|
239
|
+
c=Field(default_factory=list, doc="a list"),
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
ex = SlotClass(1)
|
|
243
|
+
ex2 = SlotClass(a=2, b=4, c=[8, 16, 32])
|
|
244
|
+
|
|
245
|
+
assert ex.a == 1
|
|
246
|
+
assert ex.b == 2
|
|
247
|
+
assert ex.c == []
|
|
248
|
+
|
|
249
|
+
assert ex2.a == 2
|
|
250
|
+
assert ex2.b == 4
|
|
251
|
+
assert ex2.c == [8, 16, 32]
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def test_slotclass_ordering():
|
|
255
|
+
with pytest.raises(SyntaxError):
|
|
256
|
+
# Non-default argument after default
|
|
257
|
+
@slotclass
|
|
258
|
+
class OrderingError:
|
|
259
|
+
__slots__ = SlotFields(
|
|
260
|
+
x=1,
|
|
261
|
+
y=Field(),
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def test_slotclass_norepr_noeq():
|
|
266
|
+
@slotclass(methods={init_desc})
|
|
267
|
+
class SlotClass:
|
|
268
|
+
__slots__ = SlotFields(
|
|
269
|
+
a=Field(),
|
|
270
|
+
b=2,
|
|
271
|
+
c=Field(default_factory=list, doc="a list"),
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
assert "__repr__" not in SlotClass.__dict__
|
|
275
|
+
assert "__eq__" not in SlotClass.__dict__
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def test_fieldclass():
|
|
279
|
+
@fieldclass
|
|
280
|
+
class NewField(Field):
|
|
281
|
+
__slots__ = SlotFields(serialize=True)
|
|
282
|
+
|
|
283
|
+
f = NewField()
|
|
284
|
+
|
|
285
|
+
assert f.default is NOTHING
|
|
286
|
+
assert f.default_factory is NOTHING
|
|
287
|
+
assert f.type is NOTHING
|
|
288
|
+
assert f.doc is None
|
|
289
|
+
assert f.serialize is True
|
|
290
|
+
|
|
291
|
+
f2 = NewField(default=1, serialize=False)
|
|
292
|
+
|
|
293
|
+
assert f2.default == 1
|
|
294
|
+
assert f2.serialize is False
|
|
295
|
+
|
|
296
|
+
with pytest.raises(TypeError):
|
|
297
|
+
# All arguments are keyword only in fieldclasses
|
|
298
|
+
NewField(42)
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def test_builder_noclass():
|
|
302
|
+
mini_slotclass = builder(gatherer=slot_gatherer, methods={init_desc})
|
|
303
|
+
|
|
304
|
+
@mini_slotclass
|
|
305
|
+
class SlotClass:
|
|
306
|
+
__slots__ = SlotFields(
|
|
307
|
+
a=Field(),
|
|
308
|
+
b=2,
|
|
309
|
+
c=Field(default_factory=list, doc="a list"),
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
assert "__init__" in SlotClass.__dict__
|
|
313
|
+
assert "__repr__" not in SlotClass.__dict__
|
|
314
|
+
assert "__eq__" not in SlotClass.__dict__
|
|
315
|
+
|
|
316
|
+
x = SlotClass(12)
|
|
317
|
+
assert x.a == 12
|
|
318
|
+
assert x.b == 2
|
|
319
|
+
assert x.c == []
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/docs/perf/performance_tests.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/src/ducktools/classbuilder/py.typed
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/tests/prefab/dynamic/test_internals.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/tests/prefab/shared/conftest.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/tests/prefab/shared/test_creation.py
RENAMED
|
File without changes
|
{ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/tests/prefab/shared/test_dunders.py
RENAMED
|
File without changes
|
{ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/tests/prefab/shared/test_frozen.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/tests/prefab/shared/test_init.py
RENAMED
|
File without changes
|
{ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/tests/prefab/shared/test_kw_only.py
RENAMED
|
File without changes
|
{ducktools_classbuilder-0.2.0 → ducktools_classbuilder-0.3.0}/tests/prefab/shared/test_repr.py
RENAMED
|
File without changes
|