ducktools-classbuilder 0.6.3__tar.gz → 0.7.1__tar.gz

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.

Files changed (93) hide show
  1. ducktools_classbuilder-0.7.1/.github/dependabot.yml +7 -0
  2. ducktools_classbuilder-0.7.1/.github/workflows/auto_test.yml +48 -0
  3. ducktools_classbuilder-0.7.1/.github/workflows/publish_to_pypi.yml +87 -0
  4. ducktools_classbuilder-0.7.1/.github/workflows/publish_to_testpypi.yml +55 -0
  5. ducktools_classbuilder-0.7.1/.gitignore +25 -0
  6. ducktools_classbuilder-0.7.1/.readthedocs.yaml +24 -0
  7. {ducktools_classbuilder-0.6.3/src/ducktools_classbuilder.egg-info → ducktools_classbuilder-0.7.1}/PKG-INFO +3 -2
  8. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/docs/extension_examples.md +2 -2
  9. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/docs/tutorial.md +10 -8
  10. ducktools_classbuilder-0.7.1/docs_code/docs_ex1_basic.py +17 -0
  11. ducktools_classbuilder-0.7.1/docs_code/docs_ex2_register.py +32 -0
  12. ducktools_classbuilder-0.7.1/docs_code/docs_ex3_iterable.py +50 -0
  13. ducktools_classbuilder-0.7.1/docs_code/docs_ex5_frozen.py +43 -0
  14. ducktools_classbuilder-0.7.1/docs_code/docs_ex7_posonly.py +131 -0
  15. ducktools_classbuilder-0.7.1/docs_code/docs_ex8_converters.py +57 -0
  16. ducktools_classbuilder-0.7.1/docs_code/docs_ex9_annotated.py +188 -0
  17. ducktools_classbuilder-0.7.1/docs_code/tutorial_code.py +154 -0
  18. ducktools_classbuilder-0.7.1/perf/cluegen.py +127 -0
  19. ducktools_classbuilder-0.7.1/perf/dataklasses.py +102 -0
  20. ducktools_classbuilder-0.7.1/perf/hyperfine_testmaker.py +311 -0
  21. ducktools_classbuilder-0.7.1/perf/perf_profile.py +291 -0
  22. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/pyproject.toml +8 -5
  23. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/src/ducktools/classbuilder/__init__.py +3 -11
  24. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/src/ducktools/classbuilder/__init__.pyi +1 -0
  25. ducktools_classbuilder-0.7.1/src/ducktools/classbuilder/_version.py +2 -0
  26. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/src/ducktools/classbuilder/annotations.py +106 -23
  27. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/src/ducktools/classbuilder/annotations.pyi +6 -0
  28. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/src/ducktools/classbuilder/prefab.py +15 -1
  29. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/src/ducktools/classbuilder/prefab.pyi +2 -0
  30. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1/src/ducktools_classbuilder.egg-info}/PKG-INFO +3 -2
  31. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/src/ducktools_classbuilder.egg-info/SOURCES.txt +23 -3
  32. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/src/ducktools_classbuilder.egg-info/requires.txt +1 -1
  33. ducktools_classbuilder-0.7.1/tests/annotations/test_future_annotations.py +45 -0
  34. ducktools_classbuilder-0.7.1/tests/conftest.py +10 -0
  35. ducktools_classbuilder-0.7.1/tests/prefab/dynamic/test_frozen.py +98 -0
  36. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/prefab/dynamic/test_slotted_class.py +0 -1
  37. ducktools_classbuilder-0.7.1/tests/py314_tests/test_forwardref_annotations.py +45 -0
  38. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/test_core.py +0 -1
  39. ducktools_classbuilder-0.6.3/tests/conftest.py +0 -12
  40. ducktools_classbuilder-0.6.3/tests/prefab/shared/examples/frozen_prefabs.py +0 -8
  41. ducktools_classbuilder-0.6.3/tests/prefab/shared/test_frozen.py +0 -60
  42. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/LICENSE.md +0 -0
  43. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/MANIFEST.in +0 -0
  44. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/README.md +0 -0
  45. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/docs/Makefile +0 -0
  46. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/docs/api.md +0 -0
  47. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/docs/approach_vs_tool.md +0 -0
  48. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/docs/conf.py +0 -0
  49. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/docs/generated_code.md +0 -0
  50. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/docs/index.md +0 -0
  51. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/docs/make.bat +0 -0
  52. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/docs/perf/performance_tests.md +0 -0
  53. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/docs/prefab/index.md +0 -0
  54. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/setup.cfg +0 -0
  55. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/src/ducktools/classbuilder/py.typed +0 -0
  56. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/src/ducktools_classbuilder.egg-info/dependency_links.txt +0 -0
  57. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/src/ducktools_classbuilder.egg-info/top_level.txt +0 -0
  58. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/annotations/test_annotated.py +0 -0
  59. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/annotations/test_annotations_module.py +0 -0
  60. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/prefab/dynamic/test_compare_attrib.py +0 -0
  61. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/prefab/dynamic/test_construction.py +0 -0
  62. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/prefab/dynamic/test_internals.py +0 -0
  63. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/prefab/dynamic/test_pre_post_init.py +0 -0
  64. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/prefab/dynamic/test_private.py +0 -0
  65. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/prefab/dynamic/test_slots_novalues.py +0 -0
  66. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/prefab/dynamic/test_subclass_implementation.py +0 -0
  67. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/conftest.py +0 -0
  68. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/examples/creation.py +0 -0
  69. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/examples/creation_empty.py +0 -0
  70. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/examples/dunders.py +0 -0
  71. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/examples/fails/creation_1.py +0 -0
  72. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/examples/fails/creation_2.py +0 -0
  73. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/examples/fails/creation_3.py +0 -0
  74. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/examples/fails/creation_5.py +0 -0
  75. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/examples/fails/inheritance_1.py +0 -0
  76. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/examples/fails/inheritance_2.py +0 -0
  77. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/examples/funcs_prefabs.py +0 -0
  78. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/examples/hint_syntax.py +0 -0
  79. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/examples/inheritance.py +0 -0
  80. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/examples/init_ex.py +0 -0
  81. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/examples/kw_only.py +0 -0
  82. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/examples/repr_func.py +0 -0
  83. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/test_creation.py +0 -0
  84. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/test_dunders.py +0 -0
  85. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/test_funcs.py +0 -0
  86. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/test_hint_syntax.py +0 -0
  87. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/test_inheritance.py +0 -0
  88. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/test_init.py +0 -0
  89. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/test_kw_only.py +0 -0
  90. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/test_repr.py +0 -0
  91. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/py312_tests/test_generic_annotations.py +0 -0
  92. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/test_field_flags.py +0 -0
  93. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/test_slotmakermeta.py +0 -0
