stranded 0.0.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 (71) hide show
  1. stranded/__init__.py +51 -0
  2. stranded/abc/__init__.py +21 -0
  3. stranded/abc/composer_.py +191 -0
  4. stranded/argparse/__init__.py +24 -0
  5. stranded/argparse/abc/__init__.py +21 -0
  6. stranded/argparse/abc/argument_parser_.py +539 -0
  7. stranded/argparse/argument_parser_.py +13 -0
  8. stranded/argparse/asyncio/__init__.py +24 -0
  9. stranded/argparse/asyncio/argument_parser_.py +50 -0
  10. stranded/argparse/threading/__init__.py +24 -0
  11. stranded/argparse/threading/argument_parser_.py +52 -0
  12. stranded/asyncio/__init__.py +21 -0
  13. stranded/asyncio/composer_.py +76 -0
  14. stranded/builtins/__init__.py +21 -0
  15. stranded/builtins/exception_.py +6 -0
  16. stranded/composer_.py +27 -0
  17. stranded/execution/__init__.py +30 -0
  18. stranded/execution/abc/__init__.py +21 -0
  19. stranded/execution/abc/scheduler_.py +59 -0
  20. stranded/execution/asyncio/__init__.py +21 -0
  21. stranded/execution/asyncio/scheduler_.py +86 -0
  22. stranded/execution/scheduler_.py +59 -0
  23. stranded/execution/threading/__init__.py +21 -0
  24. stranded/execution/threading/scheduler_.py +116 -0
  25. stranded/functools/__init__.py +51 -0
  26. stranded/functools/abc/__init__.py +39 -0
  27. stranded/functools/abc/herd_.py +96 -0
  28. stranded/functools/abc/lru_cache_.py +192 -0
  29. stranded/functools/abc/retry_.py +60 -0
  30. stranded/functools/abc/throttle_.py +97 -0
  31. stranded/functools/asyncio/__init__.py +51 -0
  32. stranded/functools/asyncio/herd_.py +89 -0
  33. stranded/functools/asyncio/lru_cache_.py +92 -0
  34. stranded/functools/asyncio/retry_.py +43 -0
  35. stranded/functools/asyncio/throttle_.py +74 -0
  36. stranded/functools/herd_.py +18 -0
  37. stranded/functools/lru_cache_.py +18 -0
  38. stranded/functools/retry_.py +13 -0
  39. stranded/functools/threading/__init__.py +51 -0
  40. stranded/functools/threading/herd_.py +88 -0
  41. stranded/functools/threading/lru_cache_.py +90 -0
  42. stranded/functools/threading/retry_.py +42 -0
  43. stranded/functools/threading/throttle_.py +75 -0
  44. stranded/functools/throttle_.py +13 -0
  45. stranded/logging/__init__.py +24 -0
  46. stranded/logging/abc/__init__.py +21 -0
  47. stranded/logging/abc/logger_.py +96 -0
  48. stranded/logging/asyncio/__init__.py +21 -0
  49. stranded/logging/asyncio/logger_.py +41 -0
  50. stranded/logging/logger_.py +13 -0
  51. stranded/logging/threading/__init__.py +21 -0
  52. stranded/logging/threading/logger_.py +41 -0
  53. stranded/py.typed +0 -0
  54. stranded/sqlite3/__init__.py +24 -0
  55. stranded/sqlite3/abc/__init__.py +21 -0
  56. stranded/sqlite3/abc/db_.py +149 -0
  57. stranded/sqlite3/asyncio/__init__.py +24 -0
  58. stranded/sqlite3/asyncio/db_.py +43 -0
  59. stranded/sqlite3/db_.py +13 -0
  60. stranded/sqlite3/threading/__init__.py +24 -0
  61. stranded/sqlite3/threading/db_.py +43 -0
  62. stranded/threading/__init__.py +21 -0
  63. stranded/threading/composer_.py +74 -0
  64. stranded/types/__init__.py +21 -0
  65. stranded/types/constraint.py +7 -0
  66. stranded/types/convert.py +158 -0
  67. stranded-0.0.1.dist-info/METADATA +173 -0
  68. stranded-0.0.1.dist-info/RECORD +71 -0
  69. stranded-0.0.1.dist-info/WHEEL +5 -0
  70. stranded-0.0.1.dist-info/licenses/LICENSE +21 -0
  71. stranded-0.0.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,96 @@
