ducktools-classbuilder 0.10.1__py3-none-any.whl → 0.10.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.

@@ -31,8 +31,9 @@
31
31
  # Field itself sidesteps this by defining __slots__ to avoid that branch.
32
32
 
33
33
  import os
34
+ import sys
34
35
 
35
- from .annotations import get_ns_annotations, is_classvar
36
+ from .annotations import get_ns_annotations, is_classvar, make_annotate_func
36
37
  from ._version import __version__, __version_tuple__ # noqa: F401
37
38
 
38
39
  # Change this name if you make heavy modifications
@@ -131,19 +132,25 @@ class GeneratedCode:
131
132
  This class provides a return value for the generated output from source code
132
133
  generators.
133
134
  """
134
- __slots__ = ("source_code", "globs")
135
+ __slots__ = ("source_code", "globs", "annotations")
135
136
 
136
- def __init__(self, source_code, globs):
137
+ def __init__(self, source_code, globs, annotations=None):
137
138
  self.source_code = source_code
138
139
  self.globs = globs
140
+ self.annotations = annotations
139
141
 
140
142
  def __repr__(self):
141
143
  first_source_line = self.source_code.split("\n")[0]
142
- return f"GeneratorOutput(source_code='{first_source_line} ...', globs={self.globs!r})"
144
+ return (
145
+ f"GeneratorOutput(source_code='{first_source_line} ...', "
146
+ f"globs={self.globs!r}, annotations={self.annotations!r})"
147
+ )
143
148
 
144
149
  def __eq__(self, other):
145
150
  if self.__class__ is other.__class__:
146
- return (self.source_code, self.globs) == (other.source_code, other.globs)
151
+ return (self.source_code, self.globs, self.annotations) == (
152
+ other.source_code, other.globs, other.annotations
153
+ )
147
154
  return NotImplemented
148
155
 
149
156
 
@@ -199,6 +206,20 @@ class MethodMaker:
199
206
  # descriptor. Don't try to rename.
200
207
  pass
201
208
 
209
+ # Apply annotations
210
+ if gen.annotations is not None:
211
+ if sys.version_info >= (3, 14):
212
+ # If __annotations__ exists on the class, either they
213
+ # are user defined or they are using __future__ annotations.
214
+ # In this case, just write __annotations__
215
+ if "__annotations__" in gen_cls.__dict__:
216
+ method.__annotations__ = gen.annotations
217
+ else:
218
+ anno_func = make_annotate_func(gen.annotations)
219
+ method.__annotate__ = anno_func
220
+ else:
221
+ method.__annotations__ = gen.annotations
222
+
202
223
  # Replace this descriptor on the class with the generated function
203
224
  setattr(gen_cls, self.funcname, method)
204
225
 
@@ -40,16 +40,20 @@ class KW_ONLY(metaclass=_KW_ONLY_META): ...
40
40
  class _CodegenType(typing.Protocol):
41
41
  def __call__(self, cls: type, funcname: str = ...) -> GeneratedCode: ...
42
42
 
43
-
44
43
  class GeneratedCode:
45
- __slots__: tuple[str, str]
44
+ __slots__: tuple[str, ...]
46
45
  source_code: str
47
46
  globs: dict[str, typing.Any]
47
+ annotations: dict[str, typing.Any]
48
48
 
49
- def __init__(self, source_code: str, globs: dict[str, typing.Any]) -> None: ...
49
+ def __init__(
50
+ self,
51
+ source_code: str,
52
+ globs: dict[str, typing.Any],
53
+ annotations: dict[str, typing.Any] | None = ...,
54
+ ) -> None: ...
50
55
  def __repr__(self) -> str: ...
51
56
 
52
-
53
57
  class MethodMaker:
54
58
  funcname: str
55
59
  code_generator: _CodegenType
@@ -1,2 +1,2 @@
1
- __version__ = "0.10.1"
2
- __version_tuple__ = (0, 10, 1)
1
+ __version__ = "0.10.2"
2
+ __version_tuple__ = (0, 10, 2)
@@ -24,9 +24,9 @@ import sys
24
24
 
25
25
  class _LazyAnnotationLib:
26
26
  def __getattr__(self, item):
27
- global _lazyannotationlib
27
+ global _lazy_annotationlib
28
28
  import annotationlib # type: ignore
29
- _lazyannotationlib = annotationlib
29
+ _lazy_annotationlib = annotationlib
30
30
  return getattr(annotationlib, item)
31
31
 
32
32
 
@@ -83,6 +83,36 @@ def get_ns_annotations(ns):
83
83
  return annotations
84
84
 
85
85
 
86
+ def make_annotate_func(annos):
87
+ # Only used in 3.14 or later so no sys.version_info gate
88
+
89
+ type_repr = _lazy_annotationlib.type_repr
90
+ Format = _lazy_annotationlib.Format
91
+ ForwardRef = _lazy_annotationlib.ForwardRef
92
+ # Construct an annotation function from __annotations__
93
+ def annotate_func(format, /):
94
+ match format:
95
+ case Format.VALUE | Format.FORWARDREF:
96
+ return {
97
+ k: v.evaluate(format=format)
98
+ if isinstance(v, ForwardRef) else v
99
+ for k, v in annos.items()
100
+ }
101
+ case Format.STRING:
102
+ string_annos = {}
103
+ for k, v in annos.items():
104
+ if isinstance(v, str):
105
+ string_annos[k] = v
106
+ elif isinstance(v, ForwardRef):
107
+ string_annos[k] = v.evaluate(format=Format.STRING)
108
+ else:
109
+ string_annos[k] = type_repr(v)
110
+ return string_annos
111
+ case _:
112
+ raise NotImplementedError(format)
113
+ return annotate_func
114
+
115
+
86
116
  def is_classvar(hint):
87
117
  if isinstance(hint, str):
88
118
  # String annotations, just check if the string 'ClassVar' is in there
@@ -1,3 +1,4 @@
1
+ from collections.abc import Callable
1
2
  import typing
2
3
  import types
3
4
 
@@ -11,6 +12,10 @@ def get_ns_annotations(
11
12
  ns: _CopiableMappings,
12
13
  ) -> dict[str, typing.Any]: ...
13
14
 
15
+ def make_annotate_func(
16
+ annos: dict[str, typing.Any]
17
+ ) -> Callable[[int], dict[str, typing.Any]]: ...
18
+
14
19
  def is_classvar(
15
20
  hint: object,
16
21
  ) -> bool: ...
@@ -64,6 +64,7 @@ def get_attributes(cls):
64
64
  # Method Generators
65
65
  def init_generator(cls, funcname="__init__"):
66
66
  globs = {}
67
+ annotations = {}
67
68
  # Get the internals dictionary and prepare attributes
68
69
  attributes = get_attributes(cls)
69
70
  flags = get_flags(cls)
@@ -106,41 +107,29 @@ def init_generator(cls, funcname="__init__"):
106
107
  kw_only_arglist = []
107
108
  for name, attrib in attributes.items():
108
109
  # post_init annotations can be used to broaden types.
109
- if name in post_init_annotations:
110
- globs[f"_{name}_type"] = post_init_annotations[name]
111
- elif attrib.type is not NOTHING:
112
- globs[f"_{name}_type"] = attrib.type
113
-
114
110
  if attrib.init:
111
+ if name in post_init_annotations:
112
+ annotations[name] = post_init_annotations[name]
113
+ elif attrib.type is not NOTHING:
114
+ annotations[name] = attrib.type
115
+
115
116
  if attrib.default is not NOTHING:
116
117
  if isinstance(attrib.default, (str, int, float, bool)):
117
118
  # Just use the literal in these cases
118
- if attrib.type is NOTHING:
119
- arg = f"{name}={attrib.default!r}"
120
- else:
121
- arg = f"{name}: _{name}_type = {attrib.default!r}"
119
+ arg = f"{name}={attrib.default!r}"
122
120
  else:
123
121
  # No guarantee repr will work for other objects
124
122
  # so store the value in a variable and put it
125
123
  # in the globals dict for eval
126
- if attrib.type is NOTHING:
127
- arg = f"{name}=_{name}_default"
128
- else:
129
- arg = f"{name}: _{name}_type = _{name}_default"
124
+ arg = f"{name}=_{name}_default"
130
125
  globs[f"_{name}_default"] = attrib.default
131
126
  elif attrib.default_factory is not NOTHING:
132
127
  # Use NONE here and call the factory later
133
128
  # This matches the behaviour of compiled
134
- if attrib.type is NOTHING:
135
- arg = f"{name}=None"
136
- else:
137
- arg = f"{name}: _{name}_type = None"
129
+ arg = f"{name}=None"
138
130
  globs[f"_{name}_factory"] = attrib.default_factory
139
131
  else:
140
- if attrib.type is NOTHING:
141
- arg = name
142
- else:
143
- arg = f"{name}: _{name}_type"
132
+ arg = name
144
133
  if attrib.kw_only or kw_only:
145
134
  kw_only_arglist.append(arg)
146
135
  else:
@@ -206,13 +195,19 @@ def init_generator(cls, funcname="__init__"):
206
195
  post_init_call = ""
207
196
 
208
197
  code = (
209
- f"def {funcname}(self, {args}) -> None:\n"
198
+ f"def {funcname}(self, {args}):\n"
210
199
  f"{pre_init_call}\n"
211
200
  f"{body}\n"
212
201
  f"{post_init_call}\n"
213
202
  )
214
203
 
215
- return GeneratedCode(code, globs)
204
+ if annotations:
205
+ annotations["return"] = None
206
+ else:
207
+ # If there are no annotations, return an unannotated init function
208
+ annotations = None
209
+
210
+ return GeneratedCode(code, globs, annotations)
216
211
 
217
212
 
218
213
  def iter_generator(cls, funcname="__iter__"):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ducktools-classbuilder
3
- Version: 0.10.1
3
+ Version: 0.10.2
4
4
  Summary: Toolkit for creating class boilerplate generators
5
5
  Author: David C Ellis
6
6
  Project-URL: Homepage, https://github.com/davidcellis/ducktools-classbuilder
@@ -0,0 +1,13 @@
1
+ ducktools/classbuilder/__init__.py,sha256=uTJI45-T5ZENOfN5CqnPJRujendDYwQsu0A-zsOdIho,38198
2
+ ducktools/classbuilder/__init__.pyi,sha256=gL_YMTFovtD0vTPeBH-wkiy_l761bxItruIBRs6Aouw,8302
3
+ ducktools/classbuilder/_version.py,sha256=DH400d3tmKMEKy7OEfbyeJl0NAXPlOLDwet_95nLc-M,54
4
+ ducktools/classbuilder/annotations.py,sha256=JtL8RhNp5hB_dfUju_zsqI19F9yTRQ1uM5ZTpas-VVw,4753
5
+ ducktools/classbuilder/annotations.pyi,sha256=oUTN_ee7DWCz4CI36I7mAD43Bxlz_e5ises_VNnCX9g,480
6
+ ducktools/classbuilder/prefab.py,sha256=UiYBvVSWM4BB4JGwUkYtG9QhPK8TzCYUiey_dgNejo8,24345
7
+ ducktools/classbuilder/prefab.pyi,sha256=eZLjFhM-oHeRBONpCWSb1wLJKGs5Xiq0w_Jeor8Go-w,6525
8
+ ducktools/classbuilder/py.typed,sha256=la67KBlbjXN-_-DfGNcdOcjYumVpKG_Tkw-8n5dnGB4,8
9
+ ducktools_classbuilder-0.10.2.dist-info/licenses/LICENSE,sha256=6Thz9Dbw8R4fWInl6sGl8Rj3UnKnRbDwrc6jZerpugQ,1070
10
+ ducktools_classbuilder-0.10.2.dist-info/METADATA,sha256=bL2jfRX1oqIJGKySrWwdEHsX_8gpOHCFPRy9i66fQnQ,9231
11
+ ducktools_classbuilder-0.10.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
12
+ ducktools_classbuilder-0.10.2.dist-info/top_level.txt,sha256=uSDLtio3ZFqdwcsMJ2O5yhjB4Q3ytbBWbA8rJREganc,10
13
+ ducktools_classbuilder-0.10.2.dist-info/RECORD,,
@@ -1,13 +0,0 @@
1
- ducktools/classbuilder/__init__.py,sha256=zJuGJ8_1EPEYJqO0XXDuknwGD1MxPJSZXS5_wFAW6B0,37299
2
- ducktools/classbuilder/__init__.pyi,sha256=NazFRfLcMpU533jGixaWHV_kARtc9J4-xXX-gkD8rFY,8177
3
- ducktools/classbuilder/_version.py,sha256=kdUPXlUQWcTv2wnqFkwrCx7Ue7WU2JVPnG1UU0gmLck,54
4
- ducktools/classbuilder/annotations.py,sha256=ImEEuzEFUrGNRMlAl7YN-H8PZT1FOK8D_yL1LygNdmo,3626
5
- ducktools/classbuilder/annotations.pyi,sha256=zAKJbEN1klvZu71ShuvKbf6OE7ddKb5USC6MbOjRPfY,336
6
- ducktools/classbuilder/prefab.py,sha256=RJfOv9yzJsySaBz6w4e5ZP8Q7K3LWX6_Buuh2zD9YDQ,24689
7
- ducktools/classbuilder/prefab.pyi,sha256=eZLjFhM-oHeRBONpCWSb1wLJKGs5Xiq0w_Jeor8Go-w,6525
8
- ducktools/classbuilder/py.typed,sha256=la67KBlbjXN-_-DfGNcdOcjYumVpKG_Tkw-8n5dnGB4,8
9
- ducktools_classbuilder-0.10.1.dist-info/licenses/LICENSE,sha256=6Thz9Dbw8R4fWInl6sGl8Rj3UnKnRbDwrc6jZerpugQ,1070
10
- ducktools_classbuilder-0.10.1.dist-info/METADATA,sha256=LkjNjXyb49v3vYBEvbldkThl9xAgCHhVc8dPNgiMsxw,9231
11
- ducktools_classbuilder-0.10.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
12
- ducktools_classbuilder-0.10.1.dist-info/top_level.txt,sha256=uSDLtio3ZFqdwcsMJ2O5yhjB4Q3ytbBWbA8rJREganc,10
13
- ducktools_classbuilder-0.10.1.dist-info/RECORD,,