ducktools-classbuilder 0.6.1__py3-none-any.whl → 0.6.2__py3-none-any.whl

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.

@@ -34,7 +34,7 @@ import sys
34
34
 
35
35
  from .annotations import get_ns_annotations, is_classvar
36
36
 
37
- __version__ = "v0.6.1"
37
+ __version__ = "v0.6.2"
38
38
 
39
39
  # Change this name if you make heavy modifications
40
40
  INTERNALS_DICT = "__classbuilder_internals__"
@@ -158,32 +158,69 @@ class MethodMaker:
158
158
  def __repr__(self):
159
159
  return f"<MethodMaker for {self.funcname!r} method>"
160
160
 
161
- def __get__(self, obj, objtype=None):
162
- if objtype is None or issubclass(objtype, type):
163
- # Called with get(ourclass, type(ourclass))
164
- cls = obj
161
+ def __get__(self, inst, cls):
162
+ local_vars = {}
163
+
164
+ # This can be called via super().funcname(...) in which case the class
165
+ # may not be the correct one. If this is the correct class
166
+ # it should have this descriptor in the class dict under
167
+ # the correct funcname.
168
+ # Otherwise is should be found in the MRO of the class.
169
+ if cls.__dict__.get(self.funcname) is self:
170
+ gen_cls = cls
165
171
  else:
166
- # Called with get(inst | None, ourclass)
167
- cls = objtype
172
+ for c in cls.__mro__[1:]: # skip 'cls' as special cased
173
+ if c.__dict__.get(self.funcname) is self:
174
+ gen_cls = c
175
+ break
176
+ else: # pragma: no cover
177
+ # This should only be reached if called with incorrect arguments
178
+ # manually
179
+ raise AttributeError(
180
+ f"Could not find {self!r} in class {cls.__name__!r} MRO."
181
+ )
168
182
 
169
- local_vars = {}
170
- gen = self.code_generator(cls, self.funcname)
183
+ gen = self.code_generator(gen_cls, self.funcname)
171
184
  exec(gen.source_code, gen.globs, local_vars)
172
185
  method = local_vars.get(self.funcname)
173
186
 
174
187
  try:
175
- method.__qualname__ = f"{cls.__qualname__}.{self.funcname}"
188
+ method.__qualname__ = f"{gen_cls.__qualname__}.{self.funcname}"
176
189
  except AttributeError:
177
190
  # This might be a property or some other special
178
191
  # descriptor. Don't try to rename.
179
192
  pass
180
193
 
181
194
  # Replace this descriptor on the class with the generated function
182
- setattr(cls, self.funcname, method)
195
+ setattr(gen_cls, self.funcname, method)
183
196
 
184
197
  # Use 'get' to return the generated function as a bound method
185
198
  # instead of as a regular function for first usage.
186
- return method.__get__(obj, objtype)
199
+ return method.__get__(inst, cls)
200
+
201
+
202
+ class _SignatureMaker:
203
+ # 'inspect.signature' calls the `__get__` method of the `__init__` methodmaker with
204
+ # the wrong arguments.
205
+ # Instead of __get__(None, cls) or __get__(inst, type(inst))
206
+ # it uses __get__(cls, type(cls)).
207
+ #
208
+ # If this is done before `__init__` has been generated then
209
+ # help(cls) will fail along with inspect.signature(cls)
210
+ # This signature maker descriptor is placed to override __signature__ and force
211
+ # the `__init__` signature to be generated first if the signature is requested.
212
+ def __get__(self, instance, cls):
213
+ import inspect # Deferred inspect import
214
+ _ = cls.__init__ # force generation of `__init__` function
215
+ # Remove this attribute from the class
216
+ # This prevents recursion back into this __get__ method.
217
+ delattr(cls, "__signature__")
218
+ sig = inspect.signature(cls)
219
+ setattr(cls, "__signature__", sig)
220
+ return sig
221
+
222
+
223
+ signature_maker = _SignatureMaker()
187
224
 
188
225
 
189
226
  def get_init_generator(null=NOTHING, extra_code=None):
@@ -402,7 +439,7 @@ _field_init_maker = MethodMaker(
402
439
  )
403
440
 
404
441
 
