ducktools-classbuilder 0.6.0__tar.gz → 0.6.1__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.

Files changed (69) hide show
  1. {ducktools_classbuilder-0.6.0/src/ducktools_classbuilder.egg-info → ducktools_classbuilder-0.6.1}/PKG-INFO +1 -1
  2. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/docs/extension_examples.md +8 -8
  3. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/docs/tutorial.md +2 -2
  4. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/src/ducktools/classbuilder/__init__.py +14 -14
  5. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/src/ducktools/classbuilder/__init__.pyi +13 -10
  6. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/src/ducktools/classbuilder/annotations.pyi +1 -1
  7. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/src/ducktools/classbuilder/prefab.py +160 -167
  8. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/src/ducktools/classbuilder/prefab.pyi +4 -6
  9. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1/src/ducktools_classbuilder.egg-info}/PKG-INFO +1 -1
  10. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/tests/test_core.py +2 -2
  11. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/LICENSE.md +0 -0
  12. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/MANIFEST.in +0 -0
  13. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/README.md +0 -0
  14. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/docs/Makefile +0 -0
  15. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/docs/api.md +0 -0
  16. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/docs/approach_vs_tool.md +0 -0
  17. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/docs/conf.py +0 -0
  18. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/docs/generated_code.md +0 -0
  19. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/docs/index.md +0 -0
  20. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/docs/make.bat +0 -0
  21. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/docs/perf/performance_tests.md +0 -0
  22. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/docs/prefab/index.md +0 -0
  23. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/pyproject.toml +0 -0
  24. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/setup.cfg +0 -0
  25. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/src/ducktools/classbuilder/annotations.py +0 -0
  26. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/src/ducktools/classbuilder/py.typed +0 -0
  27. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/src/ducktools_classbuilder.egg-info/SOURCES.txt +0 -0
  28. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/src/ducktools_classbuilder.egg-info/dependency_links.txt +0 -0
  29. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/src/ducktools_classbuilder.egg-info/requires.txt +0 -0
  30. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/src/ducktools_classbuilder.egg-info/top_level.txt +0 -0
  31. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/tests/annotations/test_annotated.py +0 -0
  32. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/tests/annotations/test_annotations_module.py +0 -0
  33. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/tests/conftest.py +0 -0
  34. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/tests/prefab/dynamic/test_compare_attrib.py +0 -0
  35. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/tests/prefab/dynamic/test_construction.py +0 -0
  36. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/tests/prefab/dynamic/test_internals.py +0 -0
  37. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/tests/prefab/dynamic/test_pre_post_init.py +0 -0
  38. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/tests/prefab/dynamic/test_slots_novalues.py +0 -0
  39. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/tests/prefab/dynamic/test_slotted_class.py +0 -0
  40. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/tests/prefab/dynamic/test_subclass_implementation.py +0 -0
  41. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/tests/prefab/shared/conftest.py +0 -0
  42. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/tests/prefab/shared/examples/creation.py +0 -0
  43. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/tests/prefab/shared/examples/creation_empty.py +0 -0
  44. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/tests/prefab/shared/examples/dunders.py +0 -0
  45. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/tests/prefab/shared/examples/fails/creation_1.py +0 -0
  46. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/tests/prefab/shared/examples/fails/creation_2.py +0 -0
  47. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/tests/prefab/shared/examples/fails/creation_3.py +0 -0
  48. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/tests/prefab/shared/examples/fails/creation_5.py +0 -0
  49. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/tests/prefab/shared/examples/fails/inheritance_1.py +0 -0
  50. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/tests/prefab/shared/examples/fails/inheritance_2.py +0 -0
  51. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/tests/prefab/shared/examples/frozen_prefabs.py +0 -0
  52. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/tests/prefab/shared/examples/funcs_prefabs.py +0 -0
  53. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/tests/prefab/shared/examples/hint_syntax.py +0 -0
  54. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/tests/prefab/shared/examples/inheritance.py +0 -0
  55. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/tests/prefab/shared/examples/init_ex.py +0 -0
  56. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/tests/prefab/shared/examples/kw_only.py +0 -0
  57. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/tests/prefab/shared/examples/repr_func.py +0 -0
  58. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/tests/prefab/shared/test_creation.py +0 -0
  59. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/tests/prefab/shared/test_dunders.py +0 -0
  60. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/tests/prefab/shared/test_frozen.py +0 -0
  61. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/tests/prefab/shared/test_funcs.py +0 -0
  62. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/tests/prefab/shared/test_hint_syntax.py +0 -0
  63. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/tests/prefab/shared/test_inheritance.py +0 -0
  64. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/tests/prefab/shared/test_init.py +0 -0
  65. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/tests/prefab/shared/test_kw_only.py +0 -0
  66. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/tests/prefab/shared/test_repr.py +0 -0
  67. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/tests/py312_tests/test_generic_annotations.py +0 -0
  68. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/tests/test_field_flags.py +0 -0
  69. {ducktools_classbuilder-0.6.0 → ducktools_classbuilder-0.6.1}/tests/test_slotmakermeta.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ducktools-classbuilder
