Files
cleveragents-core/benchmarks/project_show_after_create_bench.py
T
brent.edwards 16dc4ab18d
CI / lint (push) Successful in 16s
CI / build (push) Successful in 16s
CI / quality (push) Successful in 19s
CI / security (push) Successful in 34s
CI / typecheck (push) Successful in 38s
CI / benchmark-regression (push) Has been skipped
CI / unit_tests (push) Successful in 2m24s
CI / docker (push) Successful in 40s
CI / integration_tests (push) Successful in 3m20s
CI / coverage (push) Successful in 5m25s
CI / benchmark-publish (push) Successful in 16m50s
fix(project): show created project after creation (#593)
## Summary

Fix `agents project show` not finding a project immediately after creation. Extends the `session.commit()` fix from #589 to also cover `update()` and `delete()` in `NamespacedProjectRepository`.

## Changes

**Production fix** (`src/cleveragents/infrastructure/database/repositories.py`):
- Add `session.commit()` to `create()`, `update()`, and `delete()` methods
- Add `finally: session.close()` guard to all three methods
- Update class docstring to reflect commit-per-method pattern

**Tests & benchmarks**:
- 3 Behave BDD regression scenarios (`features/project_show_after_create.feature`)
- Robot Framework integration smoke tests with "not found" assertion (`robot/project_show_after_create.robot`)
- ASV benchmarks for create-then-show round-trip (`benchmarks/project_show_after_create_bench.py`)

## Review feedback addressed

- **F1**: Removed unrelated em-dash CHANGELOG edits — wrote clean entry from scratch
- **F2**: Kept Suite Setup/Teardown (required for `${PYTHON}` variable); updated stale docs
- **F3**: Added "not found" string assertion to Robot negative test case
- **F4**: Removed redundant `Base.metadata.create_all()` from `_make_fresh_repo()` helper
- Updated all stale TDD "expected to fail" comments — this PR includes the fix

## Process

- Single squashed commit, rebased onto `master` (no merge commits)
- Prescribed commit message from issue #590 metadata

ISSUES CLOSED: #590

Reviewed-on: #593
Reviewed-by: Rui Hu <rui.hu@cleverthis.com>
Co-authored-by: Brent E. Edwards <brent.edwards@cleverthis.com>
Co-committed-by: Brent E. Edwards <brent.edwards@cleverthis.com>
2026-03-09 07:56:38 +00:00

116 lines
3.7 KiB
Python

"""ASV benchmarks for project create + show round-trip.
Measures the cost of creating a project and then showing it via the
``NamespacedProjectRepository``, using a file-based SQLite database so
that each operation gets a fresh session (matching real CLI behaviour).
Targets bug #590 — ``create()`` now calls ``session.commit()`` so data
is visible to a subsequent ``get()`` in a separate session.
"""
from __future__ import annotations
import contextlib
import importlib
import os
import shutil
import sys
import tempfile
from pathlib import Path
# Ensure the local *source* tree is importable even when ASV has an
# older build of the package installed.
_SRC = str(Path(__file__).resolve().parents[1] / "src")
if _SRC not in sys.path:
sys.path.insert(0, _SRC)
# Force-reload so ASV picks up the source tree version.
import cleveragents # noqa: E402
importlib.reload(cleveragents)
from sqlalchemy import create_engine # noqa: E402
from sqlalchemy.orm import sessionmaker # noqa: E402
from sqlalchemy.pool import NullPool # noqa: E402
from cleveragents.domain.models.core.project import ( # noqa: E402
NamespacedProject,
parse_namespaced_name,
)
from cleveragents.infrastructure.database.models import Base # noqa: E402
from cleveragents.infrastructure.database.repositories import ( # noqa: E402
NamespacedProjectRepository,
ProjectNotFoundError,
)
def _fresh_repo(db_path: str) -> NamespacedProjectRepository:
"""Return a repo backed by a *new* engine/session for *db_path*.
``NullPool`` ensures SQLite file handles are released immediately
when the engine is no longer referenced.
"""
engine = create_engine(f"sqlite:///{db_path}", echo=False, poolclass=NullPool)
Base.metadata.create_all(engine)
factory = sessionmaker(bind=engine, expire_on_commit=False)
return NamespacedProjectRepository(session_factory=factory)
class ProjectShowAfterCreateSuite:
"""Benchmark create-then-show round trip with separate sessions."""
timeout = 30.0
def setup(self) -> None:
self._tmpdir = tempfile.mkdtemp(prefix="bench_show_590_")
self._db_path = os.path.join(self._tmpdir, "bench.db")
engine = create_engine(
f"sqlite:///{self._db_path}",
echo=False,
poolclass=NullPool,
)
Base.metadata.create_all(engine)
engine.dispose()
self._counter = 0
def teardown(self) -> None:
shutil.rmtree(self._tmpdir, ignore_errors=True)
def time_create_then_show(self) -> None:
"""Create a project in one session, show (get) in another."""
self._counter += 1
name = f"bench-show-{self._counter}"
parsed = parse_namespaced_name(name)
proj = NamespacedProject(name=parsed.name, namespace=parsed.namespace)
create_repo = _fresh_repo(self._db_path)
create_repo.create(proj)
show_repo = _fresh_repo(self._db_path)
with contextlib.suppress(ProjectNotFoundError):
show_repo.get(f"local/{name}")
def track_show_after_create_found(self) -> int:
"""Track whether show finds the created project.
Returns 1 when get() successfully finds the project; 0 if
ProjectNotFoundError is raised.
"""
self._counter += 1
name = f"bench-show-track-{self._counter}"
parsed = parse_namespaced_name(name)
proj = NamespacedProject(name=parsed.name, namespace=parsed.namespace)
create_repo = _fresh_repo(self._db_path)
create_repo.create(proj)
show_repo = _fresh_repo(self._db_path)
try:
show_repo.get(f"local/{name}")
return 1
except ProjectNotFoundError:
return 0
ProjectShowAfterCreateSuite.track_show_after_create_found.unit = "found"