feat: fold bot-bottle-orchestrator into bot_bottle/orchestrator subpackage
Moves the orchestrator into bot_bottle/orchestrator/ so one install gets everything. Entry point is now `python -m bot_bottle.orchestrator run`. - Add bot_bottle/orchestrator/ with all 14 modules (verbatim move; internal imports were already relative, so no changes inside orchestrator modules) - Rewrite bootstrap.py: remove the lazy bot_bottle import guard, use direct relative imports from ..contrib.* - Add bot_bottle/contrib/forge/base.py: ScopedForge (read-anywhere / write-scoped) - Add bot_bottle/contrib/gitea/client.py: GiteaClient + GiteaForge (urllib.request only) - Add bot_bottle/contrib/gitea/forge_state.py: ForgeState + SqliteForgeStateStore - Add tests/unit/orchestrator/ (82 tests: 63 migrated + 19 new for contrib modules) Closes #321
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
"""State store interface + an in-memory implementation.
|
||||
|
||||
The orchestrator persists one `RunRecord` per forge-targeted issue. At
|
||||
runtime `bootstrap` supplies an adapter over bot-bottle's
|
||||
`SqliteForgeStateStore`; the in-memory store here backs tests and a
|
||||
`--no-bot-bottle` dry mode.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Protocol
|
||||
|
||||
from .model import RunRecord
|
||||
|
||||
|
||||
class StateStore(Protocol):
|
||||
"""Thin CRUD surface. Mirrors bot-bottle's `ForgeStateStore` so the
|
||||
bootstrap adapter is a straight pass-through."""
|
||||
|
||||
def upsert(self, record: RunRecord) -> None: ...
|
||||
|
||||
def get(self, owner: str, repo: str, issue_number: int) -> RunRecord | None: ...
|
||||
|
||||
def delete(self, owner: str, repo: str, issue_number: int) -> None: ...
|
||||
|
||||
def all(self) -> list[RunRecord]: ...
|
||||
|
||||
|
||||
class InMemoryStateStore:
|
||||
"""Dict-backed `StateStore`, keyed by (owner, repo, issue_number)."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._by_key: dict[tuple[str, str, int], RunRecord] = {}
|
||||
|
||||
def upsert(self, record: RunRecord) -> None:
|
||||
self._by_key[(record.owner, record.repo, record.issue_number)] = record
|
||||
|
||||
def get(self, owner: str, repo: str, issue_number: int) -> RunRecord | None:
|
||||
return self._by_key.get((owner, repo, issue_number))
|
||||
|
||||
def delete(self, owner: str, repo: str, issue_number: int) -> None:
|
||||
self._by_key.pop((owner, repo, issue_number), None)
|
||||
|
||||
def all(self) -> list[RunRecord]:
|
||||
return sorted(
|
||||
self._by_key.values(),
|
||||
key=lambda r: (r.owner, r.repo, r.issue_number),
|
||||
)
|
||||
Reference in New Issue
Block a user