Skip to content

chore: eliminate sys.path.insert hacks — migrate to package-relative imports#273

Closed
sheepdestroyer wants to merge 12 commits into
masterfrom
chore/eliminate-sys-path-hacks-565508234636993796
Closed

chore: eliminate sys.path.insert hacks — migrate to package-relative imports#273
sheepdestroyer wants to merge 12 commits into
masterfrom
chore/eliminate-sys-path-hacks-565508234636993796

Conversation

@sheepdestroyer

@sheepdestroyer sheepdestroyer commented Jul 12, 2026

Copy link
Copy Markdown
Owner

What

This PR removes vestigial sys.path.insert hacks from scripts in scripts/, migrating to standard package-relative imports with ImportError fallbacks. It also adds a missing __init__.py to the router/ directory, normalizes structured content parsing in chat_helpers.py, removes dead sys.path.insert hacks from two router test files flagged by Gemini Code Assist, and moves the classifier api_base from a hardcoded URL to an env-var placeholder.

Fixes #266

Changes since PR #272

Gemini Code Assist review fixes

  • router/tests/test_dashboard_data.py: removed sys.path.insert(0, router_path) hack + dead import sys/import os
  • router/tests/test_resolve_external_urls.py: removed same hack + dead import sys

Both were doubly broken: (1) they inserted router/ into sys.path, which doesn't help from router import main — Python needs the parent dir; (2) router/tests/conftest.py already inserts the correct directory (repo root).

Classifier api_base: hardcoded URL → env-var placeholder

The bot previously hardcoded https://x570.vendeuvre.lan/llm-routing/llama/v1 in router/config.yaml. This commit:

  1. Adds os.environ/ resolution for api_base in router/main.py — same pattern already used for api_key
  2. Adds get_classifier_client() — a separate httpx client with verify=False since the classifier uses an internal self-signed TLS cert behind HAProxy
  3. Changes router/config.yaml api_base from hardcoded URL to os.environ/LLAMA_CLASSIFIER_URL
  4. Adds LLAMA_CLASSIFIER_URL to .env pointing at the canonical endpoint

This keeps config DRY — dev and prod differ only by their .env files, not by editing config.yaml directly.

Original PR #271 / #272 changes (preserved)

Import cleanup (9 scripts)

  • Removed sys.path.insert hacks from all scripts in scripts/ and verification/
  • Replaced with from scripts.chat_helpers import parse_chat_response + ImportError fallback
  • Added try/except ImportError pattern to all scripts

Router package

  • Added router/__init__.py to make router a proper Python package
  • Updated router/main.py to use from router.circuit_breaker import get_breaker

chat_helpers.py

  • Added _normalize_chat_content() helper for structured content payloads
  • Updated parse_chat_response() to use the new normalizer

upgrade-prod.sh

  • Self-copy guard, centralized cleanup via trap, non-interactive mode, pre-flight .env validation, split rsync calls

Misc

  • Removed unused imports (asyncio, time) from test files

Related

Summary by CodeRabbit

  • Bug Fixes
    • Classifier requests now use the configured LLAMA_CLASSIFIER_URL (with optional CA verification) instead of a fixed localhost endpoint.
    • Updated circuit-breaker wiring for proxy behavior.
    • Improved startup/shutdown HTTP client lifecycle for classifier traffic.
  • Documentation
    • Updated routing/classifier model references to qwen-4b-routing and clarified env-based embedding/classifier endpoint configuration.
  • Infrastructure
    • Increased Kubernetes probe startup grace periods for better rollout reliability.
  • Tests
    • Refreshed test and verification tooling imports/config to work from package context and environment-driven settings.

google-labs-jules Bot and others added 6 commits July 12, 2026 14:04
Co-authored-by: sheepdestroyer <1377479+sheepdestroyer@users.noreply.github.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
- Revert 'from .circuit_breaker' to 'from router.circuit_breaker' in main.py:
  relative imports break when main.py is imported directly as 'main'
  (tests use 'from main import ...' with PYTHONPATH=.:router)
- Add try/except ImportError fallback to 'from scripts.chat_helpers'
  across all 7 scripts (4 in scripts/, 3 in verification/)
  so they remain runnable via both package import and direct execution
…e imports

This commit:
- Removes sys.path.insert hacks from scripts/
- Adds router/__init__.py to make router a package
- Updates router/main.py and router/agy_proxy.py to use package-aware imports
- Updates tests and mocks to use absolute package paths
- Updates CI workflow to use standard PYTHONPATH=.
- Ensures both CLI and package execution modes work correctly

Co-authored-by: sheepdestroyer <1377479+sheepdestroyer@users.noreply.github.com>
Gemini Code Assist correctly identified that the PR's removal of sys.path
hacks broke direct execution of all 9 scripts. When Python runs a script,
it REPLACES sys.path[0] (the CWD) with the script's directory — so cross-
package imports (router.*, scripts.chat_helpers) fail with ModuleNotFoundError.

Two failure patterns, two fixes:

1. Scripts in scripts/ importing scripts.chat_helpers:
   try/except with bare 'from chat_helpers' fallback (found in sys.path[0])

2. Scripts in scripts/verification/ importing scripts.chat_helpers or router.*:
   try/except with sys.path.insert to repo root (parents[2])

Verified: all 9 scripts now run successfully via both 'python scripts/X.py'
(direct) and package imports (from scripts.X import ...)
Gemini Code Assist flagged that test_dashboard_data.py and
test_resolve_external_urls.py still use sys.path.insert(0, router_path)
hacks despite having migrated to absolute package imports
(from router import main).

These hacks were doubly broken:
1. They inserted router/ (the package dir) into sys.path, which doesn't
   help resolve 'from router import main' — Python needs the PARENT dir.
2. router/tests/conftest.py already inserts the correct dir (repo root).

Removed the dead sys/os imports along with the hack blocks.
All 193 tests pass. Scripts import cleanly.

@sourcery-ai sourcery-ai 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.

Sorry @sheepdestroyer, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c61e2dc9-701a-492b-8059-3362e00c2e9b

📥 Commits

Reviewing files that changed from the base of the PR and between 98fc480 and 3d4d6f4.

📒 Files selected for processing (13)
  • .env.dev
  • README.md
  • litellm/config.yaml
  • pod.yaml
  • router/config.yaml
  • router/main.py
  • scripts/benchmark_classifier.py
  • scripts/classify_direct.py
  • scripts/reclassify_all.py
  • scripts/retry_errors.py
  • start-stack.sh
  • tests/test_a2_verify.py
  • tests/test_classifier_accuracy.py

📝 Walkthrough

Walkthrough

The PR aligns router imports with package-qualified execution, adds environment-based classifier configuration and a dedicated HTTP client, updates classifier model references, adjusts deployment settings, and updates scripts, tests, and CI wiring.

Changes

Package routing alignment

