ducktools-classbuilder 0.8.0__tar.gz → 0.8.2__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.8.0 → ducktools_classbuilder-0.8.2}/MANIFEST.in +1 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/PKG-INFO +2 -24
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/docs/extension_examples.md +19 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/docs/index.md +4 -5
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/pyproject.toml +0 -1
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/src/ducktools/classbuilder/__init__.py +15 -5
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/src/ducktools/classbuilder/__init__.pyi +3 -0
- ducktools_classbuilder-0.8.2/src/ducktools/classbuilder/_version.py +2 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/src/ducktools/classbuilder/annotations.py +47 -10
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/src/ducktools/classbuilder/prefab.py +6 -1
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/src/ducktools/classbuilder/prefab.pyi +5 -2
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/src/ducktools_classbuilder.egg-info/PKG-INFO +2 -24
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/src/ducktools_classbuilder.egg-info/SOURCES.txt +1 -5
- ducktools_classbuilder-0.8.0/perf/cluegen.py +0 -127
- ducktools_classbuilder-0.8.0/perf/dataklasses.py +0 -102
- ducktools_classbuilder-0.8.0/perf/hyperfine_testmaker.py +0 -311
- ducktools_classbuilder-0.8.0/perf/perf_profile.py +0 -291
- ducktools_classbuilder-0.8.0/src/ducktools/classbuilder/_version.py +0 -2
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/.github/dependabot.yml +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/.github/workflows/auto_test.yml +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/.github/workflows/publish_to_pypi.yml +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/.github/workflows/publish_to_testpypi.yml +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/.gitignore +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/.readthedocs.yaml +0 -0
- /ducktools_classbuilder-0.8.0/LICENSE.md → /ducktools_classbuilder-0.8.2/LICENSE +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/README.md +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/docs/Makefile +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/docs/api.md +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/docs/approach_vs_tool.md +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/docs/conf.py +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/docs/generated_code.md +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/docs/make.bat +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/docs/perf/performance_tests.md +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/docs/prefab/index.md +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/docs/tutorial.md +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/docs_code/docs_ex10_frozen_attributes.py +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/docs_code/docs_ex1_basic.py +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/docs_code/docs_ex2_register.py +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/docs_code/docs_ex3_iterable.py +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/docs_code/docs_ex5_frozen.py +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/docs_code/docs_ex7_posonly.py +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/docs_code/docs_ex8_converters.py +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/docs_code/docs_ex9_annotated.py +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/docs_code/index_example.py +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/docs_code/tutorial_code.py +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/setup.cfg +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/src/ducktools/classbuilder/annotations.pyi +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/src/ducktools/classbuilder/py.typed +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/src/ducktools_classbuilder.egg-info/dependency_links.txt +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/src/ducktools_classbuilder.egg-info/requires.txt +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/src/ducktools_classbuilder.egg-info/top_level.txt +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/tests/annotations/test_annotated.py +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/tests/annotations/test_annotations_module.py +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/tests/annotations/test_future_annotations.py +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/tests/conftest.py +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/tests/prefab/dynamic/test_compare_attrib.py +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/tests/prefab/dynamic/test_construction.py +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/tests/prefab/dynamic/test_frozen.py +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/tests/prefab/dynamic/test_internals.py +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/tests/prefab/dynamic/test_pre_post_init.py +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/tests/prefab/dynamic/test_private.py +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/tests/prefab/dynamic/test_slots_novalues.py +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/tests/prefab/dynamic/test_slotted_class.py +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/tests/prefab/dynamic/test_subclass_implementation.py +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/tests/prefab/shared/conftest.py +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/tests/prefab/shared/examples/creation.py +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/tests/prefab/shared/examples/creation_empty.py +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/tests/prefab/shared/examples/dunders.py +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/tests/prefab/shared/examples/fails/creation_2.py +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/tests/prefab/shared/examples/fails/creation_3.py +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/tests/prefab/shared/examples/fails/creation_5.py +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/tests/prefab/shared/examples/fails/inheritance_1.py +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/tests/prefab/shared/examples/fails/inheritance_2.py +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/tests/prefab/shared/examples/funcs_prefabs.py +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/tests/prefab/shared/examples/hint_syntax.py +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/tests/prefab/shared/examples/inheritance.py +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/tests/prefab/shared/examples/init_ex.py +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/tests/prefab/shared/examples/kw_only.py +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/tests/prefab/shared/examples/repr_func.py +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/tests/prefab/shared/test_creation.py +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/tests/prefab/shared/test_dunders.py +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/tests/prefab/shared/test_funcs.py +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/tests/prefab/shared/test_hint_syntax.py +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/tests/prefab/shared/test_inheritance.py +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/tests/prefab/shared/test_init.py +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/tests/prefab/shared/test_kw_only.py +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/tests/prefab/shared/test_repr.py +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/tests/py312_tests/test_generic_annotations.py +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/tests/py314_tests/test_forwardref_annotations.py +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/tests/test_core.py +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/tests/test_field_flags.py +0 -0
- {ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/tests/test_slotmakermeta.py +0 -0
|
@@ -1,30 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: ducktools-classbuilder
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.2
|
|
4
4
|
Summary: Toolkit for creating class boilerplate generators
|
|
5
5
|
Author: David C Ellis
|
|
6
|
-
License: MIT License
|
|
7
|
-
|
|
8
|
-
Copyright (c) 2024 David C Ellis
|
|
9
|
-
|
|
10
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
-
in the Software without restriction, including without limitation the rights
|
|
13
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
-
furnished to do so, subject to the following conditions:
|
|
16
|
-
|
|
17
|
-
The above copyright notice and this permission notice shall be included in all
|
|
18
|
-
copies or substantial portions of the Software.
|
|
19
|
-
|
|
20
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
-
SOFTWARE.
|
|
27
|
-
|
|
28
6
|
Project-URL: Homepage, https://github.com/davidcellis/ducktools-classbuilder
|
|
29
7
|
Classifier: Development Status :: 4 - Beta
|
|
30
8
|
Classifier: Programming Language :: Python :: 3.8
|
|
@@ -37,7 +15,7 @@ Classifier: Operating System :: OS Independent
|
|
|
37
15
|
Classifier: License :: OSI Approved :: MIT License
|
|
38
16
|
Requires-Python: >=3.8
|
|
39
17
|
Description-Content-Type: text/markdown
|
|
40
|
-
License-File: LICENSE
|
|
18
|
+
License-File: LICENSE
|
|
41
19
|
Provides-Extra: testing
|
|
42
20
|
Requires-Dist: pytest>=8.2; extra == "testing"
|
|
43
21
|
Requires-Dist: pytest-cov; extra == "testing"
|
|
@@ -116,6 +116,25 @@ You could also choose to yield tuples of `name, value` pairs in your implementat
|
|
|
116
116
|
|
|
117
117
|
### Extending Field ###
|
|
118
118
|
|
|
119
|
+
The `Field` class can also be extended as if it is a slotclass, with annotations or
|
|
120
|
+
with `Field` declarations.
|
|
121
|
+
|
|
122
|
+
One notable caveat - if you want to use a `default_factory` in extending `Field` you
|
|
123
|
+
need to declare `default=FIELD_NOTHING` also in order for default to be ignored. This
|
|
124
|
+
is a special case for `Field` and is not needed in general.
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
from ducktools.classbuilder import Field, FIELD_NOTHING
|
|
128
|
+
|
|
129
|
+
class MetadataField(Field):
|
|
130
|
+
metadata: dict = Field(default=FIELD_NOTHING, default_factory=dict)
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
In regular classes the `__init__` function generator considers `NOTHING` to be an
|
|
134
|
+
ignored value, but for `Field` subclasses it is a valid value so `FIELD_NOTHING` is
|
|
135
|
+
the ignored term. This is all because `None` *is* a valid value and can't be used
|
|
136
|
+
as a sentinel for Fields (otherwise `Field(default=None)` couldn't work).
|
|
137
|
+
|
|
119
138
|
#### Positional Only Arguments? ####
|
|
120
139
|
|
|
121
140
|
This is possible, but a little longer as we also need to modify multiple methods
|
|
@@ -77,11 +77,11 @@ is created.
|
|
|
77
77
|
|
|
78
78
|
```python
|
|
79
79
|
from ducktools.classbuilder import (
|
|
80
|
-
SlotMakerMeta,
|
|
81
|
-
annotation_gatherer,
|
|
80
|
+
SlotMakerMeta,
|
|
82
81
|
builder,
|
|
83
82
|
check_argument_order,
|
|
84
83
|
default_methods,
|
|
84
|
+
unified_gatherer,
|
|
85
85
|
)
|
|
86
86
|
|
|
87
87
|
|
|
@@ -91,7 +91,7 @@ class AnnotationClass(metaclass=SlotMakerMeta):
|
|
|
91
91
|
def __init_subclass__(
|
|
92
92
|
cls,
|
|
93
93
|
methods=default_methods,
|
|
94
|
-
gatherer=
|
|
94
|
+
gatherer=unified_gatherer,
|
|
95
95
|
**kwargs
|
|
96
96
|
):
|
|
97
97
|
# Check class dict otherwise this will always be True as this base
|
|
@@ -103,12 +103,11 @@ class AnnotationClass(metaclass=SlotMakerMeta):
|
|
|
103
103
|
super().__init_subclass__(**kwargs)
|
|
104
104
|
|
|
105
105
|
|
|
106
|
-
|
|
107
106
|
class AnnotatedDC(AnnotationClass):
|
|
108
107
|
the_answer: int = 42
|
|
109
108
|
the_question: str = "What do you get if you multiply six by nine?"
|
|
110
109
|
|
|
111
|
-
|
|
110
|
+
|
|
112
111
|
ex = AnnotatedDC()
|
|
113
112
|
print(ex)
|
|
114
113
|
```
|
{ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/src/ducktools/classbuilder/__init__.py
RENAMED
|
@@ -99,11 +99,16 @@ def _get_inst_fields(inst):
|
|
|
99
99
|
# As 'None' can be a meaningful value we need a sentinel value
|
|
100
100
|
# to use to show no value has been provided.
|
|
101
101
|
class _NothingType:
|
|
102
|
+
def __init__(self, custom=None):
|
|
103
|
+
self.custom = custom
|
|
102
104
|
def __repr__(self):
|
|
105
|
+
if self.custom:
|
|
106
|
+
return f"<{self.custom} NOTHING OBJECT>"
|
|
103
107
|
return "<NOTHING OBJECT>"
|
|
104
108
|
|
|
105
109
|
|
|
106
110
|
NOTHING = _NothingType()
|
|
111
|
+
FIELD_NOTHING = _NothingType("FIELD")
|
|
107
112
|
|
|
108
113
|
|
|
109
114
|
# KW_ONLY sentinel 'type' to use to indicate all subsequent attributes are
|
|
@@ -432,11 +437,11 @@ frozen_setattr_maker = MethodMaker("__setattr__", frozen_setattr_generator)
|
|
|
432
437
|
frozen_delattr_maker = MethodMaker("__delattr__", frozen_delattr_generator)
|
|
433
438
|
default_methods = frozenset({init_maker, repr_maker, eq_maker})
|
|
434
439
|
|
|
435
|
-
# Special `__init__` maker for 'Field' subclasses
|
|
440
|
+
# Special `__init__` maker for 'Field' subclasses - needs its own NOTHING option
|
|
436
441
|
_field_init_maker = MethodMaker(
|
|
437
442
|
funcname="__init__",
|
|
438
443
|
code_generator=get_init_generator(
|
|
439
|
-
null=
|
|
444
|
+
null=FIELD_NOTHING,
|
|
440
445
|
extra_code=["self.validate_field()"],
|
|
441
446
|
)
|
|
442
447
|
)
|
|
@@ -649,7 +654,7 @@ class Field(metaclass=SlotMakerMeta):
|
|
|
649
654
|
|
|
650
655
|
def validate_field(self):
|
|
651
656
|
cls_name = self.__class__.__name__
|
|
652
|
-
if self.default is not
|
|
657
|
+
if type(self.default) is not _NothingType and type(self.default_factory) is not _NothingType:
|
|
653
658
|
raise AttributeError(
|
|
654
659
|
f"{cls_name} cannot define both a default value and a default factory."
|
|
655
660
|
)
|
|
@@ -807,6 +812,7 @@ def make_annotation_gatherer(
|
|
|
807
812
|
def make_field_gatherer(
|
|
808
813
|
field_type=Field,
|
|
809
814
|
leave_default_values=False,
|
|
815
|
+
assign_types=True,
|
|
810
816
|
):
|
|
811
817
|
def field_attribute_gatherer(cls_or_ns):
|
|
812
818
|
if isinstance(cls_or_ns, (_MappingProxyType, dict)):
|
|
@@ -819,7 +825,11 @@ def make_field_gatherer(
|
|
|
819
825
|
for k, v in cls_dict.items()
|
|
820
826
|
if isinstance(v, field_type)
|
|
821
827
|
}
|
|
822
|
-
|
|
828
|
+
|
|
829
|
+
if assign_types:
|
|
830
|
+
cls_annotations = get_ns_annotations(cls_dict)
|
|
831
|
+
else:
|
|
832
|
+
cls_annotations = {}
|
|
823
833
|
|
|
824
834
|
cls_modifications = {}
|
|
825
835
|
|
|
@@ -830,7 +840,7 @@ def make_field_gatherer(
|
|
|
830
840
|
else:
|
|
831
841
|
cls_modifications[name] = NOTHING
|
|
832
842
|
|
|
833
|
-
if (anno := cls_annotations.get(name, NOTHING)) is not NOTHING:
|
|
843
|
+
if assign_types and (anno := cls_annotations.get(name, NOTHING)) is not NOTHING:
|
|
834
844
|
cls_attributes[name] = field_type.from_field(attrib, type=anno)
|
|
835
845
|
|
|
836
846
|
return cls_attributes, cls_modifications
|
|
@@ -24,8 +24,10 @@ def get_methods(cls: type) -> types.MappingProxyType[str, MethodMaker]: ...
|
|
|
24
24
|
def _get_inst_fields(inst: typing.Any) -> dict[str, typing.Any]: ...
|
|
25
25
|
|
|
26
26
|
class _NothingType:
|
|
27
|
+
def __init__(self, custom: str | None = ...) -> None: ...
|
|
27
28
|
def __repr__(self) -> str: ...
|
|
28
29
|
NOTHING: _NothingType
|
|
30
|
+
FIELD_NOTHING: _NothingType
|
|
29
31
|
|
|
30
32
|
# noinspection PyPep8Naming
|
|
31
33
|
class _KW_ONLY_TYPE:
|
|
@@ -191,6 +193,7 @@ def make_annotation_gatherer(
|
|
|
191
193
|
def make_field_gatherer(
|
|
192
194
|
field_type: type[_FieldType],
|
|
193
195
|
leave_default_values: bool = False,
|
|
196
|
+
assign_types: bool = True,
|
|
194
197
|
) -> Callable[[type | _CopiableMappings], tuple[dict[str, _FieldType], dict[str, typing.Any]]]: ...
|
|
195
198
|
|
|
196
199
|
@typing.overload
|
|
@@ -22,6 +22,44 @@
|
|
|
22
22
|
import sys
|
|
23
23
|
|
|
24
24
|
|
|
25
|
+
class _LazyAnnotationLib:
|
|
26
|
+
def __init__(self):
|
|
27
|
+
if sys.version_info < (3, 14):
|
|
28
|
+
self.annotationlib_unavailable = True
|
|
29
|
+
else:
|
|
30
|
+
self.annotationlib_unavailable = None
|
|
31
|
+
|
|
32
|
+
def __getattr__(self, item):
|
|
33
|
+
if self.annotationlib_unavailable:
|
|
34
|
+
raise ImportError("'annotationlib' is not available")
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
import annotationlib
|
|
38
|
+
except ImportError:
|
|
39
|
+
self.annotationlib_unavailable = True
|
|
40
|
+
raise ImportError("'annotationlib' is not available")
|
|
41
|
+
else:
|
|
42
|
+
self.Format = annotationlib.Format
|
|
43
|
+
self.call_annotate_function = annotationlib.call_annotate_function
|
|
44
|
+
|
|
45
|
+
# This function keeps getting changed and renamed
|
|
46
|
+
get_ns_annotate = getattr(annotationlib, "get_annotate_from_class_namespace", None)
|
|
47
|
+
if get_ns_annotate is None:
|
|
48
|
+
get_ns_annotate = getattr(annotationlib, "get_annotate_function")
|
|
49
|
+
self.get_ns_annotate = get_ns_annotate
|
|
50
|
+
|
|
51
|
+
if item == "Format":
|
|
52
|
+
return self.Format
|
|
53
|
+
elif item == "call_annotate_function":
|
|
54
|
+
return self.call_annotate_function
|
|
55
|
+
elif item == "get_ns_annotate":
|
|
56
|
+
return get_ns_annotate
|
|
57
|
+
|
|
58
|
+
raise AttributeError(f"{item!r} is not available from this lazy importer")
|
|
59
|
+
|
|
60
|
+
_lazy_annotationlib = _LazyAnnotationLib()
|
|
61
|
+
|
|
62
|
+
|
|
25
63
|
def get_ns_annotations(ns):
|
|
26
64
|
"""
|
|
27
65
|
Given a class namespace, attempt to retrieve the
|
|
@@ -35,19 +73,18 @@ def get_ns_annotations(ns):
|
|
|
35
73
|
if annotations is not None:
|
|
36
74
|
annotations = annotations.copy()
|
|
37
75
|
else:
|
|
38
|
-
# See if we're using PEP-649 annotations
|
|
39
|
-
# Guarding this with a try/except instead of a version check
|
|
40
|
-
# In case there's a change and PEP-649 somehow doesn't make 3.14
|
|
41
76
|
try:
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
pass
|
|
45
|
-
else:
|
|
46
|
-
annotate = ns.get("__annotate__") # Works in the alphas, but may break
|
|
77
|
+
# See if we're using PEP-649 annotations
|
|
78
|
+
annotate = ns.get("__annotate__") # Works in the early alphas
|
|
47
79
|
if not annotate:
|
|
48
|
-
annotate =
|
|
80
|
+
annotate = _lazy_annotationlib.get_ns_annotate(ns)
|
|
49
81
|
if annotate:
|
|
50
|
-
annotations = call_annotate_function(
|
|
82
|
+
annotations = _lazy_annotationlib.call_annotate_function(
|
|
83
|
+
annotate,
|
|
84
|
+
format=_lazy_annotationlib.Format.FORWARDREF
|
|
85
|
+
)
|
|
86
|
+
except ImportError:
|
|
87
|
+
pass
|
|
51
88
|
|
|
52
89
|
if annotations is None:
|
|
53
90
|
annotations = {}
|
{ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/src/ducktools/classbuilder/prefab.py
RENAMED
|
@@ -26,7 +26,7 @@ A 'prebuilt' implementation of class generation.
|
|
|
26
26
|
Includes pre and post init functions along with other methods.
|
|
27
27
|
"""
|
|
28
28
|
from . import (
|
|
29
|
-
INTERNALS_DICT, NOTHING,
|
|
29
|
+
INTERNALS_DICT, NOTHING, FIELD_NOTHING,
|
|
30
30
|
Field, MethodMaker, GatheredFields, GeneratedCode, SlotMakerMeta,
|
|
31
31
|
builder, get_flags, get_fields,
|
|
32
32
|
make_unified_gatherer,
|
|
@@ -289,10 +289,12 @@ class Attribute(Field):
|
|
|
289
289
|
:param kw_only: Make this argument keyword only in init
|
|
290
290
|
:param serialize: Include this attribute in methods that serialize to dict
|
|
291
291
|
:param doc: Parameter documentation for slotted classes
|
|
292
|
+
:param metadata: Additional non-construction related metadata
|
|
292
293
|
:param type: Type of this attribute (for slotted classes)
|
|
293
294
|
"""
|
|
294
295
|
iter: bool = True
|
|
295
296
|
serialize: bool = True
|
|
297
|
+
metadata: dict = Field(default=FIELD_NOTHING, default_factory=dict)
|
|
296
298
|
|
|
297
299
|
|
|
298
300
|
# noinspection PyShadowingBuiltins
|
|
@@ -309,6 +311,7 @@ def attribute(
|
|
|
309
311
|
exclude_field=False,
|
|
310
312
|
private=False,
|
|
311
313
|
doc=None,
|
|
314
|
+
metadata=None,
|
|
312
315
|
type=NOTHING,
|
|
313
316
|
):
|
|
314
317
|
"""
|
|
@@ -326,6 +329,7 @@ def attribute(
|
|
|
326
329
|
:param exclude_field: Shorthand for setting repr, compare, iter and serialize to False
|
|
327
330
|
:param private: Short for init, repr, compare, iter, serialize = False, must have default or factory
|
|
328
331
|
:param doc: Parameter documentation for slotted classes
|
|
332
|
+
:param metadata: Dictionary for additional non-construction metadata
|
|
329
333
|
:param type: Type of this attribute (for slotted classes)
|
|
330
334
|
|
|
331
335
|
:return: Attribute generated with these parameters.
|
|
@@ -356,6 +360,7 @@ def attribute(
|
|
|
356
360
|
serialize=serialize,
|
|
357
361
|
doc=doc,
|
|
358
362
|
type=type,
|
|
363
|
+
metadata=metadata,
|
|
359
364
|
)
|
|
360
365
|
|
|
361
366
|
|
{ducktools_classbuilder-0.8.0 → ducktools_classbuilder-0.8.2}/src/ducktools/classbuilder/prefab.pyi
RENAMED
|
@@ -50,6 +50,7 @@ class Attribute(Field):
|
|
|
50
50
|
|
|
51
51
|
iter: bool
|
|
52
52
|
serialize: bool
|
|
53
|
+
metadata: dict
|
|
53
54
|
|
|
54
55
|
def __init__(
|
|
55
56
|
self,
|
|
@@ -64,6 +65,7 @@ class Attribute(Field):
|
|
|
64
65
|
iter: bool = True,
|
|
65
66
|
kw_only: bool = False,
|
|
66
67
|
serialize: bool = True,
|
|
68
|
+
metadata: dict | None = None,
|
|
67
69
|
) -> None: ...
|
|
68
70
|
|
|
69
71
|
def __repr__(self) -> str: ...
|
|
@@ -74,8 +76,6 @@ def attribute(
|
|
|
74
76
|
*,
|
|
75
77
|
default: typing.Any | _NothingType = NOTHING,
|
|
76
78
|
default_factory: typing.Any | _NothingType = NOTHING,
|
|
77
|
-
type: type | _NothingType = NOTHING,
|
|
78
|
-
doc: str | None = None,
|
|
79
79
|
init: bool = True,
|
|
80
80
|
repr: bool = True,
|
|
81
81
|
compare: bool = True,
|
|
@@ -84,6 +84,9 @@ def attribute(
|
|
|
84
84
|
serialize: bool = True,
|
|
85
85
|
exclude_field: bool = False,
|
|
86
86
|
private: bool = False,
|
|
87
|
+
doc: str | None = None,
|
|
88
|
+
metadata: dict | None = None,
|
|
89
|
+
type: type | _NothingType = NOTHING,
|
|
87
90
|
) -> Attribute: ...
|
|
88
91
|
|
|
89
92
|
def prefab_gatherer(cls_or_ns: type | MappingProxyType) -> tuple[dict[str, Attribute], dict[str, typing.Any]]: ...
|
|
@@ -1,30 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: ducktools-classbuilder
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.2
|
|
4
4
|
Summary: Toolkit for creating class boilerplate generators
|
|
5
5
|
Author: David C Ellis
|
|
6
|
-
License: MIT License
|
|
7
|
-
|
|
8
|
-
Copyright (c) 2024 David C Ellis
|
|
9
|
-
|
|
10
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
-
in the Software without restriction, including without limitation the rights
|
|
13
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
-
furnished to do so, subject to the following conditions:
|
|
16
|
-
|
|
17
|
-
The above copyright notice and this permission notice shall be included in all
|
|
18
|
-
copies or substantial portions of the Software.
|
|
19
|
-
|
|
20
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
-
SOFTWARE.
|
|
27
|
-
|
|
28
6
|
Project-URL: Homepage, https://github.com/davidcellis/ducktools-classbuilder
|
|
29
7
|
Classifier: Development Status :: 4 - Beta
|
|
30
8
|
Classifier: Programming Language :: Python :: 3.8
|
|
@@ -37,7 +15,7 @@ Classifier: Operating System :: OS Independent
|
|
|
37
15
|
Classifier: License :: OSI Approved :: MIT License
|
|
38
16
|
Requires-Python: >=3.8
|
|
39
17
|
Description-Content-Type: text/markdown
|
|
40
|
-
License-File: LICENSE
|
|
18
|
+
License-File: LICENSE
|
|
41
19
|
Provides-Extra: testing
|
|
42
20
|
Requires-Dist: pytest>=8.2; extra == "testing"
|
|
43
21
|
Requires-Dist: pytest-cov; extra == "testing"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
.gitignore
|
|
2
2
|
.readthedocs.yaml
|
|
3
|
-
LICENSE
|
|
3
|
+
LICENSE
|
|
4
4
|
MANIFEST.in
|
|
5
5
|
README.md
|
|
6
6
|
pyproject.toml
|
|
@@ -29,10 +29,6 @@ docs_code/docs_ex8_converters.py
|
|
|
29
29
|
docs_code/docs_ex9_annotated.py
|
|
30
30
|
docs_code/index_example.py
|
|
31
31
|
docs_code/tutorial_code.py
|
|
32
|
-
perf/cluegen.py
|
|
33
|
-
perf/dataklasses.py
|
|
34
|
-
perf/hyperfine_testmaker.py
|
|
35
|
-
perf/perf_profile.py
|
|
36
32
|
src/ducktools/classbuilder/__init__.py
|
|
37
33
|
src/ducktools/classbuilder/__init__.pyi
|
|
38
34
|
src/ducktools/classbuilder/_version.py
|
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
# cluegen.py
|
|
2
|
-
#
|
|
3
|
-
# Classes generated from type clues.
|
|
4
|
-
#
|
|
5
|
-
# https://github.com/dabeaz/cluegen
|
|
6
|
-
#
|
|
7
|
-
# Author: David Beazley (@dabeaz).
|
|
8
|
-
# http://www.dabeaz.com
|
|
9
|
-
#
|
|
10
|
-
# Copyright (C) 2018-2021.
|
|
11
|
-
#
|
|
12
|
-
# Permission is granted to use, copy, and modify this code in any
|
|
13
|
-
# manner as long as this copyright message and disclaimer remain in
|
|
14
|
-
# the source code. There is no warranty. Try to use the code for the
|
|
15
|
-
# greater good.
|
|
16
|
-
|
|
17
|
-
import types
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
# Collect all type clues from a class and base classes.
|
|
21
|
-
def all_clues(cls):
|
|
22
|
-
clues = {}
|
|
23
|
-
for c in reversed(cls.__mro__):
|
|
24
|
-
clues.update(getattr(c, '__annotations__', {}))
|
|
25
|
-
return clues
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
# Decorator to define methods of a class as a code generator.
|
|
29
|
-
def cluegen(func):
|
|
30
|
-
def __get__(self, instance, cls):
|
|
31
|
-
locs = {}
|
|
32
|
-
code = func(cls)
|
|
33
|
-
exec(code, locs)
|
|
34
|
-
meth = locs[func.__name__]
|
|
35
|
-
setattr(cls, func.__name__, meth)
|
|
36
|
-
return meth.__get__(instance, cls)
|
|
37
|
-
|
|
38
|
-
def __set_name__(self, cls, name):
|
|
39
|
-
methods = cls.__dict__.get('_methods', list(cls._methods))
|
|
40
|
-
if '_methods' not in cls.__dict__:
|
|
41
|
-
cls._methods = methods
|
|
42
|
-
cls._methods.append((name, self))
|
|
43
|
-
|
|
44
|
-
return type(f'ClueGen_{func.__name__}', (), dict(__get__=__get__,
|
|
45
|
-
__set_name__=__set_name__))()
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
# Base class for defining data structures
|
|
49
|
-
class DatumBase:
|
|
50
|
-
__slots__ = ()
|
|
51
|
-
_methods = []
|
|
52
|
-
|
|
53
|
-
@classmethod
|
|
54
|
-
def __init_subclass__(cls):
|
|
55
|
-
submethods = []
|
|
56
|
-
for name, val in cls._methods:
|
|
57
|
-
if name not in cls.__dict__:
|
|
58
|
-
setattr(cls, name, val)
|
|
59
|
-
submethods.append((name, val))
|
|
60
|
-
elif val is cls.__dict__[name]:
|
|
61
|
-
submethods.append((name, val))
|
|
62
|
-
|
|
63
|
-
if submethods != cls._methods:
|
|
64
|
-
cls._methods = submethods
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
class Datum(DatumBase):
|
|
68
|
-
__slots__ = ()
|
|
69
|
-
|
|
70
|
-
@classmethod
|
|
71
|
-
def __init_subclass__(cls):
|
|
72
|
-
super().__init_subclass__()
|
|
73
|
-
cls.__match_args__ = tuple(all_clues(cls))
|
|
74
|
-
|
|
75
|
-
@cluegen
|
|
76
|
-
def __init__(cls):
|
|
77
|
-
clues = all_clues(cls)
|
|
78
|
-
args = ', '.join(f'{name}={getattr(cls, name)!r}'
|
|
79
|
-
if hasattr(cls, name) and not isinstance(getattr(cls, name),
|
|
80
|
-
types.MemberDescriptorType) else name
|
|
81
|
-
for name in clues)
|
|
82
|
-
body = '\n'.join(f' self.{name} = {name}'
|
|
83
|
-
for name in clues)
|
|
84
|
-
return f'def __init__(self, {args}):\n{body}\n'
|
|
85
|
-
|
|
86
|
-
@cluegen
|
|
87
|
-
def __repr__(cls):
|
|
88
|
-
clues = all_clues(cls)
|
|
89
|
-
fmt = ', '.join('%s={self.%s!r}' % (name, name) for name in clues)
|
|
90
|
-
return 'def __repr__(self):\n' \
|
|
91
|
-
' return f"{type(self).__name__}(%s)"' % fmt
|
|
92
|
-
|
|
93
|
-
@cluegen
|
|
94
|
-
def __iter__(cls):
|
|
95
|
-
clues = all_clues(cls)
|
|
96
|
-
values = '\n'.join(f' yield self.{name}' for name in clues)
|
|
97
|
-
return 'def __iter__(self):\n' + values
|
|
98
|
-
|
|
99
|
-
@cluegen
|
|
100
|
-
def __eq__(cls):
|
|
101
|
-
clues = all_clues(cls)
|
|
102
|
-
selfvals = ','.join(f'self.{name}' for name in clues)
|
|
103
|
-
othervals = ','.join(f'other.{name}' for name in clues)
|
|
104
|
-
return 'def __eq__(self, other):\n' \
|
|
105
|
-
' if self.__class__ is other.__class__:\n' \
|
|
106
|
-
f' return ({selfvals},) == ({othervals},)\n' \
|
|
107
|
-
' else:\n' \
|
|
108
|
-
' return NotImplemented\n'
|
|
109
|
-
|
|
110
|
-
@cluegen
|
|
111
|
-
def __hash__(cls):
|
|
112
|
-
clues = all_clues(cls)
|
|
113
|
-
if clues:
|
|
114
|
-
self_tuple = '(' + ','.join(f'self.{name}' for name in clues) + ',)'
|
|
115
|
-
else:
|
|
116
|
-
self_tuple = '()'
|
|
117
|
-
return 'def __hash__(self):\n' \
|
|
118
|
-
f' return hash({self_tuple})\n'
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
# Example use
|
|
122
|
-
if __name__ == '__main__':
|
|
123
|
-
# Start defining classes
|
|
124
|
-
class Coordinates(Datum):
|
|
125
|
-
x: int
|
|
126
|
-
y: int
|
|
127
|
-
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
# dataklasses.py
|
|
2
|
-
#
|
|
3
|
-
# https://github.com/dabeaz/dataklasses
|
|
4
|
-
#
|
|
5
|
-
# Author: David Beazley (@dabeaz).
|
|
6
|
-
# http://www.dabeaz.com
|
|
7
|
-
#
|
|
8
|
-
# Copyright (C) 2021-2022.
|
|
9
|
-
#
|
|
10
|
-
# Permission is granted to use, copy, and modify this code in any
|
|
11
|
-
# manner as long as this copyright message and disclaimer remain in
|
|
12
|
-
# the source code. There is no warranty. Try to use the code for the
|
|
13
|
-
# greater good.
|
|
14
|
-
|
|
15
|
-
__all__ = ['dataklass']
|
|
16
|
-
|
|
17
|
-
from functools import lru_cache, reduce
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def codegen(func):
|
|
21
|
-
@lru_cache
|
|
22
|
-
def make_func_code(numfields):
|
|
23
|
-
names = [f'_{n}' for n in range(numfields)]
|
|
24
|
-
exec(func(names), globals(), d := {})
|
|
25
|
-
return d.popitem()[1]
|
|
26
|
-
|
|
27
|
-
return make_func_code
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def patch_args_and_attributes(func, fields, start=0):
|
|
31
|
-
return type(func)(func.__code__.replace(
|
|
32
|
-
co_names=(*func.__code__.co_names[:start], *fields),
|
|
33
|
-
co_varnames=('self', *fields),
|
|
34
|
-
), func.__globals__)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def patch_attributes(func, fields, start=0):
|
|
38
|
-
return type(func)(func.__code__.replace(
|
|
39
|
-
co_names=(*func.__code__.co_names[:start], *fields)
|
|
40
|
-
), func.__globals__)
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def all_hints(cls):
|
|
44
|
-
return reduce(lambda x, y: getattr(y, '__annotations__', {}) | x, cls.__mro__, {})
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
@codegen
|
|
48
|
-
def make__init__(fields):
|
|
49
|
-
code = 'def __init__(self, ' + ','.join(fields) + '):\n'
|
|
50
|
-
return code + '\n'.join(f' self.{name} = {name}\n' for name in fields)
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
@codegen
|
|
54
|
-
def make__repr__(fields):
|
|
55
|
-
return 'def __repr__(self):\n' \
|
|
56
|
-
' return f"{type(self).__name__}(' + \
|
|
57
|
-
', '.join('{self.' + name + '!r}' for name in fields) + ')"\n'
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
@codegen
|
|
61
|
-
def make__eq__(fields):
|
|
62
|
-
selfvals = ','.join(f'self.{name}' for name in fields)
|
|
63
|
-
othervals = ','.join(f'other.{name}' for name in fields)
|
|
64
|
-
return 'def __eq__(self, other):\n' \
|
|
65
|
-
' if self.__class__ is other.__class__:\n' \
|
|
66
|
-
f' return ({selfvals},) == ({othervals},)\n' \
|
|
67
|
-
' else:\n' \
|
|
68
|
-
' return NotImplemented\n'
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
@codegen
|
|
72
|
-
def make__iter__(fields):
|
|
73
|
-
return 'def __iter__(self):\n' + '\n'.join(f' yield self.{name}' for name in fields)
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
@codegen
|
|
77
|
-
def make__hash__(fields):
|
|
78
|
-
self_tuple = '(' + ','.join(f'self.{name}' for name in fields) + ',)'
|
|
79
|
-
return 'def __hash__(self):\n' \
|
|
80
|
-
f' return hash({self_tuple})\n'
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
def dataklass(cls):
|
|
84
|
-
fields = all_hints(cls)
|
|
85
|
-
nfields = len(fields)
|
|
86
|
-
clsdict = vars(cls)
|
|
87
|
-
if not '__init__' in clsdict: cls.__init__ = patch_args_and_attributes(make__init__(nfields), fields)
|
|
88
|
-
if not '__repr__' in clsdict: cls.__repr__ = patch_attributes(make__repr__(nfields), fields, 2)
|
|
89
|
-
if not '__eq__' in clsdict: cls.__eq__ = patch_attributes(make__eq__(nfields), fields, 1)
|
|
90
|
-
# if not '__iter__' in clsdict: cls.__iter__ = patch_attributes(make__iter__(nfields), fields)
|
|
91
|
-
# if not '__hash__' in clsdict: cls.__hash__ = patch_attributes(make__hash__(nfields), fields, 1)
|
|
92
|
-
cls.__match_args__ = tuple(fields)
|
|
93
|
-
return cls
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
# Example use
|
|
97
|
-
if __name__ == '__main__':
|
|
98
|
-
@dataklass
|
|
99
|
-
class Coordinates:
|
|
100
|
-
x: int
|
|
101
|
-
y: int
|
|
102
|
-
|