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.
- ducktools/classbuilder/__init__.py +26 -5
- ducktools/classbuilder/__init__.pyi +8 -4
- ducktools/classbuilder/_version.py +2 -2
- ducktools/classbuilder/annotations.py +32 -2
- ducktools/classbuilder/annotations.pyi +5 -0
- ducktools/classbuilder/prefab.py +18 -23
- {ducktools_classbuilder-0.10.1.dist-info → ducktools_classbuilder-0.10.2.dist-info}/METADATA +1 -1
- ducktools_classbuilder-0.10.2.dist-info/RECORD +13 -0
- ducktools_classbuilder-0.10.1.dist-info/RECORD +0 -13
- {ducktools_classbuilder-0.10.1.dist-info → ducktools_classbuilder-0.10.2.dist-info}/WHEEL +0 -0
- {ducktools_classbuilder-0.10.1.dist-info → ducktools_classbuilder-0.10.2.dist-info}/licenses/LICENSE +0 -0
- {ducktools_classbuilder-0.10.1.dist-info → ducktools_classbuilder-0.10.2.dist-info}/top_level.txt +0 -0
|
@@ -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
|
|
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) == (
|
|
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,
|
|
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__(
|
|
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.
|
|
2
|
-
__version_tuple__ = (0, 10,
|
|
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
|
|
27
|
+
global _lazy_annotationlib
|
|
28
28
|
import annotationlib # type: ignore
|
|
29
|
-
|
|
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: ...
|
ducktools/classbuilder/prefab.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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})
|
|
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
|
-
|
|
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__"):
|
|
@@ -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,,
|
|
File without changes
|
{ducktools_classbuilder-0.10.1.dist-info → ducktools_classbuilder-0.10.2.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|
{ducktools_classbuilder-0.10.1.dist-info → ducktools_classbuilder-0.10.2.dist-info}/top_level.txt
RENAMED
|
File without changes
|