Skip to content

fix(python): derive scenario.__version__ from package metadata#800

Open
langwatch-agent wants to merge 1 commit into
mainfrom
tech-debt-fixer-py-version
Open

fix(python): derive scenario.__version__ from package metadata#800
langwatch-agent wants to merge 1 commit into
mainfrom
tech-debt-fixer-py-version

Conversation

@langwatch-agent

Copy link
Copy Markdown
Contributor

Context

Handling the CodeRabbit comment on the release PR #658, which flagged that scenario.__version__ is "0.1.0" while the package ships 0.7.32.

The drift is real (__version__ = "0.1.0" at python/scenario/__init__.py:263 vs 0.7.31 on main), but the suggested fix was to set the literal to the current version, and that is what caused the bug. Nothing bumps this constant: release-please updates pyproject.toml, the changelog and the manifest, and .release-please-config.json declares no extra-files. That is why a literal survived 76 releases untouched. Setting it to 0.7.32 reads correctly today and is wrong again the moment 0.7.33 ships.

This PR is opened against main rather than pushed to #658, because #658 is a release-please branch that gets regenerated on every run (a manual commit there would be clobbered), and a release PR should carry release metadata only.

Change

__version__ now aliases the constant the tracing layer already resolves through importlib.metadata, so the version has a single source of truth: the version in pyproject.toml.

The repo already settled this exact question for the same value on the tracing side. _tracing/sdk_metadata.py reads package metadata "rather than a hardcoded literal", and issue #744's AC is explicit: "Version is NOT hardcoded — read from package metadata." __version__ was simply never brought in line. A side effect worth having: the public constant and the scenario.sdk.version trace attribute can no longer disagree about which SDK produced a run.

No new import cost or cycle: __init__.py already imports _tracing at line 102.

Evidence

Verified against the real installed distribution:

scenario.__version__ : 0.7.31      (was "0.1.0")
pyproject/metadata   : 0.7.31
MATCH                : True
scenario.sdk.version : 0.7.31
consistent w/ traces : True
Check Result
New tests 3 passed (tests/test_package_version.py)
Sibling suite 3 passed (tests/test_sdk_version_attributes.py)
Import smoke (cycle check) clean, __version__ = 0.7.31

Mutation-tested, and this is the part that makes the case. Rather than only asserting the tests pass, I ran them against both alternatives:

__init__.py state Result
"0.1.0" (main today) 3 failed — catches the reported bug
"0.7.31" (the suggested fix) 1 failed, 2 passed — the value assertions pass today; the literal guard is the one that fails
_SCENARIO_SDK_VERSION (this PR) 3 passed

That middle row is the whole argument: a hardcoded value is indistinguishable from correct until the next release. So the third test asserts the AST node for __version__ is not a constant, which is what actually prevents the regression from returning.

Not verified: the full pytest tests/ sweep timed out locally on tests that reach the network; I did not chase it, since this change touches only a module constant and the import smoke plus both version suites are green. CI is the check that matters here.

Advisory note

Advisory PR from the tech-debt-fixer bot, for human review, not to be merged by the bot.

scenario.__version__ has read "0.1.0" since the first release while the package
shipped 0.7.31. Nothing bumps it: release-please updates pyproject.toml, the
changelog and the manifest, and .release-please-config.json declares no
extra-files, so the literal never moved across 76 releases.

Alias the constant the tracing layer already resolves through
importlib.metadata, giving the version one source of truth in pyproject.toml.
Setting the literal to the current number instead would read correctly today
and drift again at the next release, which is how it reached 0.1.0 vs 0.7.31.
The repo already settled this for the same value on the tracing side: issue
#744's AC is that the version is read from package metadata, not hardcoded.

The public constant and the scenario.sdk.version trace attribute now cannot
disagree about which SDK produced a run, and both fall back to "unknown" when
metadata is unavailable.

Tests cover both value assertions plus a guard that rejects a literal
reintroduced here, since the value assertions alone would pass right up until
the next release.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@langwatch-agent langwatch-agent added the tech-debt-fixer Opened by the tech-debt-fixer agent (shared langwatch-agent bot identity) label Jul 16, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Automated low-risk assessment

This PR was evaluated against the repository's Low-Risk Pull Requests procedure and does not qualify as low risk.

The PR changes runtime behavior by replacing the public version constant with a dynamic value sourced from tracing package metadata and adds a new test file. This is a code change to a public module/constant (not just documentation, styling, or test configuration) and is therefore not covered by the allowed low-risk categories, so it requires a normal review.

This PR requires a manual review before merging.

@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown

Caution

Review failed

An error occurred during the review process. Please try again later.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch tech-debt-fixer-py-version

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 commented Jul 16, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

