ducktools-classbuilder 0.7.1__tar.gz → 0.7.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.7.1 → ducktools_classbuilder-0.7.2}/PKG-INFO +1 -1
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/docs/extension_examples.md +132 -0
- ducktools_classbuilder-0.7.2/docs_code/docs_ex10_frozen_attributes.py +125 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/src/ducktools/classbuilder/__init__.py +14 -10
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/src/ducktools/classbuilder/__init__.pyi +7 -7
- ducktools_classbuilder-0.7.2/src/ducktools/classbuilder/_version.py +2 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/src/ducktools_classbuilder.egg-info/PKG-INFO +1 -1
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/src/ducktools_classbuilder.egg-info/SOURCES.txt +1 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/tests/annotations/test_annotated.py +12 -7
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/tests/test_core.py +27 -0
- ducktools_classbuilder-0.7.1/src/ducktools/classbuilder/_version.py +0 -2
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/.github/dependabot.yml +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/.github/workflows/auto_test.yml +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/.github/workflows/publish_to_pypi.yml +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/.github/workflows/publish_to_testpypi.yml +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/.gitignore +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/.readthedocs.yaml +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/LICENSE.md +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/MANIFEST.in +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/README.md +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/docs/Makefile +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/docs/api.md +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/docs/approach_vs_tool.md +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/docs/conf.py +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/docs/generated_code.md +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/docs/index.md +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/docs/make.bat +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/docs/perf/performance_tests.md +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/docs/prefab/index.md +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/docs/tutorial.md +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/docs_code/docs_ex1_basic.py +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/docs_code/docs_ex2_register.py +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/docs_code/docs_ex3_iterable.py +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/docs_code/docs_ex5_frozen.py +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/docs_code/docs_ex7_posonly.py +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/docs_code/docs_ex8_converters.py +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/docs_code/docs_ex9_annotated.py +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/docs_code/tutorial_code.py +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/perf/cluegen.py +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/perf/dataklasses.py +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/perf/hyperfine_testmaker.py +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/perf/perf_profile.py +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/pyproject.toml +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/setup.cfg +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/src/ducktools/classbuilder/annotations.py +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/src/ducktools/classbuilder/annotations.pyi +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/src/ducktools/classbuilder/prefab.py +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/src/ducktools/classbuilder/prefab.pyi +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/src/ducktools/classbuilder/py.typed +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/src/ducktools_classbuilder.egg-info/dependency_links.txt +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/src/ducktools_classbuilder.egg-info/requires.txt +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/src/ducktools_classbuilder.egg-info/top_level.txt +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/tests/annotations/test_annotations_module.py +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/tests/annotations/test_future_annotations.py +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/tests/conftest.py +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/tests/prefab/dynamic/test_compare_attrib.py +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/tests/prefab/dynamic/test_construction.py +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/tests/prefab/dynamic/test_frozen.py +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/tests/prefab/dynamic/test_internals.py +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/tests/prefab/dynamic/test_pre_post_init.py +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/tests/prefab/dynamic/test_private.py +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/tests/prefab/dynamic/test_slots_novalues.py +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/tests/prefab/dynamic/test_slotted_class.py +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/tests/prefab/dynamic/test_subclass_implementation.py +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/tests/prefab/shared/conftest.py +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/tests/prefab/shared/examples/creation.py +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/tests/prefab/shared/examples/creation_empty.py +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/tests/prefab/shared/examples/dunders.py +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/tests/prefab/shared/examples/fails/creation_1.py +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/tests/prefab/shared/examples/fails/creation_2.py +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/tests/prefab/shared/examples/fails/creation_3.py +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/tests/prefab/shared/examples/fails/creation_5.py +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/tests/prefab/shared/examples/fails/inheritance_1.py +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/tests/prefab/shared/examples/fails/inheritance_2.py +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/tests/prefab/shared/examples/funcs_prefabs.py +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/tests/prefab/shared/examples/hint_syntax.py +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/tests/prefab/shared/examples/inheritance.py +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/tests/prefab/shared/examples/init_ex.py +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/tests/prefab/shared/examples/kw_only.py +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/tests/prefab/shared/examples/repr_func.py +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/tests/prefab/shared/test_creation.py +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/tests/prefab/shared/test_dunders.py +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/tests/prefab/shared/test_funcs.py +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/tests/prefab/shared/test_hint_syntax.py +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/tests/prefab/shared/test_inheritance.py +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/tests/prefab/shared/test_init.py +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/tests/prefab/shared/test_kw_only.py +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/tests/prefab/shared/test_repr.py +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/tests/py312_tests/test_generic_annotations.py +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/tests/py314_tests/test_forwardref_annotations.py +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/tests/test_field_flags.py +0 -0
- {ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/tests/test_slotmakermeta.py +0 -0
|
@@ -259,6 +259,138 @@ if __name__ == "__main__":
|
|
|
259
259
|
print(e)
|
|
260
260
|
```
|
|
261
261
|
|
|
262
|
+
#### Frozen Attributes ####
|
|
263
|
+
|
|
264
|
+
Here's an implementation that allows freezing of individual attributes.
|
|
265
|
+
|
|
266
|
+
```python
|
|
267
|
+
import ducktools.classbuilder as dtbuild
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
class FreezableField(dtbuild.Field):
|
|
271
|
+
frozen: bool = False
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def setattr_generator(cls, funcname="__setattr__"):
|
|
275
|
+
globs = {}
|
|
276
|
+
|
|
277
|
+
flags = dtbuild.get_flags(cls)
|
|
278
|
+
fields = dtbuild.get_fields(cls)
|
|
279
|
+
|
|
280
|
+
frozen_fields = set(
|
|
281
|
+
name for name, field in fields.items()
|
|
282
|
+
if getattr(field, "frozen", False)
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
globs["__frozen_fields"] = frozen_fields
|
|
286
|
+
|
|
287
|
+
if flags.get("slotted", True):
|
|
288
|
+
globs["__setattr_func"] = object.__setattr__
|
|
289
|
+
setattr_method = "__setattr_func(self, name, value)"
|
|
290
|
+
attrib_check = "hasattr(self, name)"
|
|
291
|
+
else:
|
|
292
|
+
setattr_method = "self.__dict__[name] = value"
|
|
293
|
+
attrib_check = "name in self.__dict__"
|
|
294
|
+
|
|
295
|
+
code = (
|
|
296
|
+
f"def {funcname}(self, name, value):\n"
|
|
297
|
+
f" if name in __frozen_fields and {attrib_check}:\n"
|
|
298
|
+
f" raise AttributeError(\n"
|
|
299
|
+
f" f'Attribute {{name!r}} does not support assignment'\n"
|
|
300
|
+
f" )\n"
|
|
301
|
+
f" else:\n"
|
|
302
|
+
f" {setattr_method}\n"
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
return dtbuild.GeneratedCode(code, globs)
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def delattr_generator(cls, funcname="__delattr__"):
|
|
309
|
+
globs = {}
|
|
310
|
+
|
|
311
|
+
flags = dtbuild.get_flags(cls)
|
|
312
|
+
fields = dtbuild.get_fields(cls)
|
|
313
|
+
|
|
314
|
+
frozen_fields = set(
|
|
315
|
+
name for name, field in fields.items()
|
|
316
|
+
if getattr(field, "frozen", False)
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
globs["__frozen_fields"] = frozen_fields
|
|
320
|
+
|
|
321
|
+
if flags.get("slotted", True):
|
|
322
|
+
globs["__delattr_func"] = object.__delattr__
|
|
323
|
+
delattr_method = "__delattr_func(self, name)"
|
|
324
|
+
else:
|
|
325
|
+
delattr_method = "del self.__dict__[name]"
|
|
326
|
+
|
|
327
|
+
code = (
|
|
328
|
+
f"def {funcname}(self, name):\n"
|
|
329
|
+
f" if name in __frozen_fields:"
|
|
330
|
+
f" raise AttributeError(\n"
|
|
331
|
+
f" f'Attribute {{name!r}} is frozen and can not be deleted'\n"
|
|
332
|
+
f" )\n"
|
|
333
|
+
f" else:\n"
|
|
334
|
+
f" {delattr_method}\n"
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
return dtbuild.GeneratedCode(code, globs)
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
frozen_setattr_field_maker = dtbuild.MethodMaker("__setattr__", setattr_generator)
|
|
341
|
+
frozen_delattr_field_maker = dtbuild.MethodMaker("__delattr__", delattr_generator)
|
|
342
|
+
gatherer = dtbuild.make_unified_gatherer(FreezableField)
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def freezable(cls=None, /, *, frozen=False):
|
|
346
|
+
if cls is None:
|
|
347
|
+
return lambda cls_: freezable(cls_, frozen=frozen)
|
|
348
|
+
|
|
349
|
+
# To make a slotted class use a base class with metaclass
|
|
350
|
+
flags = {"frozen": frozen, "slotted": False}
|
|
351
|
+
|
|
352
|
+
cls = dtbuild.builder(
|
|
353
|
+
cls,
|
|
354
|
+
gatherer=gatherer,
|
|
355
|
+
methods=dtbuild.default_methods,
|
|
356
|
+
flags=flags,
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
# Frozen attributes need to be added afterwards
|
|
360
|
+
# Due to the need to know if frozen fields exist
|
|
361
|
+
if frozen:
|
|
362
|
+
setattr(cls, "__setattr__", dtbuild.frozen_setattr_maker)
|
|
363
|
+
setattr(cls, "__delattr__", dtbuild.frozen_delattr_maker)
|
|
364
|
+
else:
|
|
365
|
+
fields = dtbuild.get_fields(cls)
|
|
366
|
+
has_frozen_fields = False
|
|
367
|
+
for f in fields.values():
|
|
368
|
+
if getattr(f, "frozen", False):
|
|
369
|
+
has_frozen_fields = True
|
|
370
|
+
break
|
|
371
|
+
|
|
372
|
+
if has_frozen_fields:
|
|
373
|
+
setattr(cls, "__setattr__", frozen_setattr_field_maker)
|
|
374
|
+
setattr(cls, "__delattr__", frozen_delattr_field_maker)
|
|
375
|
+
|
|
376
|
+
return cls
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
@freezable
|
|
380
|
+
class X:
|
|
381
|
+
a: int = 2
|
|
382
|
+
b: int = FreezableField(default=12, frozen=True)
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
x = X()
|
|
386
|
+
x.a = 21
|
|
387
|
+
|
|
388
|
+
try:
|
|
389
|
+
x.b = 43
|
|
390
|
+
except AttributeError as e:
|
|
391
|
+
print(repr(e))
|
|
392
|
+
```
|
|
393
|
+
|
|
262
394
|
#### Converters ####
|
|
263
395
|
|
|
264
396
|
Here's an implementation of basic converters that always convert when
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import ducktools.classbuilder as dtbuild
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class FreezableField(dtbuild.Field):
|
|
5
|
+
frozen: bool = False
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def setattr_generator(cls, funcname="__setattr__"):
|
|
9
|
+
globs = {}
|
|
10
|
+
|
|
11
|
+
flags = dtbuild.get_flags(cls)
|
|
12
|
+
fields = dtbuild.get_fields(cls)
|
|
13
|
+
|
|
14
|
+
frozen_fields = set(
|
|
15
|
+
name for name, field in fields.items()
|
|
16
|
+
if getattr(field, "frozen", False)
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
globs["__frozen_fields"] = frozen_fields
|
|
20
|
+
|
|
21
|
+
if flags.get("slotted", True):
|
|
22
|
+
globs["__setattr_func"] = object.__setattr__
|
|
23
|
+
setattr_method = "__setattr_func(self, name, value)"
|
|
24
|
+
attrib_check = "hasattr(self, name)"
|
|
25
|
+
else:
|
|
26
|
+
setattr_method = "self.__dict__[name] = value"
|
|
27
|
+
attrib_check = "name in self.__dict__"
|
|
28
|
+
|
|
29
|
+
code = (
|
|
30
|
+
f"def {funcname}(self, name, value):\n"
|
|
31
|
+
f" if name in __frozen_fields and {attrib_check}:\n"
|
|
32
|
+
f" raise AttributeError(\n"
|
|
33
|
+
f" f'Attribute {{name!r}} does not support assignment'\n"
|
|
34
|
+
f" )\n"
|
|
35
|
+
f" else:\n"
|
|
36
|
+
f" {setattr_method}\n"
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
return dtbuild.GeneratedCode(code, globs)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def delattr_generator(cls, funcname="__delattr__"):
|
|
43
|
+
globs = {}
|
|
44
|
+
|
|
45
|
+
flags = dtbuild.get_flags(cls)
|
|
46
|
+
fields = dtbuild.get_fields(cls)
|
|
47
|
+
|
|
48
|
+
frozen_fields = set(
|
|
49
|
+
name for name, field in fields.items()
|
|
50
|
+
if getattr(field, "frozen", False)
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
globs["__frozen_fields"] = frozen_fields
|
|
54
|
+
|
|
55
|
+
if flags.get("slotted", True):
|
|
56
|
+
globs["__delattr_func"] = object.__delattr__
|
|
57
|
+
delattr_method = "__delattr_func(self, name)"
|
|
58
|
+
else:
|
|
59
|
+
delattr_method = "del self.__dict__[name]"
|
|
60
|
+
|
|
61
|
+
code = (
|
|
62
|
+
f"def {funcname}(self, name):\n"
|
|
63
|
+
f" if name in __frozen_fields:"
|
|
64
|
+
f" raise AttributeError(\n"
|
|
65
|
+
f" f'Attribute {{name!r}} is frozen and can not be deleted'\n"
|
|
66
|
+
f" )\n"
|
|
67
|
+
f" else:\n"
|
|
68
|
+
f" {delattr_method}\n"
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
return dtbuild.GeneratedCode(code, globs)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
frozen_setattr_field_maker = dtbuild.MethodMaker("__setattr__", setattr_generator)
|
|
75
|
+
frozen_delattr_field_maker = dtbuild.MethodMaker("__delattr__", delattr_generator)
|
|
76
|
+
gatherer = dtbuild.make_unified_gatherer(FreezableField)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def freezable(cls=None, /, *, frozen=False):
|
|
80
|
+
if cls is None:
|
|
81
|
+
return lambda cls_: freezable(cls_, frozen=frozen)
|
|
82
|
+
|
|
83
|
+
# To make a slotted class use a base class with metaclass
|
|
84
|
+
flags = {"frozen": frozen, "slotted": False}
|
|
85
|
+
|
|
86
|
+
cls = dtbuild.builder(
|
|
87
|
+
cls,
|
|
88
|
+
gatherer=gatherer,
|
|
89
|
+
methods=dtbuild.default_methods,
|
|
90
|
+
flags=flags,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
# Frozen attributes need to be added afterwards
|
|
94
|
+
# Due to the need to know if frozen fields exist
|
|
95
|
+
if frozen:
|
|
96
|
+
setattr(cls, "__setattr__", dtbuild.frozen_setattr_maker)
|
|
97
|
+
setattr(cls, "__delattr__", dtbuild.frozen_delattr_maker)
|
|
98
|
+
else:
|
|
99
|
+
fields = dtbuild.get_fields(cls)
|
|
100
|
+
has_frozen_fields = False
|
|
101
|
+
for f in fields.values():
|
|
102
|
+
if getattr(f, "frozen", False):
|
|
103
|
+
has_frozen_fields = True
|
|
104
|
+
break
|
|
105
|
+
|
|
106
|
+
if has_frozen_fields:
|
|
107
|
+
setattr(cls, "__setattr__", frozen_setattr_field_maker)
|
|
108
|
+
setattr(cls, "__delattr__", frozen_delattr_field_maker)
|
|
109
|
+
|
|
110
|
+
return cls
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@freezable
|
|
114
|
+
class X:
|
|
115
|
+
a: int = 2
|
|
116
|
+
b: int = FreezableField(default=12, frozen=True)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
x = X()
|
|
120
|
+
x.a = 21
|
|
121
|
+
|
|
122
|
+
try:
|
|
123
|
+
x.b = 43
|
|
124
|
+
except AttributeError as e:
|
|
125
|
+
print(repr(e))
|
{ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/src/ducktools/classbuilder/__init__.py
RENAMED
|
@@ -363,15 +363,17 @@ def eq_generator(cls, funcname="__eq__"):
|
|
|
363
363
|
]
|
|
364
364
|
|
|
365
365
|
if field_names:
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
366
|
+
instance_comparison = "\n and ".join(
|
|
367
|
+
f"self.{name} == other.{name}" for name in field_names
|
|
368
|
+
)
|
|
369
369
|
else:
|
|
370
370
|
instance_comparison = "True"
|
|
371
371
|
|
|
372
372
|
code = (
|
|
373
373
|
f"def {funcname}(self, other):\n"
|
|
374
|
-
f" return
|
|
374
|
+
f" return (\n"
|
|
375
|
+
f" {instance_comparison}\n"
|
|
376
|
+
f" ) if {class_comparison} else NotImplemented\n"
|
|
375
377
|
)
|
|
376
378
|
globs = {}
|
|
377
379
|
|
|
@@ -390,11 +392,13 @@ def frozen_setattr_generator(cls, funcname="__setattr__"):
|
|
|
390
392
|
if flags.get("slotted", True):
|
|
391
393
|
globs["__setattr_func"] = object.__setattr__
|
|
392
394
|
setattr_method = "__setattr_func(self, name, value)"
|
|
395
|
+
hasattr_check = "hasattr(self, name)"
|
|
393
396
|
else:
|
|
394
397
|
setattr_method = "self.__dict__[name] = value"
|
|
398
|
+
hasattr_check = "name in self.__dict__"
|
|
395
399
|
|
|
396
400
|
body = (
|
|
397
|
-
f" if
|
|
401
|
+
f" if {hasattr_check} or name not in __field_names:\n"
|
|
398
402
|
f' raise TypeError(\n'
|
|
399
403
|
f' f"{{type(self).__name__!r}} object does not support "'
|
|
400
404
|
f' f"attribute assignment"\n'
|
|
@@ -741,7 +745,7 @@ def make_slot_gatherer(field_type=Field):
|
|
|
741
745
|
|
|
742
746
|
def make_annotation_gatherer(
|
|
743
747
|
field_type=Field,
|
|
744
|
-
leave_default_values=
|
|
748
|
+
leave_default_values=False,
|
|
745
749
|
):
|
|
746
750
|
"""
|
|
747
751
|
Create a new annotation gatherer that will work with `Field` instances
|
|
@@ -807,7 +811,7 @@ def make_annotation_gatherer(
|
|
|
807
811
|
|
|
808
812
|
def make_field_gatherer(
|
|
809
813
|
field_type=Field,
|
|
810
|
-
leave_default_values=
|
|
814
|
+
leave_default_values=False,
|
|
811
815
|
):
|
|
812
816
|
def field_attribute_gatherer(cls_or_ns):
|
|
813
817
|
if isinstance(cls_or_ns, (_MappingProxyType, dict)):
|
|
@@ -840,7 +844,7 @@ def make_field_gatherer(
|
|
|
840
844
|
|
|
841
845
|
def make_unified_gatherer(
|
|
842
846
|
field_type=Field,
|
|
843
|
-
leave_default_values=
|
|
847
|
+
leave_default_values=False,
|
|
844
848
|
):
|
|
845
849
|
"""
|
|
846
850
|
Create a gatherer that will work via first slots, then
|
|
@@ -890,7 +894,7 @@ annotation_gatherer = make_annotation_gatherer()
|
|
|
890
894
|
|
|
891
895
|
# The unified gatherer used for slot classes must remove default
|
|
892
896
|
# values for slots to work correctly.
|
|
893
|
-
unified_gatherer = make_unified_gatherer(
|
|
897
|
+
unified_gatherer = make_unified_gatherer()
|
|
894
898
|
|
|
895
899
|
|
|
896
900
|
# Now the gatherers have been defined, add __repr__ and __eq__ to Field.
|
|
@@ -956,7 +960,7 @@ class AnnotationClass(metaclass=SlotMakerMeta):
|
|
|
956
960
|
def __init_subclass__(
|
|
957
961
|
cls,
|
|
958
962
|
methods=default_methods,
|
|
959
|
-
gatherer=
|
|
963
|
+
gatherer=unified_gatherer,
|
|
960
964
|
**kwargs
|
|
961
965
|
):
|
|
962
966
|
# Check class dict otherwise this will always be True as this base
|
|
@@ -178,37 +178,37 @@ def make_slot_gatherer(
|
|
|
178
178
|
@typing.overload
|
|
179
179
|
def make_annotation_gatherer(
|
|
180
180
|
field_type: type[_FieldType],
|
|
181
|
-
leave_default_values: bool =
|
|
181
|
+
leave_default_values: bool = False,
|
|
182
182
|
) -> Callable[[type | _CopiableMappings], tuple[dict[str, _FieldType], dict[str, typing.Any]]]: ...
|
|
183
183
|
|
|
184
184
|
@typing.overload
|
|
185
185
|
def make_annotation_gatherer(
|
|
186
186
|
field_type: _ReturnsField = Field,
|
|
187
|
-
leave_default_values: bool =
|
|
187
|
+
leave_default_values: bool = False,
|
|
188
188
|
) -> Callable[[type | _CopiableMappings], tuple[dict[str, Field], dict[str, typing.Any]]]: ...
|
|
189
189
|
|
|
190
190
|
@typing.overload
|
|
191
191
|
def make_field_gatherer(
|
|
192
192
|
field_type: type[_FieldType],
|
|
193
|
-
leave_default_values: bool =
|
|
193
|
+
leave_default_values: bool = False,
|
|
194
194
|
) -> Callable[[type | _CopiableMappings], tuple[dict[str, _FieldType], dict[str, typing.Any]]]: ...
|
|
195
195
|
|
|
196
196
|
@typing.overload
|
|
197
197
|
def make_field_gatherer(
|
|
198
198
|
field_type: _ReturnsField = Field,
|
|
199
|
-
leave_default_values: bool =
|
|
199
|
+
leave_default_values: bool = False,
|
|
200
200
|
) -> Callable[[type | _CopiableMappings], tuple[dict[str, Field], dict[str, typing.Any]]]: ...
|
|
201
201
|
|
|
202
202
|
@typing.overload
|
|
203
203
|
def make_unified_gatherer(
|
|
204
204
|
field_type: type[_FieldType],
|
|
205
|
-
leave_default_values: bool =
|
|
205
|
+
leave_default_values: bool = False,
|
|
206
206
|
) -> Callable[[type | _CopiableMappings], tuple[dict[str, _FieldType], dict[str, typing.Any]]]: ...
|
|
207
207
|
|
|
208
208
|
@typing.overload
|
|
209
209
|
def make_unified_gatherer(
|
|
210
210
|
field_type: _ReturnsField = Field,
|
|
211
|
-
leave_default_values: bool =
|
|
211
|
+
leave_default_values: bool = False,
|
|
212
212
|
) -> Callable[[type | _CopiableMappings], tuple[dict[str, Field], dict[str, typing.Any]]]: ...
|
|
213
213
|
|
|
214
214
|
|
|
@@ -249,7 +249,7 @@ class AnnotationClass(metaclass=SlotMakerMeta):
|
|
|
249
249
|
def __init_subclass__(
|
|
250
250
|
cls,
|
|
251
251
|
methods: frozenset[MethodMaker] | set[MethodMaker] = default_methods,
|
|
252
|
-
gatherer: _gatherer_type =
|
|
252
|
+
gatherer: _gatherer_type = unified_gatherer,
|
|
253
253
|
**kwargs,
|
|
254
254
|
) -> None: ...
|
|
255
255
|
|
{ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/tests/annotations/test_annotated.py
RENAMED
|
@@ -60,9 +60,10 @@ def test_annotation_gatherer():
|
|
|
60
60
|
for key in "defgh":
|
|
61
61
|
assert key not in annos
|
|
62
62
|
|
|
63
|
-
# Instance variables
|
|
64
|
-
|
|
65
|
-
assert modifications["
|
|
63
|
+
# Instance variables to be removed from class
|
|
64
|
+
assert modifications["a"] is NOTHING
|
|
65
|
+
assert modifications["b"] is NOTHING
|
|
66
|
+
assert modifications["c"] is NOTHING
|
|
66
67
|
|
|
67
68
|
|
|
68
69
|
def test_make_annotation_gatherer():
|
|
@@ -71,7 +72,7 @@ def test_make_annotation_gatherer():
|
|
|
71
72
|
|
|
72
73
|
gatherer = make_annotation_gatherer(
|
|
73
74
|
field_type=NewField,
|
|
74
|
-
leave_default_values=
|
|
75
|
+
leave_default_values=True,
|
|
75
76
|
)
|
|
76
77
|
|
|
77
78
|
class ExampleAnnotated:
|
|
@@ -91,10 +92,11 @@ def test_make_annotation_gatherer():
|
|
|
91
92
|
|
|
92
93
|
assert annos["blank_field"] == NewField(type=str)
|
|
93
94
|
|
|
94
|
-
# ABC should be present in annos
|
|
95
|
+
# ABC should be present in annos and in the class
|
|
95
96
|
for key in "abc":
|
|
96
97
|
assert annos[key] == NewField(default=key, type=annotations[key])
|
|
97
|
-
|
|
98
|
+
|
|
99
|
+
assert modifications["c"] == "c"
|
|
98
100
|
|
|
99
101
|
# Opposite for classvar
|
|
100
102
|
for key in "defgh":
|
|
@@ -114,7 +116,10 @@ def test_annotationclass():
|
|
|
114
116
|
g: Annotated[Annotated[ClassVar[str], ""], ""] = "g"
|
|
115
117
|
h: Annotated[CV[str], ''] = "h"
|
|
116
118
|
|
|
117
|
-
for key in "
|
|
119
|
+
for key in "abc":
|
|
120
|
+
assert key not in ExampleAnnotated.__dict__
|
|
121
|
+
|
|
122
|
+
for key in "defgh":
|
|
118
123
|
assert key in ExampleAnnotated.__dict__
|
|
119
124
|
|
|
120
125
|
ex = ExampleAnnotated()
|
|
@@ -9,10 +9,13 @@ from ducktools.classbuilder import (
|
|
|
9
9
|
builder,
|
|
10
10
|
default_methods,
|
|
11
11
|
eq_maker,
|
|
12
|
+
frozen_delattr_maker,
|
|
13
|
+
frozen_setattr_maker,
|
|
12
14
|
get_fields,
|
|
13
15
|
get_flags,
|
|
14
16
|
get_methods,
|
|
15
17
|
init_maker,
|
|
18
|
+
make_unified_gatherer,
|
|
16
19
|
slot_gatherer,
|
|
17
20
|
slotclass,
|
|
18
21
|
|
|
@@ -165,6 +168,30 @@ def test_frozen_field():
|
|
|
165
168
|
delattr(f, k)
|
|
166
169
|
|
|
167
170
|
|
|
171
|
+
def test_frozen_unslotted():
|
|
172
|
+
# Test a frozen class with defaults left in place
|
|
173
|
+
|
|
174
|
+
methods = default_methods | {frozen_setattr_maker, frozen_delattr_maker}
|
|
175
|
+
gatherer = make_unified_gatherer(Field, leave_default_values=True)
|
|
176
|
+
|
|
177
|
+
def b(cls):
|
|
178
|
+
return builder(cls, methods=methods, gatherer=gatherer,
|
|
179
|
+
flags={"frozen": True, "slotted": False})
|
|
180
|
+
|
|
181
|
+
@b
|
|
182
|
+
class Ex:
|
|
183
|
+
a: int = 41
|
|
184
|
+
b: str = "Hello"
|
|
185
|
+
|
|
186
|
+
ex = Ex()
|
|
187
|
+
|
|
188
|
+
with pytest.raises(TypeError):
|
|
189
|
+
ex.a = 42
|
|
190
|
+
|
|
191
|
+
with pytest.raises(TypeError):
|
|
192
|
+
ex.b = "goodbye"
|
|
193
|
+
|
|
194
|
+
|
|
168
195
|
def test_slot_gatherer_success():
|
|
169
196
|
|
|
170
197
|
fields = {
|
|
File without changes
|
{ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/.github/workflows/auto_test.yml
RENAMED
|
File without changes
|
{ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/.github/workflows/publish_to_pypi.yml
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
|
{ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/docs/perf/performance_tests.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/docs_code/docs_ex2_register.py
RENAMED
|
File without changes
|
{ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/docs_code/docs_ex3_iterable.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/docs_code/docs_ex8_converters.py
RENAMED
|
File without changes
|
{ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/docs_code/docs_ex9_annotated.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
|
{ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/src/ducktools/classbuilder/prefab.py
RENAMED
|
File without changes
|
{ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/src/ducktools/classbuilder/prefab.pyi
RENAMED
|
File without changes
|
{ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/src/ducktools/classbuilder/py.typed
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
|
{ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/tests/prefab/dynamic/test_frozen.py
RENAMED
|
File without changes
|
{ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/tests/prefab/dynamic/test_internals.py
RENAMED
|
File without changes
|
|
File without changes
|
{ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/tests/prefab/dynamic/test_private.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/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
|
{ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/tests/prefab/shared/test_creation.py
RENAMED
|
File without changes
|
{ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/tests/prefab/shared/test_dunders.py
RENAMED
|
File without changes
|
{ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/tests/prefab/shared/test_funcs.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/tests/prefab/shared/test_init.py
RENAMED
|
File without changes
|
{ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/tests/prefab/shared/test_kw_only.py
RENAMED
|
File without changes
|
{ducktools_classbuilder-0.7.1 → ducktools_classbuilder-0.7.2}/tests/prefab/shared/test_repr.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|