Skip to content

Commit 9a996f1

Browse files
committed
update readme and readme test
1 parent 4826a67 commit 9a996f1

File tree

3 files changed

+85
-56
lines changed

3 files changed

+85
-56
lines changed

README.md

Lines changed: 26 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -17,29 +17,35 @@ Install from PyPI with:
1717
The classbuilder tools make up the core of this module and there is an implementation
1818
using these tools in the `prefab` submodule.
1919

20-
There is also a minimal `@slotclass` example that can construct classes from a special
21-
mapping used in `__slots__`.
20+
The implementation provides both a base class `Prefab` that will also generate `__slots__`
21+
and a decorator `@prefab` which does not support `__slots__`.
2222

2323
```python
24-
from ducktools.classbuilder import Field, SlotFields, slotclass
25-
26-
@slotclass
27-
class SlottedDC:
28-
__slots__ = SlotFields(
29-
the_answer=42,
30-
the_question=Field(
31-
default="What do you get if you multiply six by nine?",
32-
doc="Life, the Universe, and Everything",
33-
),
24+
from ducktools.classbuilder.prefab import Prefab, attribute
25+
26+
class Slotted(Prefab):
27+
the_answer: int = 42
28+
the_question: str = attribute(
29+
default="What do you get if you multiply six by nine?",
30+
doc="Life the universe and everything",
3431
)
3532

36-
ex = SlottedDC()
33+
ex = Slotted()
3734
print(ex)
35+
print(ex.__slots__)
36+
```
37+
38+
The generated source code for the methods can be viewed using the `print_generated_code` helper function.
39+
40+
```python
41+
from ducktools.classbuilder import print_generated_code
42+
43+
print_generated_code(SlottedDC)
3844
```
3945

4046
### Core ###
4147

42-
The core of the module provides tools for creating a customized version of the `dataclass` concept.
48+
The base `ducktools.classbuilder` module provides tools for creating a customized version of the `dataclass` concept.
4349

4450
* `MethodMaker`
4551
* This tool takes a function that generates source code and converts it into a descriptor
@@ -75,12 +81,6 @@ This prebuilt implementation is available from the `ducktools.classbuilder.prefa
7581
This includes more customization including `__prefab_pre_init__` and `__prefab_post_init__`
7682
functions for subclass customization.
7783

78-
A `@prefab` decorator and `Prefab` base class are provided.
79-
80-
`Prefab` will generate `__slots__` by default.
81-
decorated classes with `@prefab` that do not declare fields using `__slots__`
82-
will **not** be slotted and there is no `slots` argument to apply this.
83-
8484
Here is an example of applying a conversion in `__post_init__`:
8585
```python
8686
from pathlib import Path
@@ -106,7 +106,7 @@ print(steam)
106106
#### Features ####
107107

108108
`Prefab` and `@prefab` support many standard dataclass features along with
109-
a few extras.
109+
some extra features and some intentional differences in design.
110110

111111
* All standard methods are generated on-demand
112112
* This makes the construction of classes much faster in general
@@ -132,20 +132,20 @@ a few extras.
132132
* `iter=True` will include the attribute in the iterable if `__iter__` is generated
133133
* `serialize=True` decides if the attribute is include in `as_dict`
134134
* `exclude_field` is short for `repr=False`, `compare=False`, `iter=False`, `serialize=False`
135-
* `private` is short for `exclude_field=True` and `init=False` and requires a default/factory
135+
* `private` is short for `exclude_field=True` and `init=False` and requires a default or factory
136136
* `doc` will add this string as the value in slotted classes, which appears in `help()`
137137
* `build_prefab` can be used to dynamically create classes and *does* support a slots argument
138138
* Unlike dataclasses, this does not create the class twice in order to provide slots
139139

140140
There are also some intentionally missing features:
141141

142-
* The `@prefab` decorator does not and will not support a `slots` argument
142+
* The `@prefab` decorator does not and will never support a `slots` argument
143143
* Use `Prefab` for slots.
144144
* `as_dict` and the generated `.as_dict` method **do not** recurse or deep copy
145145
* `unsafe_hash` is not provided
146146
* `weakref_slot` is not available as an argument
147147
* `__weakref__` can be added to slots by declaring it as if it were an attribute
148-
* There is no check for mutable defaults
148+
* There is no safety check for mutable defaults
149149
* You should still use `default_factory` as you would for dataclasses, not doing so
150150
is still incorrect
151151
* `dataclasses` uses hashability as a proxy for mutability, but technically this is
@@ -162,7 +162,7 @@ There are also some intentionally missing features:
162162
If you want to use `__slots__` in order to save memory you have to declare
163163
them when the class is originally created as you can't add them later.
164164

165-
When you use `@dataclass(slots=True)`[^2] with `dataclasses`, the function
165+
When you use `@dataclass(slots=True)`[^1] with `dataclasses`, the function
166166
has to make a new class and attempt to copy over everything from the original.
167167