scenario.__version__ now derives from SCENARIO_SDK_VERSION. New tests verify consistency with installed package metadata and trace version stamping, and guard against reintroducing a hardcoded literal.

Changes

Package version synchronization

Layer / File(s) Summary
Metadata-backed version assignment
python/scenario/__init__.py
The public __version__ constant now uses the tracing SDK metadata version instead of a hardcoded string.
Version consistency tests
python/tests/test_package_version.py
Tests compare the public version with installed and trace metadata and reject hardcoded source assignments.

Possibly related PRs

Suggested labels: low-risk-change

Suggested reviewers: drewdrewthis, rogeriochaves, github-actions

Poem

I’m a rabbit with a version to share,
No hardcoded number hiding there.
Metadata hops through the trace,
Tests keep every release in place.
Synchronized carrots—what a case! 🐇

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly states the main change: deriving scenario.__version__ from package metadata.
Description check ✅ Passed The description is directly related to the change and explains the version-source update and tests.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch tech-debt-fixer-py-version

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

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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.

Inline comments:
In `@python/tests/test_package_version.py`:
- Line 38: Add an assertion that scenario.__file__ is not None before passing it
to pathlib.Path in the test setup, narrowing the type while validating the
module file path; keep the existing read_text flow unchanged.
- Around line 41-47: Update the AST traversal in the version-assignment check to
recognize both ast.Assign and ast.AnnAssign nodes, while preserving the existing
__version__ target detection and hardcoded-literal validation for either
assignment form.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8d5e0b27-bcf1-4083-83ec-41ccbafdeeb0

📥 Commits

Reviewing files that changed from the base of the PR and between 37be797 and c596b0a.

📒 Files selected for processing (2)
  • python/scenario/__init__.py
  • python/tests/test_package_version.py

import ast
import pathlib

source = pathlib.Path(scenario.__file__).read_text()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Assert that __file__ is not None for type safety.

The __file__ attribute of a module is typed as str | None. Passing it directly to pathlib.Path can cause a type-checking error. Add an assertion to narrow the type and ensure the file path is valid.

🛠️ Proposed fix
-    source = pathlib.Path(scenario.__file__).read_text()
+    assert scenario.__file__ is not None, "scenario module must have a __file__ attribute"
+    source = pathlib.Path(scenario.__file__).read_text()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
source = pathlib.Path(scenario.__file__).read_text()
assert scenario.__file__ is not None, "scenario module must have a __file__ attribute"
source = pathlib.Path(scenario.__file__).read_text()
🤖 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 `@python/tests/test_package_version.py` at line 38, Add an assertion that
scenario.__file__ is not None before passing it to pathlib.Path in the test
setup, narrowing the type while validating the module file path; keep the
existing read_text flow unchanged.

Comment on lines +41 to +47
for node in tree.body:
if not isinstance(node, ast.Assign):
continue
if not any(
isinstance(t, ast.Name) and t.id == "__version__" for t in node.targets
):
continue

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Handle annotated assignments to prevent test evasion.

The current check only looks for ast.Assign nodes. If a developer later introduces __version__ with a type annotation (e.g., __version__: str = "1.0.0"), it will be parsed as an ast.AnnAssign node. The test will silently skip it, allowing a hardcoded literal to be reintroduced.

Update the condition to handle both assignment types.

🛠️ Proposed fix
     for node in tree.body:
-        if not isinstance(node, ast.Assign):
-            continue
-        if not any(
-            isinstance(t, ast.Name) and t.id == "__version__" for t in node.targets
-        ):
-            continue
+        is_version_assign = False
+        if isinstance(node, ast.Assign):
+            is_version_assign = any(
+                isinstance(t, ast.Name) and t.id == "__version__" for t in node.targets
+            )
+        elif isinstance(node, ast.AnnAssign):
+            is_version_assign = isinstance(node.target, ast.Name) and node.target.id == "__version__"
+
+        if not is_version_assign:
+            continue
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for node in tree.body:
if not isinstance(node, ast.Assign):
continue
if not any(
isinstance(t, ast.Name) and t.id == "__version__" for t in node.targets
):
continue
for node in tree.body:
is_version_assign = False
if isinstance(node, ast.Assign):
is_version_assign = any(
isinstance(t, ast.Name) and t.id == "__version__" for t in node.targets
)
elif isinstance(node, ast.AnnAssign):
is_version_assign = isinstance(node.target, ast.Name) and node.target.id == "__version__"
if not is_version_assign:
continue
🤖 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 `@python/tests/test_package_version.py` around lines 41 - 47, Update the AST
traversal in the version-assignment check to recognize both ast.Assign and
ast.AnnAssign nodes, while preserving the existing __version__ target detection
and hardcoded-literal validation for either assignment form.

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

Labels

tech-debt-fixer Opened by the tech-debt-fixer agent (shared langwatch-agent bot identity)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant