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
stranded/__init__.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
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 abc
|
|
8
|
+
from . import argparse
|
|
9
|
+
from . import asyncio
|
|
10
|
+
from . import builtins
|
|
11
|
+
from . import composer_
|
|
12
|
+
from .composer_ import Composer
|
|
13
|
+
from . import execution
|
|
14
|
+
from . import functools
|
|
15
|
+
from . import logging
|
|
16
|
+
from . import sqlite3
|
|
17
|
+
from . import threading
|
|
18
|
+
from . import types
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def __getattr__(name: str) -> _typing.Any:
|
|
22
|
+
match name:
|
|
23
|
+
case 'abc': return _importlib.import_module('.abc', __name__)
|
|
24
|
+
case 'argparse': return _importlib.import_module('.argparse', __name__)
|
|
25
|
+
case 'asyncio': return _importlib.import_module('.asyncio', __name__)
|
|
26
|
+
case 'builtins': return _importlib.import_module('.builtins', __name__)
|
|
27
|
+
case 'composer_': return _importlib.import_module('.composer_', __name__)
|
|
28
|
+
case 'Composer': return _importlib.import_module('.composer_', __name__).Composer
|
|
29
|
+
case 'execution': return _importlib.import_module('.execution', __name__)
|
|
30
|
+
case 'functools': return _importlib.import_module('.functools', __name__)
|
|
31
|
+
case 'logging': return _importlib.import_module('.logging', __name__)
|
|
32
|
+
case 'sqlite3': return _importlib.import_module('.sqlite3', __name__)
|
|
33
|
+
case 'threading': return _importlib.import_module('.threading', __name__)
|
|
34
|
+
case 'types': return _importlib.import_module('.types', __name__)
|
|
35
|
+
case _: raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
__all__ = (
|
|
39
|
+
'abc',
|
|
40
|
+
'argparse',
|
|
41
|
+
'asyncio',
|
|
42
|
+
'builtins',
|
|
43
|
+
'composer_',
|
|
44
|
+
'Composer',
|
|
45
|
+
'execution',
|
|
46
|
+
'functools',
|
|
47
|
+
'logging',
|
|
48
|
+
'sqlite3',
|
|
49
|
+
'threading',
|
|
50
|
+
'types',
|
|
51
|
+
)
|
stranded/abc/__init__.py
ADDED
|
@@ -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,191 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import abc
|
|
4
|
+
import annotationlib
|
|
5
|
+
import dataclasses
|
|
6
|
+
import inspect
|
|
7
|
+
import types
|
|
8
|
+
import typing
|
|
9
|
+
|
|
10
|
+
type Instance = object
|
|
11
|
+
|
|
12
|
+
type ValueT[**ParamT_, RetT_] = Param[ParamT_] | Raise | Return[RetT_] | Stop
|
|
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
|
+
def replace[T](obj: T, /, **changes: typing.Any) -> T:
|
|
25
|
+
"""Copy `obj` - a frozen dataclass - with `changes` applied.
|
|
26
|
+
|
|
27
|
+
`dataclasses.replace` re-runs `__init__`, which costs several times as much as copying the
|
|
28
|
+
instance dict does. Binding a composition to an instance happens on every attribute access, so
|
|
29
|
+
it is worth copying directly.
|
|
30
|
+
"""
|
|
31
|
+
copy = object.__new__(type(obj))
|
|
32
|
+
copy.__dict__.update(obj.__dict__, **changes)
|
|
33
|
+
return copy
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclasses.dataclass(frozen=True)
|
|
37
|
+
class Raise:
|
|
38
|
+
exc_type: type[BaseException]
|
|
39
|
+
exc_val: BaseException
|
|
40
|
+
exc_tb: types.TracebackType | None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclasses.dataclass(frozen=True)
|
|
44
|
+
class Stop(BaseException): ...
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclasses.dataclass(frozen=True, kw_only=True)
|
|
48
|
+
class Param[**ParamT]:
|
|
49
|
+
args: ParamT.args
|
|
50
|
+
kwargs: ParamT.kwargs
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclasses.dataclass(frozen=True)
|
|
54
|
+
class Return[RetT]:
|
|
55
|
+
ret: RetT
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class Composee[**ParamT, RetT](typing.Protocol):
|
|
59
|
+
__doc__: str
|
|
60
|
+
__module__: str
|
|
61
|
+
__name__: str
|
|
62
|
+
__qualname__: str
|
|
63
|
+
def __call__(self, *args: ParamT.args, **kwargs: ParamT.kwargs) -> RetT: ...
|
|
64
|
+
def __get__(self, instance: Instance, owner: type[object] | None) -> typing.Self: ...
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@dataclasses.dataclass(frozen=True, kw_only=True)
|
|
68
|
+
class Connect[**ParamT, RetT](abc.ABC):
|
|
69
|
+
sender: Composed[ParamT, RetT]
|
|
70
|
+
receiver: Composed[typing.Any, typing.Any]
|
|
71
|
+
composer: Composer[ParamT, RetT]
|
|
72
|
+
|
|
73
|
+
def __call__(self, value: ValueT[ParamT, RetT], /) -> StackT:
|
|
74
|
+
match value:
|
|
75
|
+
case Param() as param_: return *self.receiver.stack, param_
|
|
76
|
+
case Raise() as raise_: return raise_,
|
|
77
|
+
case Return() as return_: return *self.receiver.stack, Param(args=(return_.ret,), kwargs={}),
|
|
78
|
+
case Stop() as stop_: return stop_,
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@dataclasses.dataclass(frozen=True, kw_only=True)
|
|
82
|
+
class Exit[**ParamT, RetT](abc.ABC):
|
|
83
|
+
enter: Enter[ParamT, RetT]
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
def composer(self) -> Composer[typing.Any, typing.Any]: return self.enter.composer
|
|
87
|
+
|
|
88
|
+
def __call__(self, value: ValueT[ParamT, RetT], /) -> StackT:
|
|
89
|
+
return ()
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@dataclasses.dataclass(frozen=True, kw_only=True)
|
|
93
|
+
class Enter[**ParamT, RetT](abc.ABC):
|
|
94
|
+
composer: Composer[ParamT, RetT]
|
|
95
|
+
composee: Composee[ParamT, RetT]
|
|
96
|
+
|
|
97
|
+
def __call__(self, value: ValueT[ParamT, RetT], /) -> StackT:
|
|
98
|
+
match value, self.composee:
|
|
99
|
+
case Param(), Composed() as composed_: return self.composer.Exit(enter=self), *composed_.stack
|
|
100
|
+
case Param(), composee_: return self.composer.Exit(enter=self), composee_,
|
|
101
|
+
case _: return ()
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@dataclasses.dataclass(frozen=True, kw_only=True)
|
|
105
|
+
class Composed[**ParamT, RetT](abc.ABC):
|
|
106
|
+
__doc__: str
|
|
107
|
+
__module__: str
|
|
108
|
+
__name__: str
|
|
109
|
+
__qualname__: str
|
|
110
|
+
__signature__: inspect.Signature
|
|
111
|
+
composer: Composer[ParamT, RetT]
|
|
112
|
+
stack: StackT
|
|
113
|
+
owner: type[object] | None = None
|
|
114
|
+
name: str | None = None
|
|
115
|
+
|
|
116
|
+
@abc.abstractmethod
|
|
117
|
+
def __call__(self, *args: ParamT.args, **kwargs: ParamT.kwargs) -> RetT: ...
|
|
118
|
+
|
|
119
|
+
@property
|
|
120
|
+
def enter(self) -> Enter[ParamT, RetT]:
|
|
121
|
+
"""The Enter at the bottom of the stack, which holds whatever state the composition has."""
|
|
122
|
+
match self.stack[-1]:
|
|
123
|
+
case Enter() as enter_: return enter_
|
|
124
|
+
assert False, "unreachable"
|
|
125
|
+
|
|
126
|
+
def create_enter(self, instance: Instance) -> Enter[ParamT, RetT]:
|
|
127
|
+
"""Return the Enter that `instance`'s copy of this composition composes with.
|
|
128
|
+
|
|
129
|
+
Composers that keep per-instance state override this to hand each instance its own. Note
|
|
130
|
+
that whatever they hold it in must not refer back to `instance`, or the instance can never
|
|
131
|
+
be collected - which is why they hold Enters rather than whole bound compositions.
|
|
132
|
+
"""
|
|
133
|
+
return self.enter
|
|
134
|
+
|
|
135
|
+
def __set_name__(self, owner: type[object], name: str) -> None:
|
|
136
|
+
object.__setattr__(self, 'owner', owner)
|
|
137
|
+
object.__setattr__(self, 'name', name)
|
|
138
|
+
|
|
139
|
+
def __get__(self, instance: Instance, owner: type[object] | None) -> typing.Self:
|
|
140
|
+
if instance is None:
|
|
141
|
+
return self
|
|
142
|
+
enter = self.create_enter(instance)
|
|
143
|
+
return replace(
|
|
144
|
+
self, stack=(*self.stack[:-1], replace(enter, composee=enter.composee.__get__(instance, owner))),
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
def __or__[**ReceiverParamT, ReceiverRetT](
|
|
148
|
+
self,
|
|
149
|
+
receiver: Composed[ReceiverParamT, ReceiverRetT],
|
|
150
|
+
/,
|
|
151
|
+
) -> Composed[ParamT, ReceiverRetT]:
|
|
152
|
+
return dataclasses.replace(
|
|
153
|
+
self,
|
|
154
|
+
__doc__=f"{self.__doc__}\n\n{receiver.__doc__}",
|
|
155
|
+
__signature__=inspect.Signature().replace(
|
|
156
|
+
parameters=tuple(self.__signature__.parameters.values()),
|
|
157
|
+
return_annotation=receiver.__signature__.return_annotation,
|
|
158
|
+
),
|
|
159
|
+
stack=(self.composer.Connect(composer=self.composer, receiver=receiver, sender=self), *self.stack),
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
# Aliases for annotation use. The Composer ClassVars below shadow the class names
|
|
164
|
+
# within Composer's scope, so annotations must reference these instead.
|
|
165
|
+
type ComposeeT[**ParamT, RetT] = Composee[ParamT, RetT]
|
|
166
|
+
type ConnectT[**ParamT, RetT] = Connect[ParamT, RetT]
|
|
167
|
+
type ExitT[**ParamT, RetT] = Exit[ParamT, RetT]
|
|
168
|
+
type EnterT[**ParamT, RetT] = Enter[ParamT, RetT]
|
|
169
|
+
type ComposedT[**ParamT, RetT] = Composed[ParamT, RetT]
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
@dataclasses.dataclass(frozen=True, kw_only=True)
|
|
173
|
+
class Composer[**ParamT, RetT](abc.ABC):
|
|
174
|
+
Composee: typing.ClassVar[type[Composee[typing.Any, typing.Any]]]
|
|
175
|
+
Connect: typing.ClassVar[type[Connect[typing.Any, typing.Any]]]
|
|
176
|
+
Exit: typing.ClassVar[type[Exit[typing.Any, typing.Any]]]
|
|
177
|
+
Enter: typing.ClassVar[type[Enter[typing.Any, typing.Any]]]
|
|
178
|
+
Composed: typing.ClassVar[type[Composed[typing.Any, typing.Any]]]
|
|
179
|
+
|
|
180
|
+
def __call__[**CallParamT, CallRetT](
|
|
181
|
+
self, composee: ComposeeT[CallParamT, CallRetT], /,
|
|
182
|
+
) -> ComposedT[CallParamT, CallRetT]:
|
|
183
|
+
return self.Composed(
|
|
184
|
+
__doc__=str(composee.__doc__),
|
|
185
|
+
__module__=str(composee.__module__),
|
|
186
|
+
__name__=str(composee.__name__),
|
|
187
|
+
__qualname__=str(composee.__qualname__),
|
|
188
|
+
__signature__=inspect.signature(composee, annotation_format=annotationlib.Format.FORWARDREF),
|
|
189
|
+
composer=self,
|
|
190
|
+
stack=(self.Enter(composer=self, composee=composee),)
|
|
191
|
+
)
|
|
@@ -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 argument_parser_
|
|
8
|
+
from .argument_parser_ import ArgumentParser
|
|
9
|
+
from .argument_parser_ import argument_parser
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def __getattr__(name: str) -> _typing.Any:
|
|
13
|
+
match name:
|
|
14
|
+
case 'argument_parser_': return _importlib.import_module('.argument_parser_', __name__)
|
|
15
|
+
case 'ArgumentParser': return _importlib.import_module('.argument_parser_', __name__).ArgumentParser
|
|
16
|
+
case 'argument_parser': return _importlib.import_module('.argument_parser_', __name__).argument_parser
|
|
17
|
+
case _: raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
__all__ = (
|
|
21
|
+
'argument_parser_',
|
|
22
|
+
'ArgumentParser',
|
|
23
|
+
'argument_parser',
|
|
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 argument_parser_
|
|
8
|
+
from .argument_parser_ import ArgumentParser
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def __getattr__(name: str) -> _typing.Any:
|
|
12
|
+
match name:
|
|
13
|
+
case 'argument_parser_': return _importlib.import_module('.argument_parser_', __name__)
|
|
14
|
+
case 'ArgumentParser': return _importlib.import_module('.argument_parser_', __name__).ArgumentParser
|
|
15
|
+
case _: raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
__all__ = (
|
|
19
|
+
'argument_parser_',
|
|
20
|
+
'ArgumentParser',
|
|
21
|
+
)
|