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.
- ducktools_classbuilder-0.7.1/.github/dependabot.yml +7 -0
- ducktools_classbuilder-0.7.1/.github/workflows/auto_test.yml +48 -0
- ducktools_classbuilder-0.7.1/.github/workflows/publish_to_pypi.yml +87 -0
- ducktools_classbuilder-0.7.1/.github/workflows/publish_to_testpypi.yml +55 -0
- ducktools_classbuilder-0.7.1/.gitignore +25 -0
- ducktools_classbuilder-0.7.1/.readthedocs.yaml +24 -0
- {ducktools_classbuilder-0.6.3/src/ducktools_classbuilder.egg-info → ducktools_classbuilder-0.7.1}/PKG-INFO +3 -2
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/docs/extension_examples.md +2 -2
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/docs/tutorial.md +10 -8
- ducktools_classbuilder-0.7.1/docs_code/docs_ex1_basic.py +17 -0
- ducktools_classbuilder-0.7.1/docs_code/docs_ex2_register.py +32 -0
- ducktools_classbuilder-0.7.1/docs_code/docs_ex3_iterable.py +50 -0
- ducktools_classbuilder-0.7.1/docs_code/docs_ex5_frozen.py +43 -0
- ducktools_classbuilder-0.7.1/docs_code/docs_ex7_posonly.py +131 -0
- ducktools_classbuilder-0.7.1/docs_code/docs_ex8_converters.py +57 -0
- ducktools_classbuilder-0.7.1/docs_code/docs_ex9_annotated.py +188 -0
- ducktools_classbuilder-0.7.1/docs_code/tutorial_code.py +154 -0
- ducktools_classbuilder-0.7.1/perf/cluegen.py +127 -0
- ducktools_classbuilder-0.7.1/perf/dataklasses.py +102 -0
- ducktools_classbuilder-0.7.1/perf/hyperfine_testmaker.py +311 -0
- ducktools_classbuilder-0.7.1/perf/perf_profile.py +291 -0
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/pyproject.toml +8 -5
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/src/ducktools/classbuilder/__init__.py +3 -11
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/src/ducktools/classbuilder/__init__.pyi +1 -0
- ducktools_classbuilder-0.7.1/src/ducktools/classbuilder/_version.py +2 -0
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/src/ducktools/classbuilder/annotations.py +106 -23
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/src/ducktools/classbuilder/annotations.pyi +6 -0
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/src/ducktools/classbuilder/prefab.py +15 -1
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/src/ducktools/classbuilder/prefab.pyi +2 -0
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1/src/ducktools_classbuilder.egg-info}/PKG-INFO +3 -2
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/src/ducktools_classbuilder.egg-info/SOURCES.txt +23 -3
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/src/ducktools_classbuilder.egg-info/requires.txt +1 -1
- ducktools_classbuilder-0.7.1/tests/annotations/test_future_annotations.py +45 -0
- ducktools_classbuilder-0.7.1/tests/conftest.py +10 -0
- ducktools_classbuilder-0.7.1/tests/prefab/dynamic/test_frozen.py +98 -0
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/prefab/dynamic/test_slotted_class.py +0 -1
- ducktools_classbuilder-0.7.1/tests/py314_tests/test_forwardref_annotations.py +45 -0
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/test_core.py +0 -1
- ducktools_classbuilder-0.6.3/tests/conftest.py +0 -12
- ducktools_classbuilder-0.6.3/tests/prefab/shared/examples/frozen_prefabs.py +0 -8
- ducktools_classbuilder-0.6.3/tests/prefab/shared/test_frozen.py +0 -60
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/LICENSE.md +0 -0
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/MANIFEST.in +0 -0
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/README.md +0 -0
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/docs/Makefile +0 -0
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/docs/api.md +0 -0
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/docs/approach_vs_tool.md +0 -0
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/docs/conf.py +0 -0
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/docs/generated_code.md +0 -0
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/docs/index.md +0 -0
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/docs/make.bat +0 -0
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/docs/perf/performance_tests.md +0 -0
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/docs/prefab/index.md +0 -0
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/setup.cfg +0 -0
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/src/ducktools/classbuilder/py.typed +0 -0
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/src/ducktools_classbuilder.egg-info/dependency_links.txt +0 -0
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/src/ducktools_classbuilder.egg-info/top_level.txt +0 -0
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/annotations/test_annotated.py +0 -0
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/annotations/test_annotations_module.py +0 -0
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/prefab/dynamic/test_compare_attrib.py +0 -0
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/prefab/dynamic/test_construction.py +0 -0
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/prefab/dynamic/test_internals.py +0 -0
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/prefab/dynamic/test_pre_post_init.py +0 -0
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/prefab/dynamic/test_private.py +0 -0
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/prefab/dynamic/test_slots_novalues.py +0 -0
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/prefab/dynamic/test_subclass_implementation.py +0 -0
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/conftest.py +0 -0
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/examples/creation.py +0 -0
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/examples/creation_empty.py +0 -0
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/examples/dunders.py +0 -0
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/examples/fails/creation_1.py +0 -0
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/examples/fails/creation_2.py +0 -0
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/examples/fails/creation_3.py +0 -0
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/examples/fails/creation_5.py +0 -0
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/examples/fails/inheritance_1.py +0 -0
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/examples/fails/inheritance_2.py +0 -0
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/examples/funcs_prefabs.py +0 -0
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/examples/hint_syntax.py +0 -0
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/examples/inheritance.py +0 -0
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/examples/init_ex.py +0 -0
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/examples/kw_only.py +0 -0
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/examples/repr_func.py +0 -0
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/test_creation.py +0 -0
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/test_dunders.py +0 -0
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/test_funcs.py +0 -0
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/test_hint_syntax.py +0 -0
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/test_inheritance.py +0 -0
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/test_init.py +0 -0
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/test_kw_only.py +0 -0
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/test_repr.py +0 -0
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/py312_tests/test_generic_annotations.py +0 -0
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/test_field_flags.py +0 -0
- {ducktools_classbuilder-0.6.3 → ducktools_classbuilder-0.7.1}/tests/test_slotmakermeta.py +0 -0
|
@@ -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.
|
|
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
|
|
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
|
|
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
|
+
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
194
|
-
|
|
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)
|