vsjetengine 1.6.0__tar.gz → 1.6.2__tar.gz
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.
- {vsjetengine-1.6.0 → vsjetengine-1.6.2}/PKG-INFO +1 -1
- {vsjetengine-1.6.0 → vsjetengine-1.6.2}/tests/test_futures.py +73 -16
- vsjetengine-1.6.2/vsengine/_version.py +2 -0
- {vsjetengine-1.6.0 → vsjetengine-1.6.2}/vsengine/futures.py +72 -11
- {vsjetengine-1.6.0 → vsjetengine-1.6.2}/vsengine/vpy.py +6 -10
- vsjetengine-1.6.0/vsengine/_version.py +0 -2
- {vsjetengine-1.6.0 → vsjetengine-1.6.2}/.gitignore +0 -0
- {vsjetengine-1.6.0 → vsjetengine-1.6.2}/COPYING +0 -0
- {vsjetengine-1.6.0 → vsjetengine-1.6.2}/README.md +0 -0
- {vsjetengine-1.6.0 → vsjetengine-1.6.2}/pyproject.toml +0 -0
- {vsjetengine-1.6.0 → vsjetengine-1.6.2}/tests/__init__.py +0 -0
- {vsjetengine-1.6.0 → vsjetengine-1.6.2}/tests/_testutils.py +0 -0
- {vsjetengine-1.6.0 → vsjetengine-1.6.2}/tests/conftest.py +0 -0
- {vsjetengine-1.6.0 → vsjetengine-1.6.2}/tests/fixtures/heuristic_examples.json +0 -0
- {vsjetengine-1.6.0 → vsjetengine-1.6.2}/tests/fixtures/test.vpy +0 -0
- {vsjetengine-1.6.0 → vsjetengine-1.6.2}/tests/test_helpers.py +0 -0
- {vsjetengine-1.6.0 → vsjetengine-1.6.2}/tests/test_hospice.py +0 -0
- {vsjetengine-1.6.0 → vsjetengine-1.6.2}/tests/test_loop_adapters.py +0 -0
- {vsjetengine-1.6.0 → vsjetengine-1.6.2}/tests/test_loops.py +0 -0
- {vsjetengine-1.6.0 → vsjetengine-1.6.2}/tests/test_policy.py +0 -0
- {vsjetengine-1.6.0 → vsjetengine-1.6.2}/tests/test_policy_store.py +0 -0
- {vsjetengine-1.6.0 → vsjetengine-1.6.2}/tests/test_pytest_plugin.py +0 -0
- {vsjetengine-1.6.0 → vsjetengine-1.6.2}/tests/test_video.py +0 -0
- {vsjetengine-1.6.0 → vsjetengine-1.6.2}/tests/test_vpy.py +0 -0
- {vsjetengine-1.6.0 → vsjetengine-1.6.2}/vsengine/__init__.py +0 -0
- {vsjetengine-1.6.0 → vsjetengine-1.6.2}/vsengine/_helpers.py +0 -0
- {vsjetengine-1.6.0 → vsjetengine-1.6.2}/vsengine/_hospice.py +0 -0
- {vsjetengine-1.6.0 → vsjetengine-1.6.2}/vsengine/_nodes.py +0 -0
- {vsjetengine-1.6.0 → vsjetengine-1.6.2}/vsengine/adapters/__init__.py +0 -0
- {vsjetengine-1.6.0 → vsjetengine-1.6.2}/vsengine/adapters/asyncio.py +0 -0
- {vsjetengine-1.6.0 → vsjetengine-1.6.2}/vsengine/adapters/trio.py +0 -0
- {vsjetengine-1.6.0 → vsjetengine-1.6.2}/vsengine/loops.py +0 -0
- {vsjetengine-1.6.0 → vsjetengine-1.6.2}/vsengine/policy.py +0 -0
- {vsjetengine-1.6.0 → vsjetengine-1.6.2}/vsengine/py.typed +0 -0
- {vsjetengine-1.6.0 → vsjetengine-1.6.2}/vsengine/pytest.py +0 -0
- {vsjetengine-1.6.0 → vsjetengine-1.6.2}/vsengine/video.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: vsjetengine
|
|
3
|
-
Version: 1.6.
|
|
3
|
+
Version: 1.6.2
|
|
4
4
|
Summary: An engine for vapoursynth previewers, renderers and script analyis tools.
|
|
5
5
|
Project-URL: Source Code, https://github.com/Jaded-Encoding-Thaumaturgy/vs-jet-engine
|
|
6
6
|
Project-URL: Contact, https://discord.gg/XTpc6Fa9eB
|
|
@@ -118,33 +118,33 @@ def test_unified_future_map() -> None:
|
|
|
118
118
|
def _crash(v: Any) -> str:
|
|
119
119
|
raise RuntimeError(str(v))
|
|
120
120
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
assert
|
|
121
|
+
future0 = UnifiedFuture.from_call(succeeds)
|
|
122
|
+
new_future0 = future0.map(lambda v: str(v))
|
|
123
|
+
assert new_future0.result() == "1"
|
|
124
124
|
|
|
125
|
-
|
|
126
|
-
assert isinstance(
|
|
125
|
+
new_future0 = future0.map(_crash)
|
|
126
|
+
assert isinstance(new_future0.exception(), RuntimeError)
|
|
127
127
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
assert isinstance(
|
|
128
|
+
future1 = UnifiedFuture.from_call(fails)
|
|
129
|
+
new_future1 = future1.map(lambda v: str(v))
|
|
130
|
+
assert isinstance(new_future1.exception(), RuntimeError)
|
|
131
131
|
|
|
132
132
|
|
|
133
133
|
def test_unified_future_catch() -> None:
|
|
134
134
|
def _crash(_: BaseException) -> str:
|
|
135
135
|
raise RuntimeError("test")
|
|
136
136
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
assert
|
|
137
|
+
future0 = UnifiedFuture.from_call(fails)
|
|
138
|
+
new_future0 = future0.catch(lambda e: e.__class__.__name__)
|
|
139
|
+
assert new_future0.result() == "RuntimeError"
|
|
140
140
|
|
|
141
|
-
|
|
142
|
-
assert isinstance(
|
|
141
|
+
new_future0 = future0.catch(_crash)
|
|
142
|
+
assert isinstance(new_future0.exception(), RuntimeError)
|
|
143
143
|
|
|
144
|
-
|
|
145
|
-
|
|
144
|
+
future1 = UnifiedFuture.from_call(succeeds)
|
|
145
|
+
new_future1 = future1.catch(lambda v: str(v))
|
|
146
146
|
# Result is 1 because the future succeeded (no exception to catch)
|
|
147
|
-
result =
|
|
147
|
+
result = new_future1.result()
|
|
148
148
|
assert result == 1
|
|
149
149
|
|
|
150
150
|
|
|
@@ -684,3 +684,60 @@ def test_run_as_completed_next_cycle_fails_async() -> None:
|
|
|
684
684
|
assert isinstance(state.exception(), ValueError)
|
|
685
685
|
finally:
|
|
686
686
|
set_loop(NO_LOOP)
|
|
687
|
+
|
|
688
|
+
|
|
689
|
+
def test_unified_future_cancellation_propagation() -> None:
|
|
690
|
+
raw = UnifiedFuture[int]()
|
|
691
|
+
mapped = raw.map(lambda x: x * 2)
|
|
692
|
+
chained = mapped.map(lambda x: x + 1)
|
|
693
|
+
|
|
694
|
+
assert chained.cancel() is True
|
|
695
|
+
assert chained.cancelled() is True
|
|
696
|
+
assert mapped.cancelled() is True
|
|
697
|
+
assert raw.cancelled() is True
|
|
698
|
+
|
|
699
|
+
|
|
700
|
+
def test_unified_future_child_cancelled_parent_resolves() -> None:
|
|
701
|
+
raw = UnifiedFuture[int]()
|
|
702
|
+
mapped = raw.map(lambda x: x * 2, cancel_cb=lambda: None)
|
|
703
|
+
chained = mapped.map(lambda x: x + 1)
|
|
704
|
+
|
|
705
|
+
assert chained.cancel() is True
|
|
706
|
+
assert chained.cancelled() is True
|
|
707
|
+
assert mapped.cancelled() is True
|
|
708
|
+
assert raw.cancelled() is False
|
|
709
|
+
|
|
710
|
+
# Resolving raw future whose downstream child was cancelled should not raise InvalidStateError in callbacks
|
|
711
|
+
raw.set_result(10)
|
|
712
|
+
assert raw.result() == 10
|
|
713
|
+
|
|
714
|
+
|
|
715
|
+
def test_unified_future_custom_on_cancel() -> None:
|
|
716
|
+
cancelled_calls = list[str]()
|
|
717
|
+
raw = UnifiedFuture[int]()
|
|
718
|
+
mapped = raw.map(lambda x: x * 2, cancel_cb=lambda: cancelled_calls.append("custom_cancel"))
|
|
719
|
+
|
|
720
|
+
assert mapped.cancel() is True
|
|
721
|
+
assert mapped.cancelled() is True
|
|
722
|
+
assert cancelled_calls == ["custom_cancel"]
|
|
723
|
+
# Parent raw future was not cancelled because custom on_cancel intercepted it
|
|
724
|
+
assert raw.cancelled() is False
|
|
725
|
+
|
|
726
|
+
|
|
727
|
+
def test_unified_future_parent_cancelled() -> None:
|
|
728
|
+
raw = UnifiedFuture[int]()
|
|
729
|
+
mapped = raw.map(lambda x: x * 2)
|
|
730
|
+
caught = raw.catch(lambda _: 0)
|
|
731
|
+
|
|
732
|
+
raw.cancel()
|
|
733
|
+
assert mapped.cancelled() is True
|
|
734
|
+
assert caught.cancelled() is True
|
|
735
|
+
|
|
736
|
+
|
|
737
|
+
def test_unified_future_from_future_cancellation() -> None:
|
|
738
|
+
raw = Future[int]()
|
|
739
|
+
uni = UnifiedFuture.from_future(raw)
|
|
740
|
+
|
|
741
|
+
assert uni.cancel() is True
|
|
742
|
+
assert uni.cancelled() is True
|
|
743
|
+
assert raw.cancelled() is True
|
|
@@ -78,12 +78,16 @@ class UnifiedFuture[T](Future[T], AbstractContextManager[Any], AbstractAsyncCont
|
|
|
78
78
|
result = cls()
|
|
79
79
|
|
|
80
80
|
def _receive(fn: Future[S]) -> None:
|
|
81
|
+
if future.cancelled():
|
|
82
|
+
result.cancel()
|
|
83
|
+
return
|
|
81
84
|
if (exc := future.exception()) is not None:
|
|
82
85
|
result.set_exception(exc)
|
|
83
|
-
|
|
86
|
+
elif not result.cancelled():
|
|
84
87
|
result.set_result(future.result())
|
|
85
88
|
|
|
86
89
|
future.add_done_callback(_receive)
|
|
90
|
+
result.add_done_callback(lambda f: future.cancel() if f.cancelled() else None)
|
|
87
91
|
return result
|
|
88
92
|
|
|
89
93
|
@classmethod
|
|
@@ -154,18 +158,45 @@ class UnifiedFuture[T](Future[T], AbstractContextManager[Any], AbstractAsyncCont
|
|
|
154
158
|
|
|
155
159
|
# Manipulating futures
|
|
156
160
|
@overload
|
|
157
|
-
def then[S](
|
|
161
|
+
def then[S](
|
|
162
|
+
self,
|
|
163
|
+
success_cb: Callable[[T], S],
|
|
164
|
+
*,
|
|
165
|
+
cancel_cb: Callable[[], None] | None = ...,
|
|
166
|
+
on_loop: bool = ...,
|
|
167
|
+
) -> UnifiedFuture[S]: ...
|
|
158
168
|
@overload
|
|
159
|
-
def then[S](
|
|
169
|
+
def then[S](
|
|
170
|
+
self,
|
|
171
|
+
success_cb: Callable[[T], S],
|
|
172
|
+
err_cb: None = ...,
|
|
173
|
+
cancel_cb: Callable[[], None] | None = ...,
|
|
174
|
+
*,
|
|
175
|
+
on_loop: bool = ...,
|
|
176
|
+
) -> UnifiedFuture[S]: ...
|
|
160
177
|
@overload
|
|
161
|
-
def then[V](
|
|
178
|
+
def then[V](
|
|
179
|
+
self,
|
|
180
|
+
success_cb: None,
|
|
181
|
+
err_cb: Callable[[BaseException], V],
|
|
182
|
+
cancel_cb: Callable[[], None] | None = ...,
|
|
183
|
+
*,
|
|
184
|
+
on_loop: bool = ...,
|
|
185
|
+
) -> UnifiedFuture[T | V]: ...
|
|
162
186
|
@overload
|
|
163
|
-
def then[V](
|
|
187
|
+
def then[V](
|
|
188
|
+
self,
|
|
189
|
+
*,
|
|
190
|
+
err_cb: Callable[[BaseException], V],
|
|
191
|
+
cancel_cb: Callable[[], None] | None = ...,
|
|
192
|
+
on_loop: bool = ...,
|
|
193
|
+
) -> UnifiedFuture[T | V]: ...
|
|
164
194
|
@overload
|
|
165
195
|
def then[S, V](
|
|
166
196
|
self,
|
|
167
197
|
success_cb: Callable[[T], S],
|
|
168
198
|
err_cb: Callable[[BaseException], V],
|
|
199
|
+
cancel_cb: Callable[[], None] | None = ...,
|
|
169
200
|
*,
|
|
170
201
|
on_loop: bool = ...,
|
|
171
202
|
) -> UnifiedFuture[S | V]: ...
|
|
@@ -173,6 +204,7 @@ class UnifiedFuture[T](Future[T], AbstractContextManager[Any], AbstractAsyncCont
|
|
|
173
204
|
self,
|
|
174
205
|
success_cb: Callable[[T], S] | None = None,
|
|
175
206
|
err_cb: Callable[[BaseException], V] | None = None,
|
|
207
|
+
cancel_cb: Callable[[], None] | None = None,
|
|
176
208
|
*,
|
|
177
209
|
on_loop: bool = False,
|
|
178
210
|
) -> Any:
|
|
@@ -188,17 +220,25 @@ class UnifiedFuture[T](Future[T], AbstractContextManager[Any], AbstractAsyncCont
|
|
|
188
220
|
|
|
189
221
|
:param success_cb: Called with the resolved value, or `None` to passthrough.
|
|
190
222
|
:param err_cb: Called with the exception, or `None` to passthrough.
|
|
223
|
+
:param cancel_cb: Optional callback invoked when the returned future is cancelled.
|
|
224
|
+
Defaults to cancelling the parent future.
|
|
191
225
|
:param on_loop: If True, execute the callback on the main loop.
|
|
192
226
|
:return: A new `UnifiedFuture` carrying the callback's return value.
|
|
193
227
|
"""
|
|
194
228
|
result = UnifiedFuture[Any]()
|
|
195
229
|
|
|
196
230
|
def _run_cb(cb: Callable[[Any], Any], v: T | BaseException) -> None:
|
|
231
|
+
if result.cancelled():
|
|
232
|
+
return
|
|
197
233
|
if on_loop:
|
|
198
234
|
loop_fut = get_loop().from_thread(cb, v)
|
|
199
235
|
|
|
200
236
|
def _forward(lf: Future[Any]) -> None:
|
|
201
|
-
if
|
|
237
|
+
if result.cancelled():
|
|
238
|
+
return
|
|
239
|
+
if lf.cancelled():
|
|
240
|
+
result.cancel()
|
|
241
|
+
elif (exc := lf.exception()) is not None:
|
|
202
242
|
result.set_exception(exc)
|
|
203
243
|
else:
|
|
204
244
|
result.set_result(lf.result())
|
|
@@ -213,39 +253,60 @@ class UnifiedFuture[T](Future[T], AbstractContextManager[Any], AbstractAsyncCont
|
|
|
213
253
|
result.set_result(r)
|
|
214
254
|
|
|
215
255
|
def _done(fn: Future[T]) -> None:
|
|
256
|
+
if self.cancelled():
|
|
257
|
+
result.cancel()
|
|
258
|
+
return
|
|
216
259
|
if (exc := self.exception()) is not None:
|
|
217
260
|
if err_cb is not None:
|
|
218
261
|
_run_cb(err_cb, exc)
|
|
219
262
|
else:
|
|
220
263
|
result.set_exception(exc)
|
|
221
|
-
|
|
264
|
+
elif not result.cancelled():
|
|
222
265
|
if success_cb is not None:
|
|
223
266
|
_run_cb(success_cb, self.result())
|
|
224
267
|
else:
|
|
225
268
|
result.set_result(self.result())
|
|
226
269
|
|
|
227
270
|
self.add_done_callback(_done)
|
|
271
|
+
if cancel_cb is not None:
|
|
272
|
+
result.add_done_callback(lambda f: cancel_cb() if f.cancelled() else None)
|
|
273
|
+
else:
|
|
274
|
+
result.add_done_callback(lambda f: self.cancel() if f.cancelled() else None)
|
|
228
275
|
return result
|
|
229
276
|
|
|
230
|
-
def map[V](
|
|
277
|
+
def map[V](
|
|
278
|
+
self,
|
|
279
|
+
cb: Callable[[T], V],
|
|
280
|
+
cancel_cb: Callable[[], None] | None = None,
|
|
281
|
+
*,
|
|
282
|
+
on_loop: bool = False,
|
|
283
|
+
) -> UnifiedFuture[V]:
|
|
231
284
|
"""
|
|
232
285
|
Transform the resolved value with `cb`, returning a new future.
|
|
233
286
|
|
|
234
287
|
:param cb: A callable that transforms the resolved value.
|
|
288
|
+
:param cancel_cb: Optional callback invoked when the returned future is cancelled.
|
|
235
289
|
:param on_loop: If True, execute the callback on the main loop.
|
|
236
290
|
:return: A new `UnifiedFuture` carrying the transformed value.
|
|
237
291
|
"""
|
|
238
|
-
return self.then(cb, None, on_loop=on_loop)
|
|
292
|
+
return self.then(cb, None, cancel_cb, on_loop=on_loop)
|
|
239
293
|
|
|
240
|
-
def catch[V](
|
|
294
|
+
def catch[V](
|
|
295
|
+
self,
|
|
296
|
+
cb: Callable[[BaseException], V],
|
|
297
|
+
cancel_cb: Callable[[], None] | None = None,
|
|
298
|
+
*,
|
|
299
|
+
on_loop: bool = False,
|
|
300
|
+
) -> UnifiedFuture[T | V]:
|
|
241
301
|
"""
|
|
242
302
|
Recover from a rejection by handling the exception with `cb`.
|
|
243
303
|
|
|
244
304
|
:param cb: A callable that handles the exception and returns a recovery value.
|
|
305
|
+
:param cancel_cb: Optional callback invoked when the returned future is cancelled.
|
|
245
306
|
:param on_loop: If True, execute the callback on the main loop.
|
|
246
307
|
:return: A new `UnifiedFuture` carrying the recovery value on error, or the original resolved value on success.
|
|
247
308
|
"""
|
|
248
|
-
return self.then(None, cb, on_loop=on_loop)
|
|
309
|
+
return self.then(None, cb, cancel_cb, on_loop=on_loop)
|
|
249
310
|
|
|
250
311
|
# Nicer Syntax
|
|
251
312
|
def __enter__[EnterT](self: FutureLike[AbstractContextManager[EnterT, Any]]) -> EnterT:
|
|
@@ -11,9 +11,11 @@ import ast
|
|
|
11
11
|
import io
|
|
12
12
|
import os
|
|
13
13
|
import sys
|
|
14
|
+
import textwrap
|
|
15
|
+
import traceback
|
|
14
16
|
from collections.abc import Awaitable, Buffer, Callable, Generator
|
|
15
17
|
from concurrent.futures import Future
|
|
16
|
-
from contextlib import AbstractContextManager, suppress
|
|
18
|
+
from contextlib import AbstractContextManager, nullcontext, suppress
|
|
17
19
|
from types import CodeType, ModuleType, TracebackType
|
|
18
20
|
from typing import Any, Concatenate, Self, overload
|
|
19
21
|
from uuid import uuid4
|
|
@@ -44,8 +46,6 @@ class ExecutionError(Exception):
|
|
|
44
46
|
|
|
45
47
|
:param parent_error: The original exception that occurred.
|
|
46
48
|
"""
|
|
47
|
-
import textwrap
|
|
48
|
-
|
|
49
49
|
msg = textwrap.indent(self.extract_traceback(parent_error), "| ")
|
|
50
50
|
super().__init__(f"An exception was raised while running the script.\n{msg}")
|
|
51
51
|
self.parent_error = parent_error
|
|
@@ -58,11 +58,7 @@ class ExecutionError(Exception):
|
|
|
58
58
|
:param error: The exception to extract the traceback from.
|
|
59
59
|
:return: A formatted string containing the traceback.
|
|
60
60
|
"""
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
msg = traceback.format_exception(type(error), error, error.__traceback__)
|
|
64
|
-
msg = "".join(msg)
|
|
65
|
-
return msg
|
|
61
|
+
return "".join(traceback.format_exception(type(error), error, error.__traceback__))
|
|
66
62
|
|
|
67
63
|
|
|
68
64
|
class WrapAllErrors(AbstractContextManager[None]):
|
|
@@ -455,8 +451,8 @@ def load_code(
|
|
|
455
451
|
with (
|
|
456
452
|
ctx,
|
|
457
453
|
_TempModule(module.__name__, module),
|
|
458
|
-
_ModifiedPath(path) if path else
|
|
459
|
-
_ModifiedArgv0(filename) if path else
|
|
454
|
+
_ModifiedPath(path) if path else nullcontext(),
|
|
455
|
+
_ModifiedArgv0(filename) if path else nullcontext(),
|
|
460
456
|
):
|
|
461
457
|
code = (
|
|
462
458
|
compile(script, **{"filename": filename, "dont_inherit": True, "flags": 0, "mode": "exec"} | kwargs)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|