systempy 0.1.1__py3-none-any.whl
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/__init__.py +35 -0
- systempy/libsystempy/__init__.py +20 -0
- systempy/libsystempy/callback_plan.py +60 -0
- systempy/libsystempy/check.py +52 -0
- systempy/libsystempy/class_role.py +32 -0
- systempy/libsystempy/configuration.py +56 -0
- systempy/libsystempy/constants.py +16 -0
- systempy/libsystempy/creation.py +32 -0
- systempy/libsystempy/enums.py +30 -0
- systempy/libsystempy/extraction.py +88 -0
- systempy/libsystempy/handler_type.py +170 -0
- systempy/libsystempy/hook_registry.py +50 -0
- systempy/libsystempy/local_dataclasses.py +110 -0
- systempy/libsystempy/local_typing.py +47 -0
- systempy/libsystempy/misc.py +18 -0
- systempy/libsystempy/register.py +142 -0
- systempy/libsystempy/thread_exception.py +27 -0
- systempy/libsystempy/weak_queue.py +59 -0
- systempy/py.typed +1 -0
- systempy/target.py +108 -0
- systempy/target_meta.py +86 -0
- systempy/unit/__init__.py +16 -0
- systempy/unit/_compat_signal.py +12 -0
- systempy/unit/daemon.py +91 -0
- systempy/unit/event_wait.py +23 -0
- systempy/unit/ext/__init__.py +0 -0
- systempy/unit/ext/celery.py +18 -0
- systempy/unit/ext/pretty_repl.py +37 -0
- systempy/unit/ext/ptrepl.py +60 -0
- systempy/unit/ext/starlette.py +15 -0
- systempy/unit/ext/target_ext.py +14 -0
- systempy/unit/loop.py +47 -0
- systempy/unit/repl/__init__.py +0 -0
- systempy/unit/repl/handle_interrupt.py +59 -0
- systempy/unit/repl/mixins.py +40 -0
- systempy/unit/repl/repl.py +127 -0
- systempy/unit/scripting.py +22 -0
- systempy/unit/unit.py +8 -0
- systempy-0.1.1.dist-info/METADATA +83 -0
- systempy-0.1.1.dist-info/RECORD +44 -0
- systempy-0.1.1.dist-info/WHEEL +5 -0
- systempy-0.1.1.dist-info/licenses/LICENSE +21 -0
- systempy-0.1.1.dist-info/top_level.txt +1 -0
- systempy-0.1.1.dist-info/zip-safe +1 -0
systempy/__init__.py
ADDED
|
@@ -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"
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
from collections.abc import Callable, Coroutine
|
|
2
|
+
from inspect import iscoroutinefunction
|
|
3
|
+
from typing import cast
|
|
4
|
+
from weakref import ref
|
|
5
|
+
|
|
6
|
+
from .constants import lifecycle_disallowed_attrs, lifecycle_registered_methods
|
|
7
|
+
from .enums import DIRECTION
|
|
8
|
+
from .local_dataclasses import SeparatedLFMethods
|
|
9
|
+
from .local_typing import CTuple, P, R, WeakTypeIterable
|
|
10
|
+
from .register import mark_as_target, register_direction
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def extract_attrs(iterable: WeakTypeIterable, reason: Callable) -> list[Callable]:
|
|
14
|
+
result: list[Callable] = []
|
|
15
|
+
cfg = lifecycle_registered_methods[reason]
|
|
16
|
+
interface = cfg.interface()
|
|
17
|
+
|
|
18
|
+
assert interface
|
|
19
|
+
|
|
20
|
+
for cls_ref in iterable:
|
|
21
|
+
cls = cls_ref()
|
|
22
|
+
assert cls
|
|
23
|
+
|
|
24
|
+
if not issubclass(cls, interface):
|
|
25
|
+
continue
|
|
26
|
+
|
|
27
|
+
cls_dict = cls.__dict__
|
|
28
|
+
|
|
29
|
+
if (maybe_val := cls_dict.get(reason.__name__)) is not None:
|
|
30
|
+
result.append(maybe_val)
|
|
31
|
+
|
|
32
|
+
return result
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@register_direction(DIRECTION.FORWARD)
|
|
36
|
+
@register_direction(DIRECTION.GATHER)
|
|
37
|
+
def callbacks_direct(iterable: WeakTypeIterable, reason: Callable) -> CTuple:
|
|
38
|
+
callbacks = extract_attrs(iterable, reason)
|
|
39
|
+
return tuple(callbacks)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@register_direction(DIRECTION.BACKWARD)
|
|
43
|
+
def callbacks_reversed(iterable: WeakTypeIterable, reason: Callable) -> CTuple:
|
|
44
|
+
callbacks = extract_attrs(iterable, reason)
|
|
45
|
+
callbacks.reverse()
|
|
46
|
+
return tuple(callbacks)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def extract_bases(cls: type) -> WeakTypeIterable:
|
|
50
|
+
bases = [
|
|
51
|
+
# ===
|
|
52
|
+
Base
|
|
53
|
+
for Base in cls.mro()
|
|
54
|
+
if Base not in mark_as_target
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
for base in bases:
|
|
58
|
+
clsdict = vars(base)
|
|
59
|
+
for check_attribute, description in lifecycle_disallowed_attrs:
|
|
60
|
+
if check_attribute in clsdict:
|
|
61
|
+
message = f"Attribute {check_attribute} is not allowed"
|
|
62
|
+
|
|
63
|
+
if description:
|
|
64
|
+
message = f"{message}. {description}"
|
|
65
|
+
|
|
66
|
+
raise ValueError(message, base)
|
|
67
|
+
|
|
68
|
+
first = bases.pop(0)
|
|
69
|
+
bases.append(first)
|
|
70
|
+
return tuple(map(ref, bases))
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def separate_sync_async(
|
|
74
|
+
iterable: CTuple[P, R | Coroutine[R, None, None]],
|
|
75
|
+
) -> SeparatedLFMethods[P, R]:
|
|
76
|
+
callbacks_sync: list[Callable[P, R]] = []
|
|
77
|
+
callbacks_async: list[Callable[P, Coroutine[R, None, None]]] = []
|
|
78
|
+
|
|
79
|
+
for callback in iterable:
|
|
80
|
+
if iscoroutinefunction(callback):
|
|
81
|
+
callbacks_async.append(callback)
|
|
82
|
+
else:
|
|
83
|
+
callbacks_sync.append(cast("Callable[P, R]", callback))
|
|
84
|
+
|
|
85
|
+
return SeparatedLFMethods(
|
|
86
|
+
callbacks_sync=tuple(callbacks_sync),
|
|
87
|
+
callbacks_async=tuple(callbacks_async),
|
|
88
|
+
)
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
from asyncio import gather
|
|
2
|
+
from collections.abc import Callable, Coroutine
|
|
3
|
+
from inspect import iscoroutinefunction
|
|
4
|
+
|
|
5
|
+
from .callback_plan import build_callback_plan
|
|
6
|
+
from .constants import handler_metadata
|
|
7
|
+
from .enums import DIRECTION, TYPE
|
|
8
|
+
from .extraction import separate_sync_async
|
|
9
|
+
from .local_dataclasses import CallbackMetadata, SeparatedLFMethods
|
|
10
|
+
from .local_typing import CTuple, MaybeCoro, P, R, WeakTypeIterable
|
|
11
|
+
from .register import register_handler_by_aio
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def with_repr(
|
|
15
|
+
group: str,
|
|
16
|
+
reason: Callable,
|
|
17
|
+
callbacks: CTuple,
|
|
18
|
+
) -> Callable[[Callable[P, R]], Callable[P, R]]:
|
|
19
|
+
def inner(handler: Callable[P, R]) -> Callable[P, R]:
|
|
20
|
+
call_order = tuple(".".join(c.__qualname__.split(".")[-2:]) for c in callbacks)
|
|
21
|
+
handler.__qualname__ = f"{group}[{reason.__name__}]({';'.join(call_order)})"
|
|
22
|
+
handler_metadata[handler] = CallbackMetadata(call_order)
|
|
23
|
+
return handler
|
|
24
|
+
|
|
25
|
+
return inner
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# Make closure as minimal as possible to avoid memory leak
|
|
29
|
+
def __handler_sync(
|
|
30
|
+
callbacks_total: tuple[Callable[P, None], ...],
|
|
31
|
+
with_current_repr: Callable[[Callable[P, None]], Callable[P, None]],
|
|
32
|
+
) -> Callable[P, None]:
|
|
33
|
+
@with_current_repr
|
|
34
|
+
def handler(*args: P.args, **kwargs: P.kwargs) -> None:
|
|
35
|
+
for callback in callbacks_total:
|
|
36
|
+
callback(*args, **kwargs)
|
|
37
|
+
|
|
38
|
+
return handler
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@register_handler_by_aio(TYPE.SYNC)
|
|
42
|
+
def handler_sync(
|
|
43
|
+
cls: type,
|
|
44
|
+
bases: WeakTypeIterable,
|
|
45
|
+
reason: Callable[P, None],
|
|
46
|
+
callbacks: CTuple[P, None],
|
|
47
|
+
) -> Callable[P, None]:
|
|
48
|
+
callbacks_total = build_callback_plan(cls, bases, reason, callbacks)
|
|
49
|
+
with_current_repr = with_repr("Sync", reason, callbacks_total)
|
|
50
|
+
return __handler_sync(callbacks_total, with_current_repr)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# Make closure as minimal as possible to avoid memory leak
|
|
54
|
+
def __handler_async(
|
|
55
|
+
callbacks_total: tuple[Callable[P, MaybeCoro[None]], ...],
|
|
56
|
+
with_current_repr: Callable[
|
|
57
|
+
[Callable[P, Coroutine[None, None, None]]],
|
|
58
|
+
Callable[P, Coroutine[None, None, None]],
|
|
59
|
+
],
|
|
60
|
+
) -> Callable[P, Coroutine[None, None, None]]:
|
|
61
|
+
@with_current_repr
|
|
62
|
+
async def handler(*args: P.args, **kwargs: P.kwargs) -> None:
|
|
63
|
+
for callback in callbacks_total:
|
|
64
|
+
if iscoroutinefunction(callback):
|
|
65
|
+
await callback(*args, **kwargs)
|
|
66
|
+
else:
|
|
67
|
+
callback(*args, **kwargs)
|
|
68
|
+
|
|
69
|
+
return handler
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@register_handler_by_aio(TYPE.ASYNC)
|
|
73
|
+
def handler_async(
|
|
74
|
+
cls: type,
|
|
75
|
+
bases: WeakTypeIterable,
|
|
76
|
+
reason: Callable[P, MaybeCoro[None]],
|
|
77
|
+
callbacks: CTuple[P, MaybeCoro[None]],
|
|
78
|
+
) -> Callable[P, Coroutine[None, None, None]]:
|
|
79
|
+
callbacks_total = build_callback_plan(cls, bases, reason, callbacks)
|
|
80
|
+
with_current_repr = with_repr("Async", reason, callbacks_total)
|
|
81
|
+
return __handler_async(callbacks_total, with_current_repr)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
# Make closure as minimal as possible to avoid memory leak
|
|
85
|
+
def __handler_gather_both(
|
|
86
|
+
separated: SeparatedLFMethods[P, None],
|
|
87
|
+
with_current_repr: Callable[
|
|
88
|
+
[Callable[P, Coroutine[None, None, None]]],
|
|
89
|
+
Callable[P, Coroutine[None, None, None]],
|
|
90
|
+
],
|
|
91
|
+
) -> Callable[P, Coroutine[None, None, None]]:
|
|
92
|
+
@with_current_repr
|
|
93
|
+
async def handler(*args: P.args, **kwargs: P.kwargs) -> None:
|
|
94
|
+
for cb in separated.callbacks_sync:
|
|
95
|
+
cb(*args, **kwargs)
|
|
96
|
+
|
|
97
|
+
await gather(*[cb(*args, **kwargs) for cb in separated.callbacks_async])
|
|
98
|
+
|
|
99
|
+
return handler
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
# Make closure as minimal as possible to avoid memory leak
|
|
103
|
+
def __handler_gather_async(
|
|
104
|
+
callbacks_total: tuple[Callable[P, Coroutine[None, None, None]], ...],
|
|
105
|
+
with_current_repr: Callable[
|
|
106
|
+
[Callable[P, Coroutine[None, None, None]]],
|
|
107
|
+
Callable[P, Coroutine[None, None, None]],
|
|
108
|
+
],
|
|
109
|
+
) -> Callable[P, Coroutine[None, None, None]]:
|
|
110
|
+
@with_current_repr
|
|
111
|
+
async def handler(*args: P.args, **kwargs: P.kwargs) -> None:
|
|
112
|
+
await gather(*[cb(*args, **kwargs) for cb in callbacks_total])
|
|
113
|
+
|
|
114
|
+
return handler
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
# Make closure as minimal as possible to avoid memory leak
|
|
118
|
+
def __handler_gather_sync(
|
|
119
|
+
callbacks_total: tuple[Callable[P, None], ...],
|
|
120
|
+
with_current_repr: Callable[
|
|
121
|
+
[Callable[P, Coroutine[None, None, None]]],
|
|
122
|
+
Callable[P, Coroutine[None, None, None]],
|
|
123
|
+
],
|
|
124
|
+
) -> Callable[P, Coroutine[None, None, None]]:
|
|
125
|
+
@with_current_repr
|
|
126
|
+
async def handler(*args: P.args, **kwargs: P.kwargs) -> None:
|
|
127
|
+
for callback in callbacks_total:
|
|
128
|
+
callback(*args, **kwargs)
|
|
129
|
+
|
|
130
|
+
return handler
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
# Make closure as minimal as possible to avoid memory leak
|
|
134
|
+
def __handler_gather_void(
|
|
135
|
+
with_current_repr: Callable[
|
|
136
|
+
[Callable[P, Coroutine[None, None, None]]],
|
|
137
|
+
Callable[P, Coroutine[None, None, None]],
|
|
138
|
+
],
|
|
139
|
+
) -> Callable[P, Coroutine[None, None, None]]:
|
|
140
|
+
@with_current_repr
|
|
141
|
+
async def handler(*args: P.args, **kwargs: P.kwargs) -> None: ...
|
|
142
|
+
|
|
143
|
+
return handler
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@register_handler_by_aio(DIRECTION.GATHER)
|
|
147
|
+
def handler_gather(
|
|
148
|
+
cls: type,
|
|
149
|
+
bases: WeakTypeIterable,
|
|
150
|
+
reason: Callable[P, MaybeCoro[None]],
|
|
151
|
+
callbacks: CTuple[P, MaybeCoro[None]],
|
|
152
|
+
) -> Callable[P, Coroutine[None, None, None]]:
|
|
153
|
+
callbacks_total = build_callback_plan(cls, bases, reason, callbacks)
|
|
154
|
+
|
|
155
|
+
separated = separate_sync_async(callbacks_total)
|
|
156
|
+
|
|
157
|
+
if separated.callbacks_sync and separated.callbacks_async:
|
|
158
|
+
with_current_repr = with_repr("GatherBoth", reason, callbacks_total)
|
|
159
|
+
return __handler_gather_both(separated, with_current_repr)
|
|
160
|
+
|
|
161
|
+
if separated.callbacks_async:
|
|
162
|
+
with_current_repr = with_repr("GatherAsync", reason, callbacks_total)
|
|
163
|
+
return __handler_gather_async(separated.callbacks_async, with_current_repr)
|
|
164
|
+
|
|
165
|
+
if separated.callbacks_sync:
|
|
166
|
+
with_current_repr = with_repr("GatherBoth", reason, callbacks_total)
|
|
167
|
+
return __handler_gather_sync(separated.callbacks_sync, with_current_repr)
|
|
168
|
+
|
|
169
|
+
with_current_repr = with_repr("GatherNone", reason, callbacks_total)
|
|
170
|
+
return __handler_gather_void(with_current_repr)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from inspect import iscoroutinefunction
|
|
4
|
+
from typing import (
|
|
5
|
+
ClassVar,
|
|
6
|
+
)
|
|
7
|
+
from weakref import WeakKeyDictionary
|
|
8
|
+
|
|
9
|
+
from . import register
|
|
10
|
+
from .enums import DIRECTION
|
|
11
|
+
from .local_dataclasses import BaseRegistry
|
|
12
|
+
from .local_typing import (
|
|
13
|
+
Decorator,
|
|
14
|
+
Named,
|
|
15
|
+
P,
|
|
16
|
+
R,
|
|
17
|
+
)
|
|
18
|
+
from .misc import get_key_or_create
|
|
19
|
+
from .weak_queue import WeakQueue
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass()
|
|
23
|
+
class HookRegistry(BaseRegistry[Callable[P, R], WeakQueue[Callable[P, R]]]):
|
|
24
|
+
hook_parents: ClassVar = WeakKeyDictionary[Named, Named]()
|
|
25
|
+
|
|
26
|
+
__hook_invalid_template = (
|
|
27
|
+
"You are trying to register executing asyncronous hook %s on the stage "
|
|
28
|
+
"when event loop is not started or already stopped"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
def __call__(
|
|
32
|
+
self,
|
|
33
|
+
reason: Callable[P, R],
|
|
34
|
+
direction: DIRECTION | None = None,
|
|
35
|
+
) -> Decorator[P, R]:
|
|
36
|
+
registry = get_key_or_create(self._registry, reason, WeakQueue)
|
|
37
|
+
lifecycle_method_parent = self.hook_parents.get(reason, reason)
|
|
38
|
+
parent_syncronous = not iscoroutinefunction(lifecycle_method_parent)
|
|
39
|
+
|
|
40
|
+
def inner(func: Callable[P, R]) -> Callable[P, R]:
|
|
41
|
+
if parent_syncronous and iscoroutinefunction(func):
|
|
42
|
+
raise ValueError(self.__hook_invalid_template % func)
|
|
43
|
+
|
|
44
|
+
if direction is not None:
|
|
45
|
+
register.register_target_method(direction)(func)
|
|
46
|
+
self.hook_parents[func] = lifecycle_method_parent
|
|
47
|
+
registry.append(func)
|
|
48
|
+
return func
|
|
49
|
+
|
|
50
|
+
return inner
|