ducktools-classbuilder 0.6.3__tar.gz → 0.7.0__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 (90) hide show
  1. ducktools_classbuilder-0.7.0/.github/dependabot.yml +7 -0
  2. ducktools_classbuilder-0.7.0/.github/workflows/auto_test.yml +48 -0
  3. ducktools_classbuilder-0.7.0/.github/workflows/publish_to_pypi.yml +87 -0
  4. ducktools_classbuilder-0.7.0/.github/workflows/publish_to_testpypi.yml +55 -0
  5. ducktools_classbuilder-0.7.0/.gitignore +25 -0
  6. ducktools_classbuilder-0.7.0/.readthedocs.yaml +24 -0
  7. {ducktools_classbuilder-0.6.3/src/ducktools_classbuilder.egg-info → ducktools_classbuilder-0.7.0}/PKG-INFO +2 -2
  8. ducktools_classbuilder-0.7.0/docs_code/docs_ex1_basic.py +17 -0
  9. ducktools_classbuilder-0.7.0/docs_code/docs_ex2_register.py +32 -0
  10. ducktools_classbuilder-0.7.0/docs_code/docs_ex3_iterable.py +50 -0
  11. ducktools_classbuilder-0.7.0/docs_code/docs_ex5_frozen.py +43 -0
  12. ducktools_classbuilder-0.7.0/docs_code/docs_ex7_posonly.py +131 -0
  13. ducktools_classbuilder-0.7.0/docs_code/docs_ex8_converters.py +57 -0
  14. ducktools_classbuilder-0.7.0/docs_code/docs_ex9_annotated.py +188 -0
  15. ducktools_classbuilder-0.7.0/docs_code/tutorial_code.py +152 -0
  16. ducktools_classbuilder-0.7.0/perf/cluegen.py +127 -0
  17. ducktools_classbuilder-0.7.0/perf/dataklasses.py +102 -0
  18. ducktools_classbuilder-0.7.0/perf/hyperfine_testmaker.py +311 -0
  19. ducktools_classbuilder-0.7.0/perf/perf_profile.py +291 -0
  20. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/pyproject.toml +7 -5
  21. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/src/ducktools/classbuilder/__init__.py +3 -4
  22. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/src/ducktools/classbuilder/__init__.pyi +1 -0
  23. ducktools_classbuilder-0.7.0/src/ducktools/classbuilder/_version.py +2 -0
  24. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/src/ducktools/classbuilder/prefab.py +15 -1
  25. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/src/ducktools/classbuilder/prefab.pyi +2 -0
  26. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0/src/ducktools_classbuilder.egg-info}/PKG-INFO +2 -2
  27. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/src/ducktools_classbuilder.egg-info/SOURCES.txt +20 -2
  28. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/src/ducktools_classbuilder.egg-info/requires.txt +1 -1
  29. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/tests/conftest.py +0 -2
  30. ducktools_classbuilder-0.7.0/tests/prefab/dynamic/test_frozen.py +98 -0
  31. ducktools_classbuilder-0.6.3/tests/prefab/shared/examples/frozen_prefabs.py +0 -8
  32. ducktools_classbuilder-0.6.3/tests/prefab/shared/test_frozen.py +0 -60
  33. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/LICENSE.md +0 -0
  34. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/MANIFEST.in +0 -0
  35. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/README.md +0 -0
  36. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/docs/Makefile +0 -0
  37. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/docs/api.md +0 -0
  38. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/docs/approach_vs_tool.md +0 -0
  39. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/docs/conf.py +0 -0
  40. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/docs/extension_examples.md +0 -0
  41. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/docs/generated_code.md +0 -0
  42. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/docs/index.md +0 -0
  43. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/docs/make.bat +0 -0
  44. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/docs/perf/performance_tests.md +0 -0
  45. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/docs/prefab/index.md +0 -0
  46. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/docs/tutorial.md +0 -0
  47. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/setup.cfg +0 -0
  48. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/src/ducktools/classbuilder/annotations.py +0 -0
  49. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/src/ducktools/classbuilder/annotations.pyi +0 -0
  50. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/src/ducktools/classbuilder/py.typed +0 -0
  51. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/src/ducktools_classbuilder.egg-info/dependency_links.txt +0 -0
  52. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/src/ducktools_classbuilder.egg-info/top_level.txt +0 -0
  53. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/tests/annotations/test_annotated.py +0 -0
  54. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/tests/annotations/test_annotations_module.py +0 -0
  55. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/tests/prefab/dynamic/test_compare_attrib.py +0 -0
  56. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/tests/prefab/dynamic/test_construction.py +0 -0
  57. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/tests/prefab/dynamic/test_internals.py +0 -0
  58. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/tests/prefab/dynamic/test_pre_post_init.py +0 -0
  59. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/tests/prefab/dynamic/test_private.py +0 -0
  60. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/tests/prefab/dynamic/test_slots_novalues.py +0 -0
  61. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/tests/prefab/dynamic/test_slotted_class.py +0 -0
  62. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/tests/prefab/dynamic/test_subclass_implementation.py +0 -0
  63. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/tests/prefab/shared/conftest.py +0 -0
  64. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/tests/prefab/shared/examples/creation.py +0 -0
  65. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/tests/prefab/shared/examples/creation_empty.py +0 -0
  66. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/tests/prefab/shared/examples/dunders.py +0 -0
  67. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/tests/prefab/shared/examples/fails/creation_1.py +0 -0
  68. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/tests/prefab/shared/examples/fails/creation_2.py +0 -0
  69. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/tests/prefab/shared/examples/fails/creation_3.py +0 -0
  70. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/tests/prefab/shared/examples/fails/creation_5.py +0 -0
  71. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/tests/prefab/shared/examples/fails/inheritance_1.py +0 -0
  72. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/tests/prefab/shared/examples/fails/inheritance_2.py +0 -0
  73. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/tests/prefab/shared/examples/funcs_prefabs.py +0 -0
  74. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/tests/prefab/shared/examples/hint_syntax.py +0 -0
  75. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/tests/prefab/shared/examples/inheritance.py +0 -0
  76. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/tests/prefab/shared/examples/init_ex.py +0 -0
  77. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/tests/prefab/shared/examples/kw_only.py +0 -0
  78. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/tests/prefab/shared/examples/repr_func.py +0 -0
  79. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/tests/prefab/shared/test_creation.py +0 -0
  80. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/tests/prefab/shared/test_dunders.py +0 -0
  81. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/tests/prefab/shared/test_funcs.py +0 -0
  82. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/tests/prefab/shared/test_hint_syntax.py +0 -0
  83. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/tests/prefab/shared/test_inheritance.py +0 -0
  84. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/tests/prefab/shared/test_init.py +0 -0
  85. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/tests/prefab/shared/test_kw_only.py +0 -0
  86. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/tests/prefab/shared/test_repr.py +0 -0
  87. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/tests/py312_tests/test_generic_annotations.py +0 -0
  88. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/tests/test_core.py +0 -0
  89. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/tests/test_field_flags.py +0 -0
  90. {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.0}/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.0
4
4
  Summary: Toolkit for creating class boilerplate generators
5
5
  Author: David C Ellis
6
6
  License: MIT License
@@ -38,7 +38,7 @@ Requires-Python: >=3.8
38
38
  Description-Content-Type: text/markdown
39
39
  License-File: LICENSE.md
40
40
  Provides-Extra: testing
41
- Requires-Dist: pytest; extra == "testing"
41
+ Requires-Dist: pytest>=8.2; extra == "testing"
42
42
  Requires-Dist: pytest-cov; extra == "testing"
43
43
  Requires-Dist: mypy; extra == "testing"
44
44
  Requires-Dist: typing_extensions; extra == "testing"
@@ -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" f"{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)