Pablan, as it stands
Self-hosted knowledge management for SMEs: a split-screen Markdown editor whose sections an LLM refines while you write, and RAG question answering over the documents that result. FastAPI + Postgres/pgvector on the back, SvelteKit on the front, everything OpenAI-compatible and self-hostable. Squashed into a single commit; the development history stays local. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CA43ZJda8Rbp2hKXNy8f6b
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
"""Template import: YAML → validated config → templates table.
|
||||
|
||||
Every row in `templates` is the customer's own, whichever way it got there
|
||||
— pasted YAML, a fork, or added from the shipped catalog
|
||||
(`template_catalog.py`). None of them is read-only.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
import yaml
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.authoring.schema import AuthoringTemplate
|
||||
from app.models import Template
|
||||
|
||||
logger = logging.getLogger("pablan.templates")
|
||||
|
||||
|
||||
class TemplateImportError(Exception):
|
||||
"""Sanitized import failure — safe to surface to an admin."""
|
||||
|
||||
|
||||
def parse_template(source: str) -> AuthoringTemplate:
|
||||
try:
|
||||
raw = yaml.safe_load(source)
|
||||
except yaml.YAMLError as exc:
|
||||
raise TemplateImportError(f"Invalid YAML: {type(exc).__name__}") from None
|
||||
if not isinstance(raw, dict):
|
||||
raise TemplateImportError("Template must be a YAML mapping.")
|
||||
try:
|
||||
return AuthoringTemplate.model_validate(raw)
|
||||
except ValueError as exc:
|
||||
first_error = str(exc).splitlines()[1] if "\n" in str(exc) else str(exc)
|
||||
raise TemplateImportError(
|
||||
f"Template failed validation: {first_error.strip()}"
|
||||
) from None
|
||||
|
||||
|
||||
async def upsert_template(
|
||||
db: AsyncSession, template: AuthoringTemplate
|
||||
) -> tuple[Template, bool]:
|
||||
"""Insert or update by the template's config id. Returns (row, created)."""
|
||||
existing = (
|
||||
await db.execute(
|
||||
select(Template).where(Template.config["id"].astext == template.id)
|
||||
)
|
||||
).scalar_one_or_none()
|
||||
config = template.model_dump()
|
||||
if existing is not None:
|
||||
existing.name = template.name
|
||||
existing.version = template.version
|
||||
existing.config = config
|
||||
return existing, False
|
||||
row = Template(
|
||||
name=template.name,
|
||||
version=template.version,
|
||||
config=config,
|
||||
)
|
||||
db.add(row)
|
||||
await db.flush()
|
||||
return row, True
|
||||
Reference in New Issue
Block a user