Skip to content

Enforce PY-1 import contracts with import-linter - #154

Merged
mark-torres10 merged 2 commits into
mainfrom
codex/py-1-import-linter
Feb 26, 2026
Merged

Enforce PY-1 import contracts with import-linter#154
mark-torres10 merged 2 commits into
mainfrom
codex/py-1-import-linter

Conversation

@mark-torres10

@mark-torres10 mark-torres10 commented Feb 25, 2026

Copy link
Copy Markdown
Collaborator

What

  • Add import-linter and PY-1 import contracts (configured in pyproject.toml).
  • Run in pre-commit (import-linter (PY-1)) and CI (new import_lint job).
  • Refactor existing violations so the contracts pass.

Notable refactors

  • Keep simulation.core.models pure by moving action generation into simulation/core/agent_actions.py.
  • Remove route-layer direct imports of feeds.* / simulation.core.* by moving metadata listing into simulation/api/services/metadata_service.py and translating service errors into API-layer exceptions.
  • Remove registry-level validation from simulation/core/models/runs.py (validation remains at API boundary).

How to verify

  • uv sync --extra test
  • uv run lint-imports --config pyproject.toml
  • uv run pre-commit run --all-files
  • uv run pytest

Summary by CodeRabbit

  • New Features

    • Added metadata service endpoints to retrieve registered feed algorithms and metrics.
  • Bug Fixes

    • Improved API error responses with dedicated error classes for duplicate agents, missing runs, and creation failures.
  • Chores

    • Added import boundary linting to CI and pre-commit hooks to enforce modular architecture.
    • Configured import-linter tool to maintain separation of concerns across modules.

@vercel

vercel Bot commented Feb 25, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
ui Ready Ready Preview, Comment Feb 26, 2026 3:24am

@coderabbitai

coderabbitai Bot commented Feb 25, 2026

Copy link
Copy Markdown
Contributor

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 5cd12af and 0f7ddac.

📒 Files selected for processing (6)
  • .github/workflows/ci.yml
  • .pre-commit-config.yaml
  • pyproject.toml
  • simulation/core/agent_actions.py
  • simulation/core/models/runs.py
  • tests/simulation/core/test_agent_actions.py

📝 Walkthrough

Walkthrough

This PR introduces architectural boundaries through import-linter configuration, refactors action generation from agent instance methods to module-level functions, creates API-specific error classes for cleaner route error handling, and simplifies validation logic in run configuration.

Changes

Cohort / File(s) Summary
Import Boundary Enforcement
.github/workflows/ci.yml, .pre-commit-config.yaml, pyproject.toml
Added GitHub Actions job and pre-commit hook for import-linter; configured four contracts to enforce modular boundaries across simulation.core.models, lib, ml_tooling, and simulation.api.routes packages.
API Error Abstraction
simulation/api/errors.py
New module introducing three lightweight exception classes (ApiHandleAlreadyExistsError, ApiRunNotFoundError, ApiRunCreationFailedError) to decouple API routes from internal core exceptions.
API Error Integration
simulation/api/routes/simulation.py, simulation/api/services/agent_command_service.py, simulation/api/services/run_execution_service.py, simulation/api/services/run_query_service.py
Replaced internal exceptions (HandleAlreadyExistsError, RunNotFoundError, SimulationRunFailure) with API-facing error classes; updated error handling and response construction.
API Metadata Service
simulation/api/services/metadata_service.py
New service module providing list_feed_algorithms() and list_metrics() helpers for API consumption; integrated into simulation routes.
Action Generation Refactor
simulation/core/agent_actions.py, simulation/core/command_service.py, simulation/core/models/agents.py
Extracted action generation (likes, comments, follows) from SocialMediaAgent methods into module-level functions in agent_actions; updated command_service to call new functions and removed three methods from agent model.
Validation Simplification
simulation/core/models/runs.py
Replaced registry-based validators for metric_keys and feed_algorithm with direct non-empty string checks; removed external validator imports.
Test Updates
tests/api/test_run_query_service.py, tests/simulation/core/test_command_service.py, tests/simulation/core/test_agent_actions.py
Updated imports and assertions to reflect new API error classes and refactored generator functions; added agent_handle parameter to generator mocks.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 hops with joy
Actions leap from agents free,
Boundaries drawn with linting's key,
API errors, clean and bright,
Modular walls stand tall and tight! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 64.52% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding import-linter configuration and PY-1 import contracts to enforce architectural boundaries in the codebase.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch codex/py-1-import-linter

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

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
simulation/api/services/run_query_service.py (1)

122-128: ⚠️ Potential issue | 🟡 Minor

Stale docstring: references removed RunNotFoundError.

Line 124 still documents RunNotFoundError but the function now raises ApiRunNotFoundError. Update the docstring to match.