3
- Version: 0.6.0
3
+ Version: 0.6.1
4
4
  Summary: Toolkit for creating class boilerplate generators
5
5
  Author: David C Ellis
6
6
  License: MIT License
@@ -79,12 +79,12 @@ from ducktools.classbuilder import (
79
79
  )
80
80
 
81
81
 
82
- def iter_generator(cls):
82
+ def iter_generator(cls, funcname="__iter__"):
83
83
  field_names = get_fields(cls).keys()
84
84
  field_yield = "\n".join(f" yield self.{f}" for f in field_names)
85
85
  if not field_yield:
86
86
  field_yield = " yield from ()"
87
- code = f"def __iter__(self):\n" f"{field_yield}"
87
+ code = f"def {funcname}(self):\n" f"{field_yield}"
88
88
  globs = {}
89
89
  return GeneratedCode(code, globs)
90
90
 
@@ -143,7 +143,7 @@ class PosOnlyField(Field):
143
143
  __slots__ = SlotFields(pos_only=True)
144
144
 
145
145
 
146
- def init_generator(cls):
146
+ def init_generator(cls, funcname="__init__"):
147
147
  fields = get_fields(cls)
148
148
 
149
149
  arglist = []
@@ -177,11 +177,11 @@ def init_generator(cls):
177
177
 
178
178
  args = ", ".join(arglist)
179
179
  assigns = "\n ".join(assignments)
180
- code = f"def __init__(self, {args}):\n" f" {assigns}\n"
180
+ code = f"def {funcname}(self, {args}):\n" f" {assigns}\n"
181
181
  return GeneratedCode(code, globs)
182
182
 
183
183
 
184
- def repr_generator(cls):
184
+ def repr_generator(cls, funcname="__repr__"):
185
185
  fields = get_fields(cls)
186
186
  content_list = []
187
187
  for name, field in fields.items():
@@ -193,7 +193,7 @@ def repr_generator(cls):
193
193
 
194
194
  content = ", ".join(content_list)
195
195
  code = (
196
- f"def __repr__(self):\n"
196
+ f"def {funcname}(self):\n"
197
197
  f" return f'{{type(self).__qualname__}}({content})'\n"
198
198
  )
199
199
  globs = {}
@@ -281,7 +281,7 @@ class ConverterField(Field):
281
281
  converter = Field(default=None)
282
282
 
283
283
 
284
- def setattr_generator(cls):
284
+ def setattr_generator(cls, funcname="__setattr__"):
285
285
  fields = get_fields(cls)
286
286
  converters = {}
287
287
  for k, v in fields.items():
@@ -294,7 +294,7 @@ def setattr_generator(cls):
294
294
  }
295
295
 
