ducktools-classbuilder 0.7.5__py3-none-any.whl → 0.8.0__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 +2 -2
- ducktools/classbuilder/_version.py +2 -2
- ducktools/classbuilder/annotations.py +47 -179
- ducktools/classbuilder/annotations.pyi +0 -15
- {ducktools_classbuilder-0.7.5.dist-info → ducktools_classbuilder-0.8.0.dist-info}/METADATA +15 -15
- ducktools_classbuilder-0.8.0.dist-info/RECORD +13 -0
- {ducktools_classbuilder-0.7.5.dist-info → ducktools_classbuilder-0.8.0.dist-info}/WHEEL +1 -1
- ducktools_classbuilder-0.7.5.dist-info/RECORD +0 -13
- {ducktools_classbuilder-0.7.5.dist-info → ducktools_classbuilder-0.8.0.dist-info}/LICENSE.md +0 -0
- {ducktools_classbuilder-0.7.5.dist-info → ducktools_classbuilder-0.8.0.dist-info}/top_level.txt +0 -0
|
@@ -769,7 +769,7 @@ def make_annotation_gatherer(
|
|
|
769
769
|
if is_classvar(v):
|
|
770
770
|
continue
|
|
771
771
|
|
|
772
|
-
if v is KW_ONLY:
|
|
772
|
+
if v is KW_ONLY or (isinstance(v, str) and v == "KW_ONLY"):
|
|
773
773
|
if kw_flag:
|
|
774
774
|
raise SyntaxError("KW_ONLY sentinel may only appear once.")
|
|
775
775
|
kw_flag = True
|
|
@@ -868,7 +868,7 @@ def make_unified_gatherer(
|
|
|
868
868
|
# To choose between annotation and attribute gatherers
|
|
869
869
|
# compare sets of names.
|
|
870
870
|
# Don't bother evaluating string annotations, as we only need names
|
|
871
|
-
cls_annotations = get_ns_annotations(cls_dict
|
|
871
|
+
cls_annotations = get_ns_annotations(cls_dict)
|
|
872
872
|
cls_attributes = {
|
|
873
873
|
k: v for k, v in cls_dict.items() if isinstance(v, field_type)
|
|
874
874
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
__version__ = "0.
|
|
2
|
-
__version_tuple__ = (0,
|
|
1
|
+
__version__ = "0.8.0"
|
|
2
|
+
__version_tuple__ = (0, 8, 0)
|
|
@@ -19,205 +19,73 @@
|
|
|
19
19
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
20
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
21
|
# SOFTWARE.
|
|
22
|
-
|
|
23
22
|
import sys
|
|
24
|
-
import builtins
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
class _Stringlike(str):
|
|
28
|
-
# There are typing operators that are not supported by strings
|
|
29
|
-
# This adds the 'or' operator '|'
|
|
30
|
-
|
|
31
|
-
def __or__(self, other):
|
|
32
|
-
if isinstance(other, str):
|
|
33
|
-
other_r = other
|
|
34
|
-
elif name := getattr(other, "__name__", None):
|
|
35
|
-
other_r = name
|
|
36
|
-
else:
|
|
37
|
-
other_r = str(other)
|
|
38
|
-
|
|
39
|
-
return type(self)(f"{self} | {other_r}")
|
|
40
|
-
|
|
41
|
-
def __ror__(self, other):
|
|
42
|
-
if isinstance(other, str):
|
|
43
|
-
other_r = other
|
|
44
|
-
elif name := getattr(other, "__name__", None):
|
|
45
|
-
other_r = name
|
|
46
|
-
else:
|
|
47
|
-
other_r = str(other)
|
|
48
|
-
|
|
49
|
-
return type(self)(f"{other_r} | {self}")
|
|
50
|
-
|
|
51
|
-
def __repr__(self):
|
|
52
|
-
base = super().__repr__()
|
|
53
|
-
clsname = type(self).__name__
|
|
54
|
-
return f"{clsname}({base})"
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
class _StringGlobs(dict):
|
|
58
|
-
"""
|
|
59
|
-
Based on the fake globals dictionary used for annotations
|
|
60
|
-
from 3.14. This allows us to evaluate containers which
|
|
61
|
-
include forward references.
|
|
62
|
-
|
|
63
|
-
It's just a dictionary that returns the key if the key
|
|
64
|
-
is not found.
|
|
65
|
-
"""
|
|
66
|
-
def __missing__(self, key):
|
|
67
|
-
return _Stringlike(key)
|
|
68
23
|
|
|
69
|
-
def __repr__(self):
|
|
70
|
-
cls_name = self.__class__.__name__
|
|
71
|
-
dict_repr = super().__repr__()
|
|
72
|
-
return f"{cls_name}({dict_repr})"
|
|
73
24
|
|
|
74
|
-
|
|
75
|
-
def eval_hint(hint, context=None, *, recursion_limit=2):
|
|
76
|
-
"""
|
|
77
|
-
Attempt to evaluate a string type hint in the given
|
|
78
|
-
context.
|
|
79
|
-
|
|
80
|
-
If this raises an exception, return the last string.
|
|
81
|
-
|
|
82
|
-
If the recursion limit is hit or a previous value returns
|
|
83
|
-
on evaluation, return the original hint string.
|
|
84
|
-
|
|
85
|
-
Example::
|
|
86
|
-
import builtins
|
|
87
|
-
from typing import ClassVar
|
|
88
|
-
|
|
89
|
-
from ducktools.classbuilder.annotations import eval_hint
|
|
90
|
-
|
|
91
|
-
foo = "foo"
|
|
92
|
-
|
|
93
|
-
context = {**vars(builtins), **globals(), **locals()}
|
|
94
|
-
eval_hint("foo", context) # returns 'foo'
|
|
95
|
-
|
|
96
|
-
eval_hint("ClassVar[str]", context) # returns typing.ClassVar[str]
|
|
97
|
-
eval_hint("ClassVar[forwardref]", context) # returns typing.ClassVar[ForwardRef('forwardref')]
|
|
98
|
-
|
|
99
|
-
:param hint: The existing type hint
|
|
100
|
-
:param context: merged context
|
|
101
|
-
:param recursion_limit: maximum number of evaluation loops before
|
|
102
|
-
returning the original string.
|
|
103
|
-
:return: evaluated hint, or string if it could not evaluate
|
|
104
|
-
"""
|
|
105
|
-
if context is not None:
|
|
106
|
-
context = _StringGlobs(context)
|
|
107
|
-
|
|
108
|
-
original_hint = hint
|
|
109
|
-
|
|
110
|
-
# Using a set would require the hint always be hashable
|
|
111
|
-
# This is only going to be 2 items at most usually
|
|
112
|
-
seen = []
|
|
113
|
-
i = 0
|
|
114
|
-
while isinstance(hint, str):
|
|
115
|
-
seen.append(hint)
|
|
116
|
-
|
|
117
|
-
# noinspection PyBroadException
|
|
118
|
-
try:
|
|
119
|
-
hint = eval(hint, context)
|
|
120
|
-
except Exception:
|
|
121
|
-
break
|
|
122
|
-
|
|
123
|
-
if hint in seen or i >= recursion_limit:
|
|
124
|
-
hint = original_hint
|
|
125
|
-
break
|
|
126
|
-
|
|
127
|
-
i += 1
|
|
128
|
-
|
|
129
|
-
return hint
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
def get_ns_annotations(ns, eval_str=True):
|
|
25
|
+
def get_ns_annotations(ns):
|
|
133
26
|
"""
|
|
134
27
|
Given a class namespace, attempt to retrieve the
|
|
135
|
-
annotations dictionary
|
|
136
|
-
|
|
137
|
-
Note: This only evaluates in the context of module level globals
|
|
138
|
-
and values in the class namespace. Non-local variables will not
|
|
139
|
-
be evaluated.
|
|
28
|
+
annotations dictionary.
|
|
140
29
|
|
|
141
30
|
:param ns: Class namespace (eg cls.__dict__)
|
|
142
|
-
:
|
|
143
|
-
:return: dictionary of evaluated annotations
|
|
31
|
+
:return: dictionary of annotations
|
|
144
32
|
"""
|
|
145
33
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
annotate = ns.get("__annotate__")
|
|
150
|
-
|
|
151
|
-
if annotate is not None:
|
|
152
|
-
try:
|
|
153
|
-
raw_annotations = annotate(1) # VALUE call
|
|
154
|
-
except (NameError, AttributeError):
|
|
155
|
-
# Slow path, only used if annotations can't be evaluated.
|
|
156
|
-
from annotationlib import Format, call_annotate_function
|
|
157
|
-
raw_annotations = call_annotate_function(annotate, format=Format.FORWARDREF)
|
|
34
|
+
annotations = ns.get("__annotations__")
|
|
35
|
+
if annotations is not None:
|
|
36
|
+
annotations = annotations.copy()
|
|
158
37
|
else:
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
# This will catch cases where someone has used a literal string for a
|
|
163
|
-
# single attribute.
|
|
164
|
-
if eval_str:
|
|
38
|
+
# See if we're using PEP-649 annotations
|
|
39
|
+
# Guarding this with a try/except instead of a version check
|
|
40
|
+
# In case there's a change and PEP-649 somehow doesn't make 3.14
|
|
165
41
|
try:
|
|
166
|
-
|
|
167
|
-
except
|
|
168
|
-
|
|
42
|
+
from annotationlib import Format, call_annotate_function, get_annotate_function
|
|
43
|
+
except ImportError:
|
|
44
|
+
pass
|
|
169
45
|
else:
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
obj_globals = {}
|
|
176
|
-
|
|
177
|
-
# Type parameters should be usable in hints without breaking
|
|
178
|
-
# This is for Python 3.12+
|
|
179
|
-
type_params = {
|
|
180
|
-
repr(param): param
|
|
181
|
-
for param in ns.get("__type_params__", ())
|
|
182
|
-
}
|
|
46
|
+
annotate = ns.get("__annotate__") # Works in the alphas, but may break
|
|
47
|
+
if not annotate:
|
|
48
|
+
annotate = get_annotate_function(ns)
|
|
49
|
+
if annotate:
|
|
50
|
+
annotations = call_annotate_function(annotate, format=Format.FORWARDREF)
|
|
183
51
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
annotations = {
|
|
187
|
-
k: eval_hint(v, context)
|
|
188
|
-
for k, v in raw_annotations.items()
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
else:
|
|
192
|
-
annotations = raw_annotations.copy()
|
|
52
|
+
if annotations is None:
|
|
53
|
+
annotations = {}
|
|
193
54
|
|
|
194
55
|
return annotations
|
|
195
56
|
|
|
196
57
|
|
|
197
58
|
def is_classvar(hint):
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
#
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
59
|
+
if isinstance(hint, str):
|
|
60
|
+
# String annotations, just check if the string 'ClassVar' is in there
|
|
61
|
+
# This is overly broad and could be smarter.
|
|
62
|
+
return "ClassVar" in hint
|
|
63
|
+
elif (annotationlib := sys.modules.get("annotationlib")) and isinstance(hint, annotationlib.ForwardRef):
|
|
64
|
+
return "ClassVar" in hint.__arg__
|
|
65
|
+
else:
|
|
66
|
+
_typing = sys.modules.get("typing")
|
|
67
|
+
if _typing:
|
|
68
|
+
# Annotated is a nightmare I'm never waking up from
|
|
69
|
+
# 3.8 and 3.9 need Annotated from typing_extensions
|
|
70
|
+
# 3.8 also needs get_origin from typing_extensions
|
|
71
|
+
if sys.version_info < (3, 10):
|
|
72
|
+
_typing_extensions = sys.modules.get("typing_extensions")
|
|
73
|
+
if _typing_extensions:
|
|
74
|
+
_Annotated = _typing_extensions.Annotated
|
|
75
|
+
_get_origin = _typing_extensions.get_origin
|
|
76
|
+
else:
|
|
77
|
+
_Annotated, _get_origin = None, None
|
|
208
78
|
else:
|
|
209
|
-
_Annotated
|
|
210
|
-
|
|
211
|
-
_Annotated = _typing.Annotated
|
|
212
|
-
_get_origin = _typing.get_origin
|
|
79
|
+
_Annotated = _typing.Annotated
|
|
80
|
+
_get_origin = _typing.get_origin
|
|
213
81
|
|
|
214
|
-
|
|
215
|
-
|
|
82
|
+
if _Annotated and _get_origin(hint) is _Annotated:
|
|
83
|
+
hint = getattr(hint, "__origin__", None)
|
|
216
84
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
85
|
+
if (
|
|
86
|
+
hint is _typing.ClassVar
|
|
87
|
+
or getattr(hint, "__origin__", None) is _typing.ClassVar
|
|
88
|
+
):
|
|
89
|
+
return True
|
|
222
90
|
return False
|
|
223
91
|
|
|
@@ -1,25 +1,10 @@
|
|
|
1
|
-
from collections.abc import Callable
|
|
2
1
|
import typing
|
|
3
2
|
import types
|
|
4
3
|
|
|
5
|
-
_T = typing.TypeVar("_T")
|
|
6
4
|
_CopiableMappings = dict[str, typing.Any] | types.MappingProxyType[str, typing.Any]
|
|
7
5
|
|
|
8
|
-
class _StringGlobs(dict):
|
|
9
|
-
def __missing__(self, key: _T) -> _T: ...
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def eval_hint(
|
|
13
|
-
hint: type | str,
|
|
14
|
-
context: None | dict[str, typing.Any] = None,
|
|
15
|
-
*,
|
|
16
|
-
recursion_limit: int = 2
|
|
17
|
-
) -> type | str: ...
|
|
18
|
-
|
|
19
|
-
|
|
20
6
|
def get_ns_annotations(
|
|
21
7
|
ns: _CopiableMappings,
|
|
22
|
-
eval_str: bool = True,
|
|
23
8
|
) -> dict[str, typing.Any]: ...
|
|
24
9
|
|
|
25
10
|
def is_classvar(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: ducktools-classbuilder
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.0
|
|
4
4
|
Summary: Toolkit for creating class boilerplate generators
|
|
5
5
|
Author: David C Ellis
|
|
6
6
|
License: MIT License
|
|
@@ -86,10 +86,12 @@ These tools are available from the main `ducktools.classbuilder` module.
|
|
|
86
86
|
* `@slotclass`
|
|
87
87
|
* A decorator based implementation that uses a special dict subclass assigned
|
|
88
88
|
to `__slots__` to describe the fields for method generation.
|
|
89
|
-
* `
|
|
90
|
-
* A
|
|
91
|
-
|
|
92
|
-
|
|
89
|
+
* `SlotMakerMeta`
|
|
90
|
+
* A metaclass for creating other implementations using annotations, fields or slots.
|
|
91
|
+
* This metaclass will allow for creating `__slots__` correctly in subclasses.
|
|
92
|
+
* `builder`
|
|
93
|
+
* This is the main tool used for constructing decorators and base classes to provide
|
|
94
|
+
generated methods.
|
|
93
95
|
|
|
94
96
|
Each of these forms of class generation will result in the same methods being
|
|
95
97
|
attached to the class after the field information has been obtained.
|
|
@@ -119,8 +121,9 @@ This includes more customization including `__prefab_pre_init__` and `__prefab_p
|
|
|
119
121
|
functions for subclass customization.
|
|
120
122
|
|
|
121
123
|
A `@prefab` decorator and `Prefab` base class are provided.
|
|
122
|
-
|
|
123
|
-
|
|
124
|
+
|
|
125
|
+
`Prefab` will generate `__slots__` by default.
|
|
126
|
+
decorated classes with `@prefab` that do not declare fields using `__slots__`
|
|
124
127
|
will **not** be slotted and there is no `slots` argument to apply this.
|
|
125
128
|
|
|
126
129
|
Here is an example of applying a conversion in `__post_init__`:
|
|
@@ -164,7 +167,7 @@ the fields can be set *before* the class is constructed, so the class
|
|
|
164
167
|
will work correctly without needing to be rebuilt.
|
|
165
168
|
|
|
166
169
|
For example these two classes would be roughly equivalent, except that
|
|
167
|
-
`@dataclass` has had to recreate the class from scratch while `
|
|
170
|
+
`@dataclass` has had to recreate the class from scratch while `Prefab`
|
|
168
171
|
has created `__slots__` and added the methods on to the original class.
|
|
169
172
|
This means that any references stored to the original class *before*
|
|
170
173
|
`@dataclass` has rebuilt the class will not be pointing towards the
|
|
@@ -179,7 +182,7 @@ functions.
|
|
|
179
182
|
```python
|
|
180
183
|
import json
|
|
181
184
|
from dataclasses import dataclass
|
|
182
|
-
from ducktools.classbuilder import
|
|
185
|
+
from ducktools.classbuilder.prefab import Prefab, attribute
|
|
183
186
|
|
|
184
187
|
|
|
185
188
|
class _RegisterDescriptor:
|
|
@@ -222,10 +225,10 @@ class DataCoords:
|
|
|
222
225
|
return {"x": self.x, "y": self.y}
|
|
223
226
|
|
|
224
227
|
|
|
225
|
-
# slots=True is the default for
|
|
226
|
-
class BuilderCoords(
|
|
228
|
+
# slots=True is the default for Prefab
|
|
229
|
+
class BuilderCoords(Prefab):
|
|
227
230
|
x: float = 0.0
|
|
228
|
-
y: float =
|
|
231
|
+
y: float = attribute(default=0.0, doc="y coordinate")
|
|
229
232
|
|
|
230
233
|
@register.register_method
|
|
231
234
|
def to_json(self):
|
|
@@ -289,9 +292,6 @@ It will copy values provided as the `type` to `Field` into the
|
|
|
289
292
|
Values provided to `doc` will be placed in the final `__slots__`
|
|
290
293
|
field so they are present on the class if `help(...)` is called.
|
|
291
294
|
|
|
292
|
-
`AnnotationClass` offers the same features with additional methods of gathering
|
|
293
|
-
fields.
|
|
294
|
-
|
|
295
295
|
If you want something with more features you can look at the `prefab`
|
|
296
296
|
submodule which provides more specific features that differ further from the
|
|
297
297
|
behaviour of `dataclasses`.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
ducktools/classbuilder/__init__.py,sha256=LfPJ6WgDye3juzSovwicQpFKrEmBbl6dPmhxbVLwRaQ,32381
|
|
2
|
+
ducktools/classbuilder/__init__.pyi,sha256=t2KrEsEhKFbLpl0QeX0CiCy_VTRFRZO_2ABdfndRfZY,7939
|
|
3
|
+
ducktools/classbuilder/_version.py,sha256=kiXGlQhMHQWiHk12ZeZ9CjneqMQ_CbyHdpnt8oCSThg,52
|
|
4
|
+
ducktools/classbuilder/annotations.py,sha256=BJRZnGbsXeoXCpXIgjfEDNVNvtoz65LH2VKZsasj_kw,3601
|
|
5
|
+
ducktools/classbuilder/annotations.pyi,sha256=c5vYtULdDgMYWtkzeYMsHIbmnEuT2Ru-nNZieWvYuQ4,247
|
|
6
|
+
ducktools/classbuilder/prefab.py,sha256=6YOVVYYeuMF5gv9DnK-sCvNKxBaBoqDXNLUQ22-xINk,24097
|
|
7
|
+
ducktools/classbuilder/prefab.pyi,sha256=x2ioTpkhNjQXFeKABFOzE0xNeZ8f_W5vusmuAzE19mc,4491
|
|
8
|
+
ducktools/classbuilder/py.typed,sha256=la67KBlbjXN-_-DfGNcdOcjYumVpKG_Tkw-8n5dnGB4,8
|
|
9
|
+
ducktools_classbuilder-0.8.0.dist-info/LICENSE.md,sha256=6Thz9Dbw8R4fWInl6sGl8Rj3UnKnRbDwrc6jZerpugQ,1070
|
|
10
|
+
ducktools_classbuilder-0.8.0.dist-info/METADATA,sha256=glZbRUQ85WZJvhCxWMr0tqfxFc60eGx78dkzLi6_VXQ,10887
|
|
11
|
+
ducktools_classbuilder-0.8.0.dist-info/WHEEL,sha256=iAkIy5fosb7FzIOwONchHf19Qu7_1wCWyFNR5gu9nU0,91
|
|
12
|
+
ducktools_classbuilder-0.8.0.dist-info/top_level.txt,sha256=uSDLtio3ZFqdwcsMJ2O5yhjB4Q3ytbBWbA8rJREganc,10
|
|
13
|
+
ducktools_classbuilder-0.8.0.dist-info/RECORD,,
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
ducktools/classbuilder/__init__.py,sha256=DA5ZisvBEQGK3Mx2Y_SS43ZkIOHd1WZs2JF2g9n1URs,32354
|
|
2
|
-
ducktools/classbuilder/__init__.pyi,sha256=t2KrEsEhKFbLpl0QeX0CiCy_VTRFRZO_2ABdfndRfZY,7939
|
|
3
|
-
ducktools/classbuilder/_version.py,sha256=o9df2KexrwOUrw2GuPKSVwZk5OtIJ6Z5AHyArVPvEzg,52
|
|
4
|
-
ducktools/classbuilder/annotations.py,sha256=9QWofrFwYUxtyzwZ4vrFcoFG1Hi9gMe3mrVS3h_Sn1o,7212
|
|
5
|
-
ducktools/classbuilder/annotations.pyi,sha256=hnXC7eN3bosx0DbIpu7iCK4p16p4n9JAp5rZar9qe80,557
|
|
6
|
-
ducktools/classbuilder/prefab.py,sha256=6YOVVYYeuMF5gv9DnK-sCvNKxBaBoqDXNLUQ22-xINk,24097
|
|
7
|
-
ducktools/classbuilder/prefab.pyi,sha256=x2ioTpkhNjQXFeKABFOzE0xNeZ8f_W5vusmuAzE19mc,4491
|
|
8
|
-
ducktools/classbuilder/py.typed,sha256=la67KBlbjXN-_-DfGNcdOcjYumVpKG_Tkw-8n5dnGB4,8
|
|
9
|
-
ducktools_classbuilder-0.7.5.dist-info/LICENSE.md,sha256=6Thz9Dbw8R4fWInl6sGl8Rj3UnKnRbDwrc6jZerpugQ,1070
|
|
10
|
-
ducktools_classbuilder-0.7.5.dist-info/METADATA,sha256=ZKoBZozXg4yJD5ToSOJosDJE8oeBCMgHrU_CGXymzQQ,11004
|
|
11
|
-
ducktools_classbuilder-0.7.5.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
|
|
12
|
-
ducktools_classbuilder-0.7.5.dist-info/top_level.txt,sha256=uSDLtio3ZFqdwcsMJ2O5yhjB4Q3ytbBWbA8rJREganc,10
|
|
13
|
-
ducktools_classbuilder-0.7.5.dist-info/RECORD,,
|
{ducktools_classbuilder-0.7.5.dist-info → ducktools_classbuilder-0.8.0.dist-info}/LICENSE.md
RENAMED
|
File without changes
|
{ducktools_classbuilder-0.7.5.dist-info → ducktools_classbuilder-0.8.0.dist-info}/top_level.txt
RENAMED
|
File without changes
|