📝 Proposed fix
-        RunNotFoundError: If the run does not exist.
+        ApiRunNotFoundError: If the run does not exist.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@simulation/api/services/run_query_service.py` around lines 122 - 128, Update
the Raises section of the docstring to reflect the actual exception type thrown:
replace the stale RunNotFoundError entry with ApiRunNotFoundError (or remove the
old reference), ensuring the docstring for the function that calls
engine.get_run(run_id) and raises ApiRunNotFoundError(run_id) correctly
documents ValueError for empty run_id and ApiRunNotFoundError for missing runs.
simulation/api/routes/simulation.py (1)

276-290: ⚠️ Potential issue | 🟡 Minor

Unreachable except block — DEFAULT_SIMULATION_CONFIG is a constant.

return DEFAULT_SIMULATION_CONFIG on Line 282 cannot raise, so the except Exception block (Lines 283–290) is dead code. Either remove the try/except or, if you want a defensive wrapper for future changes, leave a comment explaining why.

♻️ Suggested simplification
 `@timed`(attach_attr="duration_ms", log_level=None)
 async def _execute_get_default_config(
     request: Request,
 ) -> DefaultConfigSchema | Response:
     """Fetch default config and convert unexpected failures to HTTP responses."""
-    try:
-        return DEFAULT_SIMULATION_CONFIG
-    except Exception:
-        logger.exception("Unexpected error while fetching default config")
-        return _error_response(
-            status_code=500,
-            code="INTERNAL_ERROR",
-            message="Internal server error",
-            detail=None,
-        )
+    return DEFAULT_SIMULATION_CONFIG
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@simulation/api/routes/simulation.py` around lines 276 - 290, The try/except
in _execute_get_default_config is dead because returning
DEFAULT_SIMULATION_CONFIG cannot raise; remove the try/except and simply return
DEFAULT_SIMULATION_CONFIG, or if you want to keep a defensive wrapper for future
changes, replace the broad try/except with a short explanatory comment above the
return explaining the defensive intent and why exceptions are expected in
future, or narrow the catch to specific operations and ensure any error handling
uses logger.exception and _error_response only when real fallible work is
present.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@pyproject.toml`:
- Around line 112-115: The import-linter config lists ai_tools in root_packages
but it isn't constrained by any forbidden_modules contract; either add ai_tools
to the appropriate forbidden_modules lists (e.g., include "ai_tools" in the same
contract entries that restrict lib, ml_tooling, or simulation.core.models) so it
inherits the intended isolation, or add a short clarifying comment next to
root_packages explaining that ai_tools is intentionally unconstrained because
the directory is currently empty and reserved for future work; update the
pyproject.toml import-linter section accordingly and reference the ai_tools
identifier and the forbidden_modules contract names when making the change.

In `@simulation/core/agent_actions.py`:
- Around line 20-71: These three functions (generate_likes, generate_comments,
generate_follows) duplicate the same guard + generator retrieval +
generator.generate call; extract a small helper (e.g., _generate_action) that
accepts candidates, run_id, turn_number, agent_handle and a generator
factory/function (get_like_generator, get_comment_generator,
get_follow_generator) and performs the guard and delegation, then have each
specific function call _generate_action with the appropriate factory to remove
the repeated boilerplate.
- Line 7: Remove the unnecessary future import by deleting the line "from
__future__ import annotations" in this module; since the project targets Python
3.12 (py312) the postponed evaluation of annotations is redundant—no other code
changes are needed in functions/classes in this file (just remove that import).

In `@simulation/core/models/runs.py`:
- Around line 45-48: Duplicate one-line validators validate_feed_algorithm found
in RunConfig and Run should be replaced by a single module-level validator
function: create a top-level function (e.g., validate_feed_algorithm_field) that
calls validate_non_empty_string(..., "feed_algorithm"), then reference that
function from both `@field_validator` decorators on the feed_algorithm field in
RunConfig and Run so both models reuse the same validator implementation; update
imports/annotations if needed and remove the duplicate class methods.
- Around line 32-33: The call to validate_non_empty_iterable(...) currently
discards its return value before using v in the list comprehension; update the
code in the Metric keys validators (the function where
validate_non_empty_iterable(v, "metric_keys") and the
Run.validate_metric_keys_run method) to capture the returned iterable (e.g., v =
validate_non_empty_iterable(v, "metric_keys")) and then iterate that returned
value when building the list of validate_non_empty_string(item, "metric_keys");
alternatively, if the helper is intended only to assert non-emptiness, rename it
to assert_non_empty_iterable or add a clarifying comment and keep current
use—ensure validate_non_empty_iterable's contract is respected by either using
its return value or changing its name/semantics.

In `@tests/simulation/core/test_command_service.py`:
- Around line 289-310: Introduce a reusable contextmanager or pytest fixture
(e.g., patch_action_generators) that patches the three targets
"simulation.core.command_service.generate_likes",
"simulation.core.command_service.generate_comments", and
"simulation.core.command_service.generate_follows" with provided mocks, then
replace the repeated with-patch blocks in the tests that call
command_service._simulate_turn (and the other two test sites) by using this
helper to reduce boilerplate and ensure consistent patch targets; name the
helper clearly (patch_action_generators) and have it accept the three mock
objects and yield control so existing test bodies remain unchanged.

In `@tests/simulation/core/test_social_media_agent.py`:
- Around line 1-5: The test file name doesn't reflect the module under test;
rename the file from test_social_media_agent.py to test_agent_actions.py so
pytest discovers it as testing simulation.core.agent_actions, and update the
file-level docstring to "Tests for simulation.core.agent_actions"; ensure any
imports (e.g., generate_follows) or external references to the old filename
(CI/test runners) are updated accordingly.

---

Outside diff comments:
In `@simulation/api/routes/simulation.py`:
- Around line 276-290: The try/except in _execute_get_default_config is dead
because returning DEFAULT_SIMULATION_CONFIG cannot raise; remove the try/except
and simply return DEFAULT_SIMULATION_CONFIG, or if you want to keep a defensive
wrapper for future changes, replace the broad try/except with a short
explanatory comment above the return explaining the defensive intent and why
exceptions are expected in future, or narrow the catch to specific operations
and ensure any error handling uses logger.exception and _error_response only
when real fallible work is present.

In `@simulation/api/services/run_query_service.py`:
- Around line 122-128: Update the Raises section of the docstring to reflect the
actual exception type thrown: replace the stale RunNotFoundError entry with
ApiRunNotFoundError (or remove the old reference), ensuring the docstring for
the function that calls engine.get_run(run_id) and raises
ApiRunNotFoundError(run_id) correctly documents ValueError for empty run_id and
ApiRunNotFoundError for missing runs.

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 71facf9 and 5cd12af.

⛔ Files ignored due to path filters (1)
  • docs/SUGGESTED_CUSTOM_LINTERS.md is excluded by !**/*.md
📒 Files selected for processing (16)
  • .github/workflows/ci.yml
  • .pre-commit-config.yaml
  • pyproject.toml
  • simulation/api/errors.py
  • simulation/api/routes/simulation.py
  • simulation/api/services/agent_command_service.py
  • simulation/api/services/metadata_service.py
  • simulation/api/services/run_execution_service.py
  • simulation/api/services/run_query_service.py
  • simulation/core/agent_actions.py
  • simulation/core/command_service.py
  • simulation/core/models/agents.py
  • simulation/core/models/runs.py
  • tests/api/test_run_query_service.py
  • tests/simulation/core/test_command_service.py
  • tests/simulation/core/test_social_media_agent.py
💤 Files with no reviewable changes (1)
  • simulation/core/models/agents.py

Comment thread pyproject.toml
Comment thread simulation/core/agent_actions.py
Comment thread simulation/core/agent_actions.py
Comment thread simulation/core/models/runs.py
Comment thread simulation/core/models/runs.py
Comment on lines +289 to +310
with (
patch(
"simulation.core.command_service.generate_likes", mock_generate_likes
),
patch(
"simulation.core.command_service.generate_comments",
mock_generate_comments,
),
patch(
"simulation.core.command_service.generate_follows",
mock_generate_follows,
),
):
action_history_store = Mock()
result = command_service._simulate_turn(
run_id=sample_run.run_id,
turn_number=0,
agents=[agent],
feed_algorithm="chronological",
action_history_store=action_history_store,
turn_metric_keys=DEFAULT_TURN_METRIC_KEYS,
)

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 | 🔵 Trivial

Repeated triple-patch blocks — consider a shared helper or fixture.

The same three patch("simulation.core.command_service.generate_*", ...) context managers appear in three tests. A small helper (e.g., a @contextmanager or a pytest fixture yielding the three mocks) would reduce boilerplate and make it easier to keep the patch targets consistent if the module path changes.

♻️ Example helper
from contextlib import contextmanager

`@contextmanager`
def patch_action_generators(likes_mock, comments_mock, follows_mock):
    with (
        patch("simulation.core.command_service.generate_likes", likes_mock),
        patch("simulation.core.command_service.generate_comments", comments_mock),
        patch("simulation.core.command_service.generate_follows", follows_mock),
    ):
        yield

Then in tests:

with patch_action_generators(mock_generate_likes, mock_generate_comments, mock_generate_follows):
    result = command_service._simulate_turn(...)

Also applies to: 401-422, 634-654

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/simulation/core/test_command_service.py` around lines 289 - 310,
Introduce a reusable contextmanager or pytest fixture (e.g.,
patch_action_generators) that patches the three targets
"simulation.core.command_service.generate_likes",
"simulation.core.command_service.generate_comments", and
"simulation.core.command_service.generate_follows" with provided mocks, then
replace the repeated with-patch blocks in the tests that call
command_service._simulate_turn (and the other two test sites) by using this
helper to reduce boilerplate and ensure consistent patch targets; name the
helper clearly (patch_action_generators) and have it accept the three mock
objects and yield control so existing test bodies remain unchanged.

Comment thread tests/simulation/core/test_agent_actions.py
@mark-torres10 mark-torres10 self-assigned this Feb 26, 2026
@mark-torres10 mark-torres10 added the codex PRs generated by OpenAI Codex label Feb 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

codex PRs generated by OpenAI Codex

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant