vsjetengine 1.3.0__tar.gz → 1.4.0__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 (34) hide show
  1. {vsjetengine-1.3.0 → vsjetengine-1.4.0}/PKG-INFO +1 -1
  2. {vsjetengine-1.3.0 → vsjetengine-1.4.0}/tests/test_futures.py +116 -3
  3. vsjetengine-1.4.0/vsengine/_version.py +2 -0
  4. {vsjetengine-1.3.0 → vsjetengine-1.4.0}/vsengine/futures.py +78 -24
  5. vsjetengine-1.3.0/vsengine/_version.py +0 -2
  6. {vsjetengine-1.3.0 → vsjetengine-1.4.0}/.gitignore +0 -0
  7. {vsjetengine-1.3.0 → vsjetengine-1.4.0}/COPYING +0 -0
  8. {vsjetengine-1.3.0 → vsjetengine-1.4.0}/README.md +0 -0
  9. {vsjetengine-1.3.0 → vsjetengine-1.4.0}/pyproject.toml +0 -0
  10. {vsjetengine-1.3.0 → vsjetengine-1.4.0}/tests/__init__.py +0 -0
  11. {vsjetengine-1.3.0 → vsjetengine-1.4.0}/tests/_testutils.py +0 -0
  12. {vsjetengine-1.3.0 → vsjetengine-1.4.0}/tests/conftest.py +0 -0
  13. {vsjetengine-1.3.0 → vsjetengine-1.4.0}/tests/fixtures/heuristic_examples.json +0 -0
  14. {vsjetengine-1.3.0 → vsjetengine-1.4.0}/tests/fixtures/test.vpy +0 -0
  15. {vsjetengine-1.3.0 → vsjetengine-1.4.0}/tests/test_helpers.py +0 -0
  16. {vsjetengine-1.3.0 → vsjetengine-1.4.0}/tests/test_hospice.py +0 -0
  17. {vsjetengine-1.3.0 → vsjetengine-1.4.0}/tests/test_loop_adapters.py +0 -0
  18. {vsjetengine-1.3.0 → vsjetengine-1.4.0}/tests/test_loops.py +0 -0
  19. {vsjetengine-1.3.0 → vsjetengine-1.4.0}/tests/test_policy.py +0 -0
  20. {vsjetengine-1.3.0 → vsjetengine-1.4.0}/tests/test_policy_store.py +0 -0
  21. {vsjetengine-1.3.0 → vsjetengine-1.4.0}/tests/test_video.py +0 -0
  22. {vsjetengine-1.3.0 → vsjetengine-1.4.0}/tests/test_vpy.py +0 -0
  23. {vsjetengine-1.3.0 → vsjetengine-1.4.0}/vsengine/__init__.py +0 -0
  24. {vsjetengine-1.3.0 → vsjetengine-1.4.0}/vsengine/_helpers.py +0 -0
  25. {vsjetengine-1.3.0 → vsjetengine-1.4.0}/vsengine/_hospice.py +0 -0
  26. {vsjetengine-1.3.0 → vsjetengine-1.4.0}/vsengine/_nodes.py +0 -0
  27. {vsjetengine-1.3.0 → vsjetengine-1.4.0}/vsengine/adapters/__init__.py +0 -0
  28. {vsjetengine-1.3.0 → vsjetengine-1.4.0}/vsengine/adapters/asyncio.py +0 -0
  29. {vsjetengine-1.3.0 → vsjetengine-1.4.0}/vsengine/adapters/trio.py +0 -0
  30. {vsjetengine-1.3.0 → vsjetengine-1.4.0}/vsengine/loops.py +0 -0
  31. {vsjetengine-1.3.0 → vsjetengine-1.4.0}/vsengine/policy.py +0 -0
  32. {vsjetengine-1.3.0 → vsjetengine-1.4.0}/vsengine/py.typed +0 -0
  33. {vsjetengine-1.3.0 → vsjetengine-1.4.0}/vsengine/video.py +0 -0
  34. {vsjetengine-1.3.0 → vsjetengine-1.4.0}/vsengine/vpy.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vsjetengine
3
- Version: 1.3.0
3
+ Version: 1.4.0
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
@@ -141,9 +141,6 @@ def test_unified_future_catch() -> None:
141
141
 