168168
This is because decorators operate on classes *after they have been created*
@@ -305,8 +305,4 @@ with a specific feature, you can create or add it yourself.
305305

306306
Heavily inspired by [David Beazley's Cluegen](https://github.com/dabeaz/cluegen)
307307

308-
[^1]: `SlotFields` is actually just a subclassed `dict` with no changes. `__slots__`
309-
works with dictionaries using the values of the keys, while fields are normally
310-
used for documentation.
311-
312-
[^2]: or `@attrs.define`.
308+
[^1]: or `@attrs.define`.
Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,13 @@
1-
from ducktools.classbuilder import slotclass, Field, SlotFields
1+
from ducktools.classbuilder.prefab import Prefab, attribute
22

3-
4-
@slotclass
5-
class SlottedDC:
6-
__slots__ = SlotFields(
7-
the_answer=42,
8-
the_question=Field(
9-
default="What do you get if you multiply six by nine?",
10-
doc="Life, the Universe, and Everything",
11-
),
3+
class Slotted(Prefab):
4+
the_answer: int = 42
5+
the_question: str = attribute(
6+
default="What do you get if you multiply six by nine?",
7+
doc="Life the universe and everything",
128
)
139

14-
15-
ex = SlottedDC()
10+
ex = Slotted()
1611
print(ex)
17-
help(SlottedDC)
12+
print(ex.__slots__)
13+
help(Slotted)
Lines changed: 50 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,31 @@
1-
SlottedDC(the_answer=42, the_question='What do you get if you multiply six by nine?')
2-
Help on class SlottedDC in module __main__:
1+
Slotted(the_answer=42, the_question='What do you get if you multiply six by nine?')
2+
{'the_answer': None, 'the_question': 'Life the universe and everything'}
3+
Help on class Slotted in module __main__:
34

4-
class SlottedDC(builtins.object)
5-
| SlottedDC(
6-
| the_answer=42,
7-
| the_question='What do you get if you multiply six by nine?'
8-
| )
5+
class Slotted(ducktools.classbuilder.prefab.Prefab)
6+
| Slotted(
7+
| the_answer: int = 42,
8+
| the_question: str = 'What do you get if you multiply six by nine?'
9+
| ) -> None
10+
|
11+
| Method resolution order:
12+
| Slotted
13+
| ducktools.classbuilder.prefab.Prefab
14+
| builtins.object
915
|
1016
| Methods defined here:
1117
|
12-
| __eq__(self, other) from SlottedDC
18+
| __eq__(self, other) from Slotted
1319
|
1420
| __init__(
1521
| self,
16-
| the_answer=42,
17-
| the_question='What do you get if you multiply six by nine?'
18-
| ) from SlottedDC
22+
| the_answer: int = 42,
23+
| the_question: str = 'What do you get if you multiply six by nine?'
24+
| ) -> None from Slotted
25+
|
26+
| __replace__(self, /, **changes) from Slotted
1927
|
20-
| __repr__(self) from SlottedDC
28+
| __repr__(self) from Slotted
2129
|
2230
| __signature__
2331
|
@@ -27,12 +35,41 @@ class SlottedDC(builtins.object)
2735
| the_answer
2836
|
2937
| the_question
30-
| Life, the Universe, and Everything
38+
| Life the universe and everything
3139
|
3240
| ----------------------------------------------------------------------
3341
| Data and other attributes defined here:
3442
|
43+
| PREFAB_FIELDS = ['the_answer', 'the_question']
44+
|
45+
| __classbuilder_gathered_fields__ = ({'the_answer': Attribute(default=4...
46+
|
3547
| __classbuilder_internals__ = {'build_complete': True, 'fields': {'the_...
3648
|
3749
| __hash__ = None
50+
|
51+
| __match_args__ = ('the_answer', 'the_question')
52+
|
53+
| ----------------------------------------------------------------------
54+
| Class methods inherited from ducktools.classbuilder.prefab.Prefab:
55+
|
56+
| __init_subclass__(**kwargs)
57+
| Generate boilerplate code for dunder methods in a class.
58+
|
59+
| Use as a base class, slotted by default
60+
|
61+
| :param init: generates __init__ if true or __prefab_init__ if false
62+
| :param repr: generate __repr__
63+
| :param eq: generate __eq__
64+
| :param iter: generate __iter__
65+
| :param match_args: generate __match_args__
66+
| :param kw_only: make all attributes keyword only
67+
| :param frozen: Prevent attribute values from being changed once defined
68+
| (This does not prevent the modification of mutable attributes such as lists)
69+
| :param replace: generate a __replace__ method
70+
| :param dict_method: Include an as_dict method for faster dictionary creation
71+
| :param recursive_repr: Safely handle repr in case of recursion
72+
| :param ignore_annotations: Ignore type annotations when gathering fields, only look for
73+
| slots or attribute(...) values
74+
| :param slots: automatically generate slots for this class's attributes
3875

0 commit comments

Comments
 (0)