Skip to content

Commit 0bf2475

Browse files
authored
Merge pull request #99 from DavidCEllis/cached-source
Cache generated methods for performance, pre-generate cached methods
2 parents d65ff84 + 6421237 commit 0bf2475

19 files changed

Lines changed: 1248 additions & 384 deletions

README.md

Lines changed: 230 additions & 168 deletions
Large diffs are not rendered by default.

docs/code_examples/docs_ex08_converters.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
add_methods,
33
get_fields,
44
make_unified_gatherer,
5-
print_generated_code,
5+
get_generated_code,
66
GeneratedCode,
77
MethodMaker,
88
)
@@ -56,4 +56,8 @@ class ConverterEx(ConverterClass):
5656

5757
ex = ConverterEx("42", "42")
5858
print(ex)
59-
print_generated_code(ConverterEx)
59+
print()
60+
code = get_generated_code(ConverterEx)
61+
62+
for k in sorted(code):
63+
print(code[k].source_code)

docs/code_examples/outputs/docs_ex01_basic.output

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,6 @@ class Slotted(ducktools.classbuilder.prefab.Prefab)
7070
| (This does not prevent the modification of mutable attributes such as lists)
7171
| :param replace: generate a __replace__ method
7272
| :param dict_method: Include an as_dict method for faster dictionary creation
73-
| :param recursive_repr: Safely handle repr in case of recursion
7473
| :param ignore_annotations: Ignore type annotations when gathering fields, only look for
7574
| slots or attribute(...) values
7675
| :param slots: automatically generate slots for this class's attributes
Lines changed: 24 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,31 @@
11
ConverterEx(unconverted='42', converted=42)
2-
Source:
3-
def __eq__(self, other):
4-
if self is other:
5-
return True
6-
return (
7-
self.unconverted == other.unconverted
8-
and self.converted == other.converted
9-
) if self.__class__ is other.__class__ else NotImplemented
102

11-
def __init__(self, unconverted, converted):
12-
self.unconverted = unconverted
13-
self.converted = converted
3+
def __eq__(self, other):
4+
if self is other:
5+
return True
6+
return (
7+
self.unconverted == other.unconverted
8+
and self.converted == other.converted
9+
) if self.__class__ is other.__class__ else NotImplemented
1410

15-
def __replace__(self, /, **changes):
16-
new_kwargs = {'unconverted': self.unconverted, 'converted': self.converted}
17-
new_kwargs |= changes
18-
return self.__class__(**new_kwargs)
11+
def __init__(self, unconverted, converted):
12+
self.unconverted = unconverted
13+
self.converted = converted
1914

20-
def __repr__(self):
21-
return f'{type(self).__qualname__}(unconverted={self.unconverted!r}, converted={self.converted!r})'
15+
def __replace__(self, /, **changes):
16+
new_kwargs = {
17+
'unconverted': self.unconverted,
18+
'converted': self.converted,
19+
}
20+
new_kwargs |= changes
21+
return self.__class__(**new_kwargs)
2222

23-
def __setattr__(self, name, value):
24-
if conv := _converters.get(name):
25-
_object_setattr(self, name, conv(value))
26-
else:
27-
_object_setattr(self, name, value)
23+
def __repr__(self):
24+
return f'{type(self).__qualname__}(unconverted={self.unconverted!r}, converted={self.converted!r})'
2825

26+
def __setattr__(self, name, value):
27+
if conv := _converters.get(name):
28+
_object_setattr(self, name, conv(value))
29+
else:
30+
_object_setattr(self, name, value)
2931

30-
Globals:
31-
__setattr__: {'_converters': {'converted': <class 'int'>}, '_object_setattr': <slot wrapper '__setattr__' of 'object' objects>}
32-
33-
Annotations:
34-
__init__: {'unconverted': <class 'str'>, 'converted': <class 'int'>, 'return': None}

docs/code_examples/outputs/docs_ex09_annotated.output

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<generated class X; x='Value of x', a='Not In __init__ signature', c=[], e='Value of e', f=42>
1+
X(x='Value of x', a='Not In __init__ signature', c=[], e='Value of e', f=42)
22

33
{'x': Attribute(default=<NOTHING Sentinel>, default_factory=<NOTHING Sentinel>, type=<class 'str'>, doc=None, init=True, repr=True, compare=True, kw_only=False, iter=True, serialize=True, metadata={}),
44
'a': Attribute(default='Not In __init__ signature', default_factory=<NOTHING Sentinel>, type=<class 'int'>, doc=None, init=False, repr=True, compare=True, kw_only=False, iter=True, serialize=True, metadata={}),
@@ -9,7 +9,7 @@
99
'f': Attribute(default=42, default_factory=<NOTHING Sentinel>, type=ForwardRef('unknown'), doc=None, init=True, repr=True, compare=False, kw_only=False, iter=True, serialize=False, metadata={})}
1010

1111

12-
<generated class Y; x='Value of x', a='Not In __init__ signature', c=[], e='Value of e', f=42>
12+
Y(x='Value of x', a='Not In __init__ signature', c=[], e='Value of e', f=42)
1313

1414
Slots: {'x': None, 'a': None, 'b': None, 'c': None, 'd': None, 'e': None, 'f': None}
1515

@@ -33,10 +33,16 @@ def __init__(self, x, b='Not In Repr', c=None, f=42, *, e):
3333
self.f = f
3434

3535
def __replace__(self, /, **changes):
36-
new_kwargs = {'x': self.x, 'b': self.b, 'c': self.c, 'e': self.e, 'f': self.f}
36+
new_kwargs = {
37+
'x': self.x,
38+
'b': self.b,
39+
'c': self.c,
40+
'e': self.e,
41+
'f': self.f,
42+
}
3743
new_kwargs |= changes
3844
return self.__class__(**new_kwargs)
3945

4046
def __repr__(self):
41-
return f'<generated class {type(self).__qualname__}; x={self.x!r}, a={self.a!r}, c={self.c!r}, e={self.e!r}, f={self.f!r}>'
47+
return f'{type(self).__qualname__}(x={self.x!r}, a={self.a!r}, c={self.c!r}, e={self.e!r}, f={self.f!r})'
4248

docs/prefab/index.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ Including:
77
* Declaration by type hints, slots or `attribute(...)` assignment on the class
88
* `__prefab_pre_init__` and `__prefab_post_init__` detection to allow for validation/conversion
99
* Optional `as_dict` method generation to convert to a dictionary
10-
* Optional recursive `__repr__` handling (off by default)
1110

1211
## Usage ##
1312

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ testpaths = [
7272

7373
[tool.coverage.run]
7474
plugins = ["coverage_simple_excludes"]
75+
omit = ["_cached_methods.py"]
7576

7677
[tool.coverage.report]
7778
fail_under = 90
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from ducktools.classbuilder._create_precached_methods import write_precached_methods
2+
3+
if __name__ == "__main__":
4+
write_precached_methods()

0 commit comments

Comments
 (0)