296
296
  code = (
297
- f"def __setattr__(self, name, value):\n"
297
+ f"def {funcname}(self, name, value):\n"
298
298
  f" if conv := _converters.get(name):\n"
299
299
  f" _object_setattr(self, name, conv(value))\n"
300
300
  f" else:\n"
@@ -120,7 +120,7 @@ field_3: <HIDDEN>
120
120
  ```
121
121
 
122
122
  ```python
123
- def report_generator(cls):
123
+ def report_generator(cls, funcname="report"):
124
124
  fields = dtbuild.get_fields(cls)
125
125
 
126
126
  field_reports = []
@@ -135,7 +135,7 @@ def report_generator(cls):
135
135
 
136
136
  code = (
137
137
  "@property\n"
138
- "def report(self):\n"
138
+ f"def {funcname}(self):\n"
139
139
  f" return f\"{class_str}\\n{reports_str}\""
140
140
  )
141
141
  globs = {}
@@ -34,7 +34,7 @@ import sys
34
34
 
35
35
  from .annotations import get_ns_annotations, is_classvar
36
36
 
37
- __version__ = "v0.6.0"
37
+ __version__ = "v0.6.1"
38
38
 
39
39
  # Change this name if you make heavy modifications
40
40
  INTERNALS_DICT = "__classbuilder_internals__"
@@ -167,7 +167,7 @@ class MethodMaker:
167
167
  cls = objtype
168
168
 
169
169
  local_vars = {}
170
- gen = self.code_generator(cls)
170
+ gen = self.code_generator(cls, self.funcname)
171
171
  exec(gen.source_code, gen.globs, local_vars)
172
172
  method = local_vars.get(self.funcname)
173
173
 
@@ -187,7 +187,7 @@ class MethodMaker:
187
187
 
188
188
 
189
189
  def get_init_generator(null=NOTHING, extra_code=None):
190
- def cls_init_maker(cls):
190
+ def cls_init_maker(cls, funcname="__init__"):
191
191
  fields = get_fields(cls)
192
192
  flags = get_flags(cls)
193
193
 
@@ -239,7 +239,7 @@ def get_init_generator(null=NOTHING, extra_code=None):
239
239
 
240
240
  assigns = "\n ".join(assignments) if assignments else "pass\n"
241
241
  code = (
242
- f"def __init__(self, {args}):\n"
242
+ f"def {funcname}(self, {args}):\n"
243
243
  f" {assigns}\n"
244
244
  )
245
245
  # Handle additional function calls
@@ -265,7 +265,7 @@ def get_repr_generator(recursion_safe=False, eval_safe=False):
265
265
  not evaluate.
266
266
  :return:
267
267
  """
268
- def cls_repr_generator(cls):
268
+ def cls_repr_generator(cls, funcname="__repr__"):
269
269
  fields = get_fields(cls)
270
270
 
271
271
  globs = {}
@@ -295,19 +295,19 @@ def get_repr_generator(recursion_safe=False, eval_safe=False):
295
295
  if content:
296
296
  code = (
297
297
  f"{recursion_func}"
298
- f"def __repr__(self):\n"
298
+ f"def {funcname}(self):\n"
299
299
  f" return f'<generated class {{type(self).__qualname__}}; {content}>'\n"
300
300
  )
301
301
  else:
302
302
  code = (
303
303
  f"{recursion_func}"
304
- f"def __repr__(self):\n"
304
+ f"def {funcname}(self):\n"
305
305
  f" return f'<generated class {{type(self).__qualname__}}>'\n"
306
306
  )
307
307
  else:
308
308
  code = (
309
309
  f"{recursion_func}"
310
- f"def __repr__(self):\n"
310
+ f"def {funcname}(self):\n"
311
311
  f" return f'{{type(self).__qualname__}}({content})'\n"
312
312
  )
313
313
 
@@ -318,7 +318,7 @@ def get_repr_generator(recursion_safe=False, eval_safe=False):
318
318
  repr_generator = get_repr_generator()
319
319
 
320
320
 
321
- def eq_generator(cls):
321
+ def eq_generator(cls, funcname="__eq__"):
322
322
  class_comparison = "self.__class__ is other.__class__"
323
323
  field_names = [
324
324
  name
@@ -334,7 +334,7 @@ def eq_generator(cls):
334
334
  instance_comparison = "True"
335
335
 
336
336
  code = (
337
- f"def __eq__(self, other):\n"
337
+ f"def {funcname}(self, other):\n"
338
338
  f" return {instance_comparison} if {class_comparison} else NotImplemented\n"
339
339
  )
340
340
  globs = {}
@@ -342,7 +342,7 @@ def eq_generator(cls):
342
342
  return GeneratedCode(code, globs)
343
343
 
344
344
 
345
- def frozen_setattr_generator(cls):
345
+ def frozen_setattr_generator(cls, funcname="__setattr__"):
346
346
  globs = {}
347
347
  field_names = set(get_fields(cls))
348
348
  flags = get_flags(cls)
@@ -366,19 +366,19 @@ def frozen_setattr_generator(cls):
366
366
  f" else:\n"
367
367
  f" {setattr_method}\n"
368
368
  )
369
- code = f"def __setattr__(self, name, value):\n{body}"
369
+ code = f"def {funcname}(self, name, value):\n{body}"
370
370
 
371
371
  return GeneratedCode(code, globs)
372
372
 
373
373
 
374
- def frozen_delattr_generator(cls):
374
+ def frozen_delattr_generator(cls, funcname="__delattr__"):
375
375
  body = (
376
376
  ' raise TypeError(\n'
377
377
  ' f"{type(self).__name__!r} object "\n'
378
378
  ' f"does not support attribute deletion"\n'
379
379
  ' )\n'
380
380
  )
381
- code = f"def __delattr__(self, name):\n{body}"
381
+ code = f"def {funcname}(self, name):\n{body}"
382
382
  globs = {}
383
383
  return GeneratedCode(code, globs)
384
384
 
@@ -30,7 +30,10 @@ class _KW_ONLY_TYPE:
30
30
 
31
31
  KW_ONLY: _KW_ONLY_TYPE
32
32
  # Stub Only
33
- _codegen_type = Callable[[type], GeneratedCode]
33
+ @typing.type_check_only
34
+ class _CodegenType(typing.Protocol):
35
+ def __call__(self, cls: type, funcname: str = ...) -> GeneratedCode: ...
36
+
34
37
 
35
38
  class GeneratedCode:
36
39
  __slots__: tuple[str, str]
@@ -43,28 +46,28 @@ class GeneratedCode:
43
46
 
44
47
  class MethodMaker:
45
48
  funcname: str
46
- code_generator: _codegen_type
47
- def __init__(self, funcname: str, code_generator: _codegen_type) -> None: ...
49
+ code_generator: _CodegenType
50
+ def __init__(self, funcname: str, code_generator: _CodegenType) -> None: ...
48
51
  def __repr__(self) -> str: ...
49
52
  def __get__(self, instance, cls=None) -> Callable: ...
50
53
 
51
54
  def get_init_generator(
52
55
  null: _NothingType = NOTHING,
53
56
  extra_code: None | list[str] = None
54
- ) -> Callable[[type], GeneratedCode]: ...
57
+ ) -> _CodegenType: ...
55
58
 
56
- def init_generator(cls: type) -> GeneratedCode: ...
59
+ def init_generator(cls: type, funcname: str="__init__") -> GeneratedCode: ...
57
60
 
58
61
  def get_repr_generator(
59
62
  recursion_safe: bool = False,
60
63
  eval_safe: bool = False
61
- ) -> Callable[[type], GeneratedCode]: ...
62
- def repr_generator(cls: type) -> GeneratedCode: ...
63
- def eq_generator(cls: type) -> GeneratedCode: ...
64
+ ) -> _CodegenType: ...
65
+ def repr_generator(cls: type, funcname: str = "__repr__") -> GeneratedCode: ...
66
+ def eq_generator(cls: type, funcname: str = "__eq__") -> GeneratedCode: ...
64
67
 
65
- def frozen_setattr_generator(cls: type) -> GeneratedCode: ...
68
+ def frozen_setattr_generator(cls: type, funcname: str = "__setattr__") -> GeneratedCode: ...
66
69
 
67
- def frozen_delattr_generator(cls: type) -> GeneratedCode: ...
70
+ def frozen_delattr_generator(cls: type, funcname: str = "__delattr__") -> GeneratedCode: ...
68
71
 
69
72
  init_maker: MethodMaker
70
73
  repr_maker: MethodMaker
@@ -4,7 +4,7 @@ import types
4
4
  _T = typing.TypeVar("_T")
5
5
  _CopiableMappings = dict[str, typing.Any] | types.MappingProxyType[str, typing.Any]
6
6
 
7
- class _StringGlobs:
7
+ class _StringGlobs(dict):
8
8
  def __missing__(self, key: _T) -> _T: ...
9
9
 
10
10
 
@@ -60,202 +60,195 @@ def get_attributes(cls):
60
60
 
61
61
 
62
62
  # Method Generators
63
- def get_init_maker(*, init_name="__init__"):
64
- def __init__(cls: type) -> GeneratedCode:
65
- globs = {}
66
- # Get the internals dictionary and prepare attributes
67
- attributes = get_attributes(cls)
68
- flags = get_flags(cls)
69
-
70
- kw_only = flags.get("kw_only", False)
71
-
72
- # Handle pre/post init first - post_init can change types for __init__
73
- # Get pre and post init arguments
74
- pre_init_args = []
75
- post_init_args = []
76
- post_init_annotations = {}
77
-
78
- for func_name, func_arglist in [
79
- (PRE_INIT_FUNC, pre_init_args),
80
- (POST_INIT_FUNC, post_init_args),
81
- ]:
82
- try:
83
- func = getattr(cls, func_name)
84
- func_code = func.__code__
85
- except AttributeError:
86
- pass
87
- else:
88
- argcount = func_code.co_argcount + func_code.co_kwonlyargcount
63
+ def init_generator(cls, funcname="__init__"):
64
+ globs = {}
65
+ # Get the internals dictionary and prepare attributes
66
+ attributes = get_attributes(cls)
67
+ flags = get_flags(cls)
89
68
 
90
- # Identify if method is static, if so include first arg, otherwise skip
91
- is_static = type(cls.__dict__.get(func_name)) is staticmethod
69
+ kw_only = flags.get("kw_only", False)
92
70
 
93
- arglist = (
94
- func_code.co_varnames[:argcount]
95
- if is_static
96
- else func_code.co_varnames[1:argcount]
97
- )
71
+ # Handle pre/post init first - post_init can change types for __init__
72
+ # Get pre and post init arguments
73
+ pre_init_args = []
74
+ post_init_args = []
75
+ post_init_annotations = {}
76
+
77
+ for extra_funcname, func_arglist in [
78
+ (PRE_INIT_FUNC, pre_init_args),
79
+ (POST_INIT_FUNC, post_init_args),
80
+ ]:
81
+ try:
82
+ func = getattr(cls, extra_funcname)
83
+ func_code = func.__code__
84
+ except AttributeError:
85
+ pass
86
+ else:
87
+ argcount = func_code.co_argcount + func_code.co_kwonlyargcount
98
88
 
99
- func_arglist.extend(arglist)
100
-
101
- if func_name == POST_INIT_FUNC:
102
- post_init_annotations.update(func.__annotations__)
103
-
104
- pos_arglist = []
105
- kw_only_arglist = []
106
- for name, attrib in attributes.items():
107
- # post_init annotations can be used to broaden types.
108
- if name in post_init_annotations:
109
- globs[f"_{name}_type"] = post_init_annotations[name]
110
- elif attrib.type is not NOTHING:
111
- globs[f"_{name}_type"] = attrib.type
112
-
113
- if attrib.init:
114
- if attrib.default is not NOTHING:
115
- if isinstance(attrib.default, (str, int, float, bool)):
116
- # Just use the literal in these cases
117
- if attrib.type is NOTHING:
118
- arg = f"{name}={attrib.default!r}"
119
- else:
120
- arg = f"{name}: _{name}_type = {attrib.default!r}"
121
- else:
122
- # No guarantee repr will work for other objects
123
- # so store the value in a variable and put it
124
- # in the globals dict for eval
125
- if attrib.type is NOTHING:
126
- arg = f"{name}=_{name}_default"
127
- else:
128
- arg = f"{name}: _{name}_type = _{name}_default"
129
- globs[f"_{name}_default"] = attrib.default
130
- elif attrib.default_factory is not NOTHING:
131
- # Use NONE here and call the factory later
132
- # This matches the behaviour of compiled
89
+ # Identify if method is static, if so include first arg, otherwise skip
90
+ is_static = type(cls.__dict__.get(extra_funcname)) is staticmethod
91
+
92
+ arglist = (
93
+ func_code.co_varnames[:argcount]
94
+ if is_static
95
+ else func_code.co_varnames[1:argcount]
96
+ )
97
+
98
+ func_arglist.extend(arglist)
99
+
100
+ if extra_funcname == POST_INIT_FUNC:
101
+ post_init_annotations.update(func.__annotations__)
102
+
103
+ pos_arglist = []
104
+ kw_only_arglist = []
105
+ for name, attrib in attributes.items():
106
+ # post_init annotations can be used to broaden types.
107
+ if name in post_init_annotations:
108
+ globs[f"_{name}_type"] = post_init_annotations[name]
109
+ elif attrib.type is not NOTHING:
110
+ globs[f"_{name}_type"] = attrib.type
111
+
112
+ if attrib.init:
113
+ if attrib.default is not NOTHING:
114
+ if isinstance(attrib.default, (str, int, float, bool)):
115
+ # Just use the literal in these cases
133
116
  if attrib.type is NOTHING:
134
- arg = f"{name}=None"
117
+ arg = f"{name}={attrib.default!r}"
135
118
  else:
136
- arg = f"{name}: _{name}_type = None"
137
- globs[f"_{name}_factory"] = attrib.default_factory
119
+ arg = f"{name}: _{name}_type = {attrib.default!r}"
138
120
  else:
121
+ # No guarantee repr will work for other objects
122
+ # so store the value in a variable and put it
123
+ # in the globals dict for eval
139
124
  if attrib.type is NOTHING:
140
- arg = name
125
+ arg = f"{name}=_{name}_default"
141
126
  else:
142
- arg = f"{name}: _{name}_type"
143
- if attrib.kw_only or kw_only:
144
- kw_only_arglist.append(arg)
145
- else:
146
- pos_arglist.append(arg)
147
- # Not in init, but need to set defaults
148
- else:
149
- if attrib.default is not NOTHING:
127
+ arg = f"{name}: _{name}_type = _{name}_default"
150
128
  globs[f"_{name}_default"] = attrib.default
151
- elif attrib.default_factory is not NOTHING:
152
- globs[f"_{name}_factory"] = attrib.default_factory
153
-
154
- pos_args = ", ".join(pos_arglist)
155
- kw_args = ", ".join(kw_only_arglist)
156
- if pos_args and kw_args:
157
- args = f"{pos_args}, *, {kw_args}"
158
- elif kw_args:
159
- args = f"*, {kw_args}"
160
- else:
161
- args = pos_args
162
-
163
- assignments = []
164
- processes = [] # post_init values still need default factories to be called.
165
- for name, attrib in attributes.items():
166
- if attrib.init:
167
- if attrib.default_factory is not NOTHING:
168
- value = f"{name} if {name} is not None else _{name}_factory()"
129
+ elif attrib.default_factory is not NOTHING:
130
+ # Use NONE here and call the factory later
131
+ # This matches the behaviour of compiled
132
+ if attrib.type is NOTHING:
133
+ arg = f"{name}=None"
169
134
  else:
170
- value = name
135
+ arg = f"{name}: _{name}_type = None"
136
+ globs[f"_{name}_factory"] = attrib.default_factory
171
137
  else:
172
- if attrib.default_factory is not NOTHING:
173
- value = f"_{name}_factory()"
174
- elif attrib.default is not NOTHING:
175
- value = f"_{name}_default"
138
+ if attrib.type is NOTHING:
139
+ arg = name
176
140
  else:
177
- value = None
178
-
179
- if name in post_init_args:
180
- if attrib.default_factory is not NOTHING:
181
- processes.append((name, value))
182
- elif value is not None:
183
- assignments.append((name, value))
184
-
185
- if hasattr(cls, PRE_INIT_FUNC):
186
- pre_init_arg_call = ", ".join(f"{name}={name}" for name in pre_init_args)
187
- pre_init_call = f" self.{PRE_INIT_FUNC}({pre_init_arg_call})\n"
141
+ arg = f"{name}: _{name}_type"
142
+ if attrib.kw_only or kw_only:
143
+ kw_only_arglist.append(arg)
144
+ else:
145
+ pos_arglist.append(arg)
146
+ # Not in init, but need to set defaults
188
147
  else:
189
- pre_init_call = ""
190
-
191
- if assignments or processes:
192
- body = ""
193
- body += "\n".join(
194
- f" self.{name} = {value}" for name, value in assignments
195
- )
196
- body += "\n"
197
- body += "\n".join(f" {name} = {value}" for name, value in processes)
148
+ if attrib.default is not NOTHING:
149
+ globs[f"_{name}_default"] = attrib.default
150
+ elif attrib.default_factory is not NOTHING:
151
+ globs[f"_{name}_factory"] = attrib.default_factory
152
+
153
+ pos_args = ", ".join(pos_arglist)
154
+ kw_args = ", ".join(kw_only_arglist)
155
+ if pos_args and kw_args:
156
+ args = f"{pos_args}, *, {kw_args}"
157
+ elif kw_args:
158
+ args = f"*, {kw_args}"
159
+ else:
160
+ args = pos_args
161
+
162
+ assignments = []
163
+ processes = [] # post_init values still need default factories to be called.
164
+ for name, attrib in attributes.items():
165
+ if attrib.init:
166
+ if attrib.default_factory is not NOTHING:
167
+ value = f"{name} if {name} is not None else _{name}_factory()"
168
+ else:
169
+ value = name
198
170
  else:
199
- body = " pass"
171
+ if attrib.default_factory is not NOTHING:
172
+ value = f"_{name}_factory()"
173
+ elif attrib.default is not NOTHING:
174
+ value = f"_{name}_default"
175
+ else:
176
+ value = None
200
177
 
201
- if hasattr(cls, POST_INIT_FUNC):
202
- post_init_arg_call = ", ".join(f"{name}={name}" for name in post_init_args)
203
- post_init_call = f" self.{POST_INIT_FUNC}({post_init_arg_call})\n"
204
- else:
205
- post_init_call = ""
178
+ if name in post_init_args:
179
+ if attrib.default_factory is not NOTHING:
180
+ processes.append((name, value))
181
+ elif value is not None:
182
+ assignments.append((name, value))
183
+
184
+ if hasattr(cls, PRE_INIT_FUNC):
185
+ pre_init_arg_call = ", ".join(f"{name}={name}" for name in pre_init_args)
186
+ pre_init_call = f" self.{PRE_INIT_FUNC}({pre_init_arg_call})\n"
187
+ else:
188
+ pre_init_call = ""
206
189
 
207
- code = (
208
- f"def {init_name}(self, {args}):\n"
209
- f"{pre_init_call}\n"
210
- f"{body}\n"
211
- f"{post_init_call}\n"
190
+ if assignments or processes:
191
+ body = ""
192
+ body += "\n".join(
193
+ f" self.{name} = {value}" for name, value in assignments
212
194
  )
213
- return GeneratedCode(code, globs)
195
+ body += "\n"
196
+ body += "\n".join(f" {name} = {value}" for name, value in processes)
197
+ else:
198
+ body = " pass"
214
199
 
215
- return MethodMaker(init_name, __init__)
200
+ if hasattr(cls, POST_INIT_FUNC):
201
+ post_init_arg_call = ", ".join(f"{name}={name}" for name in post_init_args)
202
+ post_init_call = f" self.{POST_INIT_FUNC}({post_init_arg_call})\n"
203
+ else:
204
+ post_init_call = ""
205
+
206
+ code = (
207
+ f"def {funcname}(self, {args}):\n"
208
+ f"{pre_init_call}\n"
209
+ f"{body}\n"
210
+ f"{post_init_call}\n"
211
+ )
216
212
 
213
+ return GeneratedCode(code, globs)
217
214
 
218
- def get_iter_maker():
219
- def __iter__(cls: type) -> GeneratedCode:
220
- fields = get_attributes(cls)
221
215
 
222
- valid_fields = (
223
- name for name, attrib in fields.items()
224
- if attrib.iter
225
- )
216
+ def iter_generator(cls, funcname="__iter__"):
217
+ fields = get_attributes(cls)
226
218
 
227
- values = "\n".join(f" yield self.{name}" for name in valid_fields)
219
+ valid_fields = (
220
+ name for name, attrib in fields.items()
221
+ if attrib.iter
222
+ )
228
223
 
229
- # if values is an empty string
230
- if not values:
231
- values = " yield from ()"
224
+ values = "\n".join(f" yield self.{name}" for name in valid_fields)
232
225
 
233
- code = f"def __iter__(self):\n{values}"
234
- globs = {}
235
- return GeneratedCode(code, globs)
226
+ # if values is an empty string
227
+ if not values:
228
+ values = " yield from ()"
236
229
 
237
- return MethodMaker("__iter__", __iter__)
230
+ code = f"def {funcname}(self):\n{values}"
231
+ globs = {}
232
+ return GeneratedCode(code, globs)
238
233
 
239
234
 
240
- def get_asdict_maker():
241
- def as_dict_gen(cls: type) -> GeneratedCode:
242
- fields = get_attributes(cls)
235
+ def as_dict_generator(cls, funcname="as_dict"):
236
+ fields = get_attributes(cls)
243
237
 
244
- vals = ", ".join(
245
- f"'{name}': self.{name}"
246
- for name, attrib in fields.items()
247
- if attrib.serialize
248
- )
249
- out_dict = f"{{{vals}}}"
250
- code = f"def as_dict(self): return {out_dict}"
238
+ vals = ", ".join(
239
+ f"'{name}': self.{name}"
240
+ for name, attrib in fields.items()
241
+ if attrib.serialize
242
+ )
243
+ out_dict = f"{{{vals}}}"
244
+ code = f"def {funcname}(self): return {out_dict}"
251
245
 
252
- globs = {}
253
- return GeneratedCode(code, globs)
254
- return MethodMaker("as_dict", as_dict_gen)
246
+ globs = {}
247
+ return GeneratedCode(code, globs)
255
248
 
256
249
 
257
- init_maker = get_init_maker()
258
- prefab_init_maker = get_init_maker(init_name=PREFAB_INIT_FUNC)
250
+ init_maker = MethodMaker("__init__", init_generator)
251
+ prefab_init_maker = MethodMaker(PREFAB_INIT_FUNC, init_generator)
259
252
  repr_maker = MethodMaker(
260
253
  "__repr__",
261
254
  get_repr_generator(recursion_safe=False, eval_safe=True)
@@ -264,8 +257,8 @@ recursive_repr_maker = MethodMaker(
264
257
  "__repr__",
265
258
  get_repr_generator(recursion_safe=True, eval_safe=True)
266
259
  )
267
- iter_maker = get_iter_maker()
268
- asdict_maker = get_asdict_maker()
260
+ iter_maker = MethodMaker("__iter__", iter_generator)
261
+ asdict_maker = MethodMaker("as_dict", as_dict_generator)
269
262
 
270
263
 
271
264
  # Updated field with additional attributes
@@ -7,6 +7,7 @@ from collections.abc import Callable
7
7
  from . import (
8
8
  NOTHING,
9
9
  Field,
10
+ GeneratedCode,
10
11
  MethodMaker,
11
12
  SlotMakerMeta,
12
13
  )
@@ -27,12 +28,9 @@ class PrefabError(Exception): ...
27
28
 
28
29
  def get_attributes(cls: type) -> dict[str, Attribute]: ...
29
30
 
30
- def get_init_maker(*, init_name: str="__init__") -> MethodMaker: ...
31
-
32
- def get_iter_maker() -> MethodMaker: ...
33
-
34
- def get_asdict_maker() -> MethodMaker: ...
35
-
31
+ def init_generator(cls: type, funcname: str = "__init__") -> GeneratedCode: ...
32
+ def iter_generator(cls: type, funcname: str = "__iter__") -> GeneratedCode: ...
33
+ def as_dict_generator(cls: type, funcname: str = "as_dict") -> GeneratedCode: ...
36
34
 
37
35
  init_maker: MethodMaker
38
36
  prefab_init_maker: MethodMaker
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ducktools-classbuilder
3
- Version: 0.6.0
3
+ Version: 0.6.1
4
4
  Summary: Toolkit for creating class boilerplate generators
5
5
  Author: David C Ellis
6
6
  License: MIT License
@@ -50,8 +50,8 @@ def test_get_fields_flags_methods():
50
50
 
51
51
 
52
52
  def test_method_maker():
53
- def generator(cls):
54
- code = "def demo(self): return self.x"
53
+ def generator(cls, funcname="demo"):
54
+ code = f"def {funcname}(self): return self.x"
55
55
  globs = {}
56
56
  return GeneratedCode(code, globs)
57
57