@@ -0,0 +1,7 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: "github-actions"
4
+ directory: "/"
5
+ schedule:
6
+ # Check for updates to GitHub Actions every week
7
+ interval: "weekly"
@@ -0,0 +1,48 @@
1
+ name: UnitTests
2
+
3
+ on:
4
+ push:
5
+ branches: [ "main" ]
6
+ pull_request:
7
+ branches: [ "main" ]
8
+
9
+ jobs:
10
+ test-prefab:
11
+
12
+ runs-on: ${{ matrix.os }}
13
+ strategy:
14
+ fail-fast: false
15
+ matrix:
16
+ os: [ubuntu-latest]
17
+ python-version: ["3.13-dev", "3.12", "3.11", "3.10", "pypy-3.10", "3.9", "3.8"]
18
+
19
+ steps:
20
+ - uses: actions/checkout@v4
21
+ - name: Set up Python ${{ matrix.python-version }}
22
+ uses: actions/setup-python@v5
23
+ with:
24
+ python-version: ${{ matrix.python-version }}
25
+ - name: Install dependencies
26
+ run: |
27
+ python -m pip install --upgrade pip
28
+ python -m pip install -e .[testing]
29
+ - name: Test with pytest
30
+ run: |
31
+ pytest tests/ --cov=src/ --cov-report=term-missing
32
+
33
+ test-stubs:
34
+ runs-on: ubuntu-latest
35
+
36
+ steps:
37
+ - uses: actions/checkout@v4
38
+ - name: Set up Python 3.8
39
+ uses: actions/setup-python@v5
40
+ with:
41
+ python-version: "3.8"
42
+ - name: Install dependencies
43
+ run: |
44
+ python -m pip install --upgrade pip
45
+ python -m pip install -e .[testing]
46
+ - name: Check type stub files
47
+ run: |
48
+ python -m mypy.stubtest ducktools.classbuilder
@@ -0,0 +1,87 @@
1
+ name: Publish Package to PyPi
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ build:
9
+ name: Build distribution 📦
10
+ runs-on: ubuntu-latest
11
+
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+ - name: Set up Python
15
+ uses: actions/setup-python@v5
16
+ with:
17
+ python-version: "3.8"
18
+ - name: Install pypa/build
19
+ run: >-
20
+ python3 -m
21
+ pip install
22
+ build
23
+ --user
24
+ - name: Build a binary wheel and a source tarball
25
+ run: python3 -m build
26
+ - name: Store the distribution packages
27
+ uses: actions/upload-artifact@v4
28
+ with:
29
+ name: python-package-distributions
30
+ path: dist/
31
+
32
+ publish-to-pypi:
33
+ name: >-
34
+ Publish Python 🐍 distribution 📦 to PyPI
35
+ needs:
36
+ - build
37
+ runs-on: ubuntu-latest
38
+ environment:
39
+ name: pypi
40
+ url: https://pypi.org/p/ducktools-classbuilder
41
+ permissions:
42
+ id-token: write # IMPORTANT: mandatory for trusted publishing
43
+
44
+ steps:
45
+ - name: Download all the dists
46
+ uses: actions/download-artifact@v4
47
+ with:
48
+ name: python-package-distributions
49
+ path: dist/
50
+ - name: Publish distribution 📦 to PyPI
51
+ uses: pypa/gh-action-pypi-publish@release/v1
52
+
53
+ github-release:
54
+ name: >-
55
+ Sign the Python 🐍 distribution 📦 with Sigstore
56
+ and upload them to GitHub Release
57
+ needs:
58
+ - publish-to-pypi
59
+ runs-on: ubuntu-latest
60
+
61
+ permissions:
62
+ contents: write # IMPORTANT: mandatory for making GitHub Releases
63
+ id-token: write # IMPORTANT: mandatory for sigstore
64
+
65
+ steps:
66
+ - name: Download all the dists
67
+ uses: actions/download-artifact@v4
68
+ with:
69
+ name: python-package-distributions
70
+ path: dist/
71
+ - name: Sign the dists with Sigstore
72
+ uses: sigstore/gh-action-sigstore-python@v3.0.0
73
+ with:
74
+ release-signing-artifacts: false
75
+ inputs: >-
76
+ ./dist/*.tar.gz
77
+ ./dist/*.whl
78
+ - name: Upload artifact signatures to GitHub Release
79
+ env:
80
+ GITHUB_TOKEN: ${{ github.token }}
81
+ # Upload to GitHub Release using the `gh` CLI.
82
+ # `dist/` contains the built packages, and the
83
+ # sigstore-produced signatures and certificates.
84
+ run: >-
85
+ gh release upload
86
+ '${{ github.ref_name }}' dist/**
87
+ --repo '${{ github.repository }}'
@@ -0,0 +1,55 @@
1
+ name: Test Publish Package to TestPyPi
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v[0-9]+.[0-9]+.[0-9]+*"
7
+
8
+ jobs:
9
+ build:
10
+ name: Build distribution 📦
11
+ runs-on: ubuntu-latest
12
+
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+ - name: Set up Python
16
+ uses: actions/setup-python@v5
17
+ with:
18
+ python-version: "3.8"
19
+ - name: Install pypa/build
20
+ run: >-
21
+ python3 -m
22
+ pip install
23
+ build
24
+ --user
25
+ - name: Build a binary wheel and a source tarball
26
+ run: python3 -m build
27
+ - name: Store the distribution packages
28
+ uses: actions/upload-artifact@v4
29
+ with:
30
+ name: python-package-distributions
31
+ path: dist/
32
+
33
+ publish-to-testpypi:
34
+ name: Publish Python 🐍 distribution 📦 to TestPyPI
35
+ needs:
36
+ - build
37
+ runs-on: ubuntu-latest
38
+
39
+ environment:
40
+ name: testpypi
41
+ url: https://test.pypi.org/p/ducktools-classbuilder
42
+
43
+ permissions:
44
+ id-token: write # IMPORTANT: mandatory for trusted publishing
45
+
46
+ steps:
47
+ - name: Download all the dists
48
+ uses: actions/download-artifact@v4
49
+ with:
50
+ name: python-package-distributions
51
+ path: dist/
52
+ - name: Publish distribution 📦 to TestPyPI
53
+ uses: pypa/gh-action-pypi-publish@release/v1
54
+ with:
55
+ repository-url: https://test.pypi.org/legacy/
@@ -0,0 +1,25 @@
1
+ .idea
2
+ .mypy_cache
3
+ .pytest_cache
4
+ references
5
+ .coverage
6
+ scratch/
7
+ *.egg-info/
8
+ build/
9
+ _build/
10
+ dist/
11
+ htmlcov/
12
+ *.lcov
13
+ coverage.xml
14
+
15
+ /env*/
16
+
17
+ perftemp.py
18
+ hyperfine_testfiles/
19
+
20
+ .vscode/
21
+ __pycache__/
22
+ *.pyc
23
+
24
+ # setuptools-scm generated version file
25
+ _version.py
@@ -0,0 +1,24 @@
1
+ # .readthedocs.yaml
2
+ # Read the Docs configuration file
3
+ # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
4
+
5
+ # Required
6
+ version: 2
7
+
8
+ # Set the version of Python and other tools you might need
9
+ build:
10
+ os: ubuntu-22.04
11
+ tools:
12
+ python: "3.12"
13
+
14
+ # Build documentation in the docs/ directory with Sphinx
15
+ sphinx:
16
+ configuration: docs/conf.py
17
+
18
+ # Optionally declare the Python requirements required to build your docs
19
+ python:
20
+ install:
21
+ - method: pip
22
+ path: .
23
+ extra_requirements:
24
+ - docs
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ducktools-classbuilder
3
- Version: 0.6.3
3
+ Version: 0.7.1
4
4
  Summary: Toolkit for creating class boilerplate generators
