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.
- stranded/__init__.py +51 -0
- stranded/abc/__init__.py +21 -0
- stranded/abc/composer_.py +191 -0
- stranded/argparse/__init__.py +24 -0
- stranded/argparse/abc/__init__.py +21 -0
- stranded/argparse/abc/argument_parser_.py +539 -0
- stranded/argparse/argument_parser_.py +13 -0
- stranded/argparse/asyncio/__init__.py +24 -0
- stranded/argparse/asyncio/argument_parser_.py +50 -0
- stranded/argparse/threading/__init__.py +24 -0
- stranded/argparse/threading/argument_parser_.py +52 -0
- stranded/asyncio/__init__.py +21 -0
- stranded/asyncio/composer_.py +76 -0
- stranded/builtins/__init__.py +21 -0
- stranded/builtins/exception_.py +6 -0
- stranded/composer_.py +27 -0
- stranded/execution/__init__.py +30 -0
- stranded/execution/abc/__init__.py +21 -0
- stranded/execution/abc/scheduler_.py +59 -0
- stranded/execution/asyncio/__init__.py +21 -0
- stranded/execution/asyncio/scheduler_.py +86 -0
- stranded/execution/scheduler_.py +59 -0
- stranded/execution/threading/__init__.py +21 -0
- stranded/execution/threading/scheduler_.py +116 -0
- stranded/functools/__init__.py +51 -0
- stranded/functools/abc/__init__.py +39 -0
- stranded/functools/abc/herd_.py +96 -0
- stranded/functools/abc/lru_cache_.py +192 -0
- stranded/functools/abc/retry_.py +60 -0
- stranded/functools/abc/throttle_.py +97 -0
- stranded/functools/asyncio/__init__.py +51 -0
- stranded/functools/asyncio/herd_.py +89 -0
- stranded/functools/asyncio/lru_cache_.py +92 -0
- stranded/functools/asyncio/retry_.py +43 -0
- stranded/functools/asyncio/throttle_.py +74 -0
- stranded/functools/herd_.py +18 -0
- stranded/functools/lru_cache_.py +18 -0
- stranded/functools/retry_.py +13 -0
- stranded/functools/threading/__init__.py +51 -0
- stranded/functools/threading/herd_.py +88 -0
- stranded/functools/threading/lru_cache_.py +90 -0
- stranded/functools/threading/retry_.py +42 -0
- stranded/functools/threading/throttle_.py +75 -0
- stranded/functools/throttle_.py +13 -0
- stranded/logging/__init__.py +24 -0
- stranded/logging/abc/__init__.py +21 -0
- stranded/logging/abc/logger_.py +96 -0
- stranded/logging/asyncio/__init__.py +21 -0
- stranded/logging/asyncio/logger_.py +41 -0
- stranded/logging/logger_.py +13 -0
- stranded/logging/threading/__init__.py +21 -0
- stranded/logging/threading/logger_.py +41 -0
- stranded/py.typed +0 -0
- stranded/sqlite3/__init__.py +24 -0
- stranded/sqlite3/abc/__init__.py +21 -0
- stranded/sqlite3/abc/db_.py +149 -0
- stranded/sqlite3/asyncio/__init__.py +24 -0
- stranded/sqlite3/asyncio/db_.py +43 -0
- stranded/sqlite3/db_.py +13 -0
- stranded/sqlite3/threading/__init__.py +24 -0
- stranded/sqlite3/threading/db_.py +43 -0
- stranded/threading/__init__.py +21 -0
- stranded/threading/composer_.py +74 -0
- stranded/types/__init__.py +21 -0
- stranded/types/constraint.py +7 -0
- stranded/types/convert.py +158 -0
- stranded-0.0.1.dist-info/METADATA +173 -0
- stranded-0.0.1.dist-info/RECORD +71 -0
- stranded-0.0.1.dist-info/WHEEL +5 -0
- stranded-0.0.1.dist-info/licenses/LICENSE +21 -0
- 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()
|
stranded/sqlite3/db_.py
ADDED
|
@@ -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
|