Skip to content

Commit 850c208

Browse files
Add CI for Python 3.14 (#132)
* Add ci for Python 3.14 * Filter deprecation warnings for event loop policies, deprecated since Python 3.14
1 parent cc876eb commit 850c208

6 files changed

Lines changed: 35 additions & 18 deletions

File tree

.github/workflows/ci.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ jobs:
4646
pyversion: "3.12"
4747
- name: Linux py313
4848
pyversion: "3.13"
49+
- name: Linux py314
50+
pyversion: "3.14"
4951
steps:
5052
- uses: actions/checkout@v4
5153
- name: Install uv

observ/scheduler.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def register_request_flush(self, callback):
4949
self.request_flush = callback
5050

5151
def request_flush_asyncio(self):
52-
loop = asyncio.get_event_loop_policy().get_event_loop()
52+
loop = asyncio.get_event_loop()
5353
loop.call_soon(self.flush)
5454

5555
def register_asyncio(self):

observ/watcher.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ class Watcher(Generic[T]):
141141
"_deps",
142142
"_new_deps",
143143
"_number_of_callback_args",
144+
"_tasks",
144145
"callback",
145146
"callback_async",
146147
"deep",
@@ -185,6 +186,7 @@ def __init__(
185186
if deep is None:
186187
deep = True
187188
self._deps, self._new_deps = WeakSet(), WeakSet()
189+
self._tasks = set()
188190

189191
self.sync = sync
190192
if callable(callback):
@@ -265,11 +267,13 @@ def run_callback(self, new, old) -> None:
265267
self._number_of_callback_args = 2
266268

267269
if self.callback_async and maybe_coro:
268-
loop = asyncio.get_event_loop_policy().get_event_loop()
270+
loop = asyncio.get_event_loop()
269271
if not loop.is_running():
270272
loop.run_until_complete(maybe_coro)
271273
else:
272-
loop.create_task(maybe_coro)
274+
task = loop.create_task(maybe_coro)
275+
self._tasks.add(task)
276+
task.add_done_callback(self._tasks.discard)
273277

274278
def _run_callback(self, *args) -> None:
275279
"""
@@ -296,11 +300,13 @@ def get(self) -> Any:
296300
try:
297301
value_or_coro = self.fn()
298302
if self.fn_async and value_or_coro:
299-
loop = asyncio.get_event_loop_policy().get_event_loop()
303+
loop = asyncio.get_event_loop()
300304
if not loop.is_running():
301305
value_or_coro = loop.run_until_complete(value_or_coro)
302306
else:
303-
loop.create_task(value_or_coro)
307+
task = loop.create_task(value_or_coro)
308+
self._tasks.add(task)
309+
task.add_done_callback(self._tasks.discard)
304310
return
305311
if self.deep:
306312
traverse(value_or_coro)

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ dev = [
2727
]
2828
ruff = ["ruff"]
2929
qt = [
30-
"PySide6>=6.6.2,!=6.8.3 ; python_version < '3.14'",
30+
"PySide6>=6.6.2,!=6.8.3 ; python_version < '3.15'",
3131
"pytest-qt",
3232
"pytest-xvfb",
3333
]

tests/test_asyncio.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ def flush(loop):
1212

1313
def create_plain_loop():
1414
loop = asyncio.new_event_loop()
15-
asyncio.get_event_loop_policy().set_event_loop(loop)
15+
asyncio.set_event_loop(loop)
1616
return loop
1717

1818

tests/test_qt.py

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
import asyncio
2+
import warnings
23
from weakref import ref
34

45
import pytest
56

67
from observ import reactive, scheduler, watch
78

89
try:
9-
from PySide6 import QtAsyncio, QtWidgets
10+
with warnings.catch_warnings():
11+
# Filter deprecation warnings of event loop policies
12+
# in Python 3.14, which will be removed in 3.16
13+
warnings.filterwarnings("ignore", category=DeprecationWarning)
14+
from PySide6 import QtAsyncio, QtWidgets
1015

1116
has_qt = True
1217
except ImportError:
@@ -16,16 +21,20 @@
1621

1722
@pytest.fixture
1823
def qtasyncio():
19-
old_policy = asyncio.get_event_loop_policy()
20-
qt_policy = QtAsyncio.QAsyncioEventLoopPolicy(quit_qapp=False)
21-
asyncio.set_event_loop_policy(qt_policy)
22-
old_callback = scheduler.request_flush
23-
scheduler.register_asyncio()
24-
try:
25-
yield
26-
finally:
27-
asyncio.set_event_loop_policy(old_policy)
28-
scheduler.register_request_flush(old_callback)
24+
with warnings.catch_warnings():
25+
# Filter deprecation warnings of event loop policies
26+
# in Python 3.14, which will be removed in 3.16
27+
warnings.filterwarnings("ignore", category=DeprecationWarning)
28+
old_policy = asyncio.get_event_loop_policy()
29+
qt_policy = QtAsyncio.QAsyncioEventLoopPolicy(quit_qapp=False)
30+
asyncio.set_event_loop_policy(qt_policy)
31+
old_callback = scheduler.request_flush
32+
scheduler.register_asyncio()
33+
try:
34+
yield
35+
finally:
36+
asyncio.set_event_loop_policy(old_policy)
37+
scheduler.register_request_flush(old_callback)
2938

3039

3140
@pytest.mark.skipif(not has_qt, reason=qt_missing_reason)

0 commit comments

Comments
 (0)