Skip to content

fix: derive the version from package metadata instead of hardcoding it in eight places (Closes #607)#625

Open
SakethSumanBathini wants to merge 1 commit into
sreerevanth:mainfrom
SakethSumanBathini:fix/607-single-version-source
Open

fix: derive the version from package metadata instead of hardcoding it in eight places (Closes #607)#625
SakethSumanBathini wants to merge 1 commit into
sreerevanth:mainfrom
SakethSumanBathini:fix/607-single-version-source

Conversation

@SakethSumanBathini

@SakethSumanBathini SakethSumanBathini commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Closes #607

Summary

0.2.0 was written out by hand in eight places. pyproject.toml is now the only one, and everything
that displays a version reads it from the installed distribution metadata.

I filed this issue saying four places. It's eight, and the ones I'd missed are the ones that matter:

agentwatch/api/server.py:753   "version": "0.2.0"   ← served by a route
agentwatch/api/server.py:762   "version": "0.2.0"   ← served by /health
agentwatch/api/server.py:644   version="0.2.0"      ← FastAPI app, feeds the OpenAPI spec
agentwatch/telemetry/otel.py:42   service_version = "0.2.0"   ← OTel resource, tags every trace
agentwatch/__init__.py:3      __version__ = "0.2.0"

So the API was serving a hardcoded version to clients, and every exported trace was tagged with a
literal. For an observability tool, traces labelled with a version that nobody remembers to bump is
worse than a stale README badge — you can't correlate a regression to a release if the release number
is decorative.

How

New agentwatch/_version.py reads importlib.metadata.version("agentwatch-ai") once. Everything
else imports __version__ from it.

Three things worth explaining rather than leaving to be discovered in review:

It's a separate module, not just __init__. __init__.py sets __version__ before importing
core.watcher, so a submodule doing from agentwatch import __version__ would work — by accident of
import ordering. _version.py imports nothing from the package, so there's no cycle to reason about.
Verified by importing agentwatch, agentwatch.api.server, agentwatch.telemetry.otel and
agentwatch.cli.demo cold.

The lookup key is the distribution name, not the import name. The package is agentwatch-ai;
version("agentwatch") raises PackageNotFoundError. Easy to get wrong, and it only fails at runtime.

The demo banner is fixed-width box art. Interpolating a version of a different length would push
the closing | out of line and visibly break the box. The padding is computed from the border
instead, so any version renders square — checked against 0.2.0, 1.0.0rc1, 0.10.12,
1.2.3.dev20260713 and the fallback, all 64/64. At 0.2.0 it renders byte-identical to today.

The README badge

Now a dynamic PyPI endpoint (img.shields.io/pypi/v/agentwatch-ai) rather than a hardcoded
v0.2.0, so it tracks releases on its own.

Worth flagging: it will render whatever PyPI has published, which may not be 0.2.0 if the release
isn't out yet. That's arguably more honest — it shows what people can actually install — but it's a
visible change, so say the word if you'd rather I left the badge alone and kept this to the code.

README.md:474 ("v0.2.0 is being built now") is prose and can't be derived, so I've left it. It's the
one remaining place a bump has to be done by hand.

Fallback

If the package isn't installed — a contributor running straight out of a clone — importlib.metadata
raises. Rather than making the package unimportable in exactly the situation someone is most likely to
hit, it falls back to 0.0.0+unknown. Deliberately not a plausible release number, so it can't be
mistaken for one in a bug report.

CI installs the package, so CI always sees the real version.

Verified

  • ruff check and ruff format --check clean
  • pip install -e . then import agentwatch0.2.0, matching importlib.metadata.version()
  • app.version on the FastAPI app → 0.2.0, so /health and the root route now report the real one
  • no circular imports (all four touched modules imported cold)
  • python -m agentwatch.cli.demo runs end to end, box borders aligned, banner reads v0.2.0
  • no hardcoded "0.2.0" left anywhere in agentwatch/

One thing that fell out of testing and is worth recording: on a stale checkout, this printed 0.1.0
correctly, because 0.1.0 was what was installed. The old hardcoded string printed 0.2.0 regardless
of what was actually running. That's the failure mode this fixes, and it turned up on the first run.

Summary by CodeRabbit

  • Bug Fixes

    • Version information is now reported consistently across the package, API status and health endpoints, CLI demo banner, and telemetry.
    • CLI banner formatting remains aligned for version strings of varying lengths.
    • Source-tree usage now provides a safe fallback version when package metadata is unavailable.
  • Documentation

    • Updated the README badge to display the current package version dynamically.

@github-actions

Copy link
Copy Markdown
Contributor

🧪 PR Test Results

Check Result
Tests (pytest tests/) ✅ success
Lint (ruff check .) ❌ failure
Coverage (agentwatch) 74.69%

Python 3.12 · commit 385d883

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The package version now comes from installed distribution metadata with a fallback, and is reused by package exports, API responses, telemetry defaults, the CLI banner, and the README badge.

Changes

Dynamic version propagation

Layer / File(s) Summary
Distribution metadata version source
agentwatch/_version.py
Adds centralized version retrieval for agentwatch-ai, with a 0.0.0+unknown fallback when metadata is unavailable.
Runtime version reporting
agentwatch/__init__.py, agentwatch/api/server.py, agentwatch/telemetry/otel.py
Replaces hard-coded runtime versions with the centralized __version__ value.
CLI and README version surfaces
agentwatch/cli/demo.py, README.md
Uses the centralized version in the dynamically sized demo banner and changes the README badge to a dynamic PyPI badge.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

Suggested labels: Advanced

Suggested reviewers: sreerevanth, shauryasanyal3

Poem

I’m a rabbit with versions in line,
No stale little numbers to find.
From PyPI they flow,
Through banners they glow,
And every health check now shines! 🐇

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR centralizes runtime version usage, but it leaves the README release prose hardcoded and does not add the checklist alternative from #607. Also update the README release text or add a release checklist so every version-bearing file stays in sync, or document why that prose is excluded.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: moving versioning to package metadata and removing hardcoded versions.
Out of Scope Changes check ✅ Passed All changes support version centralization; no unrelated features or broad refactors are introduced.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
agentwatch/cli/demo.py (1)

570-589: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider using rich.panel.Panel to render the banner.

Since the project already uses rich for terminal formatting elsewhere, you can simplify this logic by leveraging a Panel. This avoids manual padding math and string alignment while keeping the box layout intact.

🛠️ Proposed refactor
-    # The banner is fixed-width box art, so the title cannot simply be interpolated — a version
-    # string of a different length would push the closing `|` out of alignment and the box would
-    # visibly break. The padding is computed from the border instead, so any version renders square.
-    _title = f"AgentWatch - Demo Suite v{__version__}"
-    _border = "+" + "-" * 62 + "+"
-    _interior = len(_border) - 2
-    _titled = "|" + " " * 9 + _title + " " * max(_interior - 9 - len(_title), 0) + "|"
-
-    print(
-        bold(
-            "\n"
-            + _border
-            + "\n"
-            + _titled
-            + "\n"
-            + "|  Reliability, Safety & Observability Layer for AI Agents     |"
-            + "\n"
-            + _border
-            + "\n"
-        )
-    )
+    from rich import print as rprint
+    from rich.panel import Panel
+    from rich import box
+
+    rprint()
+    rprint(
+        Panel(
+            "[bold]Reliability, Safety & Observability Layer for AI Agents[/bold]",
+            title=f"[bold]AgentWatch - Demo Suite v{__version__}[/bold]",
+            title_align="left",
+            border_style="bold",
+            expand=False,
+            box=box.ASCII
+        )
+    )
+    rprint()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agentwatch/cli/demo.py` around lines 570 - 589, Replace the manual banner
construction around `_title`, `_border`, `_interior`, and `_titled` with a
`rich.panel.Panel`, using the existing Rich styling conventions to preserve the
title and descriptive text within a boxed layout. Remove the manual padding and
border calculations while keeping the banner’s displayed content and formatting
intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@agentwatch/cli/demo.py`:
- Around line 570-589: Replace the manual banner construction around `_title`,
`_border`, `_interior`, and `_titled` with a `rich.panel.Panel`, using the
existing Rich styling conventions to preserve the title and descriptive text
within a boxed layout. Remove the manual padding and border calculations while
keeping the banner’s displayed content and formatting intact.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e465d317-90f2-4e71-ab5c-e357d80de4db

📥 Commits

Reviewing files that changed from the base of the PR and between d2980f6 and 385d883.

📒 Files selected for processing (6)
  • README.md
  • agentwatch/__init__.py
  • agentwatch/_version.py
  • agentwatch/api/server.py
  • agentwatch/cli/demo.py
  • agentwatch/telemetry/otel.py

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[CHORE] The version string is duplicated across four files with no release checklist

1 participant