5
5
  Author: David C Ellis
6
6
  License: MIT License
@@ -32,13 +32,14 @@ Classifier: Programming Language :: Python :: 3.9
32
32
  Classifier: Programming Language :: Python :: 3.10
33
33
  Classifier: Programming Language :: Python :: 3.11
34
34
  Classifier: Programming Language :: Python :: 3.12
35
+ Classifier: Programming Language :: Python :: 3.13
35
36
  Classifier: Operating System :: OS Independent
36
37
  Classifier: License :: OSI Approved :: MIT License
37
38
  Requires-Python: >=3.8
38
39
  Description-Content-Type: text/markdown
39
40
  License-File: LICENSE.md
40
41
  Provides-Extra: testing
41
- Requires-Dist: pytest; extra == "testing"
42
+ Requires-Dist: pytest>=8.2; extra == "testing"
42
43
  Requires-Dist: pytest-cov; extra == "testing"
43
44
  Requires-Dist: mypy; extra == "testing"
44
45
  Requires-Dist: typing_extensions; extra == "testing"
@@ -84,7 +84,7 @@ def iter_generator(cls, funcname="__iter__"):
84
84
  field_yield = "\n".join(f" yield self.{f}" for f in field_names)
85
85
  if not field_yield:
86
86
  field_yield = " yield from ()"
