feat(project): add ontology/graph persistence helpers to ProjectManager
- Add `from ..config import Config` import - Convert `_to_dict` from @staticmethod to @classmethod; it now opens a fresh session to populate `ontology` and `graph_id` from OntologyModel and GraphModel respectively - Use `db.expunge(proj)` before closing sessions in create_project, get_project and list_projects so `_to_dict` can open its own session without nesting conflicts (SQLite StaticPool safe) - Add five new classmethods: save_ontology, get_ontology, save_graph_record, get_latest_graph_external_id, complete_graph_record Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
9ed2412745
commit
949921344b
|
|
@ -7,6 +7,7 @@ from enum import Enum
|
||||||
|
|
||||||
from ..db import get_session
|
from ..db import get_session
|
||||||
from ..models.db_models import ProjectModel, ProjectFileModel
|
from ..models.db_models import ProjectModel, ProjectFileModel
|
||||||
|
from ..config import Config
|
||||||
|
|
||||||
|
|
||||||
class ProjectStatus(str, Enum):
|
class ProjectStatus(str, Enum):
|
||||||
|
|
@ -28,7 +29,8 @@ class ProjectManager:
|
||||||
db.add(proj)
|
db.add(proj)
|
||||||
db.commit()
|
db.commit()
|
||||||
db.refresh(proj)
|
db.refresh(proj)
|
||||||
return cls._to_dict(proj)
|
db.expunge(proj)
|
||||||
|
return cls._to_dict(proj)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_project(cls, project_id: str) -> Optional[Dict[str, Any]]:
|
def get_project(cls, project_id: str) -> Optional[Dict[str, Any]]:
|
||||||
|
|
@ -36,7 +38,8 @@ class ProjectManager:
|
||||||
proj = db.get(ProjectModel, project_id)
|
proj = db.get(ProjectModel, project_id)
|
||||||
if proj is None:
|
if proj is None:
|
||||||
return None
|
return None
|
||||||
return cls._to_dict(proj)
|
db.expunge(proj)
|
||||||
|
return cls._to_dict(proj)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def save_project(cls, project_data: Dict[str, Any]) -> None:
|
def save_project(cls, project_data: Dict[str, Any]) -> None:
|
||||||
|
|
@ -62,7 +65,9 @@ class ProjectManager:
|
||||||
with get_session() as db:
|
with get_session() as db:
|
||||||
stmt = select(ProjectModel).order_by(desc(ProjectModel.created_at)).limit(limit)
|
stmt = select(ProjectModel).order_by(desc(ProjectModel.created_at)).limit(limit)
|
||||||
projects = db.execute(stmt).scalars().all()
|
projects = db.execute(stmt).scalars().all()
|
||||||
return [cls._to_dict(p) for p in projects]
|
for p in projects:
|
||||||
|
db.expunge(p)
|
||||||
|
return [cls._to_dict(p) for p in projects]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def delete_project(cls, project_id: str, storage=None) -> bool:
|
def delete_project(cls, project_id: str, storage=None) -> bool:
|
||||||
|
|
@ -149,11 +154,110 @@ class ProjectManager:
|
||||||
return None
|
return None
|
||||||
return storage.download(storage_path).decode("utf-8")
|
return storage.download(storage_path).decode("utf-8")
|
||||||
|
|
||||||
@staticmethod
|
@classmethod
|
||||||
def _to_dict(proj: ProjectModel) -> Dict[str, Any]:
|
def save_ontology(cls, project_id: str, entity_types: list, edge_types: list) -> str:
|
||||||
|
from .db_models import OntologyModel
|
||||||
|
from sqlalchemy import select
|
||||||
|
with get_session() as db:
|
||||||
|
stmt = select(OntologyModel).where(OntologyModel.project_id == project_id).order_by(OntologyModel.version.desc())
|
||||||
|
existing = db.execute(stmt).scalars().first()
|
||||||
|
if existing:
|
||||||
|
existing.entity_types = entity_types
|
||||||
|
existing.edge_types = edge_types
|
||||||
|
db.commit()
|
||||||
|
return existing.id
|
||||||
|
else:
|
||||||
|
rec = OntologyModel(
|
||||||
|
id=str(uuid.uuid4()),
|
||||||
|
project_id=project_id,
|
||||||
|
version=1,
|
||||||
|
entity_types=entity_types,
|
||||||
|
edge_types=edge_types,
|
||||||
|
)
|
||||||
|
db.add(rec)
|
||||||
|
db.commit()
|
||||||
|
return rec.id
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_ontology(cls, project_id: str) -> Optional[Dict[str, Any]]:
|
||||||
|
from .db_models import OntologyModel
|
||||||
|
from sqlalchemy import select
|
||||||
|
with get_session() as db:
|
||||||
|
stmt = select(OntologyModel).where(OntologyModel.project_id == project_id).order_by(OntologyModel.version.desc())
|
||||||
|
rec = db.execute(stmt).scalars().first()
|
||||||
|
if rec is None:
|
||||||
|
return None
|
||||||
|
return {"entity_types": rec.entity_types or [], "edge_types": rec.edge_types or []}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def save_graph_record(cls, project_id: str, external_id: str, ontology_id: Optional[str] = None) -> str:
|
||||||
|
from .db_models import GraphModel
|
||||||
|
from sqlalchemy import select
|
||||||
|
with get_session() as db:
|
||||||
|
stmt = select(GraphModel).where(GraphModel.project_id == project_id).order_by(GraphModel.created_at.desc())
|
||||||
|
existing = db.execute(stmt).scalars().first()
|
||||||
|
if existing:
|
||||||
|
existing.external_id = external_id
|
||||||
|
existing.status = "building"
|
||||||
|
if ontology_id:
|
||||||
|
existing.ontology_id = ontology_id
|
||||||
|
db.commit()
|
||||||
|
return existing.id
|
||||||
|
else:
|
||||||
|
rec = GraphModel(
|
||||||
|
id=str(uuid.uuid4()),
|
||||||
|
project_id=project_id,
|
||||||
|
external_id=external_id,
|
||||||
|
ontology_id=ontology_id,
|
||||||
|
status="building",
|
||||||
|
backend=Config.GRAPH_BACKEND,
|
||||||
|
)
|
||||||
|
db.add(rec)
|
||||||
|
db.commit()
|
||||||
|
return rec.id
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_latest_graph_external_id(cls, project_id: str) -> Optional[str]:
|
||||||
|
from .db_models import GraphModel
|
||||||
|
from sqlalchemy import select
|
||||||
|
with get_session() as db:
|
||||||
|
stmt = select(GraphModel).where(GraphModel.project_id == project_id).order_by(GraphModel.created_at.desc())
|
||||||
|
rec = db.execute(stmt).scalars().first()
|
||||||
|
return rec.external_id if rec else None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def complete_graph_record(cls, project_id: str, node_count: int, edge_count: int) -> None:
|
||||||
|
from .db_models import GraphModel
|
||||||
|
from sqlalchemy import select
|
||||||
|
with get_session() as db:
|
||||||
|
stmt = select(GraphModel).where(GraphModel.project_id == project_id).order_by(GraphModel.created_at.desc())
|
||||||
|
rec = db.execute(stmt).scalars().first()
|
||||||
|
if rec:
|
||||||
|
rec.status = "ready"
|
||||||
|
rec.node_count = node_count
|
||||||
|
rec.edge_count = edge_count
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _to_dict(cls, proj: "ProjectModel") -> Dict[str, Any]:
|
||||||
|
from .db_models import GraphModel, OntologyModel
|
||||||
|
from sqlalchemy import select
|
||||||
|
graph_external_id = None
|
||||||
|
ontology = None
|
||||||
|
with get_session() as db2:
|
||||||
|
graph_rec = db2.execute(
|
||||||
|
select(GraphModel).where(GraphModel.project_id == proj.id).order_by(GraphModel.created_at.desc())
|
||||||
|
).scalars().first()
|
||||||
|
ont_rec = db2.execute(
|
||||||
|
select(OntologyModel).where(OntologyModel.project_id == proj.id).order_by(OntologyModel.version.desc())
|
||||||
|
).scalars().first()
|
||||||
|
if graph_rec:
|
||||||
|
graph_external_id = graph_rec.external_id
|
||||||
|
if ont_rec:
|
||||||
|
ontology = {"entity_types": ont_rec.entity_types or [], "edge_types": ont_rec.edge_types or []}
|
||||||
return {
|
return {
|
||||||
"id": proj.id,
|
"id": proj.id,
|
||||||
"project_id": proj.id, # compatibilitat amb codi existent
|
"project_id": proj.id,
|
||||||
"name": proj.name,
|
"name": proj.name,
|
||||||
"status": proj.status,
|
"status": proj.status,
|
||||||
"analysis_summary": proj.analysis_summary,
|
"analysis_summary": proj.analysis_summary,
|
||||||
|
|
@ -163,11 +267,10 @@ class ProjectManager:
|
||||||
"active_task_id": proj.active_task_id,
|
"active_task_id": proj.active_task_id,
|
||||||
"created_at": proj.created_at.isoformat(),
|
"created_at": proj.created_at.isoformat(),
|
||||||
"updated_at": proj.updated_at.isoformat(),
|
"updated_at": proj.updated_at.isoformat(),
|
||||||
# Camps llegits del model antic — ara buits per compatibilitat
|
|
||||||
"files": [],
|
"files": [],
|
||||||
"total_text_length": 0,
|
"total_text_length": 0,
|
||||||
"ontology": None,
|
"ontology": ontology,
|
||||||
"graph_id": None,
|
"graph_id": graph_external_id,
|
||||||
"graph_build_task_id": None,
|
"graph_build_task_id": None,
|
||||||
"error": None,
|
"error": None,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue