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.
Files changed (36) hide show
  1. {vsjetengine-1.6.0 → vsjetengine-1.6.2}/PKG-INFO +1 -1
  2. {vsjetengine-1.6.0 → vsjetengine-1.6.2}/tests/test_futures.py +73 -16
  3. vsjetengine-1.6.2/vsengine/_version.py +2 -0
  4. {vsjetengine-1.6.0 → vsjetengine-1.6.2}/vsengine/futures.py +72 -11
  5. {vsjetengine-1.6.0 → vsjetengine-1.6.2}/vsengine/vpy.py +6 -10
  6. vsjetengine-1.6.0/vsengine/_version.py +0 -2
  7. {vsjetengine-1.6.0 → vsjetengine-1.6.2}/.gitignore +0 -0
  8. {vsjetengine-1.6.0 → vsjetengine-1.6.2}/COPYING +0 -0
  9. {vsjetengine-1.6.0 → vsjetengine-1.6.2}/README.md +0 -0
  10. {vsjetengine-1.6.0 → vsjetengine-1.6.2}/pyproject.toml +0 -0
  11. {vsjetengine-1.6.0 → vsjetengine-1.6.2}/tests/__init__.py +0 -0
  12. {vsjetengine-1.6.0 → vsjetengine-1.6.2}/tests/_testutils.py +0 -0
  13. {vsjetengine-1.6.0 → vsjetengine-1.6.2}/tests/conftest.py +0 -0
  14. {vsjetengine-1.6.0 → vsjetengine-1.6.2}/tests/fixtures/heuristic_examples.json +0 -0
  15. {vsjetengine-1.6.0 → vsjetengine-1.6.2}/tests/fixtures/test.vpy +0 -0
  16. {vsjetengine-1.6.0 → vsjetengine-1.6.2}/tests/test_helpers.py +0 -0
  17. {vsjetengine-1.6.0 → vsjetengine-1.6.2}/tests/test_hospice.py +0 -0
  18. {vsjetengine-1.6.0 → vsjetengine-1.6.2}/tests/test_loop_adapters.py +0 -0
  19. {vsjetengine-1.6.0 → vsjetengine-1.6.2}/tests/test_loops.py +0 -0
  20. {vsjetengine-1.6.0 → vsjetengine-1.6.2}/tests/test_policy.py +0 -0
  21. {vsjetengine-1.6.0 → vsjetengine-1.6.2}/tests/test_policy_store.py +0 -0
  22. {vsjetengine-1.6.0 → vsjetengine-1.6.2}/tests/test_pytest_plugin.py +0 -0
  23. {vsjetengine-1.6.0 → vsjetengine-1.6.2}/tests/test_video.py +0 -0
  24. {vsjetengine-1.6.0 → vsjetengine-1.6.2}/tests/test_vpy.py +0 -0
  25. {vsjetengine-1.6.0 → vsjetengine-1.6.2}/vsengine/__init__.py +0 -0
  26. {vsjetengine-1.6.0 → vsjetengine-1.6.2}/vsengine/_helpers.py +0 -0
  27. {vsjetengine-1.6.0 → vsjetengine-1.6.2}/vsengine/_hospice.py +0 -0
  28. {vsjetengine-1.6.0 → vsjetengine-1.6.2}/vsengine/_nodes.py +0 -0
  29. {vsjetengine-1.6.0 → vsjetengine-1.6.2}/vsengine/adapters/__init__.py +0 -0
  30. {vsjetengine-1.6.0 → vsjetengine-1.6.2}/vsengine/adapters/asyncio.py +0 -0
  31. {vsjetengine-1.6.0 → vsjetengine-1.6.2}/vsengine/adapters/trio.py +0 -0
  32. {vsjetengine-1.6.0 → vsjetengine-1.6.2}/vsengine/loops.py +0 -0
  33. {vsjetengine-1.6.0 → vsjetengine-1.6.2}/vsengine/policy.py +0 -0
  34. {vsjetengine-1.6.0 → vsjetengine-1.6.2}/vsengine/py.typed +0 -0
  35. {vsjetengine-1.6.0 → vsjetengine-1.6.2}/vsengine/pytest.py +0 -0
  36. {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.0
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
- future = UnifiedFuture.from_call(succeeds)
122
- new_future = future.map(lambda v: str(v))
123
- assert new_future.result() == "1"
121
+ future0 = UnifiedFuture.from_call(succeeds)
122
+ new_future0 = future0.map(lambda v: str(v))
123
+ assert new_future0.result() == "1"
124
124
 
125
- new_future = future.map(_crash)
126
- assert isinstance(new_future.exception(), RuntimeError)
125
+ new_future0 = future0.map(_crash)
126
+ assert isinstance(new_future0.exception(), RuntimeError)
127
127
 
128
- future = UnifiedFuture.from_call(fails)
129
- new_future = future.map(lambda v: str(v))
130
- assert isinstance(new_future.exception(), RuntimeError)
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
- future = UnifiedFuture.from_call(fails)
138
- new_future = future.catch(lambda e: e.__class__.__name__)
139
- assert new_future.result() == "RuntimeError"
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
- new_future = future.catch(_crash)
142
- assert isinstance(new_future.exception(), RuntimeError)
141
+ new_future0 = future0.catch(_crash)
142
+ assert isinstance(new_future0.exception(), RuntimeError)
143
143
 
144
- future = UnifiedFuture.from_call(succeeds)
145
- new_future = future.catch(lambda v: str(v))
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 = new_future.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
@@ -0,0 +1,2 @@
1
+ __version__ = "1.6.2"
2
+ __version_tuple__ = (1, 6, 2)
@@ -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
- else:
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](self, success_cb: Callable[[T], S], *, on_loop: bool = ...) -> UnifiedFuture[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](self, success_cb: Callable[[T], S], err_cb: None = ..., *, on_loop: bool = ...) -> UnifiedFuture[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](self, success_cb: None, err_cb: Callable[[BaseException], V], *, on_loop: bool = ...) -> UnifiedFuture[T | V]: ... # fmt: skip # noqa: E501
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](self, *, err_cb: Callable[[BaseException], V], on_loop: bool = ...) -> UnifiedFuture[T | 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 (exc := lf.exception()) is not None:
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
- else:
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](self, cb: Callable[[T], V], *, on_loop: bool = False) -> UnifiedFuture[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](self, cb: Callable[[BaseException], V], *, on_loop: bool = False) -> UnifiedFuture[T | 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
- import traceback
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 ctx,
459
- _ModifiedArgv0(filename) if path else ctx,
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)
@@ -1,2 +0,0 @@
1
- __version__ = "1.6.0"
2
- __version_tuple__ = (1, 6, 0)
File without changes
File without changes
File without changes
File without changes