87
- code = f"def {funcname}(self):\n" f"{field_yield}"
87
+ code = f"def {funcname}(self):\n{field_yield}"
88
88
  globs = {}
89
89
  return GeneratedCode(code, globs)
90
90
 
@@ -330,7 +330,7 @@ if __name__ == "__main__":
330
330
 
331
331
  This seems to be a feature people keep requesting for `dataclasses`.
332
332
 
333
- To implement this you simply need to create a new annotated_gatherer function.
333
+ To implement this you need to create a new annotated_gatherer function.
334
334
 
335
335
  > Note: Field classes will be frozen when running under pytest.
336
336
  > They should not be mutated by gatherers.
@@ -1,6 +1,6 @@
1
1
  # Tutorial: Making a class boilerplate generator #
2
2
 
3
- The core idea is that there are 3 parts to the process of generating
3
+ The core idea is that there are 4 parts to the process of generating
4
4
  the class boilerplate that need to be handled:
5
5
 
6
6
  1. Create a new subclass of `Field` if you need to add any extra attributes to fields
@@ -162,7 +162,7 @@ print(report_generator(CodegenDemo).source_code)
162
162
  Here we will make both a simple decorator based builder and then a subclass
163
163
  based builder that can create `__slots__`.
164
164
 
165
- ### Decorator builder ###
165
+ ### 4a: Decorator builder ###
166
166
  ```python
167
167
  def reportclass(cls):
168
168
  gatherer = fields_attribute_gatherer
@@ -177,21 +177,22 @@ def reportclass(cls):
177
177
  flags = {"slotted": slotted}
178
178
 
179
179
  return dtbuild.builder(cls, gatherer=gatherer, methods=methods, flags=flags)
180
+ ```
180
181
 
