"""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"