405
- def builder(cls=None, /, *, gatherer, methods, flags=None):
442
+ def builder(cls=None, /, *, gatherer, methods, flags=None, fix_signature=True):
406
443
  """
407
444
  The main builder for class generation
408
445
 
@@ -413,6 +450,10 @@ def builder(cls=None, /, *, gatherer, methods, flags=None):
413
450
  :type methods: set[MethodMaker]
414
451
  :param flags: additional flags to store in the internals dictionary
415
452
  for use by method generators.
453
+ :type flags: None | dict[str, bool]
454
+ :param fix_signature: Add a __signature__ attribute to work-around an issue with
455
+ inspect.signature incorrectly handling __init__ descriptors.
456
+ :type fix_signature: bool
416
457
  :return: The modified class (the class itself is modified, but this is expected).
417
458
  """
418
459
  # Handle `None` to make wrapping with a decorator easier.
@@ -459,6 +500,10 @@ def builder(cls=None, /, *, gatherer, methods, flags=None):
459
500
 
460
501
  internals["methods"] = _MappingProxyType(internal_methods)
461
502
 
503
+ # Fix for inspect.signature(cls)
504
+ if fix_signature:
505
+ setattr(cls, "__signature__", signature_maker)
506
+
462
507
  return cls
463
508
 
464
509
 
@@ -1,6 +1,8 @@
1
1
  import types
2
2
  import typing
3
3
 
4
+ import inspect
5
+
4
6
  from collections.abc import Callable
5
7
  from types import MappingProxyType
6
8
  from typing_extensions import dataclass_transform
@@ -49,7 +51,12 @@ class MethodMaker:
49
51
  code_generator: _CodegenType
50
52
  def __init__(self, funcname: str, code_generator: _CodegenType) -> None: ...
51
53
  def __repr__(self) -> str: ...
52
- def __get__(self, instance, cls=None) -> Callable: ...
54
+ def __get__(self, instance, cls) -> Callable: ...
55
+
56
+ class _SignatureMaker:
57
+ def __get__(self, instance, cls) -> inspect.Signature: ...
58
+
59
+ signature_maker: _SignatureMaker
53
60
 