142
142
  @pytest.mark.asyncio
143
143
  async def test_unified_future_add_loop_callback() -> None:
144
- from vsengine.adapters.asyncio import AsyncIOLoop
145
- from vsengine.loops import set_loop
146
-
147
144
  set_loop(AsyncIOLoop())
148
145
 
149
146
  def _init_thread(fut: Future[threading.Thread]) -> None:
@@ -170,6 +167,122 @@ async def test_unified_future_add_loop_callback() -> None:
170
167
  assert cb_thread != loop_thread
171
168
 
172
169
 
170
+ @pytest.mark.asyncio
171
+ async def test_unified_future_add_loop_callback_chaining() -> None:
172
+ set_loop(AsyncIOLoop())
173
+
174
+ fut = Future[int]()
175
+ unified_fut = UnifiedFuture.from_future(fut)
176
+
177
+ recorded_value: int | None = None
178
+
179
+ def _side_effect(f: Future[int]) -> None:
180
+ nonlocal recorded_value
181
+ recorded_value = f.result() * 2
182
+
183
+ chained_fut = unified_fut.add_loop_callback(_side_effect)
184
+
185
+ results = list[int]()
186
+ chained_fut.then(lambda val: results.append(val))
187
+
188
+ fut.set_result(21)
189
+
190
+ res = await chained_fut
191
+ assert res == 21
192
+ assert results == [21]
193
+ assert recorded_value == 42
194
+
195
+
196
+ @pytest.mark.asyncio
197
+ async def test_unified_future_then_on_loop() -> None:
198
+ set_loop(AsyncIOLoop())
199
+
200
+ fut = Future[int]()
201
+ unified_fut = UnifiedFuture.from_future(fut)
202
+
203
+ loop_thread: threading.Thread | None = None
204
+ callback_thread: threading.Thread | None = None
205
+
206
+ def _init_thread() -> None:
207
+ fut.set_result(42)
208
+
209
+ def _on_success(v: int) -> int:
210
+ nonlocal callback_thread
211
+ callback_thread = threading.current_thread()
212
+ return v * 2
213
+
214
+ chained = unified_fut.then(_on_success, on_loop=True)
215
+
216
+ thr = threading.Thread(target=_init_thread)
217
+ thr.start()
218
+
219
+ res = await chained
220
+ loop_thread = threading.current_thread()
221
+
222
+ assert callback_thread == loop_thread
223
+ assert res == 84
224
+
225
+
226
+ @pytest.mark.asyncio
227
+ async def test_unified_future_map_on_loop() -> None:
228
+ set_loop(AsyncIOLoop())
229
+
230
+ fut = Future[int]()
231
+ unified_fut = UnifiedFuture.from_future(fut)
232
+
233
+ loop_thread: threading.Thread | None = None
234
+ callback_thread: threading.Thread | None = None
235
+
236
+ def _init_thread() -> None:
237
+ fut.set_result(10)
238
+
239
+ def _map_fn(v: int) -> str:
240
+ nonlocal callback_thread
241
+ callback_thread = threading.current_thread()
242
+ return f"value: {v}"
243
+
244
+ chained = unified_fut.map(_map_fn, on_loop=True)
245
+
246
+ thr = threading.Thread(target=_init_thread)
247
+ thr.start()
248
+
249
+ res = await chained
250
+ loop_thread = threading.current_thread()
251
+
252
+ assert callback_thread == loop_thread
253
+ assert res == "value: 10"
254
+
255
+
256
+ @pytest.mark.asyncio
257
+ async def test_unified_future_catch_on_loop() -> None:
258
+ set_loop(AsyncIOLoop())
259
+
260
+ fut = Future[int]()
261
+ unified_fut = UnifiedFuture.from_future(fut)
262
+
263
+ loop_thread: threading.Thread | None = None
264
+ callback_thread: threading.Thread | None = None
265
+
266
+ def _init_thread() -> None:
267
+ fut.set_exception(ValueError("error!"))
268
+
269
+ def _catch_fn(e: BaseException) -> str:
270
+ nonlocal callback_thread
271
+ callback_thread = threading.current_thread()
272
+ return str(e)
273
+
274
+ chained = unified_fut.catch(_catch_fn, on_loop=True)
275
+
276
+ thr = threading.Thread(target=_init_thread)
277
+ thr.start()
278
+
279
+ res = await chained
280
+ loop_thread = threading.current_thread()
281
+
282
+ assert callback_thread == loop_thread
283
+ assert res == "error!"
284
+
285
+
173
286
  # UnifiedIterator tests
