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.
Files changed (44) hide show
  1. systempy/__init__.py +35 -0
  2. systempy/libsystempy/__init__.py +20 -0
  3. systempy/libsystempy/callback_plan.py +60 -0
  4. systempy/libsystempy/check.py +52 -0
  5. systempy/libsystempy/class_role.py +32 -0
  6. systempy/libsystempy/configuration.py +56 -0
  7. systempy/libsystempy/constants.py +16 -0
  8. systempy/libsystempy/creation.py +32 -0
  9. systempy/libsystempy/enums.py +30 -0
  10. systempy/libsystempy/extraction.py +88 -0
  11. systempy/libsystempy/handler_type.py +170 -0
  12. systempy/libsystempy/hook_registry.py +50 -0
  13. systempy/libsystempy/local_dataclasses.py +110 -0
  14. systempy/libsystempy/local_typing.py +47 -0
  15. systempy/libsystempy/misc.py +18 -0
  16. systempy/libsystempy/register.py +142 -0
  17. systempy/libsystempy/thread_exception.py +27 -0
  18. systempy/libsystempy/weak_queue.py +59 -0
  19. systempy/py.typed +1 -0
  20. systempy/target.py +108 -0
  21. systempy/target_meta.py +86 -0
  22. systempy/unit/__init__.py +16 -0
  23. systempy/unit/_compat_signal.py +12 -0
  24. systempy/unit/daemon.py +91 -0
  25. systempy/unit/event_wait.py +23 -0
  26. systempy/unit/ext/__init__.py +0 -0
  27. systempy/unit/ext/celery.py +18 -0
  28. systempy/unit/ext/pretty_repl.py +37 -0
  29. systempy/unit/ext/ptrepl.py +60 -0
  30. systempy/unit/ext/starlette.py +15 -0
  31. systempy/unit/ext/target_ext.py +14 -0
  32. systempy/unit/loop.py +47 -0
  33. systempy/unit/repl/__init__.py +0 -0
  34. systempy/unit/repl/handle_interrupt.py +59 -0
  35. systempy/unit/repl/mixins.py +40 -0
  36. systempy/unit/repl/repl.py +127 -0
  37. systempy/unit/scripting.py +22 -0
  38. systempy/unit/unit.py +8 -0
  39. systempy-0.1.1.dist-info/METADATA +83 -0
  40. systempy-0.1.1.dist-info/RECORD +44 -0
  41. systempy-0.1.1.dist-info/WHEEL +5 -0
  42. systempy-0.1.1.dist-info/licenses/LICENSE +21 -0
  43. systempy-0.1.1.dist-info/top_level.txt +1 -0
  44. 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