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_1891.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-07-01
Related issue(s): #1891
Branch: oberstet:fix_1891
23 changes: 21 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,10 @@ When reporting issues, please include:
2. **Create a feature branch** from `master`
3. **Make your changes** following the code style
4. **Add tests** for new functionality
5. **Run the test suite** to ensure nothing is broken
6. **Submit a pull request** referencing any related issues
5. **Add a changelog entry** to `docs/changelog.rst` for any user-visible
change (kept in the PR so the branch is self-contained)
6. **Run the test suite** to ensure nothing is broken
7. **Submit a pull request** referencing any related issues

## Development Setup

Expand Down Expand Up @@ -85,6 +87,23 @@ For WebSocket changes, run the Autobahn|Testsuite:
# See docs/websocket/conformance.rst for details
```

## Versioning

This project uses [CalVer](https://calver.org/) with PEP 440 development
releases: `YY.M.PATCH[.devN]` — for example, `26.7.1` for a stable release and
`26.7.1.dev1` while in development. Between releases the working tree always
carries a `.devN` suffix.

The version is stored in two files kept in sync — `pyproject.toml` and
`src/autobahn/_version.py` — and managed with `just`:

- `just file-version` — show the current version (from both files)
- `just bump-dev` — bump to the next dev version for the current month (`YY.M.1.dev1`)
- `just bump-next 26.7.2.dev1` — set a specific next dev version
- `just prep-release` — strip the `.devN` suffix to cut a stable release

Git tags and releases are created by maintainers only.

## License

By contributing to Autobahn|Python, you agree that your contributions will
Expand Down
12 changes: 12 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@
Changelog
=========

26.7.1
------

**FlatBuffers**

* Fix ``check_zlmdb_flatbuffers_version_in_sync()`` comparing the build-time ``version()`` (which is ``(0, 0, 0, None, None)`` on installed wheels, where the vendored FlatBuffers ``__git_version__`` is unstamped) — it now compares the reliably-stamped ``__version__`` and returns a version string. Added regression tests (#1891)
* Make ``autobahn.flatbuffers.version()`` reliable on installed wheels: when the build-time ``__git_version__`` is a bare commit hash or ``"unknown"`` (shallow clone / submodule absent from the sdist), ``version()`` now falls back to parsing the static vendored ``__version__`` and returns ``(major, minor, patch, None, None)`` instead of ``(0, 0, 0, None, None)``; rich ``git describe`` detail is still returned on genuine dev/git builds. Also hardened ``hatch_build.py`` so it never stamps a non-parseable ``__git_version__``. Return shape is unchanged (5-tuple); no API break (#1891)

**Build & CI/CD**

* Add CalVer / PEP 440 version-management ``just`` recipes (``file-version``, ``bump-dev``, ``bump-next``, ``prep-release``) mirroring Crossbar.io, and document the versioning policy in ``CONTRIBUTING.md`` (#1894)

26.6.2
------

Expand Down
15 changes: 15 additions & 0 deletions hatch_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,21 @@ def _update_flatbuffers_git_version(self):
print(f" -> Using existing version in {git_version_file.name}")
return

# Defense-in-depth: never ship a __git_version__ that version() cannot parse.
# On shallow clones `git describe --tags --always` returns a bare commit hash
# (no tags), and a failed describe leaves "unknown" -- either would force the
# runtime version() to fall back. Prefer a parseable string derived from the
# reliable vendored __version__ in the sibling _version.py.
import re as _re

if not _re.match(r"^v\d+\.\d+\.\d+", git_version):
version_file = git_version_file.parent / "_version.py"
for line in version_file.read_text().splitlines():
if line.startswith("__version__"):
git_version = "v" + line.split("=", 1)[1].strip().strip('"').strip("'")
print(f" -> git describe unusable; stamping {git_version} from _version.py")
break

# Write the version file
content = """\
# Copyright 2014 Google Inc. All rights reserved.
Expand Down
47 changes: 47 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ VENV_DIR := PROJECT_DIR / '.venvs'
# Define a justfile-local variable for our environments.
ENVS := 'cpy314 cpy313 cpy312 cpy311 pypy311'

# Package version files, kept in sync (CalVer YY.M.PATCH[.devN], PEP 440).
PY_VERSION_FILE := 'src/autobahn/_version.py'
TOML_VERSION_FILE := 'pyproject.toml'

# Internal helper to map Python version short name to full uv version
_get-spec short_name:
#!/usr/bin/env bash
Expand Down Expand Up @@ -301,6 +305,49 @@ version-all:
just version ${venv}
done

# -----------------------------------------------------------------------------
# -- Package version management (CalVer YY.M.PATCH[.devN], PEP 440)
# -----------------------------------------------------------------------------
# Replicated from crossbar's version-management recipes to align the WAMP
# projects. Deviation: this repo does not track uv.lock, so (unlike crossbar)
# bump-next does not refresh or commit a lockfile.

# Display the current package version from both version files
file-version:
@echo "Python file: $(grep '__version__' {{PY_VERSION_FILE}} | cut -d ' ' -f 3)"
@echo "TOML file: $(grep '^version =' {{TOML_VERSION_FILE}} | cut -d ' ' -f 3)"

# Prepare for a stable release: strip the .devN suffix from the version files
prep-release:
@echo "Cleaning version for stable release..."
sed -i 's/\.dev[0-9]*//' {{PY_VERSION_FILE}}
sed -i 's/\.dev[0-9]*//' {{TOML_VERSION_FILE}}
@just file-version
@echo ''
@echo 'Now (maintainer, on the dev PC only -- tags are never created by AI):'
@echo ' 1. Commit: git add . && git commit -m "version bump for stable release"'
@echo ' 2. Tag: git tag -a v<VERSION> -m "tagged stable release"'
@echo ' 3. Push tag: git push upstream v<VERSION>'
@echo ' 4. Next dev: just bump-next <NEXT-VERSION>.dev1'
@echo ''

# Auto-bump to the next dev version for the current month (CalVer: YY.M.1.dev1)
bump-dev:
#!/usr/bin/env bash
set -e
NEXT="$(date +%-y).$(date +%-m).1.dev1"
echo "Auto-computed next dev version: ${NEXT}"
just bump-next "${NEXT}"

# Set a specific next version (e.g. `just bump-next 26.7.2.dev1`)
bump-next next_version:
@echo "Bumping version to {{next_version}}..."
sed -i 's/__version__ = .*/__version__ = "{{next_version}}"/' {{PY_VERSION_FILE}}
sed -i 's/^version = .*/version = "{{next_version}}"/' {{TOML_VERSION_FILE}}
git add {{PY_VERSION_FILE}} {{TOML_VERSION_FILE}}
git commit -m "chore: bump version to {{next_version}}"
@echo "Branch is now at {{next_version}} and ready for development."

# Make Python packages installed by the OS package manager available in a managed venv. Usage: `just link-system-packages "" "/usr/lib/kicad-nightly/lib/python3/dist-packages"`
link-system-packages venv="" vendors="": (create 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.2"
version = "26.7.1.dev1"
description = "WebSocket client & server library, WAMP real-time framework"
readme = "README.md"
requires-python = ">=3.11"
Expand Down
8 changes: 4 additions & 4 deletions src/autobahn/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
from autobahn import flatbuffers


def check_zlmdb_flatbuffers_version_in_sync() -> tuple[int, int, int, int | None, str | None]:
def check_zlmdb_flatbuffers_version_in_sync() -> str:
"""
Check that autobahn and zlmdb have the same vendored flatbuffers version.

Expand All @@ -45,7 +45,7 @@ def check_zlmdb_flatbuffers_version_in_sync() -> tuple[int, int, int, int | None
application payload, both libraries must use compatible FlatBuffers
runtimes to avoid subtle serialization issues.

:returns: The flatbuffers version tuple (e.g. (25, 9, 23, 2, "95053e6a"))
:returns: The vendored flatbuffers version string (e.g. "25.12.19")
if both are in sync.
:raises RuntimeError: If the versions differ.
:raises ImportError: If zlmdb is not installed.
Expand All @@ -58,8 +58,8 @@ def check_zlmdb_flatbuffers_version_in_sync() -> tuple[int, int, int, int | None
"""
import zlmdb.flatbuffers

autobahn_version = flatbuffers.version()
zlmdb_version = zlmdb.flatbuffers.version()
autobahn_version = flatbuffers.__version__
zlmdb_version = zlmdb.flatbuffers.__version__

if autobahn_version != zlmdb_version:
raise RuntimeError(
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.2"
__version__ = "26.7.1.dev1"

__build__ = "00000000-0000000"
17 changes: 15 additions & 2 deletions src/autobahn/flatbuffers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,31 @@

def version() -> tuple[int, int, int, int | None, str | None]:
"""
Return the exact git version of the vendored FlatBuffers runtime.
Return the version of the vendored FlatBuffers runtime as a 5-tuple
``(major, minor, patch, commits, commit_hash)``.

Handles:
The primary source is ``__git_version__`` (``git describe`` output), which on a
genuine dev/git build carries rich detail:

1. "v25.9.23" -> (25, 9, 23, None, None) # Release (Named Tag, CalVer Year.Month.Day)
2. "v25.9.23-71" -> (25, 9, 23, 71, None) # 71 commits ahead of the Release v25.9.23
3. "v25.9.23-71-g19b2300f" -> (25, 9, 23, 71, "19b2300f") # dito, with Git commit hash

On installed wheels / shallow clones ``__git_version__`` may be a bare commit hash
or "unknown" (``git describe`` found no tags); in that case fall back to the always
reliable static ``__version__`` and return ``(major, minor, patch, None, None)``.
"""
pattern = r"^v(\d+)\.(\d+)\.(\d+)(?:-(\d+))?(?:-g([0-9a-f]+))?$"
match = re.match(pattern, __git_version__)
if match:
major, minor, patch, commits, commit_hash = match.groups()
commits_int = int(commits) if commits else None
return (int(major), int(minor), int(patch), commits_int, commit_hash)
# Fallback: static vendored __version__ ("YY.MM.DD", no 'v' prefix) is reliable on
# wheels where __git_version__ may be a bare hash / "unknown". No end-anchor so a
# trailing dev suffix is tolerated.
fallback = re.match(r"^v?(\d+)\.(\d+)\.(\d+)", __version__)
if fallback:
major, minor, patch = fallback.groups()
return (int(major), int(minor), int(patch), None, None)
return (0, 0, 0, None, None)
52 changes: 50 additions & 2 deletions src/autobahn/test/test_flatbuffers_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,59 @@ def test_version_tuple(self):
self.assertTrue(commits is None or isinstance(commits, int))
self.assertTrue(commit_hash is None or isinstance(commit_hash, str))

def test_version_falls_back_to_dunder_version_on_bare_hash(self):
"version() falls back to __version__ when __git_version__ is a non-matching bare hash"
# On built wheels / shallow clones `git describe --tags --always` yields a bare
# commit hash that does NOT match the vX.Y.Z pattern; version() must fall back to
# the reliable static __version__, NOT return (0, 0, 0, None, None).
orig = flatbuffers.__git_version__
try:
flatbuffers.__git_version__ = "19b2300f"
v = flatbuffers.version()
self.assertNotEqual(v, (0, 0, 0, None, None))
expected = tuple(int(x) for x in flatbuffers.__version__.split(".")[:3])
self.assertEqual(v, (*expected, None, None))
finally:
flatbuffers.__git_version__ = orig

def test_version_falls_back_to_dunder_version_on_unknown(self):
"version() falls back to __version__ when __git_version__ is 'unknown'"
orig = flatbuffers.__git_version__
try:
flatbuffers.__git_version__ = "unknown" # hatch_build default on describe failure
expected = tuple(int(x) for x in flatbuffers.__version__.split(".")[:3])
self.assertEqual(flatbuffers.version(), (*expected, None, None))
finally:
flatbuffers.__git_version__ = orig

def test_version_preserves_git_describe_detail(self):
"version() still returns rich git-describe detail when __git_version__ matches"
orig = flatbuffers.__git_version__
try:
flatbuffers.__git_version__ = "v25.9.23-71-g19b2300f"
self.assertEqual(flatbuffers.version(), (25, 9, 23, 71, "19b2300f"))
finally:
flatbuffers.__git_version__ = orig

@unittest.skipUnless(HAS_ZLMDB, "zlmdb not installed")
def test_zlmdb_flatbuffers_in_sync(self):
"autobahn and zlmdb vendor the same FlatBuffers version (data-in-transit vs data-at-rest)"
version = autobahn.check_zlmdb_flatbuffers_version_in_sync()
self.assertEqual(version, flatbuffers.version())
self.assertEqual(version, flatbuffers.__version__)
import zlmdb.flatbuffers

self.assertEqual(version, zlmdb.flatbuffers.version())
self.assertEqual(version, zlmdb.flatbuffers.__version__)

@unittest.skipUnless(HAS_ZLMDB, "zlmdb not installed")
def test_zlmdb_flatbuffers_mismatch_raises(self):
"check_zlmdb_flatbuffers_version_in_sync() raises when the two vendored __version__ differ"
# guard against regressions to comparing version() (which is (0,0,0,...) on
# built wheels and would never detect a mismatch): force the two vendored
# __version__ strings apart and assert the check actually raises.
orig = flatbuffers.__version__
try:
flatbuffers.__version__ = orig + "-mismatch"
with self.assertRaises(RuntimeError):
autobahn.check_zlmdb_flatbuffers_version_in_sync()
finally:
flatbuffers.__version__ = orig
Loading