Skip to content

Commit 522e83b

Browse files
authored
Fix 1878 (#1879)
* start new dev branch; add audit file * Add import smoke test + cross-version CI job (#1878) Adds a guard for import-time annotation regressions that only surface on CPython < 3.14 (where annotations are evaluated eagerly; PEP 649 defers it on 3.14): - src/autobahn/test/test_import_all.py: imports a curated set of framework-agnostic core modules (incl. autobahn.wamp.cryptosign) and asserts crypto extras are present so HAS_CRYPTOSIGN paths are exercised. - justfile: `test-imports` recipe (installs .[all] so PyNaCl is present). - .github/workflows/main.yml: `import-smoke` matrix job running the smoke test on cpy311/cpy312/cpy313/cpy314/pypy311. The main test suite runs on cpy314 only, so 3.11-3.13 import regressions were previously invisible. This commit intentionally does NOT include the cryptosign fix yet: it is expected to FAIL on CPython 3.11/3.12/3.13 (TypeError importing autobahn.wamp.cryptosign) and pass on 3.14, demonstrating both the live 26.6.1 regression and that the guard catches it. The fix follows in the next commit. Note: This work was completed with AI assistance (Claude Code). * Fix cryptosign import TypeError via from __future__ import annotations (#1878) autobahn 26.6.1 regressed: importing autobahn.wamp.cryptosign raised `TypeError: unsupported operand type(s) for |: 'str' and 'NoneType'` on CPython 3.11/3.12/3.13 (3.14 unaffected via PEP 649). A ruff UP007 autofix in #1843 rewrote `Optional["ISecurityModule"]` -> `"ISecurityModule" | None` in a module without `from __future__ import annotations`, so the string forward-reference union was evaluated eagerly at class-definition time. This broke WAMP-cryptosign and any importer with crypto deps present (xbr, Crossbar.io) on CPython < 3.14. Fix: add `from __future__ import annotations` to cryptosign.py to defer annotation evaluation (audit confirms this was the only affected line). Bump version 26.6.1 -> 26.6.2 and add the changelog entry. With this fix the import-smoke job added in the previous commit goes green across cpy311/cpy312/cpy313/cpy314/pypy311 (it failed on 3.11-3.13 before). Note: This work was completed with AI assistance (Claude Code).
1 parent e5be16a commit 522e83b

8 files changed

Lines changed: 197 additions & 2 deletions

File tree

.audit/oberstet_fix_1878.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
- [ ] I did **not** use any AI-assistance tools to help create this pull request.
2+
- [x] I **did** use AI-assistance tools to *help* create this pull request.
3+
- [x] I have read, understood and followed the projects' [AI Policy](https://github.com/crossbario/autobahn-python/blob/main/AI_POLICY.md) when creating code, documentation etc. for this pull request.
4+
5+
Submitted by: @oberstet
6+
Date: 2026-06-19
7+
Related issue(s): #1878
8+
Branch: oberstet:1878

.github/workflows/main.yml

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,71 @@ jobs:
211211
path: ${{ github.workspace }}/test-results/serdes-${{ matrix.python-env }}/
212212
retention-days: 7
213213

214+
import-smoke:
215+
name: Import Smoke Test
216+
needs: identifiers
217+
runs-on: ubuntu-24.04
218+
219+
env:
220+
BASE_REPO: ${{ needs.identifiers.outputs.base_repo }}
221+
BASE_BRANCH: ${{ needs.identifiers.outputs.base_branch }}
222+
PR_NUMBER: ${{ needs.identifiers.outputs.pr_number }}
223+
PR_REPO: ${{ needs.identifiers.outputs.pr_repo }}
224+
PR_BRANCH: ${{ needs.identifiers.outputs.pr_branch }}
225+
226+
# Runs on ALL supported Python versions (unlike the main test suite, which
227+
# runs on cpy314 only) so import-time annotation regressions that only fire
228+
# on CPython < 3.14 - e.g. #1878 - are caught. Crypto extras are installed
229+
# (via install-dev -> autobahn[all]) so the cryptosign code paths run.
230+
strategy:
231+
matrix:
232+
python-env: [cpy314, cpy313, cpy312, cpy311, pypy311]
233+
234+
steps:
235+
- name: Checkout code
236+
uses: actions/checkout@v4
237+
with:
238+
submodules: recursive
239+
240+
- name: Install Just
241+
env:
242+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
243+
run: |
244+
curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- --to ~/bin
245+
echo "$HOME/bin" >> $GITHUB_PATH
246+
247+
- name: Install uv
248+
env:
249+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
250+
run: |
251+
curl -LsSf https://astral.sh/uv/install.sh | sh
252+
source $HOME/.cargo/env
253+
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
254+
255+
- name: Verify toolchain installation
256+
run: |
257+
just --version
258+
uv --version
259+
260+
- name: Setup uv cache
261+
uses: actions/cache@v4
262+
with:
263+
path: ${{ env.UV_CACHE_DIR }}
264+
key:
265+
uv-cache-ubuntu-imports-${{ matrix.python-env }}-${{
266+
hashFiles('pyproject.toml') }}
267+
restore-keys: |
268+
uv-cache-ubuntu-imports-${{ matrix.python-env }}-
269+
uv-cache-ubuntu-imports-
270+
271+
- name: Create Python environment
272+
run: |
273+
just create ${{ matrix.python-env }}
274+
just install-tools ${{ matrix.python-env }}
275+
276+
- name: Run import smoke test
277+
run: just test-imports ${{ matrix.python-env }}
278+
214279
documentation:
215280
name: Documentation Build
216281
needs: identifiers

docs/changelog.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,17 @@
55
Changelog
66
=========
77

8+
26.6.2
9+
------
10+
11+
**WAMP Cryptosign**
12+
13+
* Fix ``import autobahn.wamp.cryptosign`` raising ``TypeError: unsupported operand type(s) for |: 'str' and 'NoneType'`` on CPython 3.11/3.12/3.13 when crypto support (``nacl``) is installed. A ``ruff`` ``UP007`` autofix in 26.6.1 (#1843) had rewritten ``Optional["ISecurityModule"]`` to ``"ISecurityModule" | None`` in a module that lacks ``from __future__ import annotations``, so the string forward-reference union was evaluated eagerly at class-definition time (CPython 3.14 was unaffected because PEP 649 defers annotation evaluation). The regression broke WAMP-cryptosign and any importer with crypto dependencies present (e.g. ``xbr``, Crossbar.io) on CPython < 3.14. Added ``from __future__ import annotations`` to ``cryptosign.py`` to defer annotation evaluation (#1878)
14+
15+
**Build & CI/CD**
16+
17+
* Add an import smoke test that imports every public ``autobahn`` submodule with the crypto extras installed, so eager-evaluation annotation regressions like #1878 are caught in CI on all supported Python versions (#1878)
18+
819
26.6.1
920
------
1021

justfile

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1150,6 +1150,22 @@ test-asyncio venv="" use_nvx="": (install-tools venv) (install-dev venv)
11501150
USE_ASYNCIO=1 ${VENV_PYTHON} -m pytest -s -v -rfP \
11511151
--ignore=./src/autobahn/twisted ./src/autobahn
11521152
1153+
# Run the import smoke test - imports core autobahn modules with crypto extras
1154+
# present, guarding against import-time annotation regressions (e.g. #1878) that
1155+
# only surface on CPython < 3.14. (usage: `just test-imports cpy311`)
1156+
test-imports venv="": (install-tools venv) (install-dev venv)
1157+
#!/usr/bin/env bash
1158+
set -e
1159+
VENV_NAME="{{ venv }}"
1160+
if [ -z "${VENV_NAME}" ]; then
1161+
echo "==> No venv name specified. Auto-detecting from system Python..."
1162+
VENV_NAME=$(just --quiet _get-system-venv-name)
1163+
echo "==> Defaulting to venv: '${VENV_NAME}'"
1164+
fi
1165+
VENV_PYTHON=$(just --quiet _get-venv-python "${VENV_NAME}")
1166+
echo "==> Running import smoke test in ${VENV_NAME}..."
1167+
${VENV_PYTHON} -m pytest -v src/autobahn/test/test_import_all.py
1168+
11531169
# Run WAMP message serdes conformance tests (usage: `just test-serdes cpy311`)
11541170
test-serdes venv="": (install-tools venv) (install-dev venv)
11551171
#!/usr/bin/env bash

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "autobahn"
7-
version = "26.6.1"
7+
version = "26.6.2"
88
description = "WebSocket client & server library, WAMP real-time framework"
99
readme = "README.md"
1010
requires-python = ">=3.11"

src/autobahn/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,6 @@
2424
#
2525
###############################################################################
2626

27-
__version__ = "26.6.1"
27+
__version__ = "26.6.2"
2828

2929
__build__ = "00000000-0000000"
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
###############################################################################
2+
#
3+
# The MIT License (MIT)
4+
#
5+
# Copyright (c) typedef int GmbH
6+
#
7+
# Permission is hereby granted, free of charge, to any person obtaining a copy
8+
# of this software and associated documentation files (the "Software"), to deal
9+
# in the Software without restriction, including without limitation the rights
10+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
# copies of the Software, and to permit persons to whom the Software is
12+
# furnished to do so, subject to the following conditions:
13+
#
14+
# The above copyright notice and this permission notice shall be included in
15+
# all copies or substantial portions of the Software.
16+
#
17+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23+
# THE SOFTWARE.
24+
#
25+
###############################################################################
26+
27+
# Import smoke test for the framework-agnostic core modules.
28+
#
29+
# This guards against import-time regressions that only surface on some Python
30+
# versions - in particular eager evaluation of annotations on CPython < 3.14
31+
# (PEP 649 defers it on 3.14): a string forward-reference combined with
32+
# ``| None`` (e.g. ``"ISecurityModule" | None``) raises ``TypeError`` at
33+
# class-definition time. See issue #1878.
34+
#
35+
# We import an explicit, curated set rather than walking every submodule: a full
36+
# single-process walk is unreliable for autobahn because (a) Twisted and asyncio
37+
# bindings are mutually exclusive in one process (txaio backend selection) and
38+
# (b) several modules require optional extras (snappy, flatbuffers runtime).
39+
# These core modules are framework-agnostic and must always import with the
40+
# crypto extras installed.
41+
#
42+
# IMPORTANT: run with crypto extras (``autobahn[all]`` / ``[encryption]`` ->
43+
# PyNaCl) so the ``autobahn.wamp.cryptosign`` ``HAS_CRYPTOSIGN`` code paths
44+
# (where #1878 lived) are actually exercised - see ``test_crypto_extras_present``.
45+
46+
from __future__ import annotations
47+
48+
import importlib
49+
50+
import pytest
51+
52+
CORE_MODULES = [
53+
"autobahn",
54+
"autobahn.util",
55+
"autobahn.wamp",
56+
"autobahn.wamp.cryptosign", # regressed in 26.6.1 -> see #1878
57+
"autobahn.wamp.auth",
58+
"autobahn.wamp.interfaces",
59+
"autobahn.wamp.types",
60+
"autobahn.wamp.message",
61+
"autobahn.wamp.role",
62+
"autobahn.wamp.serializer",
63+
"autobahn.wamp.component",
64+
"autobahn.websocket.protocol",
65+
"autobahn.websocket.types",
66+
"autobahn.websocket.compress",
67+
]
68+
69+
70+
@pytest.mark.parametrize("modname", CORE_MODULES)
71+
def test_import(modname):
72+
"""
73+
Importing each core autobahn module must not raise.
74+
"""
75+
importlib.import_module(modname)
76+
77+
78+
def test_crypto_extras_present():
79+
"""
80+
The cryptosign import guard is only meaningful when PyNaCl is installed,
81+
since the ``autobahn.wamp.cryptosign`` class bodies (and thus the #1878
82+
regression) are gated behind ``HAS_CRYPTOSIGN``.
83+
"""
84+
import autobahn.wamp.cryptosign as cryptosign
85+
86+
assert (
87+
cryptosign.HAS_CRYPTOSIGN
88+
), "install crypto extras (autobahn[all] / [encryption]) so this import guard is exercised"

src/autobahn/wamp/cryptosign.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,13 @@
2424
#
2525
###############################################################################
2626

27+
# PEP 563/649: defer annotation evaluation so string forward-references combined
28+
# with ``| None`` (e.g. ``"ISecurityModule" | None``) are never evaluated at
29+
# class-definition time. Without this, such an annotation raises
30+
# ``TypeError: unsupported operand type(s) for |: 'str' and 'NoneType'`` on
31+
# CPython < 3.14 (where annotations are evaluated eagerly). See #1878.
32+
from __future__ import annotations
33+
2734
import binascii
2835
import os
2936
import struct

0 commit comments

Comments
 (0)