vsjetengine 1.2.0__tar.gz → 1.3.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.
- {vsjetengine-1.2.0 → vsjetengine-1.3.0}/PKG-INFO +1 -1
- {vsjetengine-1.2.0 → vsjetengine-1.3.0}/tests/test_futures.py +4 -4
- {vsjetengine-1.2.0 → vsjetengine-1.3.0}/tests/test_hospice.py +2 -2
- {vsjetengine-1.2.0 → vsjetengine-1.3.0}/tests/test_loop_adapters.py +5 -5
- {vsjetengine-1.2.0 → vsjetengine-1.3.0}/tests/test_vpy.py +2 -2
- {vsjetengine-1.2.0 → vsjetengine-1.3.0}/vsengine/__init__.py +1 -0
- {vsjetengine-1.2.0 → vsjetengine-1.3.0}/vsengine/_helpers.py +2 -2
- vsjetengine-1.3.0/vsengine/_version.py +2 -0
- {vsjetengine-1.2.0 → vsjetengine-1.3.0}/vsengine/adapters/asyncio.py +2 -2
- {vsjetengine-1.2.0 → vsjetengine-1.3.0}/vsengine/adapters/trio.py +2 -2
- vsjetengine-1.2.0/vsengine/_futures.py → vsjetengine-1.3.0/vsengine/futures.py +200 -31
- {vsjetengine-1.2.0 → vsjetengine-1.3.0}/vsengine/loops.py +3 -3
- {vsjetengine-1.2.0 → vsjetengine-1.3.0}/vsengine/policy.py +6 -6
- {vsjetengine-1.2.0 → vsjetengine-1.3.0}/vsengine/video.py +1 -1
- {vsjetengine-1.2.0 → vsjetengine-1.3.0}/vsengine/vpy.py +1 -1
- vsjetengine-1.2.0/vsengine/_version.py +0 -2
- {vsjetengine-1.2.0 → vsjetengine-1.3.0}/.gitignore +0 -0
- {vsjetengine-1.2.0 → vsjetengine-1.3.0}/COPYING +0 -0
- {vsjetengine-1.2.0 → vsjetengine-1.3.0}/README.md +0 -0
- {vsjetengine-1.2.0 → vsjetengine-1.3.0}/pyproject.toml +0 -0
- {vsjetengine-1.2.0 → vsjetengine-1.3.0}/tests/__init__.py +0 -0
- {vsjetengine-1.2.0 → vsjetengine-1.3.0}/tests/_testutils.py +0 -0
- {vsjetengine-1.2.0 → vsjetengine-1.3.0}/tests/conftest.py +0 -0
- {vsjetengine-1.2.0 → vsjetengine-1.3.0}/tests/fixtures/heuristic_examples.json +0 -0
- {vsjetengine-1.2.0 → vsjetengine-1.3.0}/tests/fixtures/test.vpy +0 -0
- {vsjetengine-1.2.0 → vsjetengine-1.3.0}/tests/test_helpers.py +0 -0
- {vsjetengine-1.2.0 → vsjetengine-1.3.0}/tests/test_loops.py +0 -0
- {vsjetengine-1.2.0 → vsjetengine-1.3.0}/tests/test_policy.py +0 -0
- {vsjetengine-1.2.0 → vsjetengine-1.3.0}/tests/test_policy_store.py +0 -0
- {vsjetengine-1.2.0 → vsjetengine-1.3.0}/tests/test_video.py +0 -0
- {vsjetengine-1.2.0 → vsjetengine-1.3.0}/vsengine/_hospice.py +0 -0
- {vsjetengine-1.2.0 → vsjetengine-1.3.0}/vsengine/_nodes.py +0 -0
- {vsjetengine-1.2.0 → vsjetengine-1.3.0}/vsengine/adapters/__init__.py +0 -0
- {vsjetengine-1.2.0 → vsjetengine-1.3.0}/vsengine/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: vsjetengine
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.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
|
|
@@ -7,14 +7,14 @@
|
|
|
7
7
|
|
|
8
8
|
import contextlib
|
|
9
9
|
import threading
|
|
10
|
-
from collections.abc import
|
|
10
|
+
from collections.abc import AsyncGenerator, Generator, Iterator
|
|
11
11
|
from concurrent.futures import Future
|
|
12
12
|
from typing import Any
|
|
13
13
|
|
|
14
14
|
import pytest
|
|
15
15
|
|
|
16
|
-
from vsengine._futures import UnifiedFuture, UnifiedIterator, unified
|
|
17
16
|
from vsengine.adapters.asyncio import AsyncIOLoop
|
|
17
|
+
from vsengine.futures import UnifiedFuture, UnifiedIterator, unified
|
|
18
18
|
from vsengine.loops import NO_LOOP, set_loop
|
|
19
19
|
|
|
20
20
|
|
|
@@ -32,7 +32,7 @@ def reject(err: BaseException) -> Future[Any]:
|
|
|
32
32
|
|
|
33
33
|
def contextmanager_helper() -> Future[Any]:
|
|
34
34
|
@contextlib.contextmanager
|
|
35
|
-
def noop() ->
|
|
35
|
+
def noop() -> Generator[int]:
|
|
36
36
|
yield 1
|
|
37
37
|
|
|
38
38
|
return resolve(noop())
|
|
@@ -40,7 +40,7 @@ def contextmanager_helper() -> Future[Any]:
|
|
|
40
40
|
|
|
41
41
|
def asynccontextmanager_helper() -> Future[Any]:
|
|
42
42
|
@contextlib.asynccontextmanager
|
|
43
|
-
async def noop() ->
|
|
43
|
+
async def noop() -> AsyncGenerator[int]:
|
|
44
44
|
yield 2
|
|
45
45
|
|
|
46
46
|
return resolve(noop())
|
|
@@ -9,7 +9,7 @@ import contextlib
|
|
|
9
9
|
import gc
|
|
10
10
|
import logging
|
|
11
11
|
import weakref
|
|
12
|
-
from collections.abc import Iterator
|
|
12
|
+
from collections.abc import Generator, Iterator
|
|
13
13
|
from typing import Any
|
|
14
14
|
|
|
15
15
|
import pytest
|
|
@@ -53,7 +53,7 @@ class MockEnv:
|
|
|
53
53
|
|
|
54
54
|
|
|
55
55
|
@contextlib.contextmanager
|
|
56
|
-
def hide_logs() ->
|
|
56
|
+
def hide_logs() -> Generator[None]:
|
|
57
57
|
logging.disable(logging.CRITICAL)
|
|
58
58
|
try:
|
|
59
59
|
yield
|
|
@@ -42,7 +42,7 @@ class AdapterTest:
|
|
|
42
42
|
"""Base class for event loop adapter tests."""
|
|
43
43
|
|
|
44
44
|
@contextlib.contextmanager
|
|
45
|
-
def with_loop(self) ->
|
|
45
|
+
def with_loop(self) -> Generator[EventLoop]:
|
|
46
46
|
loop = self.make_loop()
|
|
47
47
|
set_loop(loop)
|
|
48
48
|
try:
|
|
@@ -60,7 +60,7 @@ class AdapterTest:
|
|
|
60
60
|
raise NotImplementedError
|
|
61
61
|
|
|
62
62
|
@contextlib.contextmanager
|
|
63
|
-
def assert_cancelled(self) ->
|
|
63
|
+
def assert_cancelled(self) -> Generator[None]:
|
|
64
64
|
raise NotImplementedError
|
|
65
65
|
|
|
66
66
|
@make_async
|
|
@@ -216,7 +216,7 @@ class TestNoLoop(AdapterTest):
|
|
|
216
216
|
pass
|
|
217
217
|
|
|
218
218
|
@contextlib.contextmanager
|
|
219
|
-
def assert_cancelled(self) ->
|
|
219
|
+
def assert_cancelled(self) -> Generator[None]:
|
|
220
220
|
with pytest.raises(CancelledError):
|
|
221
221
|
yield
|
|
222
222
|
|
|
@@ -244,7 +244,7 @@ class TestAsyncIO(AsyncAdapterTest):
|
|
|
244
244
|
return await asyncio.wait_for(coro, timeout)
|
|
245
245
|
|
|
246
246
|
@contextlib.contextmanager
|
|
247
|
-
def assert_cancelled(self) ->
|
|
247
|
+
def assert_cancelled(self) -> Generator[None]:
|
|
248
248
|
with pytest.raises(asyncio.CancelledError):
|
|
249
249
|
yield
|
|
250
250
|
|
|
@@ -309,6 +309,6 @@ else:
|
|
|
309
309
|
return await coro
|
|
310
310
|
|
|
311
311
|
@contextlib.contextmanager
|
|
312
|
-
def assert_cancelled(self) ->
|
|
312
|
+
def assert_cancelled(self) -> Generator[None]:
|
|
313
313
|
with pytest.raises(trio.Cancelled):
|
|
314
314
|
yield
|
|
@@ -15,7 +15,7 @@ import textwrap
|
|
|
15
15
|
import threading
|
|
16
16
|
import types
|
|
17
17
|
import weakref
|
|
18
|
-
from collections.abc import Callable,
|
|
18
|
+
from collections.abc import Callable, Generator
|
|
19
19
|
from pathlib import Path
|
|
20
20
|
from typing import Any
|
|
21
21
|
|
|
@@ -44,7 +44,7 @@ PATH: str = os.path.join(DIR, "fixtures", "test.vpy")
|
|
|
44
44
|
|
|
45
45
|
|
|
46
46
|
@contextlib.contextmanager
|
|
47
|
-
def noop() ->
|
|
47
|
+
def noop() -> Generator[None]:
|
|
48
48
|
yield
|
|
49
49
|
|
|
50
50
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# This project is licensed under the EUPL-1.2
|
|
5
5
|
# SPDX-License-Identifier: EUPL-1.2
|
|
6
6
|
import contextlib
|
|
7
|
-
from collections.abc import
|
|
7
|
+
from collections.abc import Generator
|
|
8
8
|
|
|
9
9
|
import vapoursynth as vs
|
|
10
10
|
|
|
@@ -13,7 +13,7 @@ from vsengine.policy import ManagedEnvironment
|
|
|
13
13
|
|
|
14
14
|
# Automatically set the environment within that block.
|
|
15
15
|
@contextlib.contextmanager
|
|
16
|
-
def use_inline(function_name: str, env: vs.Environment | ManagedEnvironment | None) ->
|
|
16
|
+
def use_inline(function_name: str, env: vs.Environment | ManagedEnvironment | None) -> Generator[None]:
|
|
17
17
|
if env is None:
|
|
18
18
|
# Ensure there is actually an environment set in this block.
|
|
19
19
|
try:
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import asyncio
|
|
8
8
|
import contextlib
|
|
9
9
|
import contextvars
|
|
10
|
-
from collections.abc import Callable,
|
|
10
|
+
from collections.abc import Callable, Generator
|
|
11
11
|
from concurrent.futures import Future
|
|
12
12
|
|
|
13
13
|
from vsengine.loops import Cancelled, EventLoop
|
|
@@ -78,7 +78,7 @@ class AsyncIOLoop(EventLoop):
|
|
|
78
78
|
return await asyncio.wrap_future(future, loop=self.loop)
|
|
79
79
|
|
|
80
80
|
@contextlib.contextmanager
|
|
81
|
-
def wrap_cancelled(self) ->
|
|
81
|
+
def wrap_cancelled(self) -> Generator[None]:
|
|
82
82
|
try:
|
|
83
83
|
yield
|
|
84
84
|
except Cancelled:
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
# SPDX-License-Identifier: EUPL-1.2
|
|
6
6
|
|
|
7
7
|
import contextlib
|
|
8
|
-
from collections.abc import Callable,
|
|
8
|
+
from collections.abc import Callable, Generator
|
|
9
9
|
from concurrent.futures import Future
|
|
10
10
|
|
|
11
11
|
import trio
|
|
@@ -100,7 +100,7 @@ class TrioEventLoop(EventLoop):
|
|
|
100
100
|
raise exc
|
|
101
101
|
|
|
102
102
|
@contextlib.contextmanager
|
|
103
|
-
def wrap_cancelled(self) ->
|
|
103
|
+
def wrap_cancelled(self) -> Generator[None]:
|
|
104
104
|
try:
|
|
105
105
|
yield
|
|
106
106
|
except Cancelled:
|
|
@@ -5,20 +5,47 @@
|
|
|
5
5
|
# SPDX-License-Identifier: EUPL-1.2
|
|
6
6
|
from __future__ import annotations
|
|
7
7
|
|
|
8
|
+
import traceback
|
|
8
9
|
from collections.abc import AsyncIterator, Awaitable, Callable, Generator, Iterator
|
|
9
10
|
from concurrent.futures import Future
|
|
10
11
|
from contextlib import AbstractAsyncContextManager, AbstractContextManager
|
|
11
12
|
from functools import wraps
|
|
12
13
|
from inspect import isgeneratorfunction
|
|
13
14
|
from types import TracebackType
|
|
14
|
-
from typing import Any, Literal, Self, overload
|
|
15
|
+
from typing import Any, Literal, Protocol, Self, overload
|
|
15
16
|
|
|
16
|
-
from vsengine.loops import
|
|
17
|
+
from vsengine.loops import get_loop, keep_environment
|
|
17
18
|
|
|
19
|
+
__all__ = ["UnifiedFuture", "UnifiedIterator", "unified"]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class FutureLike[V](Protocol):
|
|
23
|
+
def result(self) -> V: ...
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class AsyncFutureLike[V](Protocol):
|
|
27
|
+
async def awaitable(self) -> V: ...
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class UnifiedFuture[T](Future[T], AbstractContextManager[Any], AbstractAsyncContextManager[Any], Awaitable[T]):
|
|
31
|
+
"""
|
|
32
|
+
A Promise-inspired Future that unifies concurrent.futures.Future
|
|
33
|
+
with Python's synchronous and asynchronous context manager and awaitable protocols.
|
|
34
|
+
"""
|
|
18
35
|
|
|
19
|
-
class UnifiedFuture[T](Future[T], AbstractContextManager[T, Any], AbstractAsyncContextManager[T, Any], Awaitable[T]):
|
|
20
36
|
@classmethod
|
|
21
37
|
def from_call[**P](cls, func: Callable[P, Future[T]], *args: P.args, **kwargs: P.kwargs) -> Self:
|
|
38
|
+
"""
|
|
39
|
+
Call `func` and wrap the returned `Future` as a `UnifiedFuture`.
|
|
40
|
+
|
|
41
|
+
Any exception raised synchronously by `func` is captured and stored as a rejection
|
|
42
|
+
on the returned future rather than propagating to the caller.
|
|
43
|
+
|
|
44
|
+
:param func: A callable that returns a `Future`.
|
|
45
|
+
:param args: Positional arguments forwarded to `func`.
|
|
46
|
+
:param kwargs: Keyword arguments forwarded to `func`.
|
|
47
|
+
:return: A `UnifiedFuture` that mirrors the result of `func`.
|
|
48
|
+
"""
|
|
22
49
|
try:
|
|
23
50
|
future = func(*args, **kwargs)
|
|
24
51
|
except Exception as e:
|
|
@@ -28,6 +55,14 @@ class UnifiedFuture[T](Future[T], AbstractContextManager[T, Any], AbstractAsyncC
|
|
|
28
55
|
|
|
29
56
|
@classmethod
|
|
30
57
|
def from_future(cls, future: Future[T]) -> Self:
|
|
58
|
+
"""
|
|
59
|
+
Wrap an existing `Future` as a `UnifiedFuture`.
|
|
60
|
+
|
|
61
|
+
If `future` is already an instance of this class it is returned unchanged.
|
|
62
|
+
|
|
63
|
+
:param future: The future to wrap.
|
|
64
|
+
:return: A `UnifiedFuture` that mirrors `future`.
|
|
65
|
+
"""
|
|
31
66
|
if isinstance(future, cls):
|
|
32
67
|
return future
|
|
33
68
|
|
|
@@ -44,22 +79,51 @@ class UnifiedFuture[T](Future[T], AbstractContextManager[T, Any], AbstractAsyncC
|
|
|
44
79
|
|
|
45
80
|
@classmethod
|
|
46
81
|
def resolve(cls, value: T) -> Self:
|
|
82
|
+
"""
|
|
83
|
+
Return an already-resolved `UnifiedFuture` carrying `value`.
|
|
84
|
+
|
|
85
|
+
:param value: The value to resolve with.
|
|
86
|
+
:return: A resolved `UnifiedFuture`.
|
|
87
|
+
"""
|
|
47
88
|
future = cls()
|
|
48
89
|
future.set_result(value)
|
|
49
90
|
return future
|
|
50
91
|
|
|
51
92
|
@classmethod
|
|
52
93
|
def reject(cls, error: BaseException) -> Self:
|
|
94
|
+
"""
|
|
95
|
+
Return an already-rejected `UnifiedFuture` carrying `error`.
|
|
96
|
+
|
|
97
|
+
:param error: The exception to reject with.
|
|
98
|
+
:return: A rejected `UnifiedFuture`.
|
|
99
|
+
"""
|
|
53
100
|
future = cls()
|
|
54
101
|
future.set_exception(error)
|
|
55
102
|
return future
|
|
56
103
|
|
|
57
104
|
# Adding callbacks
|
|
58
105
|
def add_done_callback(self, fn: Callable[[Future[T]], Any]) -> None:
|
|
106
|
+
"""
|
|
107
|
+
Register a callback to be called when this future completes.
|
|
108
|
+
|
|
109
|
+
Wraps the callback in `keep_environment` so that the VapourSynth environment active at registration time
|
|
110
|
+
is restored when the callback fires (potentially from a worker thread).
|
|
111
|
+
|
|
112
|
+
:param fn: A callable that receives the completed future.
|
|
113
|
+
"""
|
|
59
114
|
# The done_callback should inherit the environment of the current call.
|
|
60
115
|
super().add_done_callback(keep_environment(fn))
|
|
61
116
|
|
|
62
|
-
def add_loop_callback(self, func: Callable[[Future[T]],
|
|
117
|
+
def add_loop_callback(self, func: Callable[[Future[T]], Any]) -> None:
|
|
118
|
+
"""
|
|
119
|
+
Register a callback that is guaranteed to run on the event-loop thread.
|
|
120
|
+
|
|
121
|
+
Unlike `add_done_callback`, which may fire from any thread,
|
|
122
|
+
this method marshals `func` back to the main event loop via `EventLoop.from_thread`.
|
|
123
|
+
|
|
124
|
+
:param func: A callable that receives the completed future.
|
|
125
|
+
"""
|
|
126
|
+
|
|
63
127
|
def _wrapper(future: Future[T]) -> None:
|
|
64
128
|
get_loop().from_thread(func, future)
|
|
65
129
|
|
|
@@ -67,18 +131,36 @@ class UnifiedFuture[T](Future[T], AbstractContextManager[T, Any], AbstractAsyncC
|
|
|
67
131
|
|
|
68
132
|
# Manipulating futures
|
|
69
133
|
@overload
|
|
70
|
-
def then[
|
|
134
|
+
def then[S](self, success_cb: Callable[[T], S]) -> UnifiedFuture[S]: ...
|
|
135
|
+
@overload
|
|
136
|
+
def then[S](self, success_cb: Callable[[T], S], err_cb: None = ...) -> UnifiedFuture[S]: ...
|
|
71
137
|
@overload
|
|
72
138
|
def then[V](self, success_cb: None, err_cb: Callable[[BaseException], V]) -> UnifiedFuture[T | V]: ...
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
139
|
+
@overload
|
|
140
|
+
def then[V](self, *, err_cb: Callable[[BaseException], V]) -> UnifiedFuture[T | V]: ...
|
|
141
|
+
@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
|
|
144
|
+
"""
|
|
145
|
+
Attach fulfilment and/or rejection handlers, returning a new future.
|
|
146
|
+
|
|
147
|
+
* If this future resolves successfully, `success_cb` is called with the result value.
|
|
148
|
+
If `success_cb` is `None` the result is forwarded as-is.
|
|
149
|
+
* If this future is rejected, `err_cb` is called with the exception.
|
|
150
|
+
If `err_cb` is `None` the exception is forwarded as-is.
|
|
151
|
+
|
|
152
|
+
Exceptions raised inside either callback are captured and stored as rejections on the returned future.
|
|
153
|
+
|
|
154
|
+
:param success_cb: Called with the resolved value, or `None` to passthrough.
|
|
155
|
+
:param err_cb: Called with the exception, or `None` to passthrough.
|
|
156
|
+
:return: A new `UnifiedFuture` carrying the callback's return value.
|
|
157
|
+
"""
|
|
158
|
+
result = UnifiedFuture[Any]()
|
|
77
159
|
|
|
78
|
-
def _run_cb(cb: Callable[[Any],
|
|
160
|
+
def _run_cb(cb: Callable[[Any], Any], v: T | BaseException) -> None:
|
|
79
161
|
try:
|
|
80
162
|
r = cb(v)
|
|
81
|
-
except
|
|
163
|
+
except Exception as e:
|
|
82
164
|
result.set_exception(e)
|
|
83
165
|
else:
|
|
84
166
|
result.set_result(r)
|
|
@@ -99,13 +181,25 @@ class UnifiedFuture[T](Future[T], AbstractContextManager[T, Any], AbstractAsyncC
|
|
|
99
181
|
return result
|
|
100
182
|
|
|
101
183
|
def map[V](self, cb: Callable[[T], V]) -> UnifiedFuture[V]:
|
|
184
|
+
"""
|
|
185
|
+
Transform the resolved value with `cb`, returning a new future.
|
|
186
|
+
|
|
187
|
+
:param cb: A callable that transforms the resolved value.
|
|
188
|
+
:return: A new `UnifiedFuture` carrying the transformed value.
|
|
189
|
+
"""
|
|
102
190
|
return self.then(cb, None)
|
|
103
191
|
|
|
104
192
|
def catch[V](self, cb: Callable[[BaseException], V]) -> UnifiedFuture[T | V]:
|
|
193
|
+
"""
|
|
194
|
+
Recover from a rejection by handling the exception with `cb`.
|
|
195
|
+
|
|
196
|
+
:param cb: A callable that handles the exception and returns a recovery value.
|
|
197
|
+
:return: A new `UnifiedFuture` carrying the recovery value on error, or the original resolved value on success.
|
|
198
|
+
"""
|
|
105
199
|
return self.then(None, cb)
|
|
106
200
|
|
|
107
201
|
# Nicer Syntax
|
|
108
|
-
def __enter__(self) ->
|
|
202
|
+
def __enter__[EnterT](self: FutureLike[AbstractContextManager[EnterT, Any]]) -> EnterT:
|
|
109
203
|
obj = self.result()
|
|
110
204
|
|
|
111
205
|
if isinstance(obj, AbstractContextManager):
|
|
@@ -113,7 +207,12 @@ class UnifiedFuture[T](Future[T], AbstractContextManager[T, Any], AbstractAsyncC
|
|
|
113
207
|
|
|
114
208
|
raise NotImplementedError("(async) with is not implemented for this object")
|
|
115
209
|
|
|
116
|
-
def __exit__(
|
|
210
|
+
def __exit__(
|
|
211
|
+
self,
|
|
212
|
+
exc: type[BaseException] | None,
|
|
213
|
+
val: BaseException | None,
|
|
214
|
+
tb: TracebackType | None,
|
|
215
|
+
) -> bool | None:
|
|
117
216
|
obj = self.result()
|
|
118
217
|
|
|
119
218
|
if isinstance(obj, AbstractContextManager):
|
|
@@ -122,12 +221,20 @@ class UnifiedFuture[T](Future[T], AbstractContextManager[T, Any], AbstractAsyncC
|
|
|
122
221
|
raise NotImplementedError("(async) with is not implemented for this object")
|
|
123
222
|
|
|
124
223
|
async def awaitable(self) -> T:
|
|
224
|
+
"""
|
|
225
|
+
Await this future using the currently active event loop.
|
|
226
|
+
|
|
227
|
+
:return: The resolved value of this future.
|
|
228
|
+
:raises: Whatever exception this future was rejected with.
|
|
229
|
+
"""
|
|
125
230
|
return await get_loop().await_future(self)
|
|
126
231
|
|
|
127
232
|
def __await__(self) -> Generator[Any, None, T]:
|
|
128
233
|
return self.awaitable().__await__()
|
|
129
234
|
|
|
130
|
-
async def __aenter__(
|
|
235
|
+
async def __aenter__[EnterT](
|
|
236
|
+
self: AsyncFutureLike[AbstractAsyncContextManager[EnterT, Any] | AbstractContextManager[EnterT, Any]],
|
|
237
|
+
) -> EnterT:
|
|
131
238
|
result = await self.awaitable()
|
|
132
239
|
|
|
133
240
|
if isinstance(result, AbstractAsyncContextManager):
|
|
@@ -138,8 +245,11 @@ class UnifiedFuture[T](Future[T], AbstractContextManager[T, Any], AbstractAsyncC
|
|
|
138
245
|
raise NotImplementedError("(async) with is not implemented for this object")
|
|
139
246
|
|
|
140
247
|
async def __aexit__(
|
|
141
|
-
self,
|
|
142
|
-
|
|
248
|
+
self,
|
|
249
|
+
exc: type[BaseException] | None,
|
|
250
|
+
val: BaseException | None,
|
|
251
|
+
tb: TracebackType | None,
|
|
252
|
+
) -> bool | None:
|
|
143
253
|
result = await self.awaitable()
|
|
144
254
|
|
|
145
255
|
if isinstance(result, AbstractAsyncContextManager):
|
|
@@ -151,30 +261,61 @@ class UnifiedFuture[T](Future[T], AbstractContextManager[T, Any], AbstractAsyncC
|
|
|
151
261
|
|
|
152
262
|
|
|
153
263
|
class UnifiedIterator[T](Iterator[T], AsyncIterator[T]):
|
|
264
|
+
"""
|
|
265
|
+
A dual-mode iterator that wraps an `Iterator[Future[T]]`
|
|
266
|
+
and exposes it as both a synchronous `Iterator` and an asynchronous `AsyncIterator`.
|
|
267
|
+
|
|
268
|
+
In synchronous mode (`__next__`), each future is resolved by calling `Future.result()`.
|
|
269
|
+
This blocks if the future is not yet done.
|
|
270
|
+
|
|
271
|
+
In asynchronous mode (`__anext__`), each future is awaited via `EventLoop.await_future`,
|
|
272
|
+
cooperating with the active event loop.
|
|
273
|
+
"""
|
|
274
|
+
|
|
154
275
|
def __init__(self, future_iterable: Iterator[Future[T]]) -> None:
|
|
155
276
|
self.future_iterable = future_iterable
|
|
156
277
|
|
|
157
278
|
@classmethod
|
|
158
279
|
def from_call[**P](cls, func: Callable[P, Iterator[Future[T]]], *args: P.args, **kwargs: P.kwargs) -> Self:
|
|
280
|
+
"""
|
|
281
|
+
Call `func` and wrap the returned iterator as a `UnifiedIterator`.
|
|
282
|
+
|
|
283
|
+
:param func: A callable that returns an `Iterator[Future[T]]`.
|
|
284
|
+
:param args: Positional arguments forwarded to `func`.
|
|
285
|
+
:param kwargs: Keyword arguments forwarded to `func`.
|
|
286
|
+
:return: A `UnifiedIterator` wrapping the returned iterator.
|
|
287
|
+
"""
|
|
159
288
|
return cls(func(*args, **kwargs))
|
|
160
289
|
|
|
161
290
|
@property
|
|
162
291
|
def futures(self) -> Iterator[Future[T]]:
|
|
292
|
+
"""The raw underlying `Iterator[Future[T]]`."""
|
|
163
293
|
return self.future_iterable
|
|
164
294
|
|
|
165
295
|
def run_as_completed(self, callback: Callable[[Future[T]], Any]) -> UnifiedFuture[None]:
|
|
166
|
-
|
|
296
|
+
"""
|
|
297
|
+
Consume the iterator and invoke `callback` for each future as it completes.
|
|
167
298
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
299
|
+
The loop is event-loop-cooperative:
|
|
300
|
+
after each callback it calls `EventLoop.next_cycle` to yield control back to the event loop
|
|
301
|
+
so that other work can proceed.
|
|
302
|
+
|
|
303
|
+
The returned `UnifiedFuture` resolves to `None` when all futures have been processed, or is rejected if:
|
|
304
|
+
|
|
305
|
+
* the iterator raises,
|
|
306
|
+
* `callback` raises, or
|
|
307
|
+
* `callback` returns a falsy non-`None` value (signals an early stop).
|
|
308
|
+
|
|
309
|
+
Cancellation is detected by checking `Future.cancelled()` on the state future; raise `Cancelled` to abort.
|
|
310
|
+
|
|
311
|
+
:param callback: Called for each completed `Future`.
|
|
312
|
+
Return `None` or a truthy value to continue; return a falsy value to stop iteration early.
|
|
313
|
+
:return: A `UnifiedFuture` that resolves when iteration is complete.
|
|
314
|
+
"""
|
|
315
|
+
state = UnifiedFuture[None]()
|
|
175
316
|
|
|
176
317
|
def _get_next_future() -> Future[T] | None:
|
|
177
|
-
if
|
|
318
|
+
if state.done():
|
|
178
319
|
return None
|
|
179
320
|
|
|
180
321
|
try:
|
|
@@ -211,8 +352,6 @@ class UnifiedIterator[T](Iterator[T], AsyncIterator[T]):
|
|
|
211
352
|
state.set_exception(next_cycle.exception())
|
|
212
353
|
return
|
|
213
354
|
except Exception as e:
|
|
214
|
-
import traceback
|
|
215
|
-
|
|
216
355
|
traceback.print_exception(e)
|
|
217
356
|
state.set_exception(e)
|
|
218
357
|
|
|
@@ -234,12 +373,12 @@ class UnifiedIterator[T](Iterator[T], AsyncIterator[T]):
|
|
|
234
373
|
def _run_single_callback(fut: Future[T]) -> bool:
|
|
235
374
|
# True => Schedule next future.
|
|
236
375
|
# False => Cancel the loop.
|
|
237
|
-
if
|
|
376
|
+
if state.done():
|
|
238
377
|
return False
|
|
239
378
|
|
|
240
379
|
try:
|
|
241
380
|
result = callback(fut)
|
|
242
|
-
except
|
|
381
|
+
except Exception as e:
|
|
243
382
|
state.set_exception(e)
|
|
244
383
|
return False
|
|
245
384
|
else:
|
|
@@ -335,8 +474,38 @@ def unified[T, **P](
|
|
|
335
474
|
future_class: type[UnifiedFuture[Any]] = UnifiedFuture[Any],
|
|
336
475
|
) -> Any:
|
|
337
476
|
"""
|
|
338
|
-
Decorator to normalize functions
|
|
339
|
-
into functions
|
|
477
|
+
Decorator factory to normalize functions that return raw futures or iterators of futures
|
|
478
|
+
into functions that return `UnifiedFuture` or `UnifiedIterator`.
|
|
479
|
+
|
|
480
|
+
:param kind: Controls which wrapper is applied.
|
|
481
|
+
|
|
482
|
+
`"auto"` (default)
|
|
483
|
+
Automatically detects generator functions (via `isgeneratorfunction`)
|
|
484
|
+
and wraps them as `UnifiedIterator`; all other callables are wrapped as
|
|
485
|
+
`UnifiedFuture`.
|
|
486
|
+
|
|
487
|
+
`"future"`
|
|
488
|
+
Always wrap as `UnifiedFuture`.
|
|
489
|
+
|
|
490
|
+
`"generator"`
|
|
491
|
+
Always wrap as `UnifiedIterator`.
|
|
492
|
+
|
|
493
|
+
:param future_class: The concrete `UnifiedFuture` subclass to use when wrapping single-value futures.
|
|
494
|
+
:param iterable_class: The concrete `UnifiedIterator` subclass to use when wrapping generators.
|
|
495
|
+
:return: A decorator that wraps the target function.
|
|
496
|
+
|
|
497
|
+
Example usage:
|
|
498
|
+
```python
|
|
499
|
+
@unified(kind="future")
|
|
500
|
+
def request_frame(index: int) -> Future[vs.VideoFrame]:
|
|
501
|
+
return node.get_frame_async(index)
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
@unified(kind="generator")
|
|
505
|
+
def request_all_frames(node: vs.VideoNode) -> Iterator[Future[vs.VideoFrame]]:
|
|
506
|
+
for i in range(node.num_frames):
|
|
507
|
+
yield node.get_frame_async(i)
|
|
508
|
+
```
|
|
340
509
|
"""
|
|
341
510
|
|
|
342
511
|
def _decorator_generator(func: Callable[P, Iterator[Future[T]]]) -> Callable[P, UnifiedIterator[T]]:
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import threading
|
|
10
10
|
from abc import abstractmethod
|
|
11
|
-
from collections.abc import Awaitable, Callable,
|
|
11
|
+
from collections.abc import Awaitable, Callable, Generator
|
|
12
12
|
from concurrent.futures import CancelledError, Future
|
|
13
13
|
from contextlib import contextmanager
|
|
14
14
|
from functools import wraps
|
|
@@ -23,7 +23,7 @@ class Cancelled(BaseException):
|
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
@contextmanager
|
|
26
|
-
def _noop() ->
|
|
26
|
+
def _noop() -> Generator[None]:
|
|
27
27
|
yield
|
|
28
28
|
|
|
29
29
|
|
|
@@ -126,7 +126,7 @@ class EventLoop:
|
|
|
126
126
|
raise NotImplementedError
|
|
127
127
|
|
|
128
128
|
@contextmanager
|
|
129
|
-
def wrap_cancelled(self) ->
|
|
129
|
+
def wrap_cancelled(self) -> Generator[None]:
|
|
130
130
|
"""
|
|
131
131
|
Context manager to translate cancellation exceptions.
|
|
132
132
|
|
|
@@ -12,7 +12,7 @@ from __future__ import annotations
|
|
|
12
12
|
|
|
13
13
|
import threading
|
|
14
14
|
from abc import ABC, abstractmethod
|
|
15
|
-
from collections.abc import
|
|
15
|
+
from collections.abc import Generator
|
|
16
16
|
from contextlib import AbstractContextManager, contextmanager
|
|
17
17
|
from contextvars import ContextVar
|
|
18
18
|
from logging import getLogger
|
|
@@ -272,7 +272,7 @@ class ManagedEnvironment(AbstractContextManager["ManagedEnvironment"]):
|
|
|
272
272
|
del self._data
|
|
273
273
|
|
|
274
274
|
@contextmanager
|
|
275
|
-
def inline_section(self) ->
|
|
275
|
+
def inline_section(self) -> Generator[None]:
|
|
276
276
|
"""
|
|
277
277
|
Private API!
|
|
278
278
|
|
|
@@ -295,7 +295,7 @@ class ManagedEnvironment(AbstractContextManager["ManagedEnvironment"]):
|
|
|
295
295
|
self._policy.managed.inline_section_end()
|
|
296
296
|
|
|
297
297
|
@contextmanager
|
|
298
|
-
def use(self) ->
|
|
298
|
+
def use(self) -> Generator[None]:
|
|
299
299
|
"""
|
|
300
300
|
Switches to this environment within a block.
|
|
301
301
|
"""
|
|
@@ -328,16 +328,16 @@ class Policy(AbstractContextManager["Policy"]):
|
|
|
328
328
|
|
|
329
329
|
_managed: _ManagedPolicy
|
|
330
330
|
|
|
331
|
-
def __init__(self, store: EnvironmentStore, flags_creation: int = 0) -> None:
|
|
331
|
+
def __init__(self, store: EnvironmentStore | None = None, flags_creation: int = 0) -> None:
|
|
332
332
|
"""
|
|
333
333
|
Initializes a new Policy
|
|
334
334
|
|
|
335
335
|
Args:
|
|
336
|
-
store: The store to use for managing environments.
|
|
336
|
+
store: The store to use for managing environments. If None, defaults to a GlobalStore.
|
|
337
337
|
flags_creation: The flags to use when creating environments.
|
|
338
338
|
See vapoursynth.CoreCreationFlags for more information.
|
|
339
339
|
"""
|
|
340
|
-
self._managed = _ManagedPolicy(store)
|
|
340
|
+
self._managed = _ManagedPolicy(store or GlobalStore())
|
|
341
341
|
self.flags_creation = flags_creation
|
|
342
342
|
|
|
343
343
|
def __enter__(self) -> Self:
|
|
@@ -12,9 +12,9 @@ from concurrent.futures import Future
|
|
|
12
12
|
|
|
13
13
|
import vapoursynth as vs
|
|
14
14
|
|
|
15
|
-
from ._futures import UnifiedFuture, unified
|
|
16
15
|
from ._helpers import use_inline
|
|
17
16
|
from ._nodes import buffer_futures, close_when_needed
|
|
17
|
+
from .futures import UnifiedFuture, unified
|
|
18
18
|
from .policy import ManagedEnvironment
|
|
19
19
|
|
|
20
20
|
__all__ = ["frame", "frames", "planes", "render"]
|
|
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
|