dataclassbase 0.1.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.
- dataclassbase-0.1.0/.github/workflows/check_and_deploy.yml +131 -0
- dataclassbase-0.1.0/.gitignore +311 -0
- dataclassbase-0.1.0/.readthedocs.yaml +21 -0
- dataclassbase-0.1.0/LICENSE +7 -0
- dataclassbase-0.1.0/PKG-INFO +181 -0
- dataclassbase-0.1.0/README.md +156 -0
- dataclassbase-0.1.0/dataclassbase/__init__.py +1171 -0
- dataclassbase-0.1.0/dataclassbase/_util.py +25 -0
- dataclassbase-0.1.0/dataclassbase.egg-info/PKG-INFO +181 -0
- dataclassbase-0.1.0/dataclassbase.egg-info/SOURCES.txt +28 -0
- dataclassbase-0.1.0/dataclassbase.egg-info/dependency_links.txt +1 -0
- dataclassbase-0.1.0/dataclassbase.egg-info/requires.txt +5 -0
- dataclassbase-0.1.0/dataclassbase.egg-info/scm_file_list.json +25 -0
- dataclassbase-0.1.0/dataclassbase.egg-info/scm_version.json +8 -0
- dataclassbase-0.1.0/dataclassbase.egg-info/top_level.txt +1 -0
- dataclassbase-0.1.0/docs/Makefile +15 -0
- dataclassbase-0.1.0/docs/make.bat +35 -0
- dataclassbase-0.1.0/docs/source/conf.py +66 -0
- dataclassbase-0.1.0/docs/source/index.rst +9 -0
- dataclassbase-0.1.0/pyproject.toml +64 -0
- dataclassbase-0.1.0/scripts/build.sh +17 -0
- dataclassbase-0.1.0/scripts/doc.sh +9 -0
- dataclassbase-0.1.0/scripts/lint_test.sh +13 -0
- dataclassbase-0.1.0/setup.cfg +4 -0
- dataclassbase-0.1.0/tests/dataclassbase_tests/__init__.py +47 -0
- dataclassbase-0.1.0/tests/dataclassbase_tests/fields.py +35 -0
- dataclassbase-0.1.0/tests/dataclassbase_tests/test_dataclassmeta.py +229 -0
- dataclassbase-0.1.0/tests/dataclassbase_tests/test_field_provider.py +89 -0
- dataclassbase-0.1.0/tests/dataclassbase_tests/test_modifiers.py +129 -0
- dataclassbase-0.1.0/tests/dataclassbase_tests/test_typeconstrained_field.py +44 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
name: Python Lint, Format, Test, Deploy
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- "**"
|
|
7
|
+
pull_request:
|
|
8
|
+
release:
|
|
9
|
+
types: [published]
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
test:
|
|
13
|
+
name: Lint, Check formatting, Static Type-Check, Test
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- name: Checkout
|
|
18
|
+
uses: actions/checkout@v7
|
|
19
|
+
|
|
20
|
+
- name: Setup Python
|
|
21
|
+
uses: actions/setup-python@v6
|
|
22
|
+
with:
|
|
23
|
+
python-version: "3.14"
|
|
24
|
+
cache: 'pip'
|
|
25
|
+
|
|
26
|
+
- name: Install dependencies
|
|
27
|
+
run: |
|
|
28
|
+
python -m pip install --upgrade pip
|
|
29
|
+
pip install ruff mypy pytest pytest-cov pytest-markdown-summary
|
|
30
|
+
pip install -e .
|
|
31
|
+
|
|
32
|
+
- name: Install pyproject.toml
|
|
33
|
+
run: pip install .
|
|
34
|
+
|
|
35
|
+
- name: Ruff lint
|
|
36
|
+
run: ruff check --output-format=github --target-version=py314
|
|
37
|
+
|
|
38
|
+
- name: Ruff format
|
|
39
|
+
run: ruff format --diff --target-version=py314
|
|
40
|
+
|
|
41
|
+
- name: MyPy
|
|
42
|
+
run: mypy dataclassbase
|
|
43
|
+
|
|
44
|
+
- name: Run tests
|
|
45
|
+
run: |
|
|
46
|
+
mkdir summary
|
|
47
|
+
pytest --cov=dataclassbase --cov-report=markdown-append:summary/coverage.md --markdown-summary-file=summary/summary.md
|
|
48
|
+
echo -e "# Summary\n" >> $GITHUB_STEP_SUMMARY
|
|
49
|
+
cat summary/summary.md >> $GITHUB_STEP_SUMMARY
|
|
50
|
+
echo -e "\n# Coverage\n" >> $GITHUB_STEP_SUMMARY
|
|
51
|
+
cat summary/coverage.md >> $GITHUB_STEP_SUMMARY
|
|
52
|
+
|
|
53
|
+
build_for_testpypi:
|
|
54
|
+
name: Build project for TestPyPI
|
|
55
|
+
runs-on: ubuntu-latest
|
|
56
|
+
env:
|
|
57
|
+
SETUPTOOLS_SCM_OVERRIDES_FOR_DATACLASSBASE: '{local_scheme = "no-local-version-strict"}'
|
|
58
|
+
|
|
59
|
+
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master')
|
|
60
|
+
|
|
61
|
+
steps:
|
|
62
|
+
- uses: actions/checkout@v7
|
|
63
|
+
with:
|
|
64
|
+
fetch-depth: 0
|
|
65
|
+
|
|
66
|
+
- uses: hynek/build-and-inspect-python-package@v2
|
|
67
|
+
|
|
68
|
+
publish-to-test-pypi:
|
|
69
|
+
name: Publish to TestPyPI
|
|
70
|
+
runs-on: ubuntu-latest
|
|
71
|
+
needs: [build_for_testpypi, test]
|
|
72
|
+
|
|
73
|
+
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master')
|
|
74
|
+
|
|
75
|
+
environment:
|
|
76
|
+
name: testpypi
|
|
77
|
+
|
|
78
|
+
url: https://test.pypi.org/p/dataclassbase
|
|
79
|
+
|
|
80
|
+
permissions:
|
|
81
|
+
id-token: write
|
|
82
|
+
|
|
83
|
+
steps:
|
|
84
|
+
- name: Download dist
|
|
85
|
+
uses: actions/download-artifact@v6
|
|
86
|
+
with:
|
|
87
|
+
name: Packages
|
|
88
|
+
path: dist
|
|
89
|
+
|
|
90
|
+
- name: Publish
|
|
91
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
92
|
+
with:
|
|
93
|
+
repository-url: https://test.pypi.org/legacy/
|
|
94
|
+
|
|
95
|
+
# we may build and publish to PyPI
|
|
96
|
+
build_for_pypi:
|
|
97
|
+
name: Build project for PyPI
|
|
98
|
+
runs-on: ubuntu-latest
|
|
99
|
+
|
|
100
|
+
if: github.event_name == 'release'
|
|
101
|
+
|
|
102
|
+
steps:
|
|
103
|
+
- uses: actions/checkout@v7
|
|
104
|
+
with:
|
|
105
|
+
fetch-depth: 0
|
|
106
|
+
|
|
107
|
+
- uses: hynek/build-and-inspect-python-package@v2
|
|
108
|
+
|
|
109
|
+
publish-to-pypi:
|
|
110
|
+
name: Publish to PyPI
|
|
111
|
+
runs-on: ubuntu-latest
|
|
112
|
+
needs: [build_for_pypi, test]
|
|
113
|
+
|
|
114
|
+
if: github.event_name == 'release'
|
|
115
|
+
|
|
116
|
+
environment:
|
|
117
|
+
name: pypi
|
|
118
|
+
|
|
119
|
+
url: https://pypi.org/p/dataclassbase
|
|
120
|
+
permissions:
|
|
121
|
+
id-token: write
|
|
122
|
+
|
|
123
|
+
steps:
|
|
124
|
+
- name: Download dist
|
|
125
|
+
uses: actions/download-artifact@v6
|
|
126
|
+
with:
|
|
127
|
+
name: Packages
|
|
128
|
+
path: dist
|
|
129
|
+
|
|
130
|
+
- name: Publish
|
|
131
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[codz]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
# Usually these files are written by a python script from a template
|
|
31
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
32
|
+
*.manifest
|
|
33
|
+
*.spec
|
|
34
|
+
|
|
35
|
+
# Installer logs
|
|
36
|
+
pip-log.txt
|
|
37
|
+
pip-delete-this-directory.txt
|
|
38
|
+
|
|
39
|
+
# Unit test / coverage reports
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.nox/
|
|
43
|
+
.coverage
|
|
44
|
+
.coverage.*
|
|
45
|
+
.cache
|
|
46
|
+
nosetests.xml
|
|
47
|
+
coverage.xml
|
|
48
|
+
*.cover
|
|
49
|
+
*.py.cover
|
|
50
|
+
.hypothesis/
|
|
51
|
+
.pytest_cache/
|
|
52
|
+
cover/
|
|
53
|
+
|
|
54
|
+
# Translations
|
|
55
|
+
*.mo
|
|
56
|
+
*.pot
|
|
57
|
+
|
|
58
|
+
# Django stuff:
|
|
59
|
+
*.log
|
|
60
|
+
local_settings.py
|
|
61
|
+
db.sqlite3
|
|
62
|
+
db.sqlite3-journal
|
|
63
|
+
|
|
64
|
+
# Flask stuff:
|
|
65
|
+
instance/
|
|
66
|
+
.webassets-cache
|
|
67
|
+
|
|
68
|
+
# Scrapy stuff:
|
|
69
|
+
.scrapy
|
|
70
|
+
|
|
71
|
+
# Sphinx documentation
|
|
72
|
+
docs/build/doctrees
|
|
73
|
+
docs/source/_autogen
|
|
74
|
+
|
|
75
|
+
# PyBuilder
|
|
76
|
+
.pybuilder/
|
|
77
|
+
target/
|
|
78
|
+
|
|
79
|
+
# Jupyter Notebook
|
|
80
|
+
.ipynb_checkpoints
|
|
81
|
+
|
|
82
|
+
# IPython
|
|
83
|
+
profile_default/
|
|
84
|
+
ipython_config.py
|
|
85
|
+
|
|
86
|
+
# pyenv
|
|
87
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
88
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
89
|
+
# .python-version
|
|
90
|
+
|
|
91
|
+
# pipenv
|
|
92
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
93
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
94
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
95
|
+
# install all needed dependencies.
|
|
96
|
+
# Pipfile.lock
|
|
97
|
+
|
|
98
|
+
# UV
|
|
99
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
100
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
101
|
+
# commonly ignored for libraries.
|
|
102
|
+
# uv.lock
|
|
103
|
+
|
|
104
|
+
# poetry
|
|
105
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
106
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
107
|
+
# commonly ignored for libraries.
|
|
108
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
109
|
+
# poetry.lock
|
|
110
|
+
# poetry.toml
|
|
111
|
+
|
|
112
|
+
# pdm
|
|
113
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
114
|
+
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
|
115
|
+
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
|
116
|
+
# pdm.lock
|
|
117
|
+
# pdm.toml
|
|
118
|
+
.pdm-python
|
|
119
|
+
.pdm-build/
|
|
120
|
+
|
|
121
|
+
# pixi
|
|
122
|
+
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
|
123
|
+
# pixi.lock
|
|
124
|
+
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
|
125
|
+
# in the .venv directory. It is recommended not to include this directory in version control.
|
|
126
|
+
.pixi
|
|
127
|
+
|
|
128
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
129
|
+
__pypackages__/
|
|
130
|
+
|
|
131
|
+
# Celery stuff
|
|
132
|
+
celerybeat-schedule
|
|
133
|
+
celerybeat.pid
|
|
134
|
+
|
|
135
|
+
# Redis
|
|
136
|
+
*.rdb
|
|
137
|
+
*.aof
|
|
138
|
+
*.pid
|
|
139
|
+
|
|
140
|
+
# RabbitMQ
|
|
141
|
+
mnesia/
|
|
142
|
+
rabbitmq/
|
|
143
|
+
rabbitmq-data/
|
|
144
|
+
|
|
145
|
+
# ActiveMQ
|
|
146
|
+
activemq-data/
|
|
147
|
+
|
|
148
|
+
# SageMath parsed files
|
|
149
|
+
*.sage.py
|
|
150
|
+
|
|
151
|
+
# Environments
|
|
152
|
+
.env
|
|
153
|
+
.envrc
|
|
154
|
+
.venv
|
|
155
|
+
env/
|
|
156
|
+
venv/
|
|
157
|
+
ENV/
|
|
158
|
+
env.bak/
|
|
159
|
+
venv.bak/
|
|
160
|
+
|
|
161
|
+
# Spyder project settings
|
|
162
|
+
.spyderproject
|
|
163
|
+
.spyproject
|
|
164
|
+
|
|
165
|
+
# Rope project settings
|
|
166
|
+
.ropeproject
|
|
167
|
+
|
|
168
|
+
# mkdocs documentation
|
|
169
|
+
/site
|
|
170
|
+
|
|
171
|
+
# mypy
|
|
172
|
+
.mypy_cache/
|
|
173
|
+
.dmypy.json
|
|
174
|
+
dmypy.json
|
|
175
|
+
|
|
176
|
+
# Pyre type checker
|
|
177
|
+
.pyre/
|
|
178
|
+
|
|
179
|
+
# pytype static type analyzer
|
|
180
|
+
.pytype/
|
|
181
|
+
|
|
182
|
+
# Cython debug symbols
|
|
183
|
+
cython_debug/
|
|
184
|
+
|
|
185
|
+
# PyCharm
|
|
186
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
187
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
188
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
189
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
190
|
+
# .idea/
|
|
191
|
+
|
|
192
|
+
# Abstra
|
|
193
|
+
# Abstra is an AI-powered process automation framework.
|
|
194
|
+
# Ignore directories containing user credentials, local state, and settings.
|
|
195
|
+
# Learn more at https://abstra.io/docs
|
|
196
|
+
.abstra/
|
|
197
|
+
|
|
198
|
+
# Visual Studio Code
|
|
199
|
+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
|
200
|
+
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
|
201
|
+
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
|
202
|
+
# you could uncomment the following to ignore the entire vscode folder
|
|
203
|
+
# .vscode/
|
|
204
|
+
# Temporary file for partial code execution
|
|
205
|
+
tempCodeRunnerFile.py
|
|
206
|
+
|
|
207
|
+
# Ruff stuff:
|
|
208
|
+
.ruff_cache/
|
|
209
|
+
|
|
210
|
+
# PyPI configuration file
|
|
211
|
+
.pypirc
|
|
212
|
+
|
|
213
|
+
# Marimo
|
|
214
|
+
marimo/_static/
|
|
215
|
+
marimo/_lsp/
|
|
216
|
+
__marimo__/
|
|
217
|
+
|
|
218
|
+
# Streamlit
|
|
219
|
+
.streamlit/secrets.toml
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
# https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
223
|
+
# Covers JetBrains IDEs: IntelliJ, GoLand, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
|
224
|
+
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
|
225
|
+
|
|
226
|
+
# User-specific stuff
|
|
227
|
+
.idea/**/workspace.xml
|
|
228
|
+
.idea/**/tasks.xml
|
|
229
|
+
.idea/**/usage.statistics.xml
|
|
230
|
+
.idea/**/dictionaries
|
|
231
|
+
.idea/**/shelf
|
|
232
|
+
|
|
233
|
+
# AWS User-specific
|
|
234
|
+
.idea/**/aws.xml
|
|
235
|
+
|
|
236
|
+
# Generated files
|
|
237
|
+
.idea/**/contentModel.xml
|
|
238
|
+
|
|
239
|
+
# Sensitive or high-churn files
|
|
240
|
+
.idea/**/dataSources/
|
|
241
|
+
.idea/**/dataSources.ids
|
|
242
|
+
.idea/**/dataSources.local.xml
|
|
243
|
+
.idea/**/sqlDataSources.xml
|
|
244
|
+
.idea/**/dynamic.xml
|
|
245
|
+
.idea/**/uiDesigner.xml
|
|
246
|
+
.idea/**/dbnavigator.xml
|
|
247
|
+
|
|
248
|
+
# Gradle
|
|
249
|
+
.idea/**/gradle.xml
|
|
250
|
+
.idea/**/libraries
|
|
251
|
+
|
|
252
|
+
# Gradle and Maven with auto-import
|
|
253
|
+
# When using Gradle or Maven with auto-import, you should exclude module files,
|
|
254
|
+
# since they will be recreated, and may cause churn. Uncomment if using
|
|
255
|
+
# auto-import.
|
|
256
|
+
# .idea/artifacts
|
|
257
|
+
# .idea/compiler.xml
|
|
258
|
+
# .idea/jarRepositories.xml
|
|
259
|
+
# .idea/modules.xml
|
|
260
|
+
# .idea/*.iml
|
|
261
|
+
# .idea/modules
|
|
262
|
+
# *.iml
|
|
263
|
+
# *.ipr
|
|
264
|
+
|
|
265
|
+
# CMake
|
|
266
|
+
cmake-build-*/
|
|
267
|
+
|
|
268
|
+
# Mongo Explorer plugin
|
|
269
|
+
.idea/**/mongoSettings.xml
|
|
270
|
+
|
|
271
|
+
# File-based project format
|
|
272
|
+
*.iws
|
|
273
|
+
|
|
274
|
+
# IntelliJ
|
|
275
|
+
out/
|
|
276
|
+
|
|
277
|
+
# mpeltonen/sbt-idea plugin
|
|
278
|
+
.idea_modules/
|
|
279
|
+
|
|
280
|
+
# JIRA plugin
|
|
281
|
+
atlassian-ide-plugin.xml
|
|
282
|
+
|
|
283
|
+
# Cursive Clojure plugin
|
|
284
|
+
.idea/replstate.xml
|
|
285
|
+
|
|
286
|
+
# SonarLint plugin
|
|
287
|
+
.idea/sonarlint/
|
|
288
|
+
# see https://community.sonarsource.com/t/is-the-file-idea-idea-idea-sonarlint-xml-intended-to-be-under-source-control/121119
|
|
289
|
+
.idea/sonarlint.xml
|
|
290
|
+
|
|
291
|
+
# Crashlytics plugin (for Android Studio and IntelliJ)
|
|
292
|
+
com_crashlytics_export_strings.xml
|
|
293
|
+
crashlytics.properties
|
|
294
|
+
crashlytics-build.properties
|
|
295
|
+
fabric.properties
|
|
296
|
+
|
|
297
|
+
# Editor-based HTTP Client
|
|
298
|
+
.idea/httpRequests
|
|
299
|
+
http-client.private.env.json
|
|
300
|
+
|
|
301
|
+
# Android studio 3.1+ serialized cache file
|
|
302
|
+
.idea/caches/build_file_checksums.ser
|
|
303
|
+
|
|
304
|
+
# Apifox Helper cache
|
|
305
|
+
.idea/.cache/.Apifox_Helper
|
|
306
|
+
.idea/ApifoxUploaderProjectSetting.xml
|
|
307
|
+
|
|
308
|
+
# Github Copilot persisted session migrations, see: https://github.com/microsoft/copilot-intellij-feedback/issues/712#issuecomment-3322062215
|
|
309
|
+
.idea/**/copilot.data.migration.*.xml
|
|
310
|
+
|
|
311
|
+
.idea
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
|
|
3
|
+
build:
|
|
4
|
+
os: ubuntu-24.04
|
|
5
|
+
tools:
|
|
6
|
+
python: "3"
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
pre_build:
|
|
10
|
+
- sphinx-apidoc -o docs/source/_autogen python_ci_base
|
|
11
|
+
|
|
12
|
+
sphinx:
|
|
13
|
+
configuration: docs/source/conf.py
|
|
14
|
+
fail_on_warning: true
|
|
15
|
+
|
|
16
|
+
python:
|
|
17
|
+
install:
|
|
18
|
+
- method: pip
|
|
19
|
+
path: .
|
|
20
|
+
extra_requirements:
|
|
21
|
+
- docs
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
(c) Copyright 2026 Josef Mayr
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dataclassbase
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Implement your own type of dataclass without the usual boilerplate code.
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Classifier: Development Status :: 4 - Beta
|
|
7
|
+
Classifier: Intended Audience :: Developers
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
10
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
11
|
+
Classifier: Topic :: Software Development
|
|
12
|
+
Classifier: Typing :: Typed
|
|
13
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
14
|
+
Classifier: Operating System :: POSIX
|
|
15
|
+
Classifier: Operating System :: Unix
|
|
16
|
+
Classifier: Operating System :: MacOS
|
|
17
|
+
Requires-Python: >=3.14
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE
|
|
20
|
+
Provides-Extra: docs
|
|
21
|
+
Requires-Dist: pydata_sphinx_theme; extra == "docs"
|
|
22
|
+
Requires-Dist: setuptools; extra == "docs"
|
|
23
|
+
Requires-Dist: setuptools_scm; extra == "docs"
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
|
|
26
|
+
# dataclassbase: Implement custom dataclasses
|
|
27
|
+
|
|
28
|
+
## What it is and how to get it
|
|
29
|
+
|
|
30
|
+
**dataclassbase** is a small toolkit for providing your own dataclasses. For dataclasses, attributes are declared via
|
|
31
|
+
annotations, and constructors are created automatically making development fast.
|
|
32
|
+
|
|
33
|
+
However, often data in dataclasses shares common properties (e.g., data shape, etc.) or we might want to have more
|
|
34
|
+
control over the fields in dataclasses. This is where **dataclassbase** comes to help: You can define your own
|
|
35
|
+
dataclass-like behavior using `typing.dataclass_transform` or metaclasses, and control the behavior of your dataclass in
|
|
36
|
+
a fine-grained manner.
|
|
37
|
+
|
|
38
|
+
You can install it into your environment from PyPI via
|
|
39
|
+
|
|
40
|
+
```shell
|
|
41
|
+
pip install dataclassbase
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
## General note
|
|
46
|
+
|
|
47
|
+
While the project is generally usable, it is not yet production-ready. It is, however, being actively developed since I
|
|
48
|
+
plan to use it as a base for further work.
|
|
49
|
+
|
|
50
|
+
## License
|
|
51
|
+
|
|
52
|
+
[MIT](./LICENSE)
|
|
53
|
+
|
|
54
|
+
## The Basics
|
|
55
|
+
|
|
56
|
+
The `dataclasses` module provides very basic dataclasses with little customization. The objective of this package is to
|
|
57
|
+
overcome this limitation by providing customizable dataclasses.
|
|
58
|
+
|
|
59
|
+
A possible definition for the built-in dataclasses is
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
from dataclasses import dataclass
|
|
63
|
+
|
|
64
|
+
@dataclass
|
|
65
|
+
class SomeClass:
|
|
66
|
+
some_field: str
|
|
67
|
+
some_defaulting_field: int = 1
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
In this framework, a similar approach is possible:
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
from dataclassbase import dataclass
|
|
74
|
+
|
|
75
|
+
@dataclass
|
|
76
|
+
class SomeClass:
|
|
77
|
+
some_field: str
|
|
78
|
+
some_defaulting_field: int = 1
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
The most obvious difference above is that we import from `dataclassbase` rather than `dataclasses`. However, looking at
|
|
82
|
+
the signature of `dataclassbase.dataclass`, we note that there are two notable parameters: `field_provider` and
|
|
83
|
+
`object_handler`. `field_provider` allows customization of the field generation and processing (if the user provides a
|
|
84
|
+
`dataclassbase.Field` as a default value), `object_handler` implements the logic for the initialization (`__init__`) and
|
|
85
|
+
attribute assignment (`__setattr__`) of objects of the generated dataclass. We therefore can hook into or override the
|
|
86
|
+
behavior of the dataclass.
|
|
87
|
+
|
|
88
|
+
If you omit the `field_provider` or `object_handler`, the respective default implementations provided in the
|
|
89
|
+
`FieldProvider` and `ObjectHandler` classes, which reproduce the default dataclass behavior, are used instead.
|
|
90
|
+
If you provide a callable matching the signature of `dataclassbase.Field` to `field_provider`, a `FieldProvider` is
|
|
91
|
+
generated. Therefore, you can directly specify the type of field to use, as long as it matches the respective signature.
|
|
92
|
+
You can also replace the `default`-value if it indicates a value you want to process.
|
|
93
|
+
|
|
94
|
+
Note that while it suffices to call `dataclassbase.dataclass` on a class to initialize dataclass-like behavior, it does
|
|
95
|
+
not truly behave like a dataclass to static type checkers since it lacks the `typing.dataclass_transform` decorator.
|
|
96
|
+
My reason for that choice is that I would have to fix the `field_specifiers` parameter.
|
|
97
|
+
(Instances of the types provided in `field_specifiers` are allowed as default values for fields since they are
|
|
98
|
+
assumed to be fields or to be converted into some. We do that conversion in the provided `field_provider`.)
|
|
99
|
+
In doing so, I would constrain proper static type checking on the fields. Therefore, I would rather let you indicate the
|
|
100
|
+
types yourself.
|
|
101
|
+
To achieve true dataclass-like behavior on a type checker, you can call `typing.dataclass_transform` yourself or rely on
|
|
102
|
+
a metaclass derived from `DataclassMetaBase[TField]`:
|
|
103
|
+
|
|
104
|
+
Rather than using `dataclassbase.dataclass` as above, you can define your own dataclass by deriving a metaclass from
|
|
105
|
+
class `DataclassMetaBase[TField]` and attaching a `typing.dataclass_transform` to it. `DataclassMetaBase` invokes
|
|
106
|
+
`make_dataclass` when creating the new type (`dataclass` is the decorator-variant of `make_dataclass`). It provides
|
|
107
|
+
the methods `_field_provider` and `_object_handler` which act as factories for the field provider and object handler for
|
|
108
|
+
the types using the metaclass.
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
from dataclassbase import DataclassMetaBase, Field, FieldProvider
|
|
112
|
+
from typing import dataclass_transform
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class CustomField(Field):
|
|
116
|
+
# you can override the methods provided by `Field` to control the behavior of the field in derived classes,
|
|
117
|
+
# superclasses, to check the assignments, etc. You can also change the behavior of `__init__` as long as its
|
|
118
|
+
# signature matches the one from the base
|
|
119
|
+
pass
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@dataclass_transform
|
|
123
|
+
class CustomDataclassMeta(DataclassMetaBase[Field]):
|
|
124
|
+
@classmethod
|
|
125
|
+
def _field_provider(cls) -> FieldProvider[Field]:
|
|
126
|
+
# we want it to return our custom field. The factory works since the signature of `CustomField.__init__` matches
|
|
127
|
+
# the one of `Field.__init__`
|
|
128
|
+
return FieldProvider(CustomField)
|
|
129
|
+
|
|
130
|
+
class VariantA(metaclass=CustomDataclassMeta):
|
|
131
|
+
field: int
|
|
132
|
+
defaulting: int = 1
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
`dataclassbase.Field`s themselves can check whether the value assigned to them is valid. Furthermore, they can check
|
|
136
|
+
whether fields in derived classes are valid and, in the opposite direction, whether they are valid overrides of fields
|
|
137
|
+
of superclasses. As an example, you can look at the provided `dataclassbase.TypeConstrainedField` which provides
|
|
138
|
+
simple type checking via `isinstance`.
|
|
139
|
+
|
|
140
|
+
The default implementation is `DataclassMeta` which just uses the default values and allows any
|
|
141
|
+
`dataclasses.Field` as a default value for its fields. While it behaves in the same way as the built-in
|
|
142
|
+
`dataclasses`-dataclasses, it does however not give us any benefits over it.
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
from dataclassbase import DataclassMeta
|
|
146
|
+
|
|
147
|
+
class VariantB(metaclass=DataclassMeta):
|
|
148
|
+
field: int
|
|
149
|
+
defaulting: int = 1
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Fine-tuning the behavior of the dataclasses
|
|
153
|
+
|
|
154
|
+
`dataclasses.dataclass` comes with quite a lot of nice additions: We can freeze the dataclass, equality and unequality
|
|
155
|
+
operators get generated, a hash function may be provided, and so forth. This behavior can be imitated using the classes
|
|
156
|
+
derived from the `BehaviorModifier` type. For now, these are
|
|
157
|
+
|
|
158
|
+
* `Frozen`: Dataclasses cannot be assigned new values and cannot be reinitialized
|
|
159
|
+
* `Representable`: Adds a string representation via `__repr__` and `__str__`
|
|
160
|
+
* `Equatable`: Adds overloads for `__eq__` and `__ne__`, and, optionally, for `__hash__`
|
|
161
|
+
* `PositionalOnly`: The initializer only allows positional arguments
|
|
162
|
+
* `KeywordOnly`: The initializer only allows keyword arguments
|
|
163
|
+
|
|
164
|
+
Add these as `modifiers` when calling `dataclass` or `make_dataclass` or return them as `_modifiers` in metaclasses
|
|
165
|
+
derived from `DataclassMetaBase`.
|
|
166
|
+
|
|
167
|
+
Note that some of the behaviors are "unsafe", and it is up to you to decide whether your use-case is safe. For
|
|
168
|
+
example, hash codes typically change once fields of the dataclass change. Using such a class as a key is unsafe. You
|
|
169
|
+
thus will typically combine `Frozen` and `Equatable`. Note that `Frozen` does not affect nested dataclasses: If these
|
|
170
|
+
are not frozen, the dataclass itself may still be mutable. Another unsafe example is the combination of `PositionalOnly`
|
|
171
|
+
and `KeywordOnly` which does not make any sense, but is not caught for now.
|
|
172
|
+
|
|
173
|
+
I may add such safeguards later, but for now it is obvious that such combinations do not make any sense.
|
|
174
|
+
|
|
175
|
+
# When to use dataclassbase and not ...?
|
|
176
|
+
|
|
177
|
+
## Pydantic
|
|
178
|
+
|
|
179
|
+
Pydantic's objective is to provide fast data validation and serialization [[1]](https://pydantic.dev/docs/validation/latest/get-started/).
|
|
180
|
+
dataclassbase aims to be much more abstract since it is not constrained to a single use-case such as data validation,
|
|
181
|
+
and thus does (hopefully) not give too strong restrictions on what you can/cannot do.
|