174
287
 
175
288
 
@@ -0,0 +1,2 @@
1
+ __version__ = "1.4.0"
2
+ __version_tuple__ = (1, 4, 0)
@@ -27,6 +27,10 @@ class AsyncFutureLike[V](Protocol):
27
27
  async def awaitable(self) -> V: ...
28
28
 
29
29
 
30
+ type UnifiedFutureLike[T] = UnifiedFuture[T]
31
+ type UnifiedIteratorLike[T] = UnifiedIterator[T]
32
+
33
+
30
34
  class UnifiedFuture[T](Future[T], AbstractContextManager[Any], AbstractAsyncContextManager[Any], Awaitable[T]):
31
35
  """
32
36
  A Promise-inspired Future that unifies concurrent.futures.Future
@@ -34,7 +38,12 @@ class UnifiedFuture[T](Future[T], AbstractContextManager[Any], AbstractAsyncCont
34
38
  """
35
39
 
36
40
  @classmethod
37
- def from_call[**P](cls, func: Callable[P, Future[T]], *args: P.args, **kwargs: P.kwargs) -> Self:
41
+ def from_call[S, **P](
42
+ cls: type[UnifiedFutureLike[S]],
43
+ func: Callable[P, Future[S]],
44
+ *args: P.args,
45
+ **kwargs: P.kwargs,
46
+ ) -> UnifiedFutureLike[S]:
38
47
  """
39
48
  Call `func` and wrap the returned `Future` as a `UnifiedFuture`.
40
49
 
@@ -54,7 +63,7 @@ class UnifiedFuture[T](Future[T], AbstractContextManager[Any], AbstractAsyncCont
54
63
  return cls.from_future(future)
55
64
 
56
65
  @classmethod
57
- def from_future(cls, future: Future[T]) -> Self:
66
+ def from_future[S](cls: type[UnifiedFutureLike[S]], future: Future[S]) -> UnifiedFutureLike[S]:
58
67
  """
59
68
  Wrap an existing `Future` as a `UnifiedFuture`.
60
69
 
