systempy 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.
- systempy-0.1.0/LICENSE +21 -0
- systempy-0.1.0/PKG-INFO +83 -0
- systempy-0.1.0/README.md +66 -0
- systempy-0.1.0/pyproject.toml +56 -0
- systempy-0.1.0/setup.cfg +4 -0
- systempy-0.1.0/setup.py +96 -0
- systempy-0.1.0/systempy/__init__.py +35 -0
- systempy-0.1.0/systempy/libsystempy/__init__.py +20 -0
- systempy-0.1.0/systempy/libsystempy/callback_plan.py +60 -0
- systempy-0.1.0/systempy/libsystempy/check.py +52 -0
- systempy-0.1.0/systempy/libsystempy/class_role.py +32 -0
- systempy-0.1.0/systempy/libsystempy/configuration.py +56 -0
- systempy-0.1.0/systempy/libsystempy/constants.py +16 -0
- systempy-0.1.0/systempy/libsystempy/creation.py +32 -0
- systempy-0.1.0/systempy/libsystempy/enums.py +30 -0
- systempy-0.1.0/systempy/libsystempy/extraction.py +88 -0
- systempy-0.1.0/systempy/libsystempy/handler_type.py +170 -0
- systempy-0.1.0/systempy/libsystempy/hook_registry.py +50 -0
- systempy-0.1.0/systempy/libsystempy/local_dataclasses.py +110 -0
- systempy-0.1.0/systempy/libsystempy/local_typing.py +47 -0
- systempy-0.1.0/systempy/libsystempy/misc.py +18 -0
- systempy-0.1.0/systempy/libsystempy/register.py +142 -0
- systempy-0.1.0/systempy/libsystempy/thread_exception.py +27 -0
- systempy-0.1.0/systempy/libsystempy/weak_queue.py +59 -0
- systempy-0.1.0/systempy/py.typed +1 -0
- systempy-0.1.0/systempy/target.py +108 -0
- systempy-0.1.0/systempy/target_meta.py +86 -0
- systempy-0.1.0/systempy/unit/__init__.py +16 -0
- systempy-0.1.0/systempy/unit/daemon.py +90 -0
- systempy-0.1.0/systempy/unit/event_wait.py +23 -0
- systempy-0.1.0/systempy/unit/ext/__init__.py +0 -0
- systempy-0.1.0/systempy/unit/ext/celery.py +18 -0
- systempy-0.1.0/systempy/unit/ext/pretty_repl.py +37 -0
- systempy-0.1.0/systempy/unit/ext/ptrepl.py +60 -0
- systempy-0.1.0/systempy/unit/ext/starlette.py +15 -0
- systempy-0.1.0/systempy/unit/ext/target_ext.py +14 -0
- systempy-0.1.0/systempy/unit/loop.py +47 -0
- systempy-0.1.0/systempy/unit/repl/__init__.py +0 -0
- systempy-0.1.0/systempy/unit/repl/handle_interrupt.py +59 -0
- systempy-0.1.0/systempy/unit/repl/mixins.py +40 -0
- systempy-0.1.0/systempy/unit/repl/repl.py +127 -0
- systempy-0.1.0/systempy/unit/scripting.py +22 -0
- systempy-0.1.0/systempy/unit/unit.py +8 -0
- systempy-0.1.0/systempy.egg-info/PKG-INFO +83 -0
- systempy-0.1.0/systempy.egg-info/SOURCES.txt +49 -0
- systempy-0.1.0/systempy.egg-info/dependency_links.txt +1 -0
- systempy-0.1.0/systempy.egg-info/requires.txt +6 -0
- systempy-0.1.0/systempy.egg-info/top_level.txt +2 -0
- systempy-0.1.0/systempy.egg-info/zip-safe +1 -0
- systempy-0.1.0/tests/test_async.py +252 -0
- systempy-0.1.0/tests/test_basic.py +1049 -0
systempy-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022 Anton Kovalevich
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
systempy-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: systempy
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python application component initialization system
|
|
5
|
+
Home-page: https://github.com/kai3341/systemPY
|
|
6
|
+
Author: kai3341
|
|
7
|
+
Requires-Python: >=3.11
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Dist: typing-extensions
|
|
11
|
+
Provides-Extra: extra
|
|
12
|
+
Requires-Dist: celery>=5.5.1; extra == "extra"
|
|
13
|
+
Requires-Dist: ptpython>=3.0.30; extra == "extra"
|
|
14
|
+
Requires-Dist: starlette>=0.46.2; extra == "extra"
|
|
15
|
+
Dynamic: home-page
|
|
16
|
+
Dynamic: license-file
|
|
17
|
+
|
|
18
|
+
# systemPY
|
|
19
|
+
|
|
20
|
+

|
|
21
|
+
|
|
22
|
+
Python application component initialization system
|
|
23
|
+
|
|
24
|
+

|
|
25
|
+

|
|
26
|
+

|
|
27
|
+

|
|
28
|
+
[](https://systempy.readthedocs.io/en/latest/?badge=latest)
|
|
29
|
+

|
|
30
|
+
[](https://github.com/astral-sh/ruff)
|
|
31
|
+
[](https://mypy-lang.org/)
|
|
32
|
+
|
|
33
|
+
Full documantation is available at
|
|
34
|
+
[Read the Docs](https://systempy.readthedocs.io/en/latest/)
|
|
35
|
+
|
|
36
|
+
## The problem
|
|
37
|
+
|
|
38
|
+
The regular application contain many atomic components. Asyncio makes theirs
|
|
39
|
+
initializing a little bit complicated. It's OK, when you have single entrypoint
|
|
40
|
+
and initialize your application components via your framework. While you add
|
|
41
|
+
new components to your application iteratively, you don't see any problem
|
|
42
|
+
|
|
43
|
+
When you create any new entrypoint, you have to think a lot, how to initialize
|
|
44
|
+
application components again, which callbacks should be called and in which
|
|
45
|
+
order. But it's a half of the problem! You have to implement also graceful
|
|
46
|
+
shutdown
|
|
47
|
+
|
|
48
|
+
The most painful part is one-time scripts. It's kind of The Banana Gorilla
|
|
49
|
+
Problem: you wanted a banana but you have to initialize a gorilla holding the
|
|
50
|
+
banana and the entire jungle, and then graceful shutdown it
|
|
51
|
+
|
|
52
|
+
## Solution
|
|
53
|
+
|
|
54
|
+
This library allows you to implement application startup and shutdown in
|
|
55
|
+
declarative way. You have to implement a class for each your component,
|
|
56
|
+
write the startup and shutdown code. Then combine required components as
|
|
57
|
+
mixins into the current application `Unit` class. Then create an instance
|
|
58
|
+
and pass dependencies as keyword arguments. In case it's daemon run
|
|
59
|
+
`instance.run_sync()` methed
|
|
60
|
+
|
|
61
|
+
## Basic principles
|
|
62
|
+
|
|
63
|
+
There are 6 most significant stages of the application lifecycle:
|
|
64
|
+
|
|
65
|
+
- `on_init` executes exactly once on application startup
|
|
66
|
+
|
|
67
|
+
- `pre_startup` is called before event loop startup
|
|
68
|
+
|
|
69
|
+
- `on_startup` is called exactly when event loop started
|
|
70
|
+
|
|
71
|
+
- `on_shutdown` is called when application is going shutdown or reload but
|
|
72
|
+
event loop still working
|
|
73
|
+
|
|
74
|
+
- `post_shutdown` is called after event loop stopped or drained. When
|
|
75
|
+
application is going to reload, then it should be called `pre_startup`
|
|
76
|
+
|
|
77
|
+
- `on_exit` executes exactly once when application is stopping
|
|
78
|
+
|
|
79
|
+
You may to create `Unit` classes for each your application component where you
|
|
80
|
+
may put your code. Then you may combine these `Unit` class mixins into the
|
|
81
|
+
current worker class, which aggregate your defined callbacks and run in the
|
|
82
|
+
right order. Depending on application type, these callbacks may be called by
|
|
83
|
+
primary application or by you are
|
systempy-0.1.0/README.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# systemPY
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
Python application component initialization system
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+

|
|
9
|
+

|
|
10
|
+

|
|
11
|
+
[](https://systempy.readthedocs.io/en/latest/?badge=latest)
|
|
12
|
+

|
|
13
|
+
[](https://github.com/astral-sh/ruff)
|
|
14
|
+
[](https://mypy-lang.org/)
|
|
15
|
+
|
|
16
|
+
Full documantation is available at
|
|
17
|
+
[Read the Docs](https://systempy.readthedocs.io/en/latest/)
|
|
18
|
+
|
|
19
|
+
## The problem
|
|
20
|
+
|
|
21
|
+
The regular application contain many atomic components. Asyncio makes theirs
|
|
22
|
+
initializing a little bit complicated. It's OK, when you have single entrypoint
|
|
23
|
+
and initialize your application components via your framework. While you add
|
|
24
|
+
new components to your application iteratively, you don't see any problem
|
|
25
|
+
|
|
26
|
+
When you create any new entrypoint, you have to think a lot, how to initialize
|
|
27
|
+
application components again, which callbacks should be called and in which
|
|
28
|
+
order. But it's a half of the problem! You have to implement also graceful
|
|
29
|
+
shutdown
|
|
30
|
+
|
|
31
|
+
The most painful part is one-time scripts. It's kind of The Banana Gorilla
|
|
32
|
+
Problem: you wanted a banana but you have to initialize a gorilla holding the
|
|
33
|
+
banana and the entire jungle, and then graceful shutdown it
|
|
34
|
+
|
|
35
|
+
## Solution
|
|
36
|
+
|
|
37
|
+
This library allows you to implement application startup and shutdown in
|
|
38
|
+
declarative way. You have to implement a class for each your component,
|
|
39
|
+
write the startup and shutdown code. Then combine required components as
|
|
40
|
+
mixins into the current application `Unit` class. Then create an instance
|
|
41
|
+
and pass dependencies as keyword arguments. In case it's daemon run
|
|
42
|
+
`instance.run_sync()` methed
|
|
43
|
+
|
|
44
|
+
## Basic principles
|
|
45
|
+
|
|
46
|
+
There are 6 most significant stages of the application lifecycle:
|
|
47
|
+
|
|
48
|
+
- `on_init` executes exactly once on application startup
|
|
49
|
+
|
|
50
|
+
- `pre_startup` is called before event loop startup
|
|
51
|
+
|
|
52
|
+
- `on_startup` is called exactly when event loop started
|
|
53
|
+
|
|
54
|
+
- `on_shutdown` is called when application is going shutdown or reload but
|
|
55
|
+
event loop still working
|
|
56
|
+
|
|
57
|
+
- `post_shutdown` is called after event loop stopped or drained. When
|
|
58
|
+
application is going to reload, then it should be called `pre_startup`
|
|
59
|
+
|
|
60
|
+
- `on_exit` executes exactly once when application is stopping
|
|
61
|
+
|
|
62
|
+
You may to create `Unit` classes for each your application component where you
|
|
63
|
+
may put your code. Then you may combine these `Unit` class mixins into the
|
|
64
|
+
current worker class, which aggregate your defined callbacks and run in the
|
|
65
|
+
right order. Depending on application type, these callbacks may be called by
|
|
66
|
+
primary application or by you are
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "systempy"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Python application component initialization system"
|
|
5
|
+
authors = [{name = "kai3341"}]
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
requires-python = ">=3.11"
|
|
8
|
+
dependencies = [
|
|
9
|
+
"typing-extensions",
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
[project.optional-dependencies]
|
|
13
|
+
extra = [
|
|
14
|
+
"celery>=5.5.1",
|
|
15
|
+
"ptpython>=3.0.30",
|
|
16
|
+
"starlette>=0.46.2",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
[tool.pyright]
|
|
20
|
+
include = ["systempy/", "tests/", "_util/"]
|
|
21
|
+
reportMissingModuleSource = false
|
|
22
|
+
reportArgumentType = false
|
|
23
|
+
reportCallIssue = false
|
|
24
|
+
reportAssignmentType = false
|
|
25
|
+
reportGeneralTypeIssues = false
|
|
26
|
+
reportIndexIssue = false
|
|
27
|
+
reportInvalidTypeArguments = false
|
|
28
|
+
reportIncompatibleMethodOverride = false
|
|
29
|
+
|
|
30
|
+
[tool.ruff.lint]
|
|
31
|
+
select=["ALL"]
|
|
32
|
+
ignore = ["D", "S101", "TID252", "ANN401", "PTH119", "RET503", "PT009"]
|
|
33
|
+
|
|
34
|
+
[tool.ruff.format]
|
|
35
|
+
quote-style = "double"
|
|
36
|
+
indent-style = "space"
|
|
37
|
+
skip-magic-trailing-comma = false
|
|
38
|
+
line-ending = "auto"
|
|
39
|
+
|
|
40
|
+
[tool.ruff.lint.per-file-ignores]
|
|
41
|
+
"tests/*" = ["INP001", "E402", "C901"]
|
|
42
|
+
"examples/*" = ["INP001", "E402"]
|
|
43
|
+
"_util/*" = ["E402"]
|
|
44
|
+
|
|
45
|
+
[tool.mypy]
|
|
46
|
+
python_version = "3.11"
|
|
47
|
+
mypy_path = "$MYPY_CONFIG_FILE_DIR/systempy"
|
|
48
|
+
disable_error_code = ["import-untyped", "return", "empty-body"]
|
|
49
|
+
exclude = ["^build/", "^dist/"]
|
|
50
|
+
|
|
51
|
+
[dependency-groups]
|
|
52
|
+
dev = [
|
|
53
|
+
"mkdocs-material>=9.6.11",
|
|
54
|
+
"mypy[mypyc]>=1.15.0",
|
|
55
|
+
"ruff>=0.11.5",
|
|
56
|
+
]
|
systempy-0.1.0/setup.cfg
ADDED
systempy-0.1.0/setup.py
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
from os import getenv
|
|
2
|
+
|
|
3
|
+
from setuptools import setup
|
|
4
|
+
|
|
5
|
+
from setup_constants import DESCRIPTION, NAME, NAME_CANONICAL, VERSION
|
|
6
|
+
|
|
7
|
+
if getenv("USE_MYPYC") == "1":
|
|
8
|
+
from setup_mypycify import ext_modules
|
|
9
|
+
else:
|
|
10
|
+
ext_modules = []
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
requirements: list[str] = []
|
|
14
|
+
|
|
15
|
+
requirements_build: list[str] = []
|
|
16
|
+
|
|
17
|
+
packages = [
|
|
18
|
+
NAME,
|
|
19
|
+
f"{NAME}.libsystempy",
|
|
20
|
+
f"{NAME}.unit",
|
|
21
|
+
f"{NAME}.unit.ext",
|
|
22
|
+
f"{NAME}.unit.repl",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
package_data = {
|
|
26
|
+
NAME: ["py.typed"],
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
keywords = [
|
|
30
|
+
"asyncio",
|
|
31
|
+
"graceful",
|
|
32
|
+
"init",
|
|
33
|
+
"initialization",
|
|
34
|
+
"shutdown",
|
|
35
|
+
"manager",
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
classifiers__python_versions = (
|
|
39
|
+
"3 :: Only",
|
|
40
|
+
"3.9",
|
|
41
|
+
"3.10",
|
|
42
|
+
"3.11",
|
|
43
|
+
"3.12",
|
|
44
|
+
"3.13",
|
|
45
|
+
"3.14",
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
classifiers__programming_language = tuple(
|
|
49
|
+
f"Programming Language :: Python :: {i}"
|
|
50
|
+
# ===
|
|
51
|
+
for i in classifiers__python_versions
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
classifiers = [
|
|
55
|
+
"Development Status :: 3 - Alpha",
|
|
56
|
+
"Intended Audience :: Developers",
|
|
57
|
+
"License :: OSI Approved :: MIT License",
|
|
58
|
+
*classifiers__programming_language,
|
|
59
|
+
# Topic
|
|
60
|
+
"Topic :: Software Development :: Libraries",
|
|
61
|
+
"Topic :: Software Development :: Libraries :: Application Frameworks",
|
|
62
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
63
|
+
# Typing
|
|
64
|
+
"Typing :: Typed",
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
py_modules = [
|
|
68
|
+
NAME,
|
|
69
|
+
# "setup_constants",
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
with open("README.md", encoding="utf-8") as readme_file: # noqa: PTH123
|
|
73
|
+
long_description = readme_file.read()
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
setup(
|
|
77
|
+
classifiers=classifiers,
|
|
78
|
+
description=DESCRIPTION,
|
|
79
|
+
install_requires=requirements,
|
|
80
|
+
extras_require={
|
|
81
|
+
"dev": requirements_build,
|
|
82
|
+
},
|
|
83
|
+
license="MIT",
|
|
84
|
+
packages=packages,
|
|
85
|
+
package_data=package_data,
|
|
86
|
+
long_description=long_description,
|
|
87
|
+
long_description_content_type="text/markdown",
|
|
88
|
+
include_package_data=True,
|
|
89
|
+
keywords=keywords,
|
|
90
|
+
name=NAME_CANONICAL,
|
|
91
|
+
py_modules=py_modules,
|
|
92
|
+
ext_modules=ext_modules,
|
|
93
|
+
url="https://github.com/kai3341/systemPY",
|
|
94
|
+
version=VERSION,
|
|
95
|
+
zip_safe=True,
|
|
96
|
+
)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from .libsystempy import (
|
|
2
|
+
DIRECTION,
|
|
3
|
+
ROLE,
|
|
4
|
+
register_hook_after,
|
|
5
|
+
register_hook_before,
|
|
6
|
+
register_target_method,
|
|
7
|
+
)
|
|
8
|
+
from .target import AsyncMixinABC, SyncMixinABC, Target
|
|
9
|
+
from .unit import (
|
|
10
|
+
AsyncScriptUnit,
|
|
11
|
+
DaemonUnit,
|
|
12
|
+
EventWaitUnit,
|
|
13
|
+
LoopUnit,
|
|
14
|
+
ReplUnit,
|
|
15
|
+
ScriptUnit,
|
|
16
|
+
Unit,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
__all__ = (
|
|
20
|
+
"DIRECTION",
|
|
21
|
+
"ROLE",
|
|
22
|
+
"AsyncMixinABC",
|
|
23
|
+
"AsyncScriptUnit",
|
|
24
|
+
"DaemonUnit",
|
|
25
|
+
"EventWaitUnit",
|
|
26
|
+
"LoopUnit",
|
|
27
|
+
"ReplUnit",
|
|
28
|
+
"ScriptUnit",
|
|
29
|
+
"SyncMixinABC",
|
|
30
|
+
"Target",
|
|
31
|
+
"Unit",
|
|
32
|
+
"register_hook_after",
|
|
33
|
+
"register_hook_before",
|
|
34
|
+
"register_target_method",
|
|
35
|
+
)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from .constants import handler_metadata
|
|
2
|
+
from .enums import DIRECTION, ROLE
|
|
3
|
+
from .register import (
|
|
4
|
+
mark_as_target,
|
|
5
|
+
register_hook_after,
|
|
6
|
+
register_hook_before,
|
|
7
|
+
register_target,
|
|
8
|
+
register_target_method,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
__all__ = (
|
|
12
|
+
"DIRECTION",
|
|
13
|
+
"ROLE",
|
|
14
|
+
"handler_metadata",
|
|
15
|
+
"mark_as_target",
|
|
16
|
+
"register_hook_after",
|
|
17
|
+
"register_hook_before",
|
|
18
|
+
"register_target",
|
|
19
|
+
"register_target_method",
|
|
20
|
+
)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from collections.abc import Callable, Generator, Iterable
|
|
2
|
+
|
|
3
|
+
from .check import check_callback_signature
|
|
4
|
+
from .constants import lifecycle_registered_methods
|
|
5
|
+
from .hook_registry import HookRegistry
|
|
6
|
+
from .local_typing import P, R, WeakTypeIterable
|
|
7
|
+
from .register import register_hook_after, register_hook_before
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def build_callback_plan_hook_iter(
|
|
11
|
+
cls: type,
|
|
12
|
+
bases: WeakTypeIterable,
|
|
13
|
+
reason: Callable[P, R],
|
|
14
|
+
hook_registry: HookRegistry[P, R],
|
|
15
|
+
) -> Generator[Callable[P, R], None, None]:
|
|
16
|
+
if reason in hook_registry:
|
|
17
|
+
next_reasons = hook_registry[reason]
|
|
18
|
+
for next_reason in next_reasons:
|
|
19
|
+
assert callable(next_reason)
|
|
20
|
+
next_registered_methods = lifecycle_registered_methods[next_reason]
|
|
21
|
+
next_reason_interface = next_registered_methods.interface()
|
|
22
|
+
direction_handler = next_registered_methods.direction
|
|
23
|
+
assert next_reason_interface
|
|
24
|
+
|
|
25
|
+
if issubclass(cls, next_reason_interface):
|
|
26
|
+
next_callbacks = direction_handler(bases, next_reason)
|
|
27
|
+
|
|
28
|
+
for next_callback in next_callbacks:
|
|
29
|
+
check_callback_signature(next_reason, next_callback)
|
|
30
|
+
|
|
31
|
+
yield from build_callback_plan_iter(
|
|
32
|
+
cls,
|
|
33
|
+
bases,
|
|
34
|
+
next_reason,
|
|
35
|
+
next_callbacks,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def build_callback_plan_iter(
|
|
40
|
+
cls: type,
|
|
41
|
+
bases: WeakTypeIterable,
|
|
42
|
+
reason: Callable[P, R],
|
|
43
|
+
callbacks: Iterable[Callable[P, R]] = (),
|
|
44
|
+
) -> Generator[Callable[P, R], None, None]:
|
|
45
|
+
yield from build_callback_plan_hook_iter(cls, bases, reason, register_hook_before)
|
|
46
|
+
yield from callbacks
|
|
47
|
+
yield from build_callback_plan_hook_iter(cls, bases, reason, register_hook_after)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def build_callback_plan(
|
|
51
|
+
cls: type,
|
|
52
|
+
bases: WeakTypeIterable,
|
|
53
|
+
reason: Callable[P, R],
|
|
54
|
+
callbacks: Iterable[Callable[P, R]],
|
|
55
|
+
) -> tuple[Callable[P, R], ...]:
|
|
56
|
+
for func in callbacks:
|
|
57
|
+
check_callback_signature(reason, func)
|
|
58
|
+
|
|
59
|
+
callbacks_total = build_callback_plan_iter(cls, bases, reason, callbacks)
|
|
60
|
+
return tuple(callbacks_total)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
from inspect import iscoroutinefunction
|
|
3
|
+
|
|
4
|
+
from .constants import sync_or_async
|
|
5
|
+
from .enums import DIRECTION
|
|
6
|
+
from .local_typing import function_types
|
|
7
|
+
from .register import register_check_method_type
|
|
8
|
+
|
|
9
|
+
sync_or_async_names = tuple(i.value for i in sync_or_async)
|
|
10
|
+
sync_or_async_names_rev = tuple(reversed(sync_or_async_names))
|
|
11
|
+
|
|
12
|
+
CHECK_CALLBACK_ERROR_MESSAGE__TEMPLATE = (
|
|
13
|
+
"{name} must be %sronous function, but {func} is %sronous"
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
check_callback_signature__error_message: tuple[str, ...] = (
|
|
17
|
+
CHECK_CALLBACK_ERROR_MESSAGE__TEMPLATE % sync_or_async_names,
|
|
18
|
+
CHECK_CALLBACK_ERROR_MESSAGE__TEMPLATE % sync_or_async_names_rev,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def check_callback_signature(reason: Callable, func: Callable) -> None:
|
|
23
|
+
"""
|
|
24
|
+
When `reason` is syncronous, `func` have to be syncronous too
|
|
25
|
+
Asyncronous `reason` executors may to execute both `func` types
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
assert isinstance(reason, function_types)
|
|
29
|
+
assert isinstance(func, function_types)
|
|
30
|
+
|
|
31
|
+
reason_async = iscoroutinefunction(reason)
|
|
32
|
+
if reason_async:
|
|
33
|
+
return
|
|
34
|
+
|
|
35
|
+
if not iscoroutinefunction(func):
|
|
36
|
+
return
|
|
37
|
+
|
|
38
|
+
template = check_callback_signature__error_message[reason_async]
|
|
39
|
+
|
|
40
|
+
error_message = template.format(
|
|
41
|
+
name=reason.__name__,
|
|
42
|
+
func=func,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
raise ValueError(error_message)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@register_check_method_type(DIRECTION.GATHER)
|
|
49
|
+
def gather(target: Callable) -> None:
|
|
50
|
+
if not iscoroutinefunction(target):
|
|
51
|
+
error_message = "Can not `asyncio.gather` syncronous method"
|
|
52
|
+
raise ValueError(error_message, target)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from typing import Generic, NamedTuple, TypeVar
|
|
4
|
+
from typing import final as typing_final
|
|
5
|
+
|
|
6
|
+
from .configuration import apply_additional_configuration
|
|
7
|
+
from .register import mark_as_final, mark_as_target, register_target
|
|
8
|
+
|
|
9
|
+
T = TypeVar("T")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ClassRole(NamedTuple, Generic[T]):
|
|
13
|
+
app: Callable[[T], T]
|
|
14
|
+
unit: Callable[[T], T]
|
|
15
|
+
mixin: Callable[[T], T]
|
|
16
|
+
target: Callable[[T], T]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
default_dataclass_kwargs: dict[str, bool] = {
|
|
20
|
+
"kw_only": True,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
nonfinal = dataclass(**default_dataclass_kwargs, init=False, repr=False, eq=False)
|
|
24
|
+
final = dataclass(**default_dataclass_kwargs)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class_role = ClassRole[type](
|
|
28
|
+
lambda cls: final(apply_additional_configuration(mark_as_final(typing_final(cls)))),
|
|
29
|
+
nonfinal,
|
|
30
|
+
lambda cls: nonfinal(mark_as_target(cls)),
|
|
31
|
+
lambda cls: nonfinal(register_target(cls)),
|
|
32
|
+
)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from dataclasses import fields
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from .constants import lifecycle_additional_configuration
|
|
5
|
+
from .creation import create_partial_handler_generic
|
|
6
|
+
from .local_dataclasses import ClsCFG
|
|
7
|
+
from .local_typing import WeakTypeIterable
|
|
8
|
+
from .misc import get_key_or_create
|
|
9
|
+
from .register import register_addition_cfg_applier
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@register_addition_cfg_applier
|
|
13
|
+
def stack_method(cls: type, config: ClsCFG) -> None:
|
|
14
|
+
sm_cfg = config.stack_method
|
|
15
|
+
create = create_partial_handler_generic(cls)
|
|
16
|
+
|
|
17
|
+
for stage_config in sm_cfg.values():
|
|
18
|
+
create(stage_config)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def apply_additional_config(cls: type, config: ClsCFG) -> None:
|
|
22
|
+
for clscfg_field in fields(config):
|
|
23
|
+
key = clscfg_field.name
|
|
24
|
+
apply_cfg_handler = register_addition_cfg_applier[key]
|
|
25
|
+
apply_cfg_handler(cls, config)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def apply_additional_configuration(this_cls: type) -> type:
|
|
29
|
+
for cls, config in lifecycle_additional_configuration.items():
|
|
30
|
+
if issubclass(this_cls, cls):
|
|
31
|
+
apply_additional_config(this_cls, config)
|
|
32
|
+
return this_cls
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def update_annotation(
|
|
36
|
+
clsdict: dict[str, Any],
|
|
37
|
+
bases: WeakTypeIterable,
|
|
38
|
+
) -> None:
|
|
39
|
+
annotations: dict[str, type] = get_key_or_create(
|
|
40
|
+
clsdict,
|
|
41
|
+
"__annotations__",
|
|
42
|
+
dict,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
for base in bases:
|
|
46
|
+
basedict = vars(base)
|
|
47
|
+
|
|
48
|
+
if "__annotations__" not in basedict:
|
|
49
|
+
continue
|
|
50
|
+
|
|
51
|
+
target_annotations: dict[str, type] = basedict["__annotations__"]
|
|
52
|
+
annotations.update(target_annotations)
|
|
53
|
+
|
|
54
|
+
for key in target_annotations:
|
|
55
|
+
if key in basedict:
|
|
56
|
+
clsdict[key] = basedict[key]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from weakref import WeakKeyDictionary
|
|
2
|
+
|
|
3
|
+
from .enums import TYPE as _TYPE
|
|
4
|
+
from .local_typing import DisallowedAttrInfo, LFMetadata, LFRegistered, LFTypeConfig
|
|
5
|
+
|
|
6
|
+
lifecycle_additional_configuration: LFTypeConfig[type] = WeakKeyDictionary()
|
|
7
|
+
lifecycle_registered_methods: LFRegistered = WeakKeyDictionary()
|
|
8
|
+
|
|
9
|
+
sync_or_async = (_TYPE.SYNC, _TYPE.ASYNC)
|
|
10
|
+
handler_metadata: LFMetadata = WeakKeyDictionary()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
lifecycle_disallowed_attrs: list[DisallowedAttrInfo] = [
|
|
14
|
+
("__init__", "Use `on_init` instead"),
|
|
15
|
+
("__post_init__", "Use `on_init` instead"),
|
|
16
|
+
]
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
from functools import partial
|
|
3
|
+
from weakref import ref
|
|
4
|
+
|
|
5
|
+
from . import extraction
|
|
6
|
+
from .local_dataclasses import GenericHandlerSettings
|
|
7
|
+
from .local_typing import WeakTypeIterable
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def create_handler_generic(
|
|
11
|
+
cls_ref: ref[type],
|
|
12
|
+
bases: WeakTypeIterable,
|
|
13
|
+
settings: GenericHandlerSettings,
|
|
14
|
+
) -> None:
|
|
15
|
+
collect = settings.collect()
|
|
16
|
+
reason = settings.reason()
|
|
17
|
+
compose = settings.compose()
|
|
18
|
+
cls = cls_ref()
|
|
19
|
+
assert collect
|
|
20
|
+
assert reason
|
|
21
|
+
assert compose
|
|
22
|
+
assert cls
|
|
23
|
+
callbacks = collect(bases, reason)
|
|
24
|
+
handler = compose(cls, bases, reason, callbacks)
|
|
25
|
+
setattr(cls, reason.__name__, handler)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def create_partial_handler_generic(
|
|
29
|
+
cls: type,
|
|
30
|
+
) -> Callable[[GenericHandlerSettings], None]:
|
|
31
|
+
bases = extraction.extract_bases(cls)
|
|
32
|
+
return partial(create_handler_generic, ref(cls), bases)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from enum import Enum, unique
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
@unique
|
|
5
|
+
class DIRECTION(str, Enum):
|
|
6
|
+
"""
|
|
7
|
+
Public enum containing supported directions. Rely on your IDE's help
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
FORWARD = "forward"
|
|
11
|
+
BACKWARD = "backward"
|
|
12
|
+
GATHER = "gather"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@unique
|
|
16
|
+
class TYPE(str, Enum):
|
|
17
|
+
"""
|
|
18
|
+
Private internal enum
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
SYNC = "sync"
|
|
22
|
+
ASYNC = "async"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@unique
|
|
26
|
+
class ROLE(str, Enum):
|
|
27
|
+
APP = "app"
|
|
28
|
+
UNIT = "unit"
|
|
29
|
+
MIXIN = "mixin"
|
|
30
|
+
TARGET = "target"
|