Layer / File(s) Summary
Router runtime and classifier client
router/config.yaml, router/main.py, router/agy_proxy.py
Circuit-breaker imports use router package paths; classifier configuration resolves from the environment; classifier requests use a dedicated client with configurable TLS verification and shutdown cleanup.
Classifier endpoint and model deployment
.env.dev, litellm/config.yaml, start-stack.sh, pod.yaml, README.md, tests/test_classifier_accuracy.py
Classifier endpoints use environment-backed placeholders, routing references use qwen-4b-routing, probe delays increase, and accuracy/configuration documentation is updated.
Script imports and classifier model usage
scripts/*, scripts/verification/*
Scripts use guarded package imports and update direct classifier requests, discovery, logs, and benchmark metadata to the qwen routing model.
Package-qualified tests and CI execution
.github/workflows/test.yml, router/tests/*, tests/test_a2_verify.py, tests/test_models_proxy.py
Tests and mocks reference router modules, classifier tests patch the dedicated client factory, and CI runs with repository-root import resolution.

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

Sequence Diagram(s)

sequenceDiagram
  participant CI
  participant pytest
  participant router.main
  participant Classifier
  CI->>pytest: run with CONFIG_PATH and PYTHONPATH=.
  pytest->>router.main: import package-qualified router module
  router.main->>Classifier: send request through dedicated classifier client
  Classifier-->>router.main: return classification response
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning router/main.py and router/config.yaml add new classifier-client and runtime URL logic unrelated to the import refactor. Move the classifier client and config URL behavior into a separate PR so this change stays focused on import cleanup.
Docstring Coverage ⚠️ Warning Docstring coverage is 25.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
Linked Issues check ✅ Passed The PR removes the import path hacks and updates the affected scripts/tests to package-based imports as requested.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main refactor: removing sys.path.insert hacks and moving to package-aware imports.
✨ 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 chore/eliminate-sys-path-hacks-565508234636993796

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.

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request refactors imports across the codebase to use absolute imports (e.g., prefixing with router.) and replaces manual sys.path manipulation in test and benchmark scripts with robust try/except ImportError blocks. Feedback was provided on tests/test_a2_verify.py to apply a similar try/except ImportError fallback pattern to prevent ModuleNotFoundError when the script is executed directly.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread tests/test_a2_verify.py Outdated
Comment on lines +3 to +4
from router.circuit_breaker import get_breaker
from router.agy_proxy import try_agy_proxy

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.

medium

Since tests/test_a2_verify.py has a shebang (#!/usr/bin/env python3) and is designed to be executed directly as a script, running it directly (e.g., python tests/test_a2_verify.py) will fail with a ModuleNotFoundError because the repository root is not in sys.path.

To align with the other scripts in this PR and ensure it remains runnable both directly and as a package import, we should wrap the imports in a try/except ImportError block that dynamically adds the repository root to sys.path as a fallback.

Suggested change
from router.circuit_breaker import get_breaker
from router.agy_proxy import try_agy_proxy
try:
from router.circuit_breaker import get_breaker
from router.agy_proxy import try_agy_proxy
except ImportError:
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
from router.circuit_breaker import get_breaker
from router.agy_proxy import try_agy_proxy

boy added 2 commits July 12, 2026 21:13
- Add os.environ/ resolution for api_base in router/main.py (same pattern
  as api_key resolution at line 351)
- Add get_classifier_client() with verify=False for internal self-signed TLS
- Change router/config.yaml api_base to os.environ/LLAMA_CLASSIFIER_URL
  placeholder instead of hardcoded URL
- Update test patches to use get_classifier_client
- Add LLAMA_CLASSIFIER_URL to .env pointing at canonical endpoint
  https://x570.vendeuvre.lan/llm-routing/llama/v1

The bot previously hardcoded the canonical URL directly in config.yaml.
Now it's resolved from .env at runtime, keeping config DRY and making
dev/prod environments differ only by their .env files.
The bot previously hardcoded the canonical endpoint in litellm/config.yaml
for two local models (local-qwen-3.6 and nomic-embed-text). Replace with
LLAMA_CLASSIFIER_URL_PLACEHOLDER, resolved at render time by
render_litellm_config() via the same env var used for the router classifier.

The placeholder is substituted during deploy (start-stack.sh) using the
value from .env, keeping config DRY — dev/prod differ only by .env files.

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

Actionable comments posted: 4

🤖 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 `@router/main.py`:
- Around line 101-118: Update the lifespan cleanup to close the classifier
client by awaiting _classifier_client.aclose() when it exists, alongside the
existing _http_client cleanup. Preserve the singleton behavior in
get_classifier_client and ensure shutdown releases its connection pool.
- Around line 101-118: Update get_classifier_client to validate TLS using the
trusted self-signed CA bundle or SSL_CERT_FILE instead of verify=False. Extract
the duplicated httpx.Limits construction into a shared helper and update both
get_classifier_client and get_http_client to reuse it, preserving the existing
connection settings.

In `@scripts/benchmark_tokens.py`:
- Around line 9-13: Remove the sys.path.insert fallback from the benchmark
script’s router.main import. Update the execution/import strategy around
estimate_prompt_tokens and METADATA_OVERHEAD to support both documented
invocation modes through package-aware imports, without mutating global import
state.

In `@scripts/verification/verify_canonical_endpoints.py`:
- Around line 22-26: Remove the sys.path.insert fallback around
parse_chat_response in the verification script. Replace the retry with the
package-aware direct-script fallback pattern used by the other verification
scripts, while preserving the normal package import path and ensuring direct
execution remains supported.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2185b2a4-89b1-4b69-baf0-cf164fb548e4

📥 Commits

Reviewing files that changed from the base of the PR and between 3f7f503 and 98fc480.

📒 Files selected for processing (24)
  • .github/workflows/test.yml
  • router/__init__.py
  • router/agy_proxy.py
  • router/config.yaml
  • router/main.py
  • router/tests/test_dashboard_data.py
  • router/tests/test_detect_active_tool.py
  • router/tests/test_estimate_prompt_tokens.py
  • router/tests/test_get_gemini_oauth_status.py
  • router/tests/test_get_goose_sessions.py
  • router/tests/test_load_persisted_stats.py
  • router/tests/test_resolve_external_urls.py
  • router/tests/test_routing_behavior.py
  • scripts/benchmark_classifier.py
  • scripts/benchmark_tokens.py
  • scripts/classify_direct.py
  • scripts/reclassify_all.py
  • scripts/retry_errors.py
  • scripts/verification/verification_helpers.py
  • scripts/verification/verify_breaker.py
  • scripts/verification/verify_canonical_endpoints.py
  • scripts/verification/verify_ollama_routing.py
  • tests/test_a2_verify.py
  • tests/test_models_proxy.py

Comment thread router/main.py
Comment on lines +9 to +13
try:
from router.main import estimate_prompt_tokens, METADATA_OVERHEAD
except ImportError:
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
from router.main import estimate_prompt_tokens, METADATA_OVERHEAD

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.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Remove the remaining sys.path.insert fallback.

This still violates the PR objective to eliminate import-path hacks. Use a package-aware execution/import strategy that supports both documented invocation modes without mutating global import state.

🤖 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 `@scripts/benchmark_tokens.py` around lines 9 - 13, Remove the sys.path.insert
fallback from the benchmark script’s router.main import. Update the
execution/import strategy around estimate_prompt_tokens and METADATA_OVERHEAD to
support both documented invocation modes through package-aware imports, without
mutating global import state.

Comment on lines +22 to +26
try:
from scripts.chat_helpers import parse_chat_response
except ImportError:
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
from scripts.chat_helpers import parse_chat_response

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.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Remove the remaining sys.path.insert fallback.

This change still mutates sys.path and retries the same package import, so it does not satisfy the stated import-hack removal objective. Replace it with a package-aware direct-script fallback consistent with the other verification scripts.

🤖 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 `@scripts/verification/verify_canonical_endpoints.py` around lines 22 - 26,
Remove the sys.path.insert fallback around parse_chat_response in the
verification script. Replace the retry with the package-aware direct-script
fallback pattern used by the other verification scripts, while preserving the
normal package import path and ensuring direct execution remains supported.

boy added 4 commits July 12, 2026 21:22
from router.circuit_breaker fails in Docker's flat /app/ layout where
files are not nested in a router/ package directory. Add try/except
ImportError with flat 'from circuit_breaker' fallback — same pattern
used for scripts/chat_helpers imports.
Router waits up to 180s for LiteLLM startup but liveness probe fired
at 20s — on cold starts (postgres from scratch, LiteLLM migrations),
the container was killed before LiteLLM was ready, causing a restart
loop. Readiness probe also bumped from 10s to 30s.

Matches LiteLLM's own 240s liveness probe.
Replace all references to gemma4-26a4b-routing, qwen-0.8b-routing, and
qwen-2b-routing with the canonical qwen-4b-routing classifier model.

Changed files: router/config.yaml, router/main.py, README.md, test
classifier accuracy test, and all 4 batch classification scripts.
- tests/test_a2_verify.py: add try/except ImportError fallback for direct
  script execution (Gemini Code Assist review)
- router/main.py: extract shared _http_limits() helper to deduplicate
  httpx.Limits construction across get_http_client() and
  get_classifier_client() (CodeRabbit review)
- router/main.py: use CLASSIFIER_CA_BUNDLE env var for classifier TLS
  verify (defaults to False — current behavior preserved)
  (CodeRabbit security review)
- router/main.py: close _classifier_client in lifespan shutdown cleanup
  (CodeRabbit stability review)
- .env.dev: add LLAMA_CLASSIFIER_URL (shared classifier across envs)

Rejected review comments:
- benchmark_tokens.py sys.path.insert: already uses try/except ImportError
  fallback — same pattern as all other scripts, not a violation
- verify_canonical_endpoints.py sys.path.insert: same — standard pattern
- docstring coverage < 80%: global repo concern, not PR-specific
- out-of-scope classifier changes: tracked via policy as issue, not
  blocking
@sheepdestroyer

Copy link
Copy Markdown
Owner Author

Review Feedback Addressed

Addressed the valid review findings in commit 3d4d6f4. Closing this PR to open a fresh one with the fixes included.

Addressed:

  • Gemini Code Assist: Added try/except ImportError fallback to tests/test_a2_verify.py
  • CodeRabbit: Extracted shared _http_limits() helper + env-based TLS verify for classifier client
  • CodeRabbit: Added _classifier_client.aclose() in lifespan shutdown cleanup

Rejected (with reasoning):

  • benchmark_tokens.py / verify_canonical_endpoints.py sys.path.insert: These already use the try/except ImportError fallback pattern — the same pattern consistently used across ALL scripts. This is not the old unconditional hack the PR eliminates.
  • Docstring coverage < 80%: Global repo concern, not introduced by this PR.
  • Out-of-scope classifier changes: Per policy, out-of-scope items are tracked as issues.

@sheepdestroyer

Copy link
Copy Markdown
Owner Author

Superseded — opening fresh PR with review fixes applied.

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.

refactor: eliminate sys.path.insert hacks — migrate to package-relative imports

1 participant