@@ -68,7 +77,7 @@ class UnifiedFuture[T](Future[T], AbstractContextManager[Any], AbstractAsyncCont
68
77
 
69
78
  result = cls()
70
79
 
71
- def _receive(fn: Future[T]) -> None:
80
+ def _receive(fn: Future[S]) -> None:
72
81
  if (exc := future.exception()) is not None:
73
82
  result.set_exception(exc)
74
83
  else:
@@ -78,7 +87,7 @@ class UnifiedFuture[T](Future[T], AbstractContextManager[Any], AbstractAsyncCont
78
87
  return result
79
88
 
80
89
  @classmethod
81
- def resolve(cls, value: T) -> Self:
90
+ def resolve[S](cls: type[UnifiedFutureLike[S]], value: S) -> UnifiedFutureLike[S]:
82
91
  """
83
92
  Return an already-resolved `UnifiedFuture` carrying `value`.
84
93
 
@@ -90,7 +99,7 @@ class UnifiedFuture[T](Future[T], AbstractContextManager[Any], AbstractAsyncCont
90
99
  return future
91
100
 
92
101
  @classmethod
93
- def reject(cls, error: BaseException) -> Self:
102
+ def reject(cls: type[UnifiedFutureLike[Any]], error: BaseException) -> UnifiedFutureLike[Any]:
94
103
  """
95
104
  Return an already-rejected `UnifiedFuture` carrying `error`.
96
105
 
@@ -114,7 +123,7 @@ class UnifiedFuture[T](Future[T], AbstractContextManager[Any], AbstractAsyncCont
114
123
  # The done_callback should inherit the environment of the current call.
115
124
  super().add_done_callback(keep_environment(fn))
116
125
 
117
- def add_loop_callback(self, func: Callable[[Future[T]], Any]) -> None:
126
+ def add_loop_callback(self, func: Callable[[Future[T]], Any]) -> UnifiedFuture[T]:
118
127
  """
119
128
  Register a callback that is guaranteed to run on the event-loop thread.
120
129
 
@@ -122,25 +131,51 @@ class UnifiedFuture[T](Future[T], AbstractContextManager[Any], AbstractAsyncCont
122
131
  this method marshals `func` back to the main event loop via `EventLoop.from_thread`.
123
132
 
124
133
  :param func: A callable that receives the completed future.
134
+ :return: A UnifiedFuture that resolves to the original future's result once the callback completes
135
+ on the event loop.
125
136
  """
137
+ result = UnifiedFuture[T]()
126
138
 
127
139
  def _wrapper(future: Future[T]) -> None:
128
- get_loop().from_thread(func, future)
140
+ loop_fut = get_loop().from_thread(func, future)
141
+
142
+ def _forward(lf: Future[Any]) -> None:
143
+ if (exc := lf.exception()) is not None:
144
+ result.set_exception(exc)
145
+ elif (orig_exc := future.exception()) is not None:
146
+ result.set_exception(orig_exc)
147
+ else:
148
+ result.set_result(future.result())
149
+
150
+ loop_fut.add_done_callback(_forward)
129
151
 
130
152
  self.add_done_callback(_wrapper)
153
+ return result
131
154
 
132
155
  # Manipulating futures
133
156
  @overload
134
- def then[S](self, success_cb: Callable[[T], S]) -> UnifiedFuture[S]: ...
157
+ def then[S](self, success_cb: Callable[[T], S], *, on_loop: bool = ...) -> UnifiedFuture[S]: ...
135
158
  @overload
136
- def then[S](self, success_cb: Callable[[T], S], err_cb: None = ...) -> UnifiedFuture[S]: ...
159
+ def then[S](self, success_cb: Callable[[T], S], err_cb: None = ..., *, on_loop: bool = ...) -> UnifiedFuture[S]: ...
137
160
  @overload
138
- def then[V](self, success_cb: None, err_cb: Callable[[BaseException], V]) -> UnifiedFuture[T | V]: ...
161
+ def then[V](self, success_cb: None, err_cb: Callable[[BaseException], V], *, on_loop: bool = ...) -> UnifiedFuture[T | V]: ... # fmt: skip # noqa: E501
139
162
  @overload
140
- def then[V](self, *, err_cb: Callable[[BaseException], V]) -> UnifiedFuture[T | V]: ...
163
+ def then[V](self, *, err_cb: Callable[[BaseException], V], on_loop: bool = ...) -> UnifiedFuture[T | V]: ...
141
164
  @overload
142
- def then[S, V](self, success_cb: Callable[[T], S], err_cb: Callable[[BaseException], V]) -> UnifiedFuture[S | V]: ... # fmt: skip # noqa: E501
143
- def then[S, V](self, success_cb: Callable[[T], S] | None = None, err_cb: Callable[[BaseException], V] | None = None) -> Any: # fmt: skip # noqa: E501
165
+ def then[S, V](
166
+ self,
167
+ success_cb: Callable[[T], S],
168
+ err_cb: Callable[[BaseException], V],
169
+ *,
170
+ on_loop: bool = ...,
171
+ ) -> UnifiedFuture[S | V]: ...
172
+ def then[S, V](
173
+ self,
174
+ success_cb: Callable[[T], S] | None = None,
175
+ err_cb: Callable[[BaseException], V] | None = None,
176
+ *,
177
+ on_loop: bool = False,
178
+ ) -> Any:
144
179
  """
145
180
  Attach fulfilment and/or rejection handlers, returning a new future.
146
181
 
@@ -153,17 +188,29 @@ class UnifiedFuture[T](Future[T], AbstractContextManager[Any], AbstractAsyncCont
153
188
 
154
189
  :param success_cb: Called with the resolved value, or `None` to passthrough.
155
190
  :param err_cb: Called with the exception, or `None` to passthrough.
191
+ :param on_loop: If True, execute the callback on the main loop.
156
192
  :return: A new `UnifiedFuture` carrying the callback's return value.
157
193
  """
158
194
  result = UnifiedFuture[Any]()
159
195
 
160
196
  def _run_cb(cb: Callable[[Any], Any], v: T | BaseException) -> None:
161
- try:
162
- r = cb(v)
163
- except Exception as e:
164
- result.set_exception(e)
197
+ if on_loop:
198
+ loop_fut = get_loop().from_thread(cb, v)
199
+
200
+ def _forward(lf: Future[Any]) -> None:
201
+ if (exc := lf.exception()) is not None:
202
+ result.set_exception(exc)
203
+ else:
204
+ result.set_result(lf.result())
205
+
206
+ loop_fut.add_done_callback(_forward)
165
207
  else:
166
- result.set_result(r)
208
+ try:
209
+ r = cb(v)
210
+ except Exception as e:
211
+ result.set_exception(e)
212
+ else:
213
+ result.set_result(r)
167
214
 
168
215
  def _done(fn: Future[T]) -> None:
169
216
  if (exc := self.exception()) is not None:
@@ -180,23 +227,25 @@ class UnifiedFuture[T](Future[T], AbstractContextManager[Any], AbstractAsyncCont
180
227
  self.add_done_callback(_done)
181
228
  return result
182
229
 
183
- def map[V](self, cb: Callable[[T], V]) -> UnifiedFuture[V]:
230
+ def map[V](self, cb: Callable[[T], V], *, on_loop: bool = False) -> UnifiedFuture[V]:
184
231
  """
185
232
  Transform the resolved value with `cb`, returning a new future.
186
233
 
187
234
  :param cb: A callable that transforms the resolved value.
235
+ :param on_loop: If True, execute the callback on the main loop.
188
236
  :return: A new `UnifiedFuture` carrying the transformed value.
189
237
  """
190
- return self.then(cb, None)
238
+ return self.then(cb, None, on_loop=on_loop)
191
239
 
192
- def catch[V](self, cb: Callable[[BaseException], V]) -> UnifiedFuture[T | V]:
240
+ def catch[V](self, cb: Callable[[BaseException], V], *, on_loop: bool = False) -> UnifiedFuture[T | V]:
193
241
  """
194
242
  Recover from a rejection by handling the exception with `cb`.
195
243
 
196
244
  :param cb: A callable that handles the exception and returns a recovery value.
245
+ :param on_loop: If True, execute the callback on the main loop.
197
246
  :return: A new `UnifiedFuture` carrying the recovery value on error, or the original resolved value on success.
198
247
  """
199
- return self.then(None, cb)
248
+ return self.then(None, cb, on_loop=on_loop)
200
249
 
201
250
  # Nicer Syntax
202
251
  def __enter__[EnterT](self: FutureLike[AbstractContextManager[EnterT, Any]]) -> EnterT:
@@ -276,11 +325,16 @@ class UnifiedIterator[T](Iterator[T], AsyncIterator[T]):
276
325
  self.future_iterable = future_iterable
277
326
 
278
327
  @classmethod
279
- def from_call[**P](cls, func: Callable[P, Iterator[Future[T]]], *args: P.args, **kwargs: P.kwargs) -> Self:
328
+ def from_call[S, **P](
329
+ cls: type[UnifiedIteratorLike[S]],
330
+ func: Callable[P, Iterator[Future[S]]],
331
+ *args: P.args,
332
+ **kwargs: P.kwargs,
333
+ ) -> UnifiedIteratorLike[S]:
280
334
  """
281
335
  Call `func` and wrap the returned iterator as a `UnifiedIterator`.
282
336
 
283
- :param func: A callable that returns an `Iterator[Future[T]]`.
337
+ :param func: A callable that returns an `Iterator[Future[S]]`.
284
338
  :param args: Positional arguments forwarded to `func`.
285
339
  :param kwargs: Keyword arguments forwarded to `func`.
286
340
  :return: A `UnifiedIterator` wrapping the returned iterator.
@@ -1,2 +0,0 @@
1
- __version__ = "1.3.0"
2
- __version_tuple__ = (1, 3, 0)
File without changes
File without changes
File without changes
File without changes
File without changes