thds.core 1.44.20250721225423__py3-none-any.whl → 1.44.20250722150732__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.
- thds/core/__init__.py +1 -0
- thds/core/futures.py +227 -0
- {thds_core-1.44.20250721225423.dist-info → thds_core-1.44.20250722150732.dist-info}/METADATA +1 -1
- {thds_core-1.44.20250721225423.dist-info → thds_core-1.44.20250722150732.dist-info}/RECORD +7 -6
- {thds_core-1.44.20250721225423.dist-info → thds_core-1.44.20250722150732.dist-info}/WHEEL +0 -0
- {thds_core-1.44.20250721225423.dist-info → thds_core-1.44.20250722150732.dist-info}/entry_points.txt +0 -0
- {thds_core-1.44.20250721225423.dist-info → thds_core-1.44.20250722150732.dist-info}/top_level.txt +0 -0
thds/core/__init__.py
CHANGED
thds/core/futures.py
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import concurrent.futures
|
|
2
|
+
import typing as ty
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from functools import partial
|
|
5
|
+
|
|
6
|
+
from typing_extensions import ParamSpec
|
|
7
|
+
|
|
8
|
+
from . import lazy
|
|
9
|
+
|
|
10
|
+
R = ty.TypeVar("R")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class PFuture(ty.Protocol[R]):
|
|
14
|
+
"""
|
|
15
|
+
A Protocol defining the behavior of a future-like object.
|
|
16
|
+
|
|
17
|
+
This defines an interface for an object that acts as a placeholder
|
|
18
|
+
for a result that will be available later. It is structurally
|
|
19
|
+
compatible with concurrent.futures.Future but omits cancellation.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def running(self) -> bool:
|
|
23
|
+
"""Return True if the future is currently executing."""
|
|
24
|
+
...
|
|
25
|
+
|
|
26
|
+
def done(self) -> bool:
|
|
27
|
+
"""Return True if the future is done (finished)."""
|
|
28
|
+
...
|
|
29
|
+
|
|
30
|
+
def result(self, timeout: ty.Optional[float] = None) -> R:
|
|
31
|
+
"""Return the result of the work item.
|
|
32
|
+
|
|
33
|
+
If the work item raised an exception, this method raises the same exception.
|
|
34
|
+
If the timeout is reached, it raises TimeoutError.
|
|
35
|
+
Other exceptions (e.g. CancelledException) may also be raised depending on the
|
|
36
|
+
implementation.
|
|
37
|
+
"""
|
|
38
|
+
...
|
|
39
|
+
|
|
40
|
+
def exception(self, timeout: ty.Optional[float] = None) -> ty.Optional[BaseException]:
|
|
41
|
+
"""
|
|
42
|
+
Return the exception raised by the work item.
|
|
43
|
+
|
|
44
|
+
Returns None if the work item completed without raising.
|
|
45
|
+
If the timeout is reached, it raises TimeoutError.
|
|
46
|
+
Other exceptions (e.g. CancelledException) may also be raised depending on the
|
|
47
|
+
implementation.
|
|
48
|
+
"""
|
|
49
|
+
...
|
|
50
|
+
|
|
51
|
+
def add_done_callback(self, fn: ty.Callable[["PFuture[R]"], None]) -> None:
|
|
52
|
+
"""
|
|
53
|
+
Attaches a callable that will be called when the future is done.
|
|
54
|
+
|
|
55
|
+
The callable will be called with the future object as its only
|
|
56
|
+
argument.
|
|
57
|
+
"""
|
|
58
|
+
...
|
|
59
|
+
|
|
60
|
+
def set_result(self, result: R) -> None:
|
|
61
|
+
"""Set the result of the future, marking it as done."""
|
|
62
|
+
...
|
|
63
|
+
|
|
64
|
+
def set_exception(self, exception: BaseException) -> None:
|
|
65
|
+
"""Set the exception of the future, marking it as done."""
|
|
66
|
+
...
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class LazyFuture(PFuture[R]):
|
|
70
|
+
def __init__(self, mk_future: ty.Callable[[], PFuture[R]]) -> None:
|
|
71
|
+
"""mk_future should generally be serializable.
|
|
72
|
+
|
|
73
|
+
It also needs to be repeatable - i.e. if it were resolved in two different
|
|
74
|
+
processes, it should return the same 'result' (value or exception) in both.
|
|
75
|
+
"""
|
|
76
|
+
self._mk_future = mk_future
|
|
77
|
+
self._lazy_future = lazy.lazy(mk_future)
|
|
78
|
+
|
|
79
|
+
def __getstate__(self) -> dict[str, ty.Any]:
|
|
80
|
+
"""Return the state of the LazyFuture for serialization."""
|
|
81
|
+
return {"_mk_future": self._mk_future}
|
|
82
|
+
|
|
83
|
+
def __setstate__(self, state: dict[str, ty.Any]) -> None:
|
|
84
|
+
"""Restore the state of the LazyFuture from serialization."""
|
|
85
|
+
self._mk_future = state["_mk_future"]
|
|
86
|
+
self._lazy_future = lazy.lazy(self._mk_future)
|
|
87
|
+
|
|
88
|
+
def running(self) -> bool:
|
|
89
|
+
"""Return True if the future is currently executing."""
|
|
90
|
+
return self._lazy_future().running()
|
|
91
|
+
|
|
92
|
+
def done(self) -> bool:
|
|
93
|
+
"""Return True if the future is done (finished)."""
|
|
94
|
+
return self._lazy_future().done()
|
|
95
|
+
|
|
96
|
+
def result(self, timeout: ty.Optional[float] = None) -> R:
|
|
97
|
+
"""
|
|
98
|
+
Return the result of the work item.
|
|
99
|
+
|
|
100
|
+
If the work item raised an exception, this method raises the same
|
|
101
|
+
exception. If the timeout is reached, it raises TimeoutError.
|
|
102
|
+
"""
|
|
103
|
+
return self._lazy_future().result(timeout)
|
|
104
|
+
|
|
105
|
+
def exception(self, timeout: ty.Optional[float] = None) -> ty.Optional[BaseException]:
|
|
106
|
+
"""
|
|
107
|
+
Return the exception raised by the work item.
|
|
108
|
+
|
|
109
|
+
Returns None if the work item completed without raising.
|
|
110
|
+
If the timeout is reached, it raises TimeoutError.
|
|
111
|
+
"""
|
|
112
|
+
return self._lazy_future().exception(timeout)
|
|
113
|
+
|
|
114
|
+
def add_done_callback(self, fn: ty.Callable[["PFuture[R]"], None]) -> None:
|
|
115
|
+
"""
|
|
116
|
+
Attaches a callable that will be called when the future is done.
|
|
117
|
+
|
|
118
|
+
The callable will be called with the future object as its only
|
|
119
|
+
argument.
|
|
120
|
+
"""
|
|
121
|
+
self._lazy_future().add_done_callback(fn)
|
|
122
|
+
|
|
123
|
+
def set_result(self, result: R) -> None:
|
|
124
|
+
"""Set the result of the future, marking it as done."""
|
|
125
|
+
self._lazy_future().set_result(result)
|
|
126
|
+
|
|
127
|
+
def set_exception(self, exception: BaseException) -> None:
|
|
128
|
+
"""Set the exception of the future, marking it as done."""
|
|
129
|
+
self._lazy_future().set_exception(exception)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
P = ParamSpec("P")
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def make_lazy(mk_future: ty.Callable[P, PFuture[R]]) -> ty.Callable[P, LazyFuture[R]]:
|
|
136
|
+
"""Create a LazyFuture that will lazily resolve the given callable."""
|
|
137
|
+
|
|
138
|
+
def mk_future_with_params(*args: P.args, **kwargs: P.kwargs) -> LazyFuture[R]:
|
|
139
|
+
return LazyFuture(partial(mk_future, *args, **kwargs))
|
|
140
|
+
|
|
141
|
+
return mk_future_with_params
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
@dataclass(frozen=True)
|
|
145
|
+
class ResolvedFuture(PFuture[R]):
|
|
146
|
+
_result: R
|
|
147
|
+
_done: bool = True
|
|
148
|
+
|
|
149
|
+
def running(self) -> bool:
|
|
150
|
+
return False
|
|
151
|
+
|
|
152
|
+
def done(self) -> bool:
|
|
153
|
+
return self._done
|
|
154
|
+
|
|
155
|
+
def result(self, timeout: ty.Optional[float] = None) -> R:
|
|
156
|
+
return self._result
|
|
157
|
+
|
|
158
|
+
def exception(self, timeout: ty.Optional[float] = None) -> ty.Optional[BaseException]:
|
|
159
|
+
return None
|
|
160
|
+
|
|
161
|
+
def add_done_callback(self, fn: ty.Callable[["PFuture[R]"], None]) -> None:
|
|
162
|
+
fn(self)
|
|
163
|
+
|
|
164
|
+
def set_result(self, result: R) -> None:
|
|
165
|
+
raise RuntimeError("Cannot set result on a resolved future.")
|
|
166
|
+
|
|
167
|
+
def set_exception(self, exception: BaseException) -> None:
|
|
168
|
+
raise RuntimeError("Cannot set exception on a resolved future.")
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def resolved(result: R) -> ResolvedFuture[R]:
|
|
172
|
+
return ResolvedFuture(result)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
# what's below doesn't absolutely have to exist here, as it adds a 'dependency' on
|
|
176
|
+
# concurrent.futures... but practically speaking I think that's a pretty safe default
|
|
177
|
+
# 'actual Future' type.
|
|
178
|
+
|
|
179
|
+
R1 = ty.TypeVar("R1")
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def identity(x: R) -> R:
|
|
183
|
+
return x
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def translate_done(
|
|
187
|
+
out_future: PFuture[R1],
|
|
188
|
+
translate_result: ty.Callable[[R], R1],
|
|
189
|
+
done_fut: PFuture[R],
|
|
190
|
+
) -> None:
|
|
191
|
+
try:
|
|
192
|
+
result = done_fut.result()
|
|
193
|
+
out_future.set_result(translate_result(result))
|
|
194
|
+
except Exception as e:
|
|
195
|
+
out_future.set_exception(e)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
OF_R1 = ty.TypeVar("OF_R1", bound=PFuture)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def chain_futures(
|
|
202
|
+
inner_future: PFuture[R],
|
|
203
|
+
outer_future: OF_R1,
|
|
204
|
+
translate_future: ty.Callable[[R], R1],
|
|
205
|
+
) -> OF_R1:
|
|
206
|
+
"""Chain two futures together with a translator in the middle."""
|
|
207
|
+
outer_done_cb = partial(translate_done, outer_future, translate_future)
|
|
208
|
+
inner_future.add_done_callback(outer_done_cb)
|
|
209
|
+
return outer_future
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def reify_future(future: PFuture[R]) -> concurrent.futures.Future[R]:
|
|
213
|
+
"""Reify a PFuture into a concurrent.futures.Future."""
|
|
214
|
+
if isinstance(future, concurrent.futures.Future):
|
|
215
|
+
return future
|
|
216
|
+
return chain_futures(future, concurrent.futures.Future[R](), identity)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def as_completed(
|
|
220
|
+
futures: ty.Iterable[PFuture[R]],
|
|
221
|
+
) -> ty.Iterator[concurrent.futures.Future[R]]:
|
|
222
|
+
"""Return an iterator that yields futures as they complete.
|
|
223
|
+
|
|
224
|
+
We do need to actually create Future objects here, using the add_done_callback method
|
|
225
|
+
so that the these things actually work with concurrent.futures.as_completed.
|
|
226
|
+
"""
|
|
227
|
+
yield from concurrent.futures.as_completed(map(reify_future, futures))
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
thds/core/__init__.py,sha256=
|
|
1
|
+
thds/core/__init__.py,sha256=p5ZTz0yURvtiJlUspnI6aMb9nIekTkzU8sqr8HrnyiM,968
|
|
2
2
|
thds/core/ansi_esc.py,sha256=QZ3CptZbX4N_hyP2IgqfTbNt9tBPaqy7ReTMQIzGbrc,870
|
|
3
3
|
thds/core/cache.py,sha256=nL0oAyZrhPqyBBLevnOWSWVoEBrftaG3aE6Qq6tvmAA,7153
|
|
4
4
|
thds/core/calgitver.py,sha256=6ioH5MGE65l_Dp924oD5CWrLyxKgmhtn46YwGxFpHfM,2497
|
|
@@ -14,6 +14,7 @@ thds/core/exit_after.py,sha256=0lz63nz2NTiIdyBDYyRa9bQShxQKe7eISy8VhXeW4HU,3485
|
|
|
14
14
|
thds/core/files.py,sha256=NJlPXj7BejKd_Pa06MOywVv_YapT4bVedfsJHrWX8nI,4579
|
|
15
15
|
thds/core/fp.py,sha256=S9hM7YmjbmaYbe4l5jSGnzf3HWhEaItmUOv6GMQpHo8,508
|
|
16
16
|
thds/core/fretry.py,sha256=PKgOxCMjcF4zsFfXFvPXpomv5J6KU6llB1EaKukugig,6942
|
|
17
|
+
thds/core/futures.py,sha256=JgqEP9TFC5UVr4tpfaVePHuI-pTsKAFHDWLxkE4aDb0,7372
|
|
17
18
|
thds/core/generators.py,sha256=rcdFpPj0NMJWSaSZTnBfTeZxTTORNB633Lng-BW1284,1939
|
|
18
19
|
thds/core/git.py,sha256=cfdN1oXyfz7k7T2XaseTqL6Ng53B9lfKtzDLmFjojRs,2947
|
|
19
20
|
thds/core/hash_cache.py,sha256=jSFijG33UUQjVSkbuACdg4KzIBaf28i7hSQXCO49Qh0,4066
|
|
@@ -72,8 +73,8 @@ thds/core/sqlite/structured.py,sha256=SvZ67KcVcVdmpR52JSd52vMTW2ALUXmlHEeD-VrzWV
|
|
|
72
73
|
thds/core/sqlite/types.py,sha256=oUkfoKRYNGDPZRk29s09rc9ha3SCk2SKr_K6WKebBFs,1308
|
|
73
74
|
thds/core/sqlite/upsert.py,sha256=BmKK6fsGVedt43iY-Lp7dnAu8aJ1e9CYlPVEQR2pMj4,5827
|
|
74
75
|
thds/core/sqlite/write.py,sha256=z0219vDkQDCnsV0WLvsj94keItr7H4j7Y_evbcoBrWU,3458
|
|
75
|
-
thds_core-1.44.
|
|
76
|
-
thds_core-1.44.
|
|
77
|
-
thds_core-1.44.
|
|
78
|
-
thds_core-1.44.
|
|
79
|
-
thds_core-1.44.
|
|
76
|
+
thds_core-1.44.20250722150732.dist-info/METADATA,sha256=Qz4IITKRw-rs7BobWBWWZNYEtcz4RAd6kXCyVT6H6wk,2216
|
|
77
|
+
thds_core-1.44.20250722150732.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
78
|
+
thds_core-1.44.20250722150732.dist-info/entry_points.txt,sha256=bOCOVhKZv7azF3FvaWX6uxE6yrjK6FcjqhtxXvLiFY8,161
|
|
79
|
+
thds_core-1.44.20250722150732.dist-info/top_level.txt,sha256=LTZaE5SkWJwv9bwOlMbIhiS-JWQEEIcjVYnJrt-CriY,5
|
|
80
|
+
thds_core-1.44.20250722150732.dist-info/RECORD,,
|
|
File without changes
|
{thds_core-1.44.20250721225423.dist-info → thds_core-1.44.20250722150732.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{thds_core-1.44.20250721225423.dist-info → thds_core-1.44.20250722150732.dist-info}/top_level.txt
RENAMED
|
File without changes
|