181
- # Step 4b: Define a base class builder
182
+ ### 4b: Base class Builder ###
183
+ ```python
182
184
  # Once slots have been made, slot_gatherer should be used.
183
185
  slot_gatherer = dtbuild.make_slot_gatherer(CustomField)
184
- ```
185
186
 
186
- ### Base class Builder ###
187
- ```python
187
+
188
188
  class ReportClass(metaclass=dtbuild.SlotMakerMeta):
189
189
  __slots__ = {}
190
190
  _meta_gatherer = fields_attribute_gatherer
191
191
 
192
192
  def __init_subclass__(cls):
193
- slotted = '__slots__' in vars(cls) and isinstance(cls.__slots__, dtbuild.SlotFields)
194
- gatherer = slot_gatherer if slotted else fields_attribute_gatherer
193
+ # Check if the metaclass has generated slots
194
+ meta_slotted = '__slots__' in vars(cls) and isinstance(cls.__slots__, dtbuild.SlotFields)
195
+ gatherer = slot_gatherer if meta_slotted else fields_attribute_gatherer
195
196
  methods = {
196
197
  dtbuild.eq_maker,
197
198
  dtbuild.repr_maker,
@@ -199,6 +200,7 @@ class ReportClass(metaclass=dtbuild.SlotMakerMeta):
199
200
  report_maker
200
201
  }
201
202
 
203
+ # The class may still have slots unrelated to code generation
202
204
  slotted = "__slots__" in vars(cls)
203
205
  flags = {"slotted": slotted}
204
206
 
@@ -0,0 +1,17 @@
1
+ from ducktools.classbuilder import slotclass, Field, SlotFields
2
+
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
+ ),
12
+ )
13
+
14
+
15
+ ex = SlottedDC()
16
+ print(ex)
17
+ help(SlottedDC)
@@ -0,0 +1,32 @@
1
+ from dataclasses import dataclass
2
+ from ducktools.classbuilder import slotclass, SlotFields
3
+
4
+ class_register = {}
5
+
6
+
7
+ def register(cls):
8
+ class_register[cls.__name__] = cls
9
+ return cls
10
+
11
+
12
+ @dataclass(slots=True)
13
+ @register
14
+ class DataCoords:
15
+ x: float = 0.0
16
+ y: float = 0.0
17
+
18
+
19
+ @slotclass
20
+ @register
21
+ class SlotCoords:
22
+ __slots__ = SlotFields(x=0.0, y=0.0)
23
+ # Type hints don't affect class construction, these are optional.
24
+ x: float
25
+ y: float
26
+
27
+
28
+ print(DataCoords())
29
+ print(SlotCoords())
30
+
31
+ print(f"{DataCoords is class_register[DataCoords.__name__] = }")
32
+ print(f"{SlotCoords is class_register[SlotCoords.__name__] = }")
@@ -0,0 +1,50 @@
1
+ from ducktools.classbuilder import (
2
+ default_methods,
3
+ get_fields,
4
+ slotclass,
5
+ GeneratedCode,
6
+ MethodMaker,
7
+ SlotFields,
8
+ )
9
+
10
+
11
+ def iter_generator(cls, funcname="__iter__"):
12
+ field_names = get_fields(cls).keys()
13
+ field_yield = "\n".join(f" yield self.{f}" for f in field_names)
14
+ if not field_yield:
15
+ field_yield = " yield from ()"
16
+ code = f"def {funcname}(self):\n{field_yield}"
17
+ globs = {}
18
+ return GeneratedCode(code, globs)
19
+
20
+
21
+ iter_maker = MethodMaker("__iter__", iter_generator)
22
+ new_methods = frozenset(default_methods | {iter_maker})
23
+
24
+
25
+ def iterclass(cls=None, /):
26
+ return slotclass(cls, methods=new_methods)
27
+
28
+
29
+ if __name__ == "__main__":
30
+ @iterclass
31
+ class IterDemo:
32
+ __slots__ = SlotFields(
33
+ a=1,
34
+ b=2,
35
+ c=3,
36
+ d=4,
37
+ e=5,
38
+ )
39
+
40
+ ex = IterDemo()
41
+ print([item for item in ex])
42
+
43
+
44
+ @iterclass
45
+ class IterDemo:
46
+ __slots__ = SlotFields()
47
+
48
+
49
+ ex = IterDemo()
50
+ print([item for item in ex])
@@ -0,0 +1,43 @@
1
+ from ducktools.classbuilder import (
2
+ slotclass,
3
+ SlotFields,
4
+ default_methods,
5
+ frozen_setattr_maker,
6
+ frozen_delattr_maker,
7
+ )
8
+
9
+
10
+ new_methods = default_methods | {frozen_setattr_maker, frozen_delattr_maker}
11
+
12
+
13
+ def frozen(cls, /):
14
+ return slotclass(cls, methods=new_methods)
15
+
16
+
17
+ if __name__ == "__main__":
18
+ @frozen
19
+ class FrozenEx:
20
+ __slots__ = SlotFields(
21
+ x=6,
22
+ y=9,
23
+ product=42,
24
+ )
25
+
26
+
27
+ ex = FrozenEx()
28
+ print(ex)
29
+
30
+ try:
31
+ ex.y = 7
32
+ except TypeError as e:
33
+ print(e)
34
+
35
+ try:
36
+ ex.z = "new value"
37
+ except TypeError as e:
38
+ print(e)
39
+
40
+ try:
41
+ del ex.y
42
+ except TypeError as e:
43
+ print(e)
@@ -0,0 +1,131 @@
1
+ from ducktools.classbuilder import (
2
+ builder,
3
+ eq_maker,
4
+ get_fields,
5
+ slot_gatherer,
6
+ Field,
7
+ GeneratedCode,
8
+ SlotFields,
9
+ NOTHING,
10
+ MethodMaker,
11
+ )
12
+
13
+
14
+ class PosOnlyField(Field):
15
+ __slots__ = SlotFields(pos_only=True)
16
+
17
+
18
+ def init_generator(cls, funcname="__init__"):
19
+ fields = get_fields(cls)
20
+
21
+ arglist = []
22
+ assignments = []
23
+ globs = {}
24
+
25
+ used_posonly = False
26
+ used_kw = False
27
+
28
+ for k, v in fields.items():
29
+ if getattr(v, "pos_only", False):
30
+ used_posonly = True
31
+ elif used_posonly and not used_kw:
32
+ used_kw = True
33
+ arglist.append("/")
34
+
35
+ if v.default is not NOTHING:
36
+ globs[f"_{k}_default"] = v.default
37
+ arg = f"{k}=_{k}_default"
38
+ assignment = f"self.{k} = {k}"
39
+ elif v.default_factory is not NOTHING:
40
+ globs[f"_{k}_factory"] = v.default_factory
41
+ arg = f"{k}=None"
42
+ assignment = f"self.{k} = _{k}_factory() if {k} is None else {k}"
43
+ else:
44
+ arg = f"{k}"
45
+ assignment = f"self.{k} = {k}"
46
+
47
+ arglist.append(arg)
48
+ assignments.append(assignment)
49
+
50
+ args = ", ".join(arglist)
51
+ assigns = "\n ".join(assignments)
52
+ code = f"def {funcname}(self, {args}):\n" f" {assigns}\n"
53
+ return GeneratedCode(code, globs)
54
+
55
+
56
+ def repr_generator(cls, funcname="__repr__"):
57
+ fields = get_fields(cls)
58
+ content_list = []
59
+ for name, field in fields.items():
60
+ if getattr(field, "pos_only", False):
61
+ assign = f"{{self.{name}!r}}"
62
+ else:
63
+ assign = f"{name}={{self.{name}!r}}"
64
+ content_list.append(assign)
65
+
66
+ content = ", ".join(content_list)
67
+ code = (
68
+ f"def {funcname}(self):\n"
69
+ f" return f'{{type(self).__qualname__}}({content})'\n"
70
+ )
71
+ globs = {}
72
+ return GeneratedCode(code, globs)
73
+
74
+
75
+ init_maker = MethodMaker("__init__", init_generator)
76
+ repr_maker = MethodMaker("__repr__", repr_generator)
77
+ new_methods = frozenset({init_maker, repr_maker, eq_maker})
78
+
79
+
80
+ def pos_slotclass(cls, /):
81
+ cls = builder(
82
+ cls,
83
+ gatherer=slot_gatherer,
84
+ methods=new_methods,
85
+ )
86
+
87
+ # Check no positional-only args after keyword args
88
+ flds = get_fields(cls)
89
+ used_kwarg = False
90
+ for k, v in flds.items():
91
+ if getattr(v, "pos_only", False):
92
+ if used_kwarg:
93
+ raise SyntaxError(
94
+ f"Positional only parameter {k!r}"
95
+ f" follows keyword parameters on {cls.__name__!r}"
96
+ )
97
+ else:
98
+ used_kwarg = True
99
+
100
+ return cls
101
+
102
+
103
+ if __name__ == "__main__":
104
+ @pos_slotclass
105
+ class WorkingEx:
106
+ __slots__ = SlotFields(
107
+ a=PosOnlyField(default=42),
108
+ x=6,
109
+ y=9,
110
+ )
111
+
112
+ ex = WorkingEx()
113
+ print(ex)
114
+ ex = WorkingEx(42, x=6, y=9)
115
+ print(ex)
116
+
117
+ try:
118
+ ex = WorkingEx(a=54)
119
+ except TypeError as e:
120
+ print(e)
121
+
122
+ try:
123
+ @pos_slotclass
124
+ class FailEx:
125
+ __slots__ = SlotFields(
126
+ a=42,
127
+ x=PosOnlyField(default=6),
128
+ y=PosOnlyField(default=9),
129
+ )
130
+ except SyntaxError as e:
131
+ print(e)
@@ -0,0 +1,57 @@
1
+ from ducktools.classbuilder import (
2
+ builder,
3
+ default_methods,
4
+ get_fields,
5
+ slot_gatherer,
6
+ Field,
7
+ GeneratedCode,
8
+ SlotFields,
9
+ MethodMaker,
10
+ )
11
+
12
+
13
+ class ConverterField(Field):
14
+ converter = Field(default=None)
15
+
16
+
17
+ def setattr_generator(cls, funcname="__setattr__"):
18
+ fields = get_fields(cls)
19
+ converters = {}
20
+ for k, v in fields.items():
21
+ if conv := getattr(v, "converter", None):
22
+ converters[k] = conv
23
+
24
+ globs = {
25
+ "_converters": converters,
26
+ "_object_setattr": object.__setattr__,
27
+ }
28
+
29
+ code = (
30
+ f"def {funcname}(self, name, value):\n"
31
+ f" if conv := _converters.get(name):\n"
32
+ f" _object_setattr(self, name, conv(value))\n"
33
+ f" else:\n"
34
+ f" _object_setattr(self, name, value)\n"
35
+ )
36
+
37
+ return GeneratedCode(code, globs)
38
+
39
+
40
+ setattr_maker = MethodMaker("__setattr__", setattr_generator)
41
+ methods = frozenset(default_methods | {setattr_maker})
42
+
43
+
44
+ def converterclass(cls, /):
45
+ return builder(cls, gatherer=slot_gatherer, methods=methods)
46
+
47
+
48
+ if __name__ == "__main__":
49
+ @converterclass
50
+ class ConverterEx:
51
+ __slots__ = SlotFields(
52
+ unconverted=ConverterField(),
53
+ converted=ConverterField(converter=int),
54
+ )
55
+
56
+ ex = ConverterEx("42", "42")
57
+ print(ex)