extendable 1.2.2__tar.gz → 1.4.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.
- extendable-1.4.0/.gitignore +129 -0
- extendable-1.4.0/.pre-commit-config.yaml +18 -0
- extendable-1.4.0/HISTORY.rst +135 -0
- {extendable-1.2.2 → extendable-1.4.0}/PKG-INFO +48 -26
- {extendable-1.2.2 → extendable-1.4.0}/README.md +15 -8
- extendable-1.4.0/news/.gitignore +1 -0
- {extendable-1.2.2 → extendable-1.4.0}/pyproject.toml +35 -19
- {extendable-1.2.2 → extendable-1.4.0}/src/extendable/__init__.py +4 -1
- {extendable-1.2.2 → extendable-1.4.0}/src/extendable/main.py +54 -28
- {extendable-1.2.2 → extendable-1.4.0}/src/extendable/registry.py +45 -24
- {extendable-1.2.2 → extendable-1.4.0}/src/extendable/utils.py +3 -2
- extendable-1.4.0/tests/__init__.py +0 -0
- extendable-1.4.0/tests/conftest.py +32 -0
- extendable-1.4.0/tests/mod_base/__init__.py +1 -0
- extendable-1.4.0/tests/mod_base/base.py +6 -0
- extendable-1.4.0/tests/mod_ext1/__init__.py +1 -0
- extendable-1.4.0/tests/mod_ext1/base.py +7 -0
- extendable-1.4.0/tests/mod_ext2/__init__.py +1 -0
- extendable-1.4.0/tests/mod_ext2/base.py +7 -0
- extendable-1.4.0/tests/mod_mixin/__init__.py +0 -0
- extendable-1.4.0/tests/mod_mixin/a.py +5 -0
- extendable-1.4.0/tests/mod_mixin/b.py +6 -0
- extendable-1.4.0/tests/mod_mixin/m.py +5 -0
- extendable-1.4.0/tests/mod_mixin/mext.py +6 -0
- extendable-1.4.0/tests/test_registry_init.py +152 -0
- extendable-1.4.0/tests/test_simple.py +348 -0
- extendable-1.4.0/tox.ini +53 -0
- extendable-1.2.2/src/extendable/version.py +0 -1
- {extendable-1.2.2 → extendable-1.4.0}/LICENSE +0 -0
- {extendable-1.2.2 → extendable-1.4.0}/src/extendable/context.py +0 -0
- {extendable-1.2.2 → extendable-1.4.0}/src/extendable/exceptions.py +0 -0
- {extendable-1.2.2 → extendable-1.4.0}/src/extendable/py.typed +0 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
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
|
+
pip-wheel-metadata/
|
|
24
|
+
share/python-wheels/
|
|
25
|
+
*.egg-info/
|
|
26
|
+
.installed.cfg
|
|
27
|
+
*.egg
|
|
28
|
+
MANIFEST
|
|
29
|
+
|
|
30
|
+
# PyInstaller
|
|
31
|
+
# Usually these files are written by a python script from a template
|
|
32
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
33
|
+
*.manifest
|
|
34
|
+
*.spec
|
|
35
|
+
|
|
36
|
+
# Installer logs
|
|
37
|
+
pip-log.txt
|
|
38
|
+
pip-delete-this-directory.txt
|
|
39
|
+
|
|
40
|
+
# Unit test / coverage reports
|
|
41
|
+
htmlcov/
|
|
42
|
+
.tox/
|
|
43
|
+
.nox/
|
|
44
|
+
.coverage
|
|
45
|
+
.coverage.*
|
|
46
|
+
.cache
|
|
47
|
+
nosetests.xml
|
|
48
|
+
coverage.xml
|
|
49
|
+
*.cover
|
|
50
|
+
*.py,cover
|
|
51
|
+
.hypothesis/
|
|
52
|
+
.pytest_cache/
|
|
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/
|
|
73
|
+
|
|
74
|
+
# PyBuilder
|
|
75
|
+
target/
|
|
76
|
+
|
|
77
|
+
# Jupyter Notebook
|
|
78
|
+
.ipynb_checkpoints
|
|
79
|
+
|
|
80
|
+
# IPython
|
|
81
|
+
profile_default/
|
|
82
|
+
ipython_config.py
|
|
83
|
+
|
|
84
|
+
# pyenv
|
|
85
|
+
.python-version
|
|
86
|
+
|
|
87
|
+
# pipenv
|
|
88
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
89
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
90
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
91
|
+
# install all needed dependencies.
|
|
92
|
+
#Pipfile.lock
|
|
93
|
+
|
|
94
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
|
95
|
+
__pypackages__/
|
|
96
|
+
|
|
97
|
+
# Celery stuff
|
|
98
|
+
celerybeat-schedule
|
|
99
|
+
celerybeat.pid
|
|
100
|
+
|
|
101
|
+
# SageMath parsed files
|
|
102
|
+
*.sage.py
|
|
103
|
+
|
|
104
|
+
# Environments
|
|
105
|
+
.env
|
|
106
|
+
.venv
|
|
107
|
+
env/
|
|
108
|
+
venv/
|
|
109
|
+
ENV/
|
|
110
|
+
env.bak/
|
|
111
|
+
venv.bak/
|
|
112
|
+
|
|
113
|
+
# Spyder project settings
|
|
114
|
+
.spyderproject
|
|
115
|
+
.spyproject
|
|
116
|
+
|
|
117
|
+
# Rope project settings
|
|
118
|
+
.ropeproject
|
|
119
|
+
|
|
120
|
+
# mkdocs documentation
|
|
121
|
+
/site
|
|
122
|
+
|
|
123
|
+
# mypy
|
|
124
|
+
.mypy_cache/
|
|
125
|
+
.dmypy.json
|
|
126
|
+
dmypy.json
|
|
127
|
+
|
|
128
|
+
# Pyre type checker
|
|
129
|
+
.pyre/
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
default_language_version:
|
|
2
|
+
python: python3
|
|
3
|
+
repos:
|
|
4
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
5
|
+
rev: v0.17.0
|
|
6
|
+
hooks:
|
|
7
|
+
- id: ruff-check
|
|
8
|
+
args: ["--fix"]
|
|
9
|
+
- id: ruff-format
|
|
10
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
11
|
+
rev: v6.0.0
|
|
12
|
+
hooks:
|
|
13
|
+
- id: check-merge-conflict
|
|
14
|
+
- id: check-toml
|
|
15
|
+
- id: check-yaml
|
|
16
|
+
- id: debug-statements
|
|
17
|
+
- id: end-of-file-fixer
|
|
18
|
+
- id: trailing-whitespace
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
1.4.0 (2026-10-22)
|
|
2
|
+
==================
|
|
3
|
+
|
|
4
|
+
Bugfixes
|
|
5
|
+
--------
|
|
6
|
+
|
|
7
|
+
- Each class is built once during registry initialization. Previously, if a class had to wait for one of its bases, all classes were rebuilt on each pass, potentially leaving the waiting class based on a class that was later replaced in the registry
|
|
8
|
+
- The classes built by the registry now keep the qualified name of the original
|
|
9
|
+
class instead of a generated one (``MyClass0``, ``MyClass1``, ...). Since these
|
|
10
|
+
classes are the same class for the end user, error messages and schemas
|
|
11
|
+
generated by third party libraries (e.g. pydantic) now use the expected name.
|
|
12
|
+
|
|
13
|
+
Also add a custom repr for aggregated classes to make them easier to
|
|
14
|
+
distinguish.
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
Deprecations and Removals
|
|
18
|
+
-------------------------
|
|
19
|
+
|
|
20
|
+
- Drop support for python < 3.10 (python 3.9 and older versions have reached
|
|
21
|
+
their end of life). Add support for python 3.14.
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
1.3.0 (2025-04-16)
|
|
25
|
+
==================
|
|
26
|
+
|
|
27
|
+
Bugfixes
|
|
28
|
+
--------
|
|
29
|
+
|
|
30
|
+
- When checking if an instance of extendable class is a type of itself, first check if both classes have the same __xreg_name__ attribute. (`#18 <https://github.com/lmignon/extendable/issues/18>`_)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
1.2.2 (2023-10-03)
|
|
34
|
+
==================
|
|
35
|
+
|
|
36
|
+
Bugfixes
|
|
37
|
+
--------
|
|
38
|
+
|
|
39
|
+
- Fix error when a class is extended to add others aggregated classes as parent
|
|
40
|
+
classes. Before this fix, the builder used the list of all base classes of
|
|
41
|
+
the final class as base classes for every class into the hierarchy. (`#15 <https://github.com/lmignon/extendable/issues/15>`_)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
1.2.1 (2023-09-19)
|
|
45
|
+
==================
|
|
46
|
+
|
|
47
|
+
Bugfixes
|
|
48
|
+
--------
|
|
49
|
+
|
|
50
|
+
- Fix isinstance and issublcass method on multi levels inheritance hierarchy. (`#14 <https://github.com/lmignon/extendable/pull/14>`_)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
1.2.0 (2023-07-28)
|
|
54
|
+
==================
|
|
55
|
+
|
|
56
|
+
Features
|
|
57
|
+
--------
|
|
58
|
+
|
|
59
|
+
- Add a `before_init_registry` hook method into the `ExtendableRegistryListener` class.
|
|
60
|
+
This methods allows you to udpate the list of modules to load into the registry if
|
|
61
|
+
you need to do so. (`#before_hook <https://github.com/lmignon/extendable/pull/13>`_)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
1.1.0 (2023-07-15)
|
|
65
|
+
==================
|
|
66
|
+
|
|
67
|
+
Features
|
|
68
|
+
--------
|
|
69
|
+
|
|
70
|
+
- Simpler syntax for defining a class extending another class. The `extends` parameter now accepts `True` as its value. `class PersonExt(Person, extends=True)` means that `PersonExt` extends its first base class: `Person`. This is equivalent to `class PersonExt(Person, extends=Person)`. (`#2 <https://github.com/lmignon/extendable/issues/2>`_)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
1.0.0 (2023-07-14)
|
|
74
|
+
==================
|
|
75
|
+
|
|
76
|
+
Features
|
|
77
|
+
--------
|
|
78
|
+
|
|
79
|
+
- Access to the context variable used to store the current extended Classes
|
|
80
|
+
returns None if no context is available. Previously the access to the context
|
|
81
|
+
throws an exception if no context was available.
|
|
82
|
+
- Calls to the classmethod "_get_assembled_cls" now raises RegistryNotInitializedError
|
|
83
|
+
if the registry is not initialized.
|
|
84
|
+
- The metadaclass now provides the method `_wrap_class_method`. This method
|
|
85
|
+
can be used to wrap class methods in a way that when the method is called
|
|
86
|
+
the logic is delegated to the aggregated class if it exists.
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
0.0.5 (2023-07-13)
|
|
90
|
+
==================
|
|
91
|
+
|
|
92
|
+
Bugfixes
|
|
93
|
+
--------
|
|
94
|
+
|
|
95
|
+
- Preserve kwargs values in class defintion to ensure to properly create a concrete class with the same attributes as the original one.
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
0.0.4 (2023-06-26)
|
|
99
|
+
==================
|
|
100
|
+
|
|
101
|
+
Bugfixes
|
|
102
|
+
--------
|
|
103
|
+
|
|
104
|
+
- Fix registry rebuild (`Details <https://github.com/lmignon/extendable/pull/8/commits/120c1b749081f48893ca74d711091621c3c3481e>`_)
|
|
105
|
+
- Add missing type annotation
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
Deprecations and Removals
|
|
109
|
+
-------------------------
|
|
110
|
+
|
|
111
|
+
- Drop support fo py3.6
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
0.0.3 (2022-11-01)
|
|
115
|
+
==================
|
|
116
|
+
|
|
117
|
+
Bugfixes
|
|
118
|
+
--------
|
|
119
|
+
|
|
120
|
+
- Fix error when '__qualname__' is not provided (`#6 <https://github.com/lmignon/extendable/issues/6>`_)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
0.0.2 (2022-06-15)
|
|
124
|
+
==================
|
|
125
|
+
|
|
126
|
+
Bugfixes
|
|
127
|
+
--------
|
|
128
|
+
|
|
129
|
+
- Fix compatibility issue with py3.11. (`#4 <https://github.com/lmignon/extendable/issues/4>`_)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
v0.0.1 (2021-30-12)
|
|
133
|
+
===================
|
|
134
|
+
|
|
135
|
+
First release.
|
|
@@ -1,27 +1,43 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: extendable
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.0
|
|
4
4
|
Summary: A lib to define class extendable at runtime.
|
|
5
|
+
Project-URL: Source, https://github.com/lmignon/extendable
|
|
6
|
+
Project-URL: Changelog, https://github.com/lmignon/extendable/blob/master/HISTORY.rst
|
|
5
7
|
Author-email: Laurent Mignon <laurent.mignon@acsone.eu>
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
License: The MIT License (MIT)
|
|
9
|
+
|
|
10
|
+
Copyright (c) 2021 Laurent Mignon
|
|
11
|
+
|
|
12
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
13
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
14
|
+
in the Software without restriction, including without limitation the rights
|
|
15
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
16
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
17
|
+
furnished to do so, subject to the following conditions:
|
|
18
|
+
|
|
19
|
+
The above copyright notice and this permission notice shall be included in
|
|
20
|
+
all copies or substantial portions of the Software.
|
|
21
|
+
|
|
22
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
23
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
24
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
25
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
26
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
27
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
28
|
+
THE SOFTWARE.
|
|
29
|
+
License-File: LICENSE
|
|
9
30
|
Classifier: Intended Audience :: Developers
|
|
31
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
32
|
Classifier: Operating System :: OS Independent
|
|
11
|
-
Requires-
|
|
12
|
-
Requires-Dist: types-contextvars;python_version<'3.7'
|
|
13
|
-
Requires-Dist: typing_extensions;python_version<'3.7'
|
|
14
|
-
Requires-Dist: mypy ; extra == "mypy"
|
|
15
|
-
Requires-Dist: towncrier ; extra == "release"
|
|
16
|
-
Requires-Dist: bumpversion ; extra == "release"
|
|
17
|
-
Requires-Dist: pytest ; extra == "test"
|
|
18
|
-
Requires-Dist: pytest-mock ; extra == "test"
|
|
19
|
-
Requires-Dist: coverage[toml] ; extra == "test"
|
|
20
|
-
Project-URL: Changelog, https://github.com/lmignon/extendable/blob/master/HISTORY.rst
|
|
21
|
-
Project-URL: Source, https://github.com/lmignon/extendable
|
|
33
|
+
Requires-Python: >=3.10
|
|
22
34
|
Provides-Extra: mypy
|
|
23
|
-
|
|
35
|
+
Requires-Dist: mypy; extra == 'mypy'
|
|
24
36
|
Provides-Extra: test
|
|
37
|
+
Requires-Dist: coverage[toml]; extra == 'test'
|
|
38
|
+
Requires-Dist: pytest; extra == 'test'
|
|
39
|
+
Requires-Dist: pytest-mock; extra == 'test'
|
|
40
|
+
Description-Content-Type: text/markdown
|
|
25
41
|
|
|
26
42
|
[](https://github.com/lmignon/extendable/actions/workflows/ci.yml)
|
|
27
43
|
[](https://codecov.io/gh/lmignon/extendable)
|
|
@@ -188,22 +204,28 @@ method into the metaclass.
|
|
|
188
204
|
To run tests, use `tox`. You will get a test coverage report in `htmlcov/index.html`.
|
|
189
205
|
An easy way to install tox is `pipx install tox`.
|
|
190
206
|
|
|
191
|
-
This project uses pre-commit to enforce linting
|
|
192
|
-
|
|
207
|
+
This project uses pre-commit to enforce linting and formatting (ruff). The type
|
|
208
|
+
checking (mypy) is run by `tox -e typing`.
|
|
193
209
|
|
|
194
210
|
To make sure linters run locally on each of your commits, install pre-commit
|
|
195
211
|
(`pipx install pre-commit` is recommended), and run `pre-commit install` in your
|
|
196
212
|
local clone of the extendable repository.
|
|
197
213
|
|
|
198
|
-
|
|
214
|
+
## Release
|
|
199
215
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
216
|
+
The version of the package is computed from the git tags (hatch-vcs) and the
|
|
217
|
+
changelog is built from the news fragments (towncrier).
|
|
218
|
+
|
|
219
|
+
* Each change worth mentioning into the changelog comes with a news fragment
|
|
220
|
+
into the `news` directory: `news/<issue number>.<type>`, or
|
|
221
|
+
`news/+<name>.<type>` for a change without issue, where `<type>` is one of
|
|
222
|
+
`feature`, `bugfix`, `doc`, `removal` or `misc`.
|
|
223
|
+
* To release a new version, run the `release` workflow from the GitHub "Actions"
|
|
224
|
+
tab and choose the part of the version to increase (`patch`, `minor` or
|
|
225
|
+
`major`). Once the run is approved (required reviewers of the
|
|
226
|
+
`release-approval` environment), the workflow updates `HISTORY.rst`, tags the
|
|
227
|
+
release, publishes the package to PyPI and creates the GitHub release.
|
|
205
228
|
|
|
206
229
|
## Contributing
|
|
207
230
|
|
|
208
231
|
All kind of contributions are welcome.
|
|
209
|
-
|
|
@@ -163,20 +163,27 @@ method into the metaclass.
|
|
|
163
163
|
To run tests, use `tox`. You will get a test coverage report in `htmlcov/index.html`.
|
|
164
164
|
An easy way to install tox is `pipx install tox`.
|
|
165
165
|
|
|
166
|
-
This project uses pre-commit to enforce linting
|
|
167
|
-
|
|
166
|
+
This project uses pre-commit to enforce linting and formatting (ruff). The type
|
|
167
|
+
checking (mypy) is run by `tox -e typing`.
|
|
168
168
|
|
|
169
169
|
To make sure linters run locally on each of your commits, install pre-commit
|
|
170
170
|
(`pipx install pre-commit` is recommended), and run `pre-commit install` in your
|
|
171
171
|
local clone of the extendable repository.
|
|
172
172
|
|
|
173
|
-
|
|
173
|
+
## Release
|
|
174
174
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
175
|
+
The version of the package is computed from the git tags (hatch-vcs) and the
|
|
176
|
+
changelog is built from the news fragments (towncrier).
|
|
177
|
+
|
|
178
|
+
* Each change worth mentioning into the changelog comes with a news fragment
|
|
179
|
+
into the `news` directory: `news/<issue number>.<type>`, or
|
|
180
|
+
`news/+<name>.<type>` for a change without issue, where `<type>` is one of
|
|
181
|
+
`feature`, `bugfix`, `doc`, `removal` or `misc`.
|
|
182
|
+
* To release a new version, run the `release` workflow from the GitHub "Actions"
|
|
183
|
+
tab and choose the part of the version to increase (`patch`, `minor` or
|
|
184
|
+
`major`). Once the run is approved (required reviewers of the
|
|
185
|
+
`release-approval` environment), the workflow updates `HISTORY.rst`, tags the
|
|
186
|
+
release, publishes the package to PyPI and creates the GitHub release.
|
|
180
187
|
|
|
181
188
|
## Contributing
|
|
182
189
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
!.gitignore
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
[build-system]
|
|
2
|
-
requires = ["
|
|
3
|
-
build-backend = "
|
|
2
|
+
requires = ["hatchling", "hatch-vcs"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "extendable"
|
|
7
|
+
description = "A lib to define class extendable at runtime."
|
|
7
8
|
authors = [{name = "Laurent Mignon", email = "laurent.mignon@acsone.eu"}]
|
|
8
9
|
readme = "README.md"
|
|
9
10
|
license = {file = "LICENSE"}
|
|
@@ -12,13 +13,9 @@ classifiers = [
|
|
|
12
13
|
"Intended Audience :: Developers",
|
|
13
14
|
"Operating System :: OS Independent",
|
|
14
15
|
]
|
|
15
|
-
dynamic = ["version"
|
|
16
|
-
dependencies = [
|
|
17
|
-
|
|
18
|
-
"types-contextvars;python_version<'3.7'",
|
|
19
|
-
"typing_extensions;python_version<'3.7'",
|
|
20
|
-
]
|
|
21
|
-
requires-python = ">=3.6"
|
|
16
|
+
dynamic = ["version"]
|
|
17
|
+
dependencies = []
|
|
18
|
+
requires-python = ">=3.10"
|
|
22
19
|
|
|
23
20
|
[project.optional-dependencies]
|
|
24
21
|
test = [
|
|
@@ -29,21 +26,41 @@ test = [
|
|
|
29
26
|
mypy = [
|
|
30
27
|
"mypy",
|
|
31
28
|
]
|
|
32
|
-
release = [
|
|
33
|
-
"towncrier",
|
|
34
|
-
"bumpversion",
|
|
35
|
-
]
|
|
36
29
|
|
|
37
30
|
[project.urls]
|
|
38
31
|
Source = "https://github.com/lmignon/extendable"
|
|
39
32
|
Changelog = "https://github.com/lmignon/extendable/blob/master/HISTORY.rst"
|
|
40
33
|
|
|
41
|
-
[tool.
|
|
34
|
+
[tool.hatch.version]
|
|
35
|
+
# the version is computed from the git tags
|
|
36
|
+
source = "vcs"
|
|
37
|
+
|
|
38
|
+
[tool.hatch.build.targets.sdist]
|
|
42
39
|
exclude = [".github"]
|
|
43
40
|
|
|
44
|
-
[tool.
|
|
45
|
-
|
|
46
|
-
|
|
41
|
+
[tool.hatch.build.targets.wheel]
|
|
42
|
+
packages = ["src/extendable"]
|
|
43
|
+
|
|
44
|
+
[tool.ruff]
|
|
45
|
+
# keep the examples of the documentation as written (e.g. the "#>" outputs)
|
|
46
|
+
extend-exclude = ["*.md"]
|
|
47
|
+
|
|
48
|
+
[tool.ruff.lint]
|
|
49
|
+
select = [
|
|
50
|
+
"E", # pycodestyle errors
|
|
51
|
+
"W", # pycodestyle warnings
|
|
52
|
+
"F", # pyflakes
|
|
53
|
+
"I", # isort
|
|
54
|
+
"B", # flake8-bugbear
|
|
55
|
+
"C4", # flake8-comprehensions
|
|
56
|
+
"UP", # pyupgrade
|
|
57
|
+
]
|
|
58
|
+
ignore = [
|
|
59
|
+
"E501", # line too long, handled by the formatter
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
[tool.ruff.lint.per-file-ignores]
|
|
63
|
+
"__init__.py" = ["F401"]
|
|
47
64
|
|
|
48
65
|
[tool.coverage.run]
|
|
49
66
|
branch = true
|
|
@@ -59,8 +76,7 @@ exclude_lines = [
|
|
|
59
76
|
]
|
|
60
77
|
|
|
61
78
|
[tool.towncrier]
|
|
62
|
-
|
|
63
|
-
package_dir = "src"
|
|
79
|
+
name = "Extendable"
|
|
64
80
|
filename = "HISTORY.rst"
|
|
65
81
|
directory = "news"
|
|
66
82
|
issue_format = "`#{issue} <https://github.com/lmignon/extendable/issues/{issue}>`_"
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
"""A lib to define class extendable at runtime."""
|
|
2
2
|
|
|
3
|
+
from importlib.metadata import version
|
|
4
|
+
|
|
3
5
|
# shortcut to main used class
|
|
4
6
|
from .main import ExtendableMeta
|
|
5
|
-
|
|
7
|
+
|
|
8
|
+
__version__ = version("extendable")
|
|
6
9
|
|
|
7
10
|
# declare "public" members
|
|
8
11
|
# __all__ doesn't restrict access to others members, but they are at least
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import collections
|
|
2
2
|
import functools
|
|
3
3
|
import inspect
|
|
4
|
-
import sys
|
|
5
|
-
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Type, no_type_check
|
|
6
|
-
|
|
7
|
-
if sys.version_info >= (3, 7):
|
|
8
|
-
from typing import OrderedDict
|
|
9
|
-
else:
|
|
10
|
-
from typing_extensions import OrderedDict
|
|
11
|
-
|
|
12
4
|
from abc import ABCMeta
|
|
5
|
+
from collections import OrderedDict
|
|
6
|
+
from typing import (
|
|
7
|
+
TYPE_CHECKING,
|
|
8
|
+
Any,
|
|
9
|
+
Optional,
|
|
10
|
+
no_type_check,
|
|
11
|
+
)
|
|
13
12
|
|
|
14
13
|
from .context import extendable_registry
|
|
15
14
|
from .exceptions import RegistryNotInitializedError
|
|
@@ -23,23 +22,23 @@ if TYPE_CHECKING:
|
|
|
23
22
|
|
|
24
23
|
class ExtendableClassDef:
|
|
25
24
|
name: str
|
|
26
|
-
base_names:
|
|
27
|
-
original_base_names:
|
|
28
|
-
namespace:
|
|
25
|
+
base_names: list[str]
|
|
26
|
+
original_base_names: list[str]
|
|
27
|
+
namespace: dict[str, Any]
|
|
29
28
|
original_name: str
|
|
30
|
-
others_bases:
|
|
31
|
-
hierarchy:
|
|
29
|
+
others_bases: list[Any]
|
|
30
|
+
hierarchy: list["ExtendableClassDef"]
|
|
32
31
|
metaclass: "ExtendableMeta"
|
|
33
|
-
original_cls:
|
|
34
|
-
kwargs:
|
|
32
|
+
original_cls: type["ExtendableMeta"]
|
|
33
|
+
kwargs: dict[str, Any]
|
|
35
34
|
|
|
36
35
|
def __init__(
|
|
37
36
|
self,
|
|
38
37
|
original_name: str,
|
|
39
|
-
bases:
|
|
40
|
-
namespace:
|
|
38
|
+
bases: list[Any],
|
|
39
|
+
namespace: dict[str, Any],
|
|
41
40
|
metaclass: "ExtendableMeta",
|
|
42
|
-
kwargs:
|
|
41
|
+
kwargs: dict[str, Any],
|
|
43
42
|
) -> None:
|
|
44
43
|
self.namespace = namespace
|
|
45
44
|
self.name = namespace["__xreg_name__"]
|
|
@@ -87,28 +86,27 @@ class ExtendableClassDef:
|
|
|
87
86
|
return clone
|
|
88
87
|
|
|
89
88
|
|
|
90
|
-
_extendable_class_defs_by_module: OrderedDict[
|
|
91
|
-
|
|
92
|
-
|
|
89
|
+
_extendable_class_defs_by_module: OrderedDict[str, list[ExtendableClassDef]] = (
|
|
90
|
+
collections.OrderedDict()
|
|
91
|
+
)
|
|
93
92
|
|
|
94
93
|
|
|
95
94
|
def __register_class_def__(module: str, cls_def: ExtendableClassDef) -> None:
|
|
96
|
-
global _extendable_class_defs_by_module
|
|
97
95
|
if module not in _extendable_class_defs_by_module:
|
|
98
96
|
_extendable_class_defs_by_module[module] = []
|
|
99
97
|
_extendable_class_defs_by_module[module].append(cls_def)
|
|
100
98
|
|
|
101
99
|
|
|
102
100
|
class ExtendableMeta(ABCMeta):
|
|
103
|
-
__xreg_base_names__:
|
|
101
|
+
__xreg_base_names__: list[str]
|
|
104
102
|
__xreg_name__: str
|
|
105
|
-
__xreg_all_base_names__:
|
|
103
|
+
__xreg_all_base_names__: set[str]
|
|
106
104
|
_is_aggregated_class: bool
|
|
107
105
|
_original_cls: "ExtendableMeta"
|
|
108
106
|
|
|
109
107
|
@no_type_check
|
|
110
108
|
def __new__(metacls, name, bases, namespace, extends=None, **kwargs):
|
|
111
|
-
"""
|
|
109
|
+
"""Create the expected class and collect the class definition that will be used
|
|
112
110
|
at the end of registry load process to build the final class."""
|
|
113
111
|
class_def = None
|
|
114
112
|
if isinstance(extends, bool) and extends:
|
|
@@ -191,10 +189,10 @@ class ExtendableMeta(ABCMeta):
|
|
|
191
189
|
return super().__new__(metacls, name, bases, namespace, **kwargs)
|
|
192
190
|
|
|
193
191
|
@classmethod
|
|
194
|
-
def _wrap_class_methods(metacls, namespace:
|
|
192
|
+
def _wrap_class_methods(metacls, namespace: dict[str, Any]) -> dict[str, Any]:
|
|
195
193
|
"""Wrap classmethods defined into the namespace to delegate the call to the
|
|
196
194
|
final class."""
|
|
197
|
-
new_namespace:
|
|
195
|
+
new_namespace: dict[str, Any] = {}
|
|
198
196
|
for key, value in namespace.items():
|
|
199
197
|
if isinstance(value, classmethod):
|
|
200
198
|
new_namespace[key] = metacls._wrap_class_method(value, key)
|
|
@@ -203,7 +201,7 @@ class ExtendableMeta(ABCMeta):
|
|
|
203
201
|
return new_namespace
|
|
204
202
|
|
|
205
203
|
@classmethod
|
|
206
|
-
def _is_extendable(metacls, cls:
|
|
204
|
+
def _is_extendable(metacls, cls: type[Any]) -> bool:
|
|
207
205
|
return issubclass(type(cls), ExtendableMeta)
|
|
208
206
|
|
|
209
207
|
@classmethod
|
|
@@ -248,9 +246,37 @@ class ExtendableMeta(ABCMeta):
|
|
|
248
246
|
return super().__call__(*args, **kwargs)
|
|
249
247
|
return cls._get_assembled_cls()(*args, **kwargs)
|
|
250
248
|
|
|
249
|
+
def __repr__(cls) -> str:
|
|
250
|
+
"""Add to the representation of the classes built by the registry their level
|
|
251
|
+
into the class hierarchy and the class definition they come from.
|
|
252
|
+
|
|
253
|
+
e.g. ``<class 'addon_b.Location' #2 (addon_b.LocationCode)>``
|
|
254
|
+
"""
|
|
255
|
+
level = cls.__dict__.get("__xreg_level__")
|
|
256
|
+
if level is None:
|
|
257
|
+
return super().__repr__()
|
|
258
|
+
name = f"{cls.__module__}.{cls.__qualname__}"
|
|
259
|
+
original_cls = cls.__dict__["__xreg_original_cls__"]
|
|
260
|
+
original_name = f"{original_cls.__module__}.{original_cls.__qualname__}"
|
|
261
|
+
if original_name == name:
|
|
262
|
+
return f"<class '{name}' #{level}>"
|
|
263
|
+
return f"<class '{name}' #{level} ({original_name})>"
|
|
264
|
+
|
|
251
265
|
###############################################################
|
|
252
266
|
# concrete methods provided to the final class by the metaclass
|
|
253
267
|
###############################################################
|
|
268
|
+
def __instancecheck__(self, instance: Any) -> bool: # noqa: B902
|
|
269
|
+
"""Implement issubclass(sub, cls)."""
|
|
270
|
+
if not hasattr(instance, "__xreg_name__"):
|
|
271
|
+
return False
|
|
272
|
+
|
|
273
|
+
if instance.__xreg_name__ == self.__xreg_name__:
|
|
274
|
+
# this is the same class
|
|
275
|
+
return True
|
|
276
|
+
# self is a class and instance is an instance of a class
|
|
277
|
+
if self.__xreg_name__ in instance.__xreg_all_base_names__:
|
|
278
|
+
return True
|
|
279
|
+
return super().__instancecheck__(instance)
|
|
254
280
|
|
|
255
281
|
def __subclasscheck__(cls, subclass: Any) -> bool: # noqa: B902
|
|
256
282
|
"""Implement issubclass(sub, cls)."""
|