From f033dfc631e5b3afcf98b470f3b9a8de3102d4be Mon Sep 17 00:00:00 2001 From: Max Rothman Date: Fri, 22 May 2026 11:58:52 -0700 Subject: [PATCH 1/5] Expose Synapse API version from synapse-api/VERSION setup.py reads the canonical API version from the synapse-api submodule's VERSION file and stamps it into synapse/_api_version.py. The constant is re-exported from synapse so clients can do: from synapse import SYNAPSE_API_VERSION to compare against a server's reported version. _api_version.py is committed (with the current 2.4.0 value) so that sdist installs without the submodule still work; setup.py only updates it when the submodule is present. NOTE: requires bumping the synapse-api submodule pointer to a commit that contains VERSION before the stamping path becomes active. The current pointer (6c0905e) predates that file. --- setup.py | 23 +++++++++++++++++++++++ synapse/__init__.py | 1 + synapse/_api_version.py | 2 ++ 3 files changed, 26 insertions(+) create mode 100644 synapse/_api_version.py diff --git a/setup.py b/setup.py index 263df0fb..da77379e 100644 --- a/setup.py +++ b/setup.py @@ -4,6 +4,29 @@ this_directory = Path(__file__).parent long_description = (this_directory / "README.md").read_text() + +def stamp_api_version(): + """Mirror synapse-api/VERSION into synapse/_api_version.py. + + The submodule is the source of truth for the protocol version. When + installing from an sdist the submodule isn't present, so we leave the + committed _api_version.py alone. + """ + version_file = this_directory / "synapse-api" / "VERSION" + if not version_file.exists(): + return + api_version = version_file.read_text().strip() + output = this_directory / "synapse" / "_api_version.py" + contents = ( + "# Generated from synapse-api/VERSION by setup.py. Do not edit.\n" + f'SYNAPSE_API_VERSION = "{api_version}"\n' + ) + if not output.exists() or output.read_text() != contents: + output.write_text(contents) + + +stamp_api_version() + setup( name="science-synapse", version="2.5.0", diff --git a/synapse/__init__.py b/synapse/__init__.py index b73cb136..3841437a 100644 --- a/synapse/__init__.py +++ b/synapse/__init__.py @@ -1 +1,2 @@ +from synapse._api_version import SYNAPSE_API_VERSION from synapse.client import * diff --git a/synapse/_api_version.py b/synapse/_api_version.py new file mode 100644 index 00000000..5c7f6df9 --- /dev/null +++ b/synapse/_api_version.py @@ -0,0 +1,2 @@ +# Generated from synapse-api/VERSION by setup.py. Do not edit. +SYNAPSE_API_VERSION = "2.4.0" From 6762168caf029abd283dca8f40d6a2d5314ff096 Mon Sep 17 00:00:00 2001 From: Max Rothman Date: Fri, 22 May 2026 13:03:25 -0700 Subject: [PATCH 2/5] Include Synapse API version in synapsectl --version Previously printed only the science-synapse package version. Now also prints the API version the client targets, so users debugging a mismatch can see both at a glance: $ synapsectl --version synapsectl 2.5.0 (Synapse API 2.4.0) --- synapse/cli/__main__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/synapse/cli/__main__.py b/synapse/cli/__main__.py index af66f34e..87c3c625 100755 --- a/synapse/cli/__main__.py +++ b/synapse/cli/__main__.py @@ -8,6 +8,7 @@ from rich.console import Console from rich.logging import RichHandler +from synapse import SYNAPSE_API_VERSION from synapse.cli import ( apps, discover, @@ -59,7 +60,8 @@ def main(): parser.add_argument( "--version", action="version", - version="synapsectl %s" % metadata.version("science-synapse"), + version="synapsectl %s (Synapse API %s)" + % (metadata.version("science-synapse"), SYNAPSE_API_VERSION), ) parser.add_argument( "--verbose", From 68be7483a1edde234eb6949b1a87b14fe1a57c8d Mon Sep 17 00:00:00 2001 From: Max Rothman Date: Fri, 22 May 2026 13:05:22 -0700 Subject: [PATCH 3/5] Bump synapse-api submodule to versioning branch (bb095ce) Picks up the new VERSION file so setup.py's stamp_api_version() now has a source to read from. Without this bump the stamping was a no-op and synapse/_api_version.py would have drifted as a hand-edited file. --- synapse-api | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/synapse-api b/synapse-api index 6c0905e5..bb095ce7 160000 --- a/synapse-api +++ b/synapse-api @@ -1 +1 @@ -Subproject commit 6c0905e5ce5f2680ce0fa460f0659bd7f1de715f +Subproject commit bb095ce72ca8cd856e307a8ea533063d98740406 From b01d5ca9d83f4f7fd3975e89e0af60f712ff8222 Mon Sep 17 00:00:00 2001 From: Max Rothman Date: Fri, 22 May 2026 13:23:17 -0700 Subject: [PATCH 4/5] Decode DeviceInfo.synapse_version uint32 to major.minor.patch DeviceInfo.synapse_version is encoded by the server as a uint32 with the layout [major:8][minor:8][patch:16]. Add a decoder in synapse.utils.version and use it in the CLI's info display so users see "Synapse Version: 2.4.0" instead of "Synapse Version: 33816576". --- synapse/cli/device_info_display.py | 9 ++++++++- synapse/utils/version.py | 12 ++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 synapse/utils/version.py diff --git a/synapse/cli/device_info_display.py b/synapse/cli/device_info_display.py index 15e8299a..72b038f4 100644 --- a/synapse/cli/device_info_display.py +++ b/synapse/cli/device_info_display.py @@ -2,6 +2,7 @@ from rich.tree import Tree from google.protobuf.json_format import MessageToDict from synapse.client.device import Device +from synapse.utils.version import decode_synapse_version def visualize_configuration(info_dict, status): @@ -137,8 +138,14 @@ def summary(self, device: Device): f"Serial: {info_dict.get('serial', 'Unknown')}", highlight=False, ) + synapse_version_raw = info_dict.get("synapse_version") + synapse_version_str = ( + decode_synapse_version(synapse_version_raw) + if synapse_version_raw is not None + else "Unknown" + ) self.console.print( - f"Synapse Version: {info_dict.get('synapse_version', 'Unknown')}", + f"Synapse Version: {synapse_version_str}", highlight=False, ) self.console.print( diff --git a/synapse/utils/version.py b/synapse/utils/version.py new file mode 100644 index 00000000..d4f72bd8 --- /dev/null +++ b/synapse/utils/version.py @@ -0,0 +1,12 @@ +def decode_synapse_version(v: int) -> str: + """Decode a Synapse API version uint32 into a "major.minor.patch" string. + + Encoding (see synapse-api device.proto, DeviceInfo.synapse_version): + bits [31:24] major (0-255) + bits [23:16] minor (0-255) + bits [15:0] patch (0-65535) + """ + major = (v >> 24) & 0xFF + minor = (v >> 16) & 0xFF + patch = v & 0xFFFF + return f"{major}.{minor}.{patch}" From 0445311843f20af0e041cb31f4b4b3fdb032ca00 Mon Sep 17 00:00:00 2001 From: Max Rothman Date: Fri, 22 May 2026 14:15:29 -0700 Subject: [PATCH 5/5] Update synapse-api submodule to main HEAD (2.4.0) --- synapse-api | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/synapse-api b/synapse-api index bb095ce7..27b6261e 160000 --- a/synapse-api +++ b/synapse-api @@ -1 +1 @@ -Subproject commit bb095ce72ca8cd856e307a8ea533063d98740406 +Subproject commit 27b6261e253dc0163b62b01fb8b02c2710091958