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,92 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import dataclasses
|
|
3
|
+
import typing
|
|
4
|
+
|
|
5
|
+
from ..abc import lru_cache_
|
|
6
|
+
from ...asyncio import composer_
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
Raise = composer_.Raise
|
|
10
|
+
Stop = composer_.Stop
|
|
11
|
+
Param = composer_.Param
|
|
12
|
+
Return = composer_.Return
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@typing.runtime_checkable
|
|
16
|
+
class Composee[**ParamT, RetT](composer_.Composee[ParamT, RetT], lru_cache_.Composee[ParamT, RetT], typing.Protocol): ...
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@typing.final
|
|
20
|
+
@dataclasses.dataclass(frozen=True, kw_only=True)
|
|
21
|
+
class Future[RetT](lru_cache_.Future[RetT]):
|
|
22
|
+
future: asyncio.Future[RetT] = dataclasses.field(default_factory=asyncio.Future)
|
|
23
|
+
|
|
24
|
+
@typing.override
|
|
25
|
+
def set_value(self, value: Return[RetT] | Raise | Stop) -> None:
|
|
26
|
+
match value:
|
|
27
|
+
case Return(ret=ret): self.future.set_result(ret)
|
|
28
|
+
case Raise() as raise_: self.future.set_exception(raise_.exc_val)
|
|
29
|
+
case Stop() as stop_: self.future.set_exception(stop_)
|
|
30
|
+
|
|
31
|
+
@typing.override
|
|
32
|
+
async def __call__(self, *args: typing.Any, **kwargs: typing.Any) -> RetT:
|
|
33
|
+
return await self.future
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@typing.final
|
|
37
|
+
@dataclasses.dataclass(frozen=True, kw_only=True)
|
|
38
|
+
class Connect[**ParamT, RetT](composer_.Connect[ParamT, RetT], lru_cache_.Connect[ParamT, RetT]): ...
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@typing.final
|
|
42
|
+
@dataclasses.dataclass(frozen=True, kw_only=True)
|
|
43
|
+
class Exit[**ParamT, RetT](composer_.Exit[ParamT, RetT], lru_cache_.Exit[ParamT, RetT, Future[RetT]]):
|
|
44
|
+
@typing.override
|
|
45
|
+
async def __call__(self, value: composer_.ValueT[ParamT, RetT], /) -> composer_.StackT: # type: ignore[override]
|
|
46
|
+
match value:
|
|
47
|
+
case Param(): pass
|
|
48
|
+
case Return() | Raise() | Stop(): self.future.set_value(value)
|
|
49
|
+
|
|
50
|
+
return ()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@typing.final
|
|
54
|
+
@dataclasses.dataclass(frozen=True, kw_only=True)
|
|
55
|
+
class Enter[**ParamT, RetT](composer_.Enter[ParamT, RetT], lru_cache_.Enter[ParamT, RetT, Future[RetT]]):
|
|
56
|
+
# TODO: Dedup this with the threading version.
|
|
57
|
+
@typing.override
|
|
58
|
+
async def __call__( # type: ignore[override]
|
|
59
|
+
self, value: composer_.ValueT[ParamT, RetT], /,
|
|
60
|
+
) -> composer_.StackT:
|
|
61
|
+
match value:
|
|
62
|
+
case Param():
|
|
63
|
+
key = self.create_key(*value.args, **value.kwargs)
|
|
64
|
+
if (future := self.get_future(key)) is not None:
|
|
65
|
+
return future,
|
|
66
|
+
future = self.set_future(key)
|
|
67
|
+
return self.composer.Exit(enter=self, future=future, key=key), self.composee # type: ignore[call-arg, return-value]
|
|
68
|
+
case _: return ()
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@typing.final
|
|
72
|
+
@dataclasses.dataclass(frozen=True, kw_only=True)
|
|
73
|
+
class Composed[**ParamT, RetT](composer_.Composed[ParamT, RetT], lru_cache_.Composed[ParamT, RetT]): ...
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@typing.final
|
|
77
|
+
@dataclasses.dataclass(frozen=True, kw_only=True)
|
|
78
|
+
class LruCache[**ParamT = ..., RetT = typing.Any](composer_.Composer[ParamT, RetT], lru_cache_.Composer[ParamT, RetT]):
|
|
79
|
+
Composee: typing.ClassVar = Composee
|
|
80
|
+
Connect: typing.ClassVar = Connect
|
|
81
|
+
Exit: typing.ClassVar = Exit # type: ignore[assignment]
|
|
82
|
+
Enter: typing.ClassVar = Enter
|
|
83
|
+
Composed: typing.ClassVar = Composed
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
@typing.override
|
|
87
|
+
def future_t(self) -> type[Future[RetT]]:
|
|
88
|
+
return Future
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
Composer = LruCache
|
|
92
|
+
lru_cache = LruCache()
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import dataclasses
|
|
2
|
+
import typing
|
|
3
|
+
|
|
4
|
+
from ..abc import retry_
|
|
5
|
+
from ...asyncio import composer_
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@typing.runtime_checkable
|
|
9
|
+
class Composee[**ParamT, RetT](composer_.Composee[ParamT, RetT], retry_.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], retry_.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], retry_.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], retry_.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], retry_.Composed[ParamT, RetT]): ...
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@typing.final
|
|
33
|
+
@dataclasses.dataclass(frozen=True, kw_only=True)
|
|
34
|
+
class Retry[**ParamT = ..., RetT = typing.Any](composer_.Composer[ParamT, RetT], retry_.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 = Retry
|
|
43
|
+
retry = Retry()
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import dataclasses
|
|
3
|
+
import typing
|
|
4
|
+
|
|
5
|
+
from ..abc import throttle_
|
|
6
|
+
from ...asyncio import composer_
|
|
7
|
+
|
|
8
|
+
type _Condition = asyncio.Condition
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@typing.runtime_checkable
|
|
12
|
+
class Composee[**ParamT, RetT](composer_.Composee[ParamT, RetT], throttle_.Composee[ParamT, RetT], typing.Protocol): ...
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
Param = composer_.Param
|
|
16
|
+
Raise = composer_.Raise
|
|
17
|
+
Return = composer_.Return
|
|
18
|
+
Stop = composer_.Stop
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@typing.final
|
|
22
|
+
@dataclasses.dataclass(frozen=True, kw_only=True)
|
|
23
|
+
class Connect[**ParamT, RetT](composer_.Connect[ParamT, RetT], throttle_.Connect[ParamT, RetT]): ...
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@typing.final
|
|
27
|
+
@dataclasses.dataclass(frozen=True, kw_only=True)
|
|
28
|
+
class Exit[**ParamT, RetT](composer_.Exit[ParamT, RetT], throttle_.Exit[ParamT, RetT]):
|
|
29
|
+
async def __call__(self, value: composer_.ValueT[ParamT, RetT], /) -> composer_.StackT: # type: ignore[override]
|
|
30
|
+
async with self.enter.condition: # type: ignore[attr-defined]
|
|
31
|
+
return await super().__call__(value)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@typing.final
|
|
35
|
+
@dataclasses.dataclass(frozen=True, kw_only=True)
|
|
36
|
+
class Enter[**ParamT, RetT](composer_.Enter[ParamT, RetT], throttle_.Enter[ParamT, RetT]):
|
|
37
|
+
condition: _Condition = dataclasses.field(default_factory=asyncio.Condition)
|
|
38
|
+
|
|
39
|
+
async def __call__(self, value: composer_.ValueT[ParamT, RetT], /) -> composer_.StackT: # type: ignore[override]
|
|
40
|
+
# TODO: this is mostly duplicate with the threading version. Try to consolidate.
|
|
41
|
+
match value:
|
|
42
|
+
case Param():
|
|
43
|
+
state = self.state
|
|
44
|
+
async with self.condition:
|
|
45
|
+
if state.num_waiting >= self.composer.max_waiting: # type: ignore[attr-defined]
|
|
46
|
+
raise Exception(f'Exceeded {self.composer.max_waiting=}.') # type: ignore[attr-defined]
|
|
47
|
+
elif 0 < state.num_waiting or state.cap_running <= state.num_running:
|
|
48
|
+
state.num_waiting += 1
|
|
49
|
+
await self.condition.wait_for(lambda: state.num_running < state.cap_running)
|
|
50
|
+
state.num_waiting -= 1
|
|
51
|
+
state.num_running += 1
|
|
52
|
+
|
|
53
|
+
return self.composer.Exit(enter=self), self.composee, # type: ignore[return-value]
|
|
54
|
+
case _:
|
|
55
|
+
return ()
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@typing.final
|
|
59
|
+
@dataclasses.dataclass(frozen=True, kw_only=True)
|
|
60
|
+
class Composed[**ParamT, RetT](composer_.Composed[ParamT, RetT], throttle_.Composed[ParamT, RetT]): ...
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@typing.final
|
|
64
|
+
@dataclasses.dataclass(frozen=True, kw_only=True)
|
|
65
|
+
class Throttle[**ParamT = ..., RetT = typing.Any](composer_.Composer[ParamT, RetT], throttle_.Composer[ParamT, RetT]):
|
|
66
|
+
Composee: typing.ClassVar = Composee
|
|
67
|
+
Connect: typing.ClassVar = Connect
|
|
68
|
+
Exit: typing.ClassVar = Exit
|
|
69
|
+
Enter: typing.ClassVar = Enter
|
|
70
|
+
Composed: typing.ClassVar = Composed
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
Composer = Throttle
|
|
74
|
+
throttle = Throttle()
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import dataclasses
|
|
2
|
+
import typing
|
|
3
|
+
|
|
4
|
+
from .abc import herd_
|
|
5
|
+
from .. import composer_
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclasses.dataclass(frozen=True, kw_only=True)
|
|
9
|
+
class Herd(composer_.Composer[..., typing.Any], herd_.Herd[..., typing.Any]):
|
|
10
|
+
@property
|
|
11
|
+
def future_t(self) -> type:
|
|
12
|
+
raise NotImplementedError(
|
|
13
|
+
f'{type(self).__name__} is a dispatcher; future_t is provided by the threading or asyncio variant.'
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
Composer = Herd
|
|
18
|
+
herd = Herd()
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import dataclasses
|
|
2
|
+
import typing
|
|
3
|
+
|
|
4
|
+
from .abc import lru_cache_
|
|
5
|
+
from .. import composer_
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclasses.dataclass(frozen=True, kw_only=True)
|
|
9
|
+
class LruCache(composer_.Composer[..., typing.Any], lru_cache_.LruCache[..., typing.Any]):
|
|
10
|
+
@property
|
|
11
|
+
def future_t(self) -> type:
|
|
12
|
+
raise NotImplementedError(
|
|
13
|
+
f'{type(self).__name__} is a dispatcher; future_t is provided by the threading or asyncio variant.'
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
Composer = LruCache
|
|
18
|
+
lru_cache = LruCache()
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import dataclasses
|
|
2
|
+
import typing
|
|
3
|
+
|
|
4
|
+
from .abc import retry_
|
|
5
|
+
from .. import composer_
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclasses.dataclass(frozen=True, kw_only=True)
|
|
9
|
+
class Retry(composer_.Composer[..., typing.Any], retry_.Retry[..., typing.Any]): ...
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
Composer = Retry
|
|
13
|
+
retry = Retry()
|
|
@@ -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 herd_
|
|
8
|
+
from .herd_ import Herd
|
|
9
|
+
from .herd_ import herd
|
|
10
|
+
from . import lru_cache_
|
|
11
|
+
from .lru_cache_ import LruCache
|
|
12
|
+
from .lru_cache_ import lru_cache
|
|
13
|
+
from . import retry_
|
|
14
|
+
from .retry_ import Retry
|
|
15
|
+
from .retry_ import retry
|
|
16
|
+
from . import throttle_
|
|
17
|
+
from .throttle_ import Throttle
|
|
18
|
+
from .throttle_ import throttle
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def __getattr__(name: str) -> _typing.Any:
|
|
22
|
+
match name:
|
|
23
|
+
case 'herd_': return _importlib.import_module('.herd_', __name__)
|
|
24
|
+
case 'Herd': return _importlib.import_module('.herd_', __name__).Herd
|
|
25
|
+
case 'herd': return _importlib.import_module('.herd_', __name__).herd
|
|
26
|
+
case 'lru_cache_': return _importlib.import_module('.lru_cache_', __name__)
|
|
27
|
+
case 'LruCache': return _importlib.import_module('.lru_cache_', __name__).LruCache
|
|
28
|
+
case 'lru_cache': return _importlib.import_module('.lru_cache_', __name__).lru_cache
|
|
29
|
+
case 'retry_': return _importlib.import_module('.retry_', __name__)
|
|
30
|
+
case 'Retry': return _importlib.import_module('.retry_', __name__).Retry
|
|
31
|
+
case 'retry': return _importlib.import_module('.retry_', __name__).retry
|
|
32
|
+
case 'throttle_': return _importlib.import_module('.throttle_', __name__)
|
|
33
|
+
case 'Throttle': return _importlib.import_module('.throttle_', __name__).Throttle
|
|
34
|
+
case 'throttle': return _importlib.import_module('.throttle_', __name__).throttle
|
|
35
|
+
case _: raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
__all__ = (
|
|
39
|
+
'herd_',
|
|
40
|
+
'Herd',
|
|
41
|
+
'herd',
|
|
42
|
+
'lru_cache_',
|
|
43
|
+
'LruCache',
|
|
44
|
+
'lru_cache',
|
|
45
|
+
'retry_',
|
|
46
|
+
'Retry',
|
|
47
|
+
'retry',
|
|
48
|
+
'throttle_',
|
|
49
|
+
'Throttle',
|
|
50
|
+
'throttle',
|
|
51
|
+
)
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import concurrent.futures
|
|
2
|
+
import dataclasses
|
|
3
|
+
import typing
|
|
4
|
+
|
|
5
|
+
from ..abc import herd_
|
|
6
|
+
from ...threading import composer_
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
Raise = composer_.Raise
|
|
10
|
+
Stop = composer_.Stop
|
|
11
|
+
Param = composer_.Param
|
|
12
|
+
Return = composer_.Return
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@typing.runtime_checkable
|
|
16
|
+
class Composee[**ParamT, RetT](composer_.Composee[ParamT, RetT], herd_.Composee[ParamT, RetT], typing.Protocol): ...
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@typing.final
|
|
20
|
+
@dataclasses.dataclass(frozen=True, kw_only=True)
|
|
21
|
+
class Future[RetT](herd_.Future[RetT]):
|
|
22
|
+
future: concurrent.futures.Future[RetT] = dataclasses.field(default_factory=concurrent.futures.Future)
|
|
23
|
+
|
|
24
|
+
@typing.override
|
|
25
|
+
def set_value(self, value: Return[RetT] | Raise | Stop) -> None:
|
|
26
|
+
match value:
|
|
27
|
+
case Return(ret=ret): self.future.set_result(ret)
|
|
28
|
+
case Raise() as raise_: self.future.set_exception(raise_.exc_val)
|
|
29
|
+
case Stop() as stop_: self.future.set_exception(stop_)
|
|
30
|
+
|
|
31
|
+
@typing.override
|
|
32
|
+
def __call__(self, *args: typing.Any, **kwargs: typing.Any) -> RetT:
|
|
33
|
+
return self.future.result()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@typing.final
|
|
37
|
+
@dataclasses.dataclass(frozen=True, kw_only=True)
|
|
38
|
+
class Connect[**ParamT, RetT](composer_.Connect[ParamT, RetT], herd_.Connect[ParamT, RetT]): ...
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@typing.final
|
|
42
|
+
@dataclasses.dataclass(frozen=True, kw_only=True)
|
|
43
|
+
class Exit[**ParamT, RetT](composer_.Exit[ParamT, RetT], herd_.Exit[ParamT, RetT, Future[RetT]]):
|
|
44
|
+
future: Future[RetT] = dataclasses.field(default_factory=Future)
|
|
45
|
+
|
|
46
|
+
def __call__(self, value: composer_.ValueT[ParamT, RetT], /) -> composer_.StackT:
|
|
47
|
+
self.enter.future_by_key.pop(self.key, None) # type: ignore[attr-defined]
|
|
48
|
+
match value:
|
|
49
|
+
case Param(): pass
|
|
50
|
+
case Return() | Raise() | Stop(): self.future.set_value(value)
|
|
51
|
+
|
|
52
|
+
return ()
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@typing.final
|
|
56
|
+
@dataclasses.dataclass(frozen=True, kw_only=True)
|
|
57
|
+
class Enter[**ParamT, RetT](composer_.Enter[ParamT, RetT], herd_.Enter[ParamT, RetT, Future[RetT]]):
|
|
58
|
+
@typing.override
|
|
59
|
+
def __call__(
|
|
60
|
+
self, value: composer_.ValueT[ParamT, RetT], /,
|
|
61
|
+
) -> composer_.StackT:
|
|
62
|
+
match value:
|
|
63
|
+
case Param(): return self._dispatch(*value.args, **value.kwargs) # type: ignore[return-value]
|
|
64
|
+
case _: return ()
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@typing.final
|
|
68
|
+
@dataclasses.dataclass(frozen=True, kw_only=True)
|
|
69
|
+
class Composed[**ParamT, RetT](composer_.Composed[ParamT, RetT], herd_.Composed[ParamT, RetT]): ...
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@typing.final
|
|
73
|
+
@dataclasses.dataclass(frozen=True, kw_only=True)
|
|
74
|
+
class Herd[**ParamT = ..., RetT = typing.Any](composer_.Composer[ParamT, RetT], herd_.Herd[ParamT, RetT]):
|
|
75
|
+
Composee: typing.ClassVar = Composee
|
|
76
|
+
Connect: typing.ClassVar = Connect
|
|
77
|
+
Exit: typing.ClassVar = Exit # type: ignore[assignment]
|
|
78
|
+
Enter: typing.ClassVar = Enter
|
|
79
|
+
Composed: typing.ClassVar = Composed
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
@typing.override
|
|
83
|
+
def future_t(self) -> type[Future[RetT]]:
|
|
84
|
+
return Future
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
Composer = Herd
|
|
88
|
+
herd = Herd()
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import concurrent.futures
|
|
2
|
+
import dataclasses
|
|
3
|
+
import typing
|
|
4
|
+
|
|
5
|
+
from ..abc import lru_cache_
|
|
6
|
+
from ...threading import composer_
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
Raise = composer_.Raise
|
|
10
|
+
Stop = composer_.Stop
|
|
11
|
+
Param = composer_.Param
|
|
12
|
+
Return = composer_.Return
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@typing.runtime_checkable
|
|
16
|
+
class Composee[**ParamT, RetT](composer_.Composee[ParamT, RetT], lru_cache_.Composee[ParamT, RetT], typing.Protocol): ...
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@typing.final
|
|
20
|
+
@dataclasses.dataclass(frozen=True, kw_only=True)
|
|
21
|
+
class Future[RetT](lru_cache_.Future[RetT]):
|
|
22
|
+
future: concurrent.futures.Future[RetT] = dataclasses.field(default_factory=concurrent.futures.Future)
|
|
23
|
+
|
|
24
|
+
@typing.override
|
|
25
|
+
def set_value(self, value: Return[RetT] | Raise | Stop) -> None:
|
|
26
|
+
match value:
|
|
27
|
+
case Return(ret=ret): self.future.set_result(ret)
|
|
28
|
+
case Raise() as raise_: self.future.set_exception(raise_.exc_val)
|
|
29
|
+
case Stop() as stop_: self.future.set_exception(stop_)
|
|
30
|
+
|
|
31
|
+
@typing.override
|
|
32
|
+
def __call__(self, *args: typing.Any, **kwargs: typing.Any) -> RetT:
|
|
33
|
+
return self.future.result()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@typing.final
|
|
37
|
+
@dataclasses.dataclass(frozen=True, kw_only=True)
|
|
38
|
+
class Connect[**ParamT, RetT](composer_.Connect[ParamT, RetT], lru_cache_.Connect[ParamT, RetT]): ...
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@typing.final
|
|
42
|
+
@dataclasses.dataclass(frozen=True, kw_only=True)
|
|
43
|
+
class Exit[**ParamT, RetT](composer_.Exit[ParamT, RetT], lru_cache_.Exit[ParamT, RetT, Future[RetT]]):
|
|
44
|
+
def __call__(self, value: composer_.ValueT[ParamT, RetT], /) -> composer_.StackT:
|
|
45
|
+
match value:
|
|
46
|
+
case Param(): pass
|
|
47
|
+
case Return() | Raise() | Stop(): self.future.set_value(value)
|
|
48
|
+
|
|
49
|
+
return ()
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@typing.final
|
|
53
|
+
@dataclasses.dataclass(frozen=True, kw_only=True)
|
|
54
|
+
class Enter[**ParamT, RetT](composer_.Enter[ParamT, RetT], lru_cache_.Enter[ParamT, RetT, Future[RetT]]):
|
|
55
|
+
# TODO: Dedup this with the asyncio version.
|
|
56
|
+
def __call__(
|
|
57
|
+
self, value: composer_.ValueT[ParamT, RetT], /,
|
|
58
|
+
) -> composer_.StackT:
|
|
59
|
+
match value:
|
|
60
|
+
case Param():
|
|
61
|
+
key = self.create_key(*value.args, **value.kwargs)
|
|
62
|
+
if (future := self.get_future(key)) is not None:
|
|
63
|
+
return future,
|
|
64
|
+
future = self.set_future(key)
|
|
65
|
+
return self.composer.Exit(enter=self, future=future, key=key), self.composee # type: ignore[call-arg, return-value]
|
|
66
|
+
case _: return ()
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@typing.final
|
|
70
|
+
@dataclasses.dataclass(frozen=True, kw_only=True)
|
|
71
|
+
class Composed[**ParamT, RetT](composer_.Composed[ParamT, RetT], lru_cache_.Composed[ParamT, RetT]): ...
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@typing.final
|
|
75
|
+
@dataclasses.dataclass(frozen=True, kw_only=True)
|
|
76
|
+
class LruCache[**ParamT = ..., RetT = typing.Any](composer_.Composer[ParamT, RetT], lru_cache_.Composer[ParamT, RetT]):
|
|
77
|
+
Composee: typing.ClassVar = Composee
|
|
78
|
+
Connect: typing.ClassVar = Connect
|
|
79
|
+
Exit: typing.ClassVar = Exit # type: ignore[assignment]
|
|
80
|
+
Enter: typing.ClassVar = Enter
|
|
81
|
+
Composed: typing.ClassVar = Composed
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
@typing.override
|
|
85
|
+
def future_t(self) -> type[Future[RetT]]:
|
|
86
|
+
return Future
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
Composer = LruCache
|
|
90
|
+
lru_cache = LruCache()
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import dataclasses
|
|
2
|
+
import typing
|
|
3
|
+
|
|
4
|
+
from ..abc import retry_
|
|
5
|
+
from ...threading import composer_
|
|
6
|
+
|
|
7
|
+
@typing.runtime_checkable
|
|
8
|
+
class Composee[**ParamT, RetT](composer_.Composee[ParamT, RetT], retry_.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], retry_.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], retry_.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], retry_.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], retry_.Composed[ParamT, RetT]): ...
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@typing.final
|
|
32
|
+
@dataclasses.dataclass(frozen=True, kw_only=True)
|
|
33
|
+
class Retry[**ParamT = ..., RetT = typing.Any](composer_.Composer[ParamT, RetT], retry_.Composer[ParamT, RetT]):
|
|
34
|
+
Composee: typing.ClassVar = Composee
|
|
35
|
+
Connect: typing.ClassVar = Connect
|
|
36
|
+
Exit: typing.ClassVar = Exit
|
|
37
|
+
Enter: typing.ClassVar = Enter
|
|
38
|
+
Composed: typing.ClassVar = Composed
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
Composer = Retry
|
|
42
|
+
retry = Retry()
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import dataclasses
|
|
2
|
+
import threading
|
|
3
|
+
import typing
|
|
4
|
+
|
|
5
|
+
from ...threading import composer_
|
|
6
|
+
from ..abc import throttle_
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
type _Condition = threading.Condition
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@typing.runtime_checkable
|
|
13
|
+
class Composee[**ParamT, RetT](composer_.Composee[ParamT, RetT], throttle_.Composee[ParamT, RetT], typing.Protocol): ...
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
Param = composer_.Param
|
|
17
|
+
Raise = composer_.Raise
|
|
18
|
+
Return = composer_.Return
|
|
19
|
+
Stop = composer_.Stop
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@typing.final
|
|
23
|
+
@dataclasses.dataclass(frozen=True, kw_only=True)
|
|
24
|
+
class Connect[**ParamT, RetT](composer_.Connect[ParamT, RetT], throttle_.Connect[ParamT, RetT]): ...
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@typing.final
|
|
28
|
+
@dataclasses.dataclass(frozen=True, kw_only=True)
|
|
29
|
+
class Exit[**ParamT, RetT](composer_.Exit[ParamT, RetT], throttle_.Exit[ParamT, RetT]):
|
|
30
|
+
def __call__(self, value: composer_.ValueT[ParamT, RetT], /) -> composer_.StackT:
|
|
31
|
+
with self.enter.condition: # type: ignore[attr-defined]
|
|
32
|
+
return super().__call__(value)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@typing.final
|
|
36
|
+
@dataclasses.dataclass(frozen=True, kw_only=True)
|
|
37
|
+
class Enter[**ParamT, RetT](composer_.Enter[ParamT, RetT], throttle_.Enter[ParamT, RetT]):
|
|
38
|
+
condition: _Condition = dataclasses.field(default_factory=threading.Condition)
|
|
39
|
+
|
|
40
|
+
def __call__(self, value: composer_.ValueT[ParamT, RetT], /) -> composer_.StackT:
|
|
41
|
+
# TODO: this is mostly duplicate with the asyncio version. Try to consolidate.
|
|
42
|
+
match value:
|
|
43
|
+
case Param():
|
|
44
|
+
state = self.state
|
|
45
|
+
with self.condition:
|
|
46
|
+
if state.num_waiting >= self.composer.max_waiting: # type: ignore[attr-defined]
|
|
47
|
+
raise Exception(f'Exceeded {self.composer.max_waiting=}.') # type: ignore[attr-defined]
|
|
48
|
+
elif 0 < state.num_waiting or state.cap_running <= state.num_running:
|
|
49
|
+
state.num_waiting += 1
|
|
50
|
+
self.condition.wait_for(lambda: state.num_running < state.cap_running)
|
|
51
|
+
state.num_waiting -= 1
|
|
52
|
+
state.num_running += 1
|
|
53
|
+
|
|
54
|
+
return self.composer.Exit(enter=self), self.composee, # type: ignore[return-value]
|
|
55
|
+
case _:
|
|
56
|
+
return ()
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@typing.final
|
|
60
|
+
@dataclasses.dataclass(frozen=True, kw_only=True)
|
|
61
|
+
class Composed[**ParamT, RetT](composer_.Composed[ParamT, RetT], throttle_.Composed[ParamT, RetT]): ...
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@typing.final
|
|
65
|
+
@dataclasses.dataclass(frozen=True, kw_only=True)
|
|
66
|
+
class Throttle[**ParamT = ..., RetT = typing.Any](composer_.Composer[ParamT, RetT], throttle_.Composer[ParamT, RetT]):
|
|
67
|
+
Composee: typing.ClassVar = Composee
|
|
68
|
+
Connect: typing.ClassVar = Connect
|
|
69
|
+
Exit: typing.ClassVar = Exit
|
|
70
|
+
Enter: typing.ClassVar = Enter
|
|
71
|
+
Composed: typing.ClassVar = Composed
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
Composer = Throttle
|
|
75
|
+
throttle = Throttle()
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import dataclasses
|
|
2
|
+
import typing
|
|
3
|
+
|
|
4
|
+
from .abc import throttle_
|
|
5
|
+
from .. import composer_
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclasses.dataclass(frozen=True, kw_only=True)
|
|
9
|
+
class Throttle(composer_.Composer[..., typing.Any], throttle_.Throttle[..., typing.Any]): ...
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
Composer = Throttle
|
|
13
|
+
throttle = Throttle()
|
|
@@ -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 logger_
|
|
8
|
+
from .logger_ import Logger
|
|
9
|
+
from .logger_ import logger
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def __getattr__(name: str) -> _typing.Any:
|
|
13
|
+
match name:
|
|
14
|
+
case 'logger_': return _importlib.import_module('.logger_', __name__)
|
|
15
|
+
case 'Logger': return _importlib.import_module('.logger_', __name__).Logger
|
|
16
|
+
case 'logger': return _importlib.import_module('.logger_', __name__).logger
|
|
17
|
+
case _: raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
__all__ = (
|
|
21
|
+
'logger_',
|
|
22
|
+
'Logger',
|
|
23
|
+
'logger',
|
|
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 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
|
+
)
|