54
61
  def get_init_generator(
55
62
  null: _NothingType = NOTHING,
@@ -86,6 +93,7 @@ def builder(
86
93
  gatherer: Callable[[type], tuple[dict[str, Field], dict[str, typing.Any]]],
87
94
  methods: frozenset[MethodMaker] | set[MethodMaker],
88
95
  flags: dict[str, bool] | None = None,
96
+ fix_signature: bool = ...,
89
97
  ) -> type[_T]: ...
90
98
 
91
99
  @typing.overload
@@ -96,6 +104,7 @@ def builder(
96
104
  gatherer: Callable[[type], tuple[dict[str, Field], dict[str, typing.Any]]],
97
105
  methods: frozenset[MethodMaker] | set[MethodMaker],
98
106
  flags: dict[str, bool] | None = None,
107
+ fix_signature: bool = ...,
99
108
  ) -> Callable[[type[_T]], type[_T]]: ...
100
109
 
101
110
 
@@ -126,6 +135,7 @@ class Field(metaclass=SlotMakerMeta):
126
135
 
127
136
  __slots__: dict[str, str]
128
137
  __classbuilder_internals__: dict
138
+ __signature__: inspect.Signature
129
139
 
130
140
  def __init__(
131
141
  self,
@@ -249,6 +259,7 @@ class GatheredFields:
249
259
  modifications: dict[str, typing.Any]
250
260
 
251
261
  __classbuilder_internals__: dict
262
+ __signature__: inspect.Signature
252
263
 
253
264
  def __init__(
254
265
  self,
@@ -2,6 +2,8 @@ import typing
2
2
  from types import MappingProxyType
3
3
  from typing_extensions import dataclass_transform
4
4
 
5
+ import inspect
6
+
5
7
  from collections.abc import Callable
6
8
 
7
9
  from . import (
@@ -42,6 +44,7 @@ asdict_maker: MethodMaker
42
44
 
43
45
  class Attribute(Field):
44
46
  __slots__: dict
47
+ __signature__: inspect.Signature
45
48
 
46
49
  iter: bool
47
50
  serialize: bool
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ducktools-classbuilder
3
- Version: 0.6.1
3
+ Version: 0.6.2
4
4
  Summary: Toolkit for creating class boilerplate generators
5
5
  Author: David C Ellis
6
6
  License: MIT License
@@ -0,0 +1,12 @@
1
+ ducktools/classbuilder/__init__.py,sha256=9IBwSOSXY9SFbbV6msfS9oUx2BneNrQvjZsb-iLSLZ4,32820
2
+ ducktools/classbuilder/__init__.pyi,sha256=JaAfc6y4Jj920y4lm9peduNXeb9XUJwKN-QqFpqG5pw,7924
3
+ ducktools/classbuilder/annotations.py,sha256=17g1WnWfyMWBHKQp2HD_hvV-n7CKXy_zv8NTODpOsuU,5437
4
+ ducktools/classbuilder/annotations.pyi,sha256=cTZQ-pp2IkfdvwHiU3pIySUzzBKxfOtO-e5PkA8TNwo,520
5
+ ducktools/classbuilder/prefab.py,sha256=LCrnS2zKADX5FS0SdzCYLF4n5yR43CIlv2QLL6PDjl4,23292
6
+ ducktools/classbuilder/prefab.pyi,sha256=kmqZcP2aGHEAb3-cLCBlYi6gclzsQAAmY7cnHvKT7jQ,4360
7
+ ducktools/classbuilder/py.typed,sha256=la67KBlbjXN-_-DfGNcdOcjYumVpKG_Tkw-8n5dnGB4,8
8
+ ducktools_classbuilder-0.6.2.dist-info/LICENSE.md,sha256=6Thz9Dbw8R4fWInl6sGl8Rj3UnKnRbDwrc6jZerpugQ,1070
9
+ ducktools_classbuilder-0.6.2.dist-info/METADATA,sha256=fGjzXJdePiVfTkzZozk9-HHFW-zZJakoPnBrTmMepAE,10911
10
+ ducktools_classbuilder-0.6.2.dist-info/WHEEL,sha256=y4mX-SOX4fYIkonsAGA5N0Oy-8_gI4FXw5HNI1xqvWg,91
11
+ ducktools_classbuilder-0.6.2.dist-info/top_level.txt,sha256=uSDLtio3ZFqdwcsMJ2O5yhjB4Q3ytbBWbA8rJREganc,10
12
+ ducktools_classbuilder-0.6.2.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (70.1.0)
2
+ Generator: setuptools (70.2.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,12 +0,0 @@
1
- ducktools/classbuilder/__init__.py,sha256=i0NqVICBsMUBQtxX_2UsnETE9y0X15cwTSoSlE6_fJI,30820
2
- ducktools/classbuilder/__init__.pyi,sha256=tdYtI5Wqn-iur-MGGSpk4EbQTtl91F_MOGHXwYf4BYo,7656
3
- ducktools/classbuilder/annotations.py,sha256=17g1WnWfyMWBHKQp2HD_hvV-n7CKXy_zv8NTODpOsuU,5437
4
- ducktools/classbuilder/annotations.pyi,sha256=cTZQ-pp2IkfdvwHiU3pIySUzzBKxfOtO-e5PkA8TNwo,520
5
- ducktools/classbuilder/prefab.py,sha256=LCrnS2zKADX5FS0SdzCYLF4n5yR43CIlv2QLL6PDjl4,23292
6
- ducktools/classbuilder/prefab.pyi,sha256=1YkrSSTcjzhNn_AXSYiGIaX7kLm3DFckCR_zytFp0PE,4307
7
- ducktools/classbuilder/py.typed,sha256=la67KBlbjXN-_-DfGNcdOcjYumVpKG_Tkw-8n5dnGB4,8
8
- ducktools_classbuilder-0.6.1.dist-info/LICENSE.md,sha256=6Thz9Dbw8R4fWInl6sGl8Rj3UnKnRbDwrc6jZerpugQ,1070
9
- ducktools_classbuilder-0.6.1.dist-info/METADATA,sha256=7sRGN_m6jeEKYCdajAu8NN-7cs3U4E7s6YUdEj643m4,10911
10
- ducktools_classbuilder-0.6.1.dist-info/WHEEL,sha256=cpQTJ5IWu9CdaPViMhC9YzF8gZuS5-vlfoFihTBC86A,91
11
- ducktools_classbuilder-0.6.1.dist-info/top_level.txt,sha256=uSDLtio3ZFqdwcsMJ2O5yhjB4Q3ytbBWbA8rJREganc,10
12
- ducktools_classbuilder-0.6.1.dist-info/RECORD,,