Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .audit/oberstet_fix_1878.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
- [ ] I did **not** use any AI-assistance tools to help create this pull request.
- [x] I **did** use AI-assistance tools to *help* create this pull request.
- [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.

Submitted by: @oberstet
Date: 2026-06-19
Related issue(s): #1878
Branch: oberstet:1878
65 changes: 65 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,71 @@ jobs:
path: ${{ github.workspace }}/test-results/serdes-${{ matrix.python-env }}/
retention-days: 7

import-smoke:
name: Import Smoke Test
needs: identifiers
runs-on: ubuntu-24.04

env:
BASE_REPO: ${{ needs.identifiers.outputs.base_repo }}
BASE_BRANCH: ${{ needs.identifiers.outputs.base_branch }}
PR_NUMBER: ${{ needs.identifiers.outputs.pr_number }}
PR_REPO: ${{ needs.identifiers.outputs.pr_repo }}
PR_BRANCH: ${{ needs.identifiers.outputs.pr_branch }}

# Runs on ALL supported Python versions (unlike the main test suite, which
# runs on cpy314 only) so import-time annotation regressions that only fire
# on CPython < 3.14 - e.g. #1878 - are caught. Crypto extras are installed
# (via install-dev -> autobahn[all]) so the cryptosign code paths run.
strategy:
matrix:
python-env: [cpy314, cpy313, cpy312, cpy311, pypy311]

steps:
- name: Checkout code
uses: actions/checkout@v4
with:
submodules: recursive

- name: Install Just
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- --to ~/bin
echo "$HOME/bin" >> $GITHUB_PATH

- name: Install uv
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
curl -LsSf https://astral.sh/uv/install.sh | sh
source $HOME/.cargo/env
echo "$HOME/.cargo/bin" >> $GITHUB_PATH

- name: Verify toolchain installation
run: |
just --version
uv --version

- name: Setup uv cache
uses: actions/cache@v4
with:
path: ${{ env.UV_CACHE_DIR }}
key:
uv-cache-ubuntu-imports-${{ matrix.python-env }}-${{
hashFiles('pyproject.toml') }}
restore-keys: |
uv-cache-ubuntu-imports-${{ matrix.python-env }}-
uv-cache-ubuntu-imports-

- name: Create Python environment
run: |
just create ${{ matrix.python-env }}
just install-tools ${{ matrix.python-env }}

- name: Run import smoke test
run: just test-imports ${{ matrix.python-env }}

documentation:
name: Documentation Build
needs: identifiers
Expand Down
11 changes: 11 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@
Changelog
=========

26.6.2
------

**WAMP Cryptosign**

* 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)

**Build & CI/CD**

* 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)

26.6.1
------

Expand Down
16 changes: 16 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -1150,6 +1150,22 @@ test-asyncio venv="" use_nvx="": (install-tools venv) (install-dev venv)
USE_ASYNCIO=1 ${VENV_PYTHON} -m pytest -s -v -rfP \
--ignore=./src/autobahn/twisted ./src/autobahn

# Run the import smoke test - imports core autobahn modules with crypto extras
# present, guarding against import-time annotation regressions (e.g. #1878) that
# only surface on CPython < 3.14. (usage: `just test-imports cpy311`)
test-imports venv="": (install-tools venv) (install-dev venv)
#!/usr/bin/env bash
set -e
VENV_NAME="{{ venv }}"
if [ -z "${VENV_NAME}" ]; then
echo "==> No venv name specified. Auto-detecting from system Python..."
VENV_NAME=$(just --quiet _get-system-venv-name)
echo "==> Defaulting to venv: '${VENV_NAME}'"
fi
VENV_PYTHON=$(just --quiet _get-venv-python "${VENV_NAME}")
echo "==> Running import smoke test in ${VENV_NAME}..."
${VENV_PYTHON} -m pytest -v src/autobahn/test/test_import_all.py

# Run WAMP message serdes conformance tests (usage: `just test-serdes cpy311`)
test-serdes venv="": (install-tools venv) (install-dev venv)
#!/usr/bin/env bash
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "autobahn"
version = "26.6.1"
version = "26.6.2"
description = "WebSocket client & server library, WAMP real-time framework"
readme = "README.md"
requires-python = ">=3.11"
Expand Down
2 changes: 1 addition & 1 deletion src/autobahn/_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,6 @@
#
###############################################################################

__version__ = "26.6.1"
__version__ = "26.6.2"

__build__ = "00000000-0000000"
88 changes: 88 additions & 0 deletions src/autobahn/test/test_import_all.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
###############################################################################
#
# The MIT License (MIT)
#
# Copyright (c) typedef int GmbH
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
###############################################################################

# Import smoke test for the framework-agnostic core modules.
#
# This guards against import-time regressions that only surface on some Python
# versions - in particular eager evaluation of annotations on CPython < 3.14
# (PEP 649 defers it on 3.14): a string forward-reference combined with
# ``| None`` (e.g. ``"ISecurityModule" | None``) raises ``TypeError`` at
# class-definition time. See issue #1878.
#
# We import an explicit, curated set rather than walking every submodule: a full
# single-process walk is unreliable for autobahn because (a) Twisted and asyncio
# bindings are mutually exclusive in one process (txaio backend selection) and
# (b) several modules require optional extras (snappy, flatbuffers runtime).
# These core modules are framework-agnostic and must always import with the
# crypto extras installed.
#
# IMPORTANT: run with crypto extras (``autobahn[all]`` / ``[encryption]`` ->
# PyNaCl) so the ``autobahn.wamp.cryptosign`` ``HAS_CRYPTOSIGN`` code paths
# (where #1878 lived) are actually exercised - see ``test_crypto_extras_present``.

from __future__ import annotations

import importlib

import pytest

CORE_MODULES = [
"autobahn",
"autobahn.util",
"autobahn.wamp",
"autobahn.wamp.cryptosign", # regressed in 26.6.1 -> see #1878
"autobahn.wamp.auth",
"autobahn.wamp.interfaces",
"autobahn.wamp.types",
"autobahn.wamp.message",
"autobahn.wamp.role",
"autobahn.wamp.serializer",
"autobahn.wamp.component",
"autobahn.websocket.protocol",
"autobahn.websocket.types",
"autobahn.websocket.compress",
]


@pytest.mark.parametrize("modname", CORE_MODULES)
def test_import(modname):
"""
Importing each core autobahn module must not raise.
"""
importlib.import_module(modname)


def test_crypto_extras_present():
"""
The cryptosign import guard is only meaningful when PyNaCl is installed,
since the ``autobahn.wamp.cryptosign`` class bodies (and thus the #1878
regression) are gated behind ``HAS_CRYPTOSIGN``.
"""
import autobahn.wamp.cryptosign as cryptosign

assert (
cryptosign.HAS_CRYPTOSIGN
), "install crypto extras (autobahn[all] / [encryption]) so this import guard is exercised"
7 changes: 7 additions & 0 deletions src/autobahn/wamp/cryptosign.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@
#
###############################################################################

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

import binascii
import os
import struct
Expand Down
Loading