Description: fix: add support for Python 3.14 (#196)
Author: Julien Enselme <jenselme@jujens.eu>
Date: Tue, 23 Sep 2025 14:56:50 +0200
Origin: upstream, https://github.com/crossbario/txaio/commit/d85785de3d62f2fc33ae7f49751bfb9237a4b419
Bug-Debian: https://bugs.debian.org/1125125
Last-Update: 2026-01-12

diff --git a/.audit/python314 b/.audit/python314
new file mode 100644
index 0000000..ca68719
--- /dev/null
+++ b/.audit/python314
@@ -0,0 +1,11 @@
+## AI Assistance Disclosure
+
+- [x] I did **not** use AI-assisted tools to help create this pull request.
+- [ ] I used AI-assisted tools to help create this pull request.
+
+- [x] I have read, understood and followed the projects' [AI Policy](../AI_POLICY.rst) when creating this pull request.
+
+Submitted by: @Jenselme
+Date: 2025-09-03
+Related issue(s): #193
+Branch: python314
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 3580b6c..5b9d75e 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -35,7 +35,7 @@ jobs:
         os: [ubuntu-24.04]
 
         # https://github.com/actions/setup-python#specifying-a-pypy-version
-        python-version: ['3.10', '3.12', 'pypy-3.10']
+        python-version: ['3.10', '3.12', '3.14', 'pypy-3.10']
 
     # https://github.blog/changelog/2020-04-15-github-actions-new-workflow-features/
     # https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstepscontinue-on-error
diff --git a/test/util.py b/test/util.py
index 5d96e4d..4f6702a 100644
--- a/test/util.py
+++ b/test/util.py
@@ -47,7 +47,7 @@ def run_once():
             from _asyncio_test_utils import run_once as _run_once
         else:
             from asyncio.test_utils import run_once as _run_once
-        return _run_once(txaio.config.loop or asyncio.get_event_loop())
+        return _run_once(txaio.config.loop or _get_loop())
 
     except ImportError:
         import trollius as asyncio
@@ -66,6 +66,16 @@ def run_once():
         asyncio.gather(*asyncio.Task.all_tasks())
 
 
+def _get_loop():
+    import asyncio
+    try:
+        return asyncio.get_event_loop()
+    except RuntimeError:
+        loop = asyncio.new_event_loop()
+        asyncio.set_event_loop(loop)
+        return loop
+
+
 def _await(future):
     """
     Essentially just a way to call "run_until_complete" that becomes a
diff --git a/tox.ini b/tox.ini
index a2f3e12..a8065b3 100644
--- a/tox.ini
+++ b/tox.ini
@@ -4,6 +4,7 @@ envlist =
     flake8
     py310-{tw2210,tw255,twtrunk,asyncio}
     py312-{tw2210,tw255,twtrunk,asyncio}
+    py314-{tw2210,tw255,twtrunk,asyncio}
     pypy310-{tw2210,tw255,twtrunk,asyncio}
 
 # Python 3.11.11 (0253c85bf5f8, Feb 26 2025, 10:42:42)
@@ -21,12 +22,13 @@ envlist =
 python =
     3.10: py310
     3.12: py312
+    3.14: py314
     pypy-3.10: pypy310
 
 
 [testenv]
 deps =
-    pytest==7.2.1
+    pytest==8.4.0
     coverage==7.0.5
     tw2210: twisted==22.10.0
     tw255: twisted==25.5.0
diff --git a/txaio/aio.py b/txaio/aio.py
index e24a0c7..0f6fcc3 100644
--- a/txaio/aio.py
+++ b/txaio/aio.py
@@ -321,7 +321,12 @@ def _loop(self):
         # otherwise give out the event loop of the thread this is called in
         # rather fetching the loop once in __init__, which may not neccessarily
         # be called from the thread we now run the event loop in.
-        return asyncio.get_event_loop()
+        try:
+            return asyncio.get_event_loop()
+        except RuntimeError:
+            loop = asyncio.new_event_loop()
+            asyncio.set_event_loop(loop)
+            return loop
 
     def failure_message(self, fail):
         """
