26de6bb7de
CI / lint (push) Successful in 15s
CI / build (push) Successful in 17s
CI / quality (push) Successful in 18s
CI / typecheck (push) Successful in 38s
CI / benchmark-regression (push) Has been skipped
CI / security (push) Successful in 50s
CI / unit_tests (push) Successful in 2m13s
CI / docker (push) Successful in 41s
CI / integration_tests (push) Successful in 3m11s
CI / coverage (push) Successful in 4m30s
CI / benchmark-publish (push) Successful in 16m39s
## Summary
Fix `agents project create` not persisting projects to the database. `NamespacedProjectRepository.create()` called `session.flush()` but never `session.commit()`, so projects were invisible to subsequent `agents project list` invocations that open a separate session.
## Changes
**Production fix** (`src/cleveragents/infrastructure/database/repositories.py`):
- Replace `session.flush()` with `session.commit()` in `NamespacedProjectRepository.create()`
- Add `finally: session.close()` guard for proper session lifecycle
**Tests & benchmarks**:
- 4 Behave BDD regression scenarios (`features/project_create_persist.feature`)
- Robot Framework integration smoke tests (`robot/project_create_persist.robot`)
- ASV benchmarks for create-then-list round-trip (`benchmarks/project_create_persist_bench.py`)
## Review feedback addressed
- **H3**: Added `finally: session.close()` to `create()` method
- **M1**: Removed redundant `session.flush()` before `session.commit()`
- **M2**: Updated stale TDD "expected to fail" comments — this PR includes the fix
- **M4**: Rewrote CHANGELOG entry to describe the fix, not just tests
- **F1**: Updated Robot documentation (Suite Setup/Teardown kept — needed for `${PYTHON}`)
- **F2**: Fixed bare assertion `"my-app"` to `"local/my-app"` in namespace scenario
- **F3**: Removed redundant `Base.metadata.create_all()` from `_make_fresh_repo()`
## Process
- Single squashed commit, rebased onto `master` (no merge commits)
- Prescribed commit message from issue #589 metadata
ISSUES CLOSED: #589
Reviewed-on: #591
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>
111 lines
3.7 KiB
Python
111 lines
3.7 KiB
Python
"""ASV benchmarks for project create + list persistence round-trip.
|
|
|
|
Measures the cost of creating a project and then listing it back via the
|
|
``NamespacedProjectRepository``, using a file-based SQLite database so
|
|
that each operation gets a fresh session (matching real CLI behaviour).
|
|
|
|
Targets bug #589 — ``create()`` now calls ``session.commit()`` so data
|
|
is visible to a subsequent ``list()`` in a separate session.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
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,
|
|
)
|
|
|
|
|
|
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 ProjectPersistRoundTripSuite:
|
|
"""Benchmark create-then-list round trip with separate sessions."""
|
|
|
|
timeout = 30.0
|
|
|
|
def setup(self) -> None:
|
|
self._tmpdir = tempfile.mkdtemp(prefix="bench_persist_589_")
|
|
self._db_path = os.path.join(self._tmpdir, "bench.db")
|
|
# Initialise schema once.
|
|
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_list(self) -> None:
|
|
"""Create a project in one session, list in another."""
|
|
self._counter += 1
|
|
name = f"bench-{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)
|
|
|
|
list_repo = _fresh_repo(self._db_path)
|
|
list_repo.list_projects()
|
|
|
|
def track_list_after_create_count(self) -> int:
|
|
"""Track whether list sees the created project.
|
|
|
|
Returns the number of projects visible after create + commit.
|
|
Expected value is 1 (or more if called repeatedly).
|
|
"""
|
|
self._counter += 1
|
|
name = f"bench-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)
|
|
|
|
list_repo = _fresh_repo(self._db_path)
|
|
projects = list_repo.list_projects()
|
|
return len(projects)
|
|
|
|
|
|
ProjectPersistRoundTripSuite.track_list_after_create_count.unit = "projects" # type: ignore[attr-defined]
|