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.
Files changed (51) hide show
  1. systempy-0.1.0/LICENSE +21 -0
  2. systempy-0.1.0/PKG-INFO +83 -0
  3. systempy-0.1.0/README.md +66 -0
  4. systempy-0.1.0/pyproject.toml +56 -0
  5. systempy-0.1.0/setup.cfg +4 -0
  6. systempy-0.1.0/setup.py +96 -0
  7. systempy-0.1.0/systempy/__init__.py +35 -0
  8. systempy-0.1.0/systempy/libsystempy/__init__.py +20 -0
  9. systempy-0.1.0/systempy/libsystempy/callback_plan.py +60 -0
  10. systempy-0.1.0/systempy/libsystempy/check.py +52 -0
  11. systempy-0.1.0/systempy/libsystempy/class_role.py +32 -0
  12. systempy-0.1.0/systempy/libsystempy/configuration.py +56 -0
  13. systempy-0.1.0/systempy/libsystempy/constants.py +16 -0
  14. systempy-0.1.0/systempy/libsystempy/creation.py +32 -0
  15. systempy-0.1.0/systempy/libsystempy/enums.py +30 -0
  16. systempy-0.1.0/systempy/libsystempy/extraction.py +88 -0
  17. systempy-0.1.0/systempy/libsystempy/handler_type.py +170 -0
  18. systempy-0.1.0/systempy/libsystempy/hook_registry.py +50 -0
  19. systempy-0.1.0/systempy/libsystempy/local_dataclasses.py +110 -0
  20. systempy-0.1.0/systempy/libsystempy/local_typing.py +47 -0
  21. systempy-0.1.0/systempy/libsystempy/misc.py +18 -0
  22. systempy-0.1.0/systempy/libsystempy/register.py +142 -0
  23. systempy-0.1.0/systempy/libsystempy/thread_exception.py +27 -0
  24. systempy-0.1.0/systempy/libsystempy/weak_queue.py +59 -0
  25. systempy-0.1.0/systempy/py.typed +1 -0
  26. systempy-0.1.0/systempy/target.py +108 -0
  27. systempy-0.1.0/systempy/target_meta.py +86 -0
  28. systempy-0.1.0/systempy/unit/__init__.py +16 -0
  29. systempy-0.1.0/systempy/unit/daemon.py +90 -0
  30. systempy-0.1.0/systempy/unit/event_wait.py +23 -0
  31. systempy-0.1.0/systempy/unit/ext/__init__.py +0 -0
  32. systempy-0.1.0/systempy/unit/ext/celery.py +18 -0
  33. systempy-0.1.0/systempy/unit/ext/pretty_repl.py +37 -0
  34. systempy-0.1.0/systempy/unit/ext/ptrepl.py +60 -0
  35. systempy-0.1.0/systempy/unit/ext/starlette.py +15 -0
  36. systempy-0.1.0/systempy/unit/ext/target_ext.py +14 -0
  37. systempy-0.1.0/systempy/unit/loop.py +47 -0
  38. systempy-0.1.0/systempy/unit/repl/__init__.py +0 -0
  39. systempy-0.1.0/systempy/unit/repl/handle_interrupt.py +59 -0
  40. systempy-0.1.0/systempy/unit/repl/mixins.py +40 -0
  41. systempy-0.1.0/systempy/unit/repl/repl.py +127 -0
  42. systempy-0.1.0/systempy/unit/scripting.py +22 -0
  43. systempy-0.1.0/systempy/unit/unit.py +8 -0
  44. systempy-0.1.0/systempy.egg-info/PKG-INFO +83 -0
  45. systempy-0.1.0/systempy.egg-info/SOURCES.txt +49 -0
  46. systempy-0.1.0/systempy.egg-info/dependency_links.txt +1 -0
  47. systempy-0.1.0/systempy.egg-info/requires.txt +6 -0
  48. systempy-0.1.0/systempy.egg-info/top_level.txt +2 -0
  49. systempy-0.1.0/systempy.egg-info/zip-safe +1 -0
  50. systempy-0.1.0/tests/test_async.py +252 -0
  51. 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.
@@ -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
+ ![Logo](https://raw.githubusercontent.com/kai3341/systemPY/main/docs/images/systempy-logo.png)
21
+
22
+ Python application component initialization system
23
+
24
+ ![python](https://img.shields.io/pypi/pyversions/systemPY)
25
+ ![version](https://img.shields.io/pypi/v/systemPY)
26
+ ![downloads](https://img.shields.io/pypi/dm/systemPY)
27
+ ![format](https://img.shields.io/pypi/format/systemPY)
28
+ [![Documentation Status](https://readthedocs.org/projects/systempy/badge/?version=latest)](https://systempy.readthedocs.io/en/latest/?badge=latest)
29
+ ![GitHub issues](https://img.shields.io/github/issues/kai3341/systemPY)
30
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
31
+ [![Checked with mypy](https://www.mypy-lang.org/static/mypy_badge.svg)](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
@@ -0,0 +1,66 @@
1
+ # systemPY
2
+
3
+ ![Logo](https://raw.githubusercontent.com/kai3341/systemPY/main/docs/images/systempy-logo.png)
4
+
5
+ Python application component initialization system
6
+
7
+ ![python](https://img.shields.io/pypi/pyversions/systemPY)
8
+ ![version](https://img.shields.io/pypi/v/systemPY)
9
+ ![downloads](https://img.shields.io/pypi/dm/systemPY)
10
+ ![format](https://img.shields.io/pypi/format/systemPY)
11
+ [![Documentation Status](https://readthedocs.org/projects/systempy/badge/?version=latest)](https://systempy.readthedocs.io/en/latest/?badge=latest)
12
+ ![GitHub issues](https://img.shields.io/github/issues/kai3341/systemPY)
13
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
14
+ [![Checked with mypy](https://www.mypy-lang.org/static/mypy_badge.svg)](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
+ ]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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"