1
+ from __future__ import annotations
2
+
3
+ import abc
4
+ import dataclasses
5
+ import inspect
6
+ import logging
7
+ import typing
8
+
9
+ from ...abc import composer_
10
+ from ...builtins import exception_
11
+
12
+
13
+ Level = typing.Literal['CRITICAL', 'ERROR', 'WARNING', 'INFO', 'DEBUG', 'NOTSET']
14
+
15
+
16
+ class Exception(exception_.Exception): ... # noqa
17
+
18
+
19
+ @typing.runtime_checkable
20
+ class Composee[**ParamT, RetT](composer_.Composee[ParamT, RetT], typing.Protocol): ...
21
+
22
+
23
+ Param = composer_.Param
24
+ Raise = composer_.Raise
25
+ Return = composer_.Return
26
+ Stop = composer_.Stop
27
+
28
+
29
+ @dataclasses.dataclass(frozen=True, kw_only=True)
30
+ class Connect[**ParamT, RetT](composer_.Connect[ParamT, RetT], abc.ABC): ...
31
+
32
+
33
+ @dataclasses.dataclass(frozen=True, kw_only=True)
34
+ class Exit[**ParamT, RetT](composer_.Exit[ParamT, RetT], abc.ABC):
35
+ bound_arguments: inspect.BoundArguments
36
+
37
+ def __call__(self, value: composer_.ValueT[ParamT, RetT], /) -> composer_.StackT:
38
+ match value:
39
+ case Raise() as raise_:
40
+ logging.getLogger(f'{self.enter.composee.__module__}.{self.enter.composee.__name__}').log(
41
+ logging.getLevelNamesMapping()[self.enter.composer.exception_level], # type: ignore[attr-defined]
42
+ '%s :: %s !! %s',
43
+ inspect.signature(self.enter.composee),
44
+ self.bound_arguments.arguments,
45
+ raise_.exc_val,
46
+ )
47
+ case Return(ret=ret):
48
+ level = (
49
+ self.enter.composer.none_level # type: ignore[attr-defined]
50
+ if ret is None
51
+ else self.enter.composer.return_level # type: ignore[attr-defined]
52
+ )
53
+ logging.getLogger(f'{self.enter.composee.__module__}.{self.enter.composee.__name__}').log(
54
+ logging.getLevelNamesMapping()[level],
55
+ '%s :: %s -> %s',
56
+ inspect.signature(self.enter.composee),
57
+ self.bound_arguments.arguments,
58
+ ret,
59
+ )
60
+
61
+ return ()
62
+
63
+
64
+ @dataclasses.dataclass(frozen=True, kw_only=True)
65
+ class Enter[**ParamT, RetT](composer_.Enter[ParamT, RetT], abc.ABC):
66
+ def __call__(self, value: composer_.ValueT[ParamT, RetT], /) -> composer_.StackT:
67
+ match value:
68
+ case Param() as param_:
69
+ bound_arguments = inspect.signature(self.composee).bind(*param_.args, **param_.kwargs)
70
+
71
+ logging.getLogger(f'{self.composee.__module__}.{self.composee.__name__}').log(
72
+ logging.getLevelNamesMapping()[self.composer.call_level], # type: ignore[attr-defined]
73
+ '%s',
74
+ bound_arguments,
75
+ )
76
+
77
+ return self.composer.Exit(enter=self, bound_arguments=bound_arguments), self.composee, # type: ignore[call-arg]
78
+ case _:
79
+ return ()
80
+
81
+
82
+ @dataclasses.dataclass(frozen=True, kw_only=True)
83
+ class Composed[**ParamT, RetT](composer_.Composed[ParamT, RetT], abc.ABC): ...
84
+
85
+
86
+ @dataclasses.dataclass(frozen=True, kw_only=True)
87
+ class Logger[**ParamT, RetT](composer_.Composer[ParamT, RetT], abc.ABC):
88
+ call_level: Level = 'DEBUG'
89
+ exception_level: Level = 'ERROR'
90
+ none_level: Level = 'WARNING'
91
+ return_level: Level = 'INFO'
92
+
93
+ Level: typing.ClassVar[type[Level]] = Level # type: ignore[assignment, valid-type]
94
+
95
+
96
+ Composer = Logger
@@ -0,0 +1,21 @@
1
+ from __future__ import annotations
2
+
3
+ import importlib as _importlib
4
+ import typing as _typing
5
+
6
+ if _typing.TYPE_CHECKING:
7
+ from . import logger_
8
+ from .logger_ import Logger
9
+
10
+
11
+ def __getattr__(name: str) -> _typing.Any:
12
+ match name:
13
+ case 'logger_': return _importlib.import_module('.logger_', __name__)
14
+ case 'Logger': return _importlib.import_module('.logger_', __name__).Logger
15
+ case _: raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
16
+
17
+
18
+ __all__ = (
19
+ 'logger_',
20
+ 'Logger',
21
+ )
@@ -0,0 +1,41 @@
1
+ import dataclasses
2
+ import typing
3
+
4
+ from ...asyncio import composer_
5
+ from ..abc import logger_
6
+
7
+ @typing.runtime_checkable
8
+ class Composee[**ParamT, RetT](composer_.Composee[ParamT, RetT], logger_.Composee[ParamT, RetT], typing.Protocol): ...
9
+
10
+
11
+ @typing.final
12
+ @dataclasses.dataclass(frozen=True, kw_only=True)
13
+ class Connect[**ParamT, RetT](composer_.Connect[ParamT, RetT], logger_.Connect[ParamT, RetT]): ...
14
+
15
+
16
+ @typing.final
17
+ @dataclasses.dataclass(frozen=True, kw_only=True)
18
+ class Exit[**ParamT, RetT](composer_.Exit[ParamT, RetT], logger_.Exit[ParamT, RetT]): ... # type: ignore[misc]
19
+
20
+
21
+ @typing.final
22
+ @dataclasses.dataclass(frozen=True, kw_only=True)
23
+ class Enter[**ParamT, RetT](composer_.Enter[ParamT, RetT], logger_.Enter[ParamT, RetT]): ... # type: ignore[misc]
24
+
25
+
26
+ @typing.final
27
+ @dataclasses.dataclass(frozen=True, kw_only=True)
28
+ class Composed[**ParamT, RetT](composer_.Composed[ParamT, RetT], logger_.Composed[ParamT, RetT]): ...
29
+
30
+
31
+ @typing.final
32
+ @dataclasses.dataclass(frozen=True, kw_only=True)
33
+ class Logger[**ParamT, RetT](composer_.Composer[ParamT, RetT], logger_.Composer[ParamT, RetT]):
34
+ Composee: typing.ClassVar = Composee
35
+ Connect: typing.ClassVar = Connect
36
+ Exit: typing.ClassVar = Exit # type: ignore[assignment]
37
+ Enter: typing.ClassVar = Enter
38
+ Composed: typing.ClassVar = Composed
39
+
40
+
41
+ Composer = Logger
@@ -0,0 +1,13 @@
1
+ import dataclasses
2
+ import typing
3
+
4
+ from .. import composer_
5
+ from .abc import logger_
6
+
7
+
8
+ @dataclasses.dataclass(frozen=True, kw_only=True)
9
+ class Logger(composer_.Composer[..., typing.Any], logger_.Logger[..., typing.Any]): ...
10
+
11
+
12
+ Composer = Logger
13
+ logger = Logger()
@@ -0,0 +1,21 @@
1
+ from __future__ import annotations
2
+
3
+ import importlib as _importlib
4
+ import typing as _typing
5
+
6
+ if _typing.TYPE_CHECKING:
7
+ from . import logger_
8
+ from .logger_ import Logger
9
+
10
+
11
+ def __getattr__(name: str) -> _typing.Any:
12
+ match name:
13
+ case 'logger_': return _importlib.import_module('.logger_', __name__)
14
+ case 'Logger': return _importlib.import_module('.logger_', __name__).Logger
15
+ case _: raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
16
+
17
+
18
+ __all__ = (
19
+ 'logger_',
20
+ 'Logger',
21
+ )
@@ -0,0 +1,41 @@
1
+ import dataclasses
2
+ import typing
3
+
4
+ from ...threading import composer_
5
+ from ..abc import logger_
6
+
7
+ @typing.runtime_checkable
8
+ class Composee[**ParamT, RetT](composer_.Composee[ParamT, RetT], logger_.Composee[ParamT, RetT], typing.Protocol): ...
9
+
10
+
11
+ @typing.final
12
+ @dataclasses.dataclass(frozen=True, kw_only=True)
13
+ class Connect[**ParamT, RetT](composer_.Connect[ParamT, RetT], logger_.Connect[ParamT, RetT]): ...
14
+
15
+
16
+ @typing.final
17
+ @dataclasses.dataclass(frozen=True, kw_only=True)
18
+ class Exit[**ParamT, RetT](composer_.Exit[ParamT, RetT], logger_.Exit[ParamT, RetT]): ...
19
+
20
+
21
+ @typing.final
22
+ @dataclasses.dataclass(frozen=True, kw_only=True)
23
+ class Enter[**ParamT, RetT](composer_.Enter[ParamT, RetT], logger_.Enter[ParamT, RetT]): ...
24
+
25
+
26
+ @typing.final
27
+ @dataclasses.dataclass(frozen=True, kw_only=True)
28
+ class Composed[**ParamT, RetT](composer_.Composed[ParamT, RetT], logger_.Composed[ParamT, RetT]): ...
29
+
30
+
31
+ @typing.final
32
+ @dataclasses.dataclass(frozen=True, kw_only=True)
33
+ class Logger[**ParamT, RetT](composer_.Composer[ParamT, RetT], logger_.Composer[ParamT, RetT]):
34
+ Composee: typing.ClassVar = Composee
35
+ Connect: typing.ClassVar = Connect
36
+ Exit: typing.ClassVar = Exit # type: ignore[assignment]
37
+ Enter: typing.ClassVar = Enter
38
+ Composed: typing.ClassVar = Composed
39
+
40
+
41
+ Composer = Logger
stranded/py.typed ADDED
File without changes
@@ -0,0 +1,24 @@
1
+ from __future__ import annotations
2
+
3
+ import importlib as _importlib
4
+ import typing as _typing
5
+
6
+ if _typing.TYPE_CHECKING:
7
+ from . import db_
8
+ from .db_ import Db
9
+ from .db_ import db
10
+
11
+
12
+ def __getattr__(name: str) -> _typing.Any:
13
+ match name:
14
+ case 'db_': return _importlib.import_module('.db_', __name__)
15
+ case 'Db': return _importlib.import_module('.db_', __name__).Db
16
+ case 'db': return _importlib.import_module('.db_', __name__).db
17
+ case _: raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
18
+
19
+
20
+ __all__ = (
21
+ 'db_',
22
+ 'Db',
23
+ 'db',
24
+ )
@@ -0,0 +1,21 @@
1
+ from __future__ import annotations
2
+
3
+ import importlib as _importlib
4
+ import typing as _typing
5
+
6
+ if _typing.TYPE_CHECKING:
7
+ from . import db_
8
+ from .db_ import Db
9
+
10
+
11
+ def __getattr__(name: str) -> _typing.Any:
12
+ match name:
13
+ case 'db_': return _importlib.import_module('.db_', __name__)
14
+ case 'Db': return _importlib.import_module('.db_', __name__).Db
15
+ case _: raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
16
+
17
+
18
+ __all__ = (
19
+ 'db_',
20
+ 'Db',
21
+ )
@@ -0,0 +1,149 @@
1
+ from __future__ import annotations
2
+
3
+ import abc
4
+ import ast
5
+ import dataclasses
6
+ import inspect
7
+ import pathlib
8
+ import sqlite3
9
+ import threading
10
+ import typing
11
+
12
+ from ...abc import composer_
13
+
14
+ type GenerateKey = typing.Callable[..., Key]
15
+ type Key = typing.Hashable
16
+
17
+
18
+ Raise = composer_.Raise
19
+ Stop = composer_.Stop
20
+ Param = composer_.Param
21
+ Return = composer_.Return
22
+
23
+
24
+ @typing.runtime_checkable
25
+ class Composee[**ParamT, RetT](composer_.Composee[ParamT, RetT], typing.Protocol): ...
26
+
27
+
28
+ @dataclasses.dataclass(frozen=True, kw_only=True)
29
+ class Connect[**ParamT, RetT](composer_.Connect[ParamT, RetT], abc.ABC): ...
30
+
31
+
32
+ @dataclasses.dataclass(frozen=True, kw_only=True)
33
+ class Exit[**ParamT, RetT](composer_.Exit[ParamT, RetT], abc.ABC):
34
+ key: str
35
+
36
+ def __call__(self, value: composer_.ValueT[ParamT, RetT], /) -> composer_.StackT:
37
+ match value:
38
+ case Param() | Raise() | Stop(): pass
39
+ case Return():
40
+ assert self.enter.composer.deserialize( # type: ignore[attr-defined]
41
+ ret := self.enter.composer.serialize(value.ret) # type: ignore[attr-defined]
42
+ ) == value.ret, 'Return value must be deserializable from its serialized form.'
43
+
44
+ with self.enter.lock: # type: ignore[attr-defined]
45
+ self.enter.connection.execute( # type: ignore[attr-defined]
46
+ f'INSERT INTO `{self.enter.table_name}` (key, ret) VALUES (?, ?)', # type: ignore[attr-defined]
47
+ (self.key, ret,)
48
+ )
49
+
50
+ return ()
51
+
52
+
53
+ @dataclasses.dataclass(frozen=True, kw_only=True)
54
+ class Enter[**ParamT, RetT](composer_.Enter[ParamT, RetT], abc.ABC):
55
+ # The connection and its per-composition metadata live on the Enter now that
56
+ # Enter/Exit no longer reach Composed.
57
+ connection: sqlite3.Connection
58
+ instance: composer_.Instance
59
+ table_name: str
60
+ lock: threading.Lock = dataclasses.field(default_factory=threading.Lock)
61
+
62
+ def __call__(self, value: composer_.ValueT[ParamT, RetT], /) -> composer_.StackT:
63
+ match value:
64
+ case Raise() | Return() | Stop(): return ()
65
+ case Param():
66
+ (bound := inspect.signature(self.composee).bind(*value.args, **value.kwargs)).apply_defaults()
67
+ key = repr((self.instance, bound.args, tuple(sorted(bound.kwargs))))
68
+
69
+ with self.lock:
70
+ ret = self.connection.execute(
71
+ f'SELECT ret FROM `{self.table_name}` WHERE key = ?',
72
+ (key,),
73
+ ).fetchone()
74
+ if ret:
75
+ return Return(ret=self.composer.deserialize(ret[0])), # type: ignore[attr-defined]
76
+
77
+ return self.composer.Exit(enter=self, key=key), self.composee, # type: ignore[call-arg]
78
+
79
+ raise ValueError(f'Invalid {value=}')
80
+
81
+
82
+ @dataclasses.dataclass(frozen=True, kw_only=True)
83
+ class Composed[**ParamT, RetT](composer_.Composed[ParamT, RetT], abc.ABC):
84
+ def __get__(self, instance: composer_.Instance, owner: type[object] | None) -> typing.Self:
85
+ match self.stack[-1]:
86
+ case Enter() as enter_:
87
+ return dataclasses.replace(
88
+ self,
89
+ stack=(
90
+ *self.stack[:-1],
91
+ dataclasses.replace(
92
+ enter_, composee=enter_.composee.__get__(instance, owner), instance=instance,
93
+ ),
94
+ ),
95
+ )
96
+ assert False, "unreachable"
97
+
98
+
99
+ # Aliases for annotation use. The Db ClassVars below shadow the class names within
100
+ # Db's scope, so annotations must reference these instead.
101
+ type ComposeeT[**ParamT, RetT] = Composee[ParamT, RetT]
102
+ type ConnectT[**ParamT, RetT] = Connect[ParamT, RetT]
103
+ type ExitT[**ParamT, RetT] = Exit[ParamT, RetT]
104
+ type EnterT[**ParamT, RetT] = Enter[ParamT, RetT]
105
+ type ComposedT[**ParamT, RetT] = Composed[ParamT, RetT]
106
+
107
+
108
+ @dataclasses.dataclass(frozen=True, kw_only=True)
109
+ class Db[**ParamT, RetT](composer_.Composer[ParamT, RetT], abc.ABC):
110
+ type Version = str
111
+
112
+ deserialize: typing.Callable[[str], RetT] = ast.literal_eval
113
+ path: pathlib.Path = pathlib.Path.home() / '.cache' / 'stranded' / 'sqlite3' / 'db'
114
+ serialize: typing.Callable[[RetT], str] = repr
115
+ version: Version = '0.0.0'
116
+
117
+ Composee: typing.ClassVar[type[Composee[typing.Any, typing.Any]]]
118
+ Connect: typing.ClassVar[type[Connect[typing.Any, typing.Any]]]
119
+ Exit: typing.ClassVar[type[Exit[typing.Any, typing.Any]]]
120
+ Enter: typing.ClassVar[type[Enter[typing.Any, typing.Any]]]
121
+ Composed: typing.ClassVar[type[Composed[typing.Any, typing.Any]]]
122
+
123
+ def __call__(self, composee: ComposeeT[ParamT, RetT], /) -> ComposedT[ParamT, RetT]:
124
+ table_name = f'{composee.__module__}.{composee.__qualname__}.{self.version}'
125
+ (connection := sqlite3.connect(self.path, check_same_thread=False, isolation_level=None)).execute(
126
+ f'CREATE TABLE IF NOT EXISTS `{table_name}` ' # noqa
127
+ f'(key TEXT PRIMARY KEY NOT NULL UNIQUE, ret TEXT NOT NULL)', # noqa
128
+ )
129
+
130
+ return self.Composed( # type: ignore[return-value]
131
+ __doc__=str(composee.__doc__),
132
+ __module__=str(composee.__module__),
133
+ __name__=str(composee.__name__),
134
+ __qualname__=str(composee.__qualname__),
135
+ __signature__=inspect.signature(composee),
136
+ composer=self,
137
+ stack=(
138
+ self.Enter( # type: ignore[call-arg]
139
+ composer=self,
140
+ composee=composee,
141
+ connection=connection,
142
+ instance=None,
143
+ table_name=table_name,
144
+ ),
145
+ ),
146
+ )
147
+
148
+
149
+ Composer = Db
@@ -0,0 +1,24 @@
1
+ from __future__ import annotations
2
+
3
+ import importlib as _importlib
4
+ import typing as _typing
5
+
6
+ if _typing.TYPE_CHECKING:
7
+ from . import db_
8
+ from .db_ import Db
9
+ from .db_ import db
10
+
11
+
12
+ def __getattr__(name: str) -> _typing.Any:
13
+ match name:
14
+ case 'db_': return _importlib.import_module('.db_', __name__)
15
+ case 'Db': return _importlib.import_module('.db_', __name__).Db
16
+ case 'db': return _importlib.import_module('.db_', __name__).db
17
+ case _: raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
18
+
19
+
20
+ __all__ = (
21
+ 'db_',
22
+ 'Db',
23
+ 'db',
24
+ )
@@ -0,0 +1,43 @@
1
+ import dataclasses
2
+ import typing
3
+
4
+ from ...asyncio import composer_
5
+ from ..abc import db_
6
+
7
+
8
+ @typing.runtime_checkable
9
+ class Composee[**ParamT, RetT](composer_.Composee[ParamT, RetT], db_.Composee[ParamT, RetT], typing.Protocol): ...
10
+
11
+
12
+ @typing.final
13
+ @dataclasses.dataclass(frozen=True, kw_only=True)
14
+ class Connect[**ParamT, RetT](composer_.Connect[ParamT, RetT], db_.Connect[ParamT, RetT]): ...
15
+
16
+
17
+ @typing.final
18
+ @dataclasses.dataclass(frozen=True, kw_only=True)
19
+ class Exit[**ParamT, RetT](composer_.Exit[ParamT, RetT], db_.Exit[ParamT, RetT]): ... # type: ignore[misc]
20
+
21
+
22
+ @typing.final
23
+ @dataclasses.dataclass(frozen=True, kw_only=True)
24
+ class Enter[**ParamT, RetT](composer_.Enter[ParamT, RetT], db_.Enter[ParamT, RetT]): ... # type: ignore[misc]
25
+
26
+
27
+ @typing.final
28
+ @dataclasses.dataclass(frozen=True, kw_only=True)
29
+ class Composed[**ParamT, RetT](composer_.Composed[ParamT, RetT], db_.Composed[ParamT, RetT]): ...
30
+
31
+
32
+ @typing.final
33
+ @dataclasses.dataclass(frozen=True, kw_only=True)
34
+ class Db[**ParamT = ..., RetT = typing.Any](composer_.Composer[ParamT, RetT], db_.Composer[ParamT, RetT]):
35
+ Composee: typing.ClassVar = Composee
36
+ Connect: typing.ClassVar = Connect
37
+ Exit: typing.ClassVar = Exit
38
+ Enter: typing.ClassVar = Enter
39
+ Composed: typing.ClassVar = Composed
40
+
41
+
42
+ Composer = Db
43
+ db = Db()
@@ -0,0 +1,13 @@
1
+ import dataclasses
2
+ import typing
3
+
4
+ from .. import composer_
5
+ from .abc import db_
6
+
7
+
8
+ @dataclasses.dataclass(frozen=True, kw_only=True)
9
+ class Db(composer_.Composer[..., typing.Any], db_.Db[..., typing.Any]): ... # type: ignore[misc]
10
+
11
+
12
+ Composer = Db
13
+ db = Db()
@@ -0,0 +1,24 @@
1
+ from __future__ import annotations
2
+
3
+ import importlib as _importlib
4
+ import typing as _typing
5
+
6
+ if _typing.TYPE_CHECKING:
7
+ from . import db_
8
+ from .db_ import Db
9
+ from .db_ import db
10
+
11
+
12
+ def __getattr__(name: str) -> _typing.Any:
13
+ match name:
14
+ case 'db_': return _importlib.import_module('.db_', __name__)
15
+ case 'Db': return _importlib.import_module('.db_', __name__).Db
16
+ case 'db': return _importlib.import_module('.db_', __name__).db
17
+ case _: raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
18
+
19
+
20
+ __all__ = (
21
+ 'db_',
22
+ 'Db',
23
+ 'db',
24
+ )
@@ -0,0 +1,43 @@
1
+ import dataclasses
2
+ import typing
3
+
4
+ from ...threading import composer_
5
+ from ..abc import db_
6
+
7
+
8
+ @typing.runtime_checkable
9
+ class Composee[**ParamT, RetT](composer_.Composee[ParamT, RetT], db_.Composee[ParamT, RetT], typing.Protocol): ...
10
+
11
+
12
+ @typing.final
13
+ @dataclasses.dataclass(frozen=True, kw_only=True)
14
+ class Connect[**ParamT, RetT](composer_.Connect[ParamT, RetT], db_.Connect[ParamT, RetT]): ...
15
+
16
+
17
+ @typing.final
18
+ @dataclasses.dataclass(frozen=True, kw_only=True)
19
+ class Exit[**ParamT, RetT](composer_.Exit[ParamT, RetT], db_.Exit[ParamT, RetT]): ...
20
+
21
+
22
+ @typing.final
23
+ @dataclasses.dataclass(frozen=True, kw_only=True)
24
+ class Enter[**ParamT, RetT](composer_.Enter[ParamT, RetT], db_.Enter[ParamT, RetT]): ...
25
+
26
+
27
+ @typing.final
28
+ @dataclasses.dataclass(frozen=True, kw_only=True)
29
+ class Composed[**ParamT, RetT](composer_.Composed[ParamT, RetT], db_.Composed[ParamT, RetT]): ...
30
+
31
+
32
+ @typing.final
33
+ @dataclasses.dataclass(frozen=True, kw_only=True)
34
+ class Db[**ParamT = ..., RetT = typing.Any](composer_.Composer[ParamT, RetT], db_.Composer[ParamT, RetT]):
35
+ Composee: typing.ClassVar = Composee
36
+ Connect: typing.ClassVar = Connect
37
+ Exit: typing.ClassVar = Exit
38
+ Enter: typing.ClassVar = Enter
39
+ Composed: typing.ClassVar = Composed
40
+
41
+
42
+ Composer = Db
43
+ db = Db()
@@ -0,0 +1,21 @@
1
+ from __future__ import annotations
2
+
3
+ import importlib as _importlib
4
+ import typing as _typing
5
+
6
+ if _typing.TYPE_CHECKING:
7
+ from . import composer_
8
+ from .composer_ import Composer
9
+
10
+
11
+ def __getattr__(name: str) -> _typing.Any:
12
+ match name:
13
+ case 'composer_': return _importlib.import_module('.composer_', __name__)
14
+ case 'Composer': return _importlib.import_module('.composer_', __name__).Composer
15
+ case _: raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
16
+
17
+
18
+ __all__ = (
19
+ 'composer_',
20
+ 'Composer',
21
+ )
@@ -0,0 +1,74 @@
1
+ import abc
2
+ import dataclasses
3
+ import typing
4
+
5
+ from ..abc import composer_
6
+
7
+
8
+ Raise = composer_.Raise
9
+ Stop = composer_.Stop
10
+ Param = composer_.Param
11
+ Return = composer_.Return
12
+ type ValueT[**ParamT_, RetT_] = composer_.ValueT[ParamT_, RetT_]
13
+ type StackT = tuple[
14
+ ValueT[typing.Any, typing.Any]
15
+ | Composee[typing.Any, typing.Any]
16
+ | Connect[typing.Any, typing.Any]
17
+ | Exit[typing.Any, typing.Any]
18
+ | Enter[typing.Any, typing.Any]
19
+ | Composed[typing.Any, typing.Any],
20
+ ...,
21
+ ]
22
+
23
+
24
+ class Composee[**ParamT, RetT](composer_.Composee[ParamT, RetT], typing.Protocol):
25
+ def __call__(*args: ParamT.args, **kwargs: ParamT.kwargs) -> RetT: ...
26
+
27
+
28
+ @dataclasses.dataclass(frozen=True, kw_only=True)
29
+ class Connect[**ParamT, RetT](composer_.Connect[ParamT, RetT], abc.ABC):
30
+ def __call__(self, value: ValueT[ParamT, RetT], /) -> StackT: return super().__call__(value)
31
+
32
+
33
+ @dataclasses.dataclass(frozen=True, kw_only=True)
34
+ class Exit[**ParamT, RetT](composer_.Exit[ParamT, RetT], abc.ABC):
35
+ def __call__(self, value: ValueT[ParamT, RetT], /) -> StackT: return super().__call__(value)
36
+
37
+
38
+ @dataclasses.dataclass(frozen=True, kw_only=True)
39
+ class Enter[**ParamT, RetT](composer_.Enter[ParamT, RetT], abc.ABC):
40
+ def __call__(self, value: ValueT[ParamT, RetT], /) -> StackT: return super().__call__(value)
41
+
42
+
43
+ @dataclasses.dataclass(frozen=True, kw_only=True)
44
+ class Composed[**ParamT, RetT](composer_.Composed[ParamT, RetT], abc.ABC):
45
+ def __call__(self, *args: ParamT.args, **kwargs: ParamT.kwargs) -> RetT:
46
+ value: Param[ParamT] | Raise | Return[RetT] | Stop = Param(args=args, kwargs=kwargs)
47
+ stack = list(self.stack)
48
+ while stack:
49
+ match stack.pop():
50
+ case Param() | Raise() | Return() | Stop() as value_: value = value_
51
+ case Enter() | Exit() | Connect() as get_stack_: stack += get_stack_(value)
52
+ case Composed() as composed_ if isinstance(value, Param): stack += composed_.stack
53
+ case composee_ if isinstance(value, Param):
54
+ try:
55
+ value = Return(ret=composee_(*value.args, **value.kwargs))
56
+ except Stop as stopped_:
57
+ value = stopped_
58
+ except Exception as exception_:
59
+ value = Raise(exc_type=type(exception_), exc_val=exception_, exc_tb=exception_.__traceback__)
60
+
61
+ match value:
62
+ case Stop() as stop_: raise stop_
63
+ case Raise() as raise_: raise raise_.exc_val
64
+ case Return() as return_: return return_.ret # type: ignore[no-any-return]
65
+ case _: raise Exception(f'{type(self).__name__} call completed with invalid {value=!r}')
66
+
67
+
68
+ @dataclasses.dataclass(frozen=True, kw_only=True)
69
+ class Composer[**ParamT, RetT](composer_.Composer[ParamT, RetT]):
70
+ Composee: typing.ClassVar = Composee
71
+ Connect: typing.ClassVar = Connect
72
+ Exit: typing.ClassVar = Exit
73
+ Enter: typing.ClassVar = Enter
74
+ Composed: typing.ClassVar = Composed