feat(crud): full CRUD for Relation — wire to SQLAlchemy, drop store.py dep
CI / basic-check (push) Has been cancelled

This commit is contained in:
2026-08-06 16:10:56 +00:00
parent f47443ad98
commit 76886b93d3
+86 -17
View File
@@ -1,8 +1,17 @@
from fastapi import APIRouter, HTTPException, Query import json
from datetime import datetime, timezone
from uuid import uuid4
from app.schemas import EntityResponse, ListResponse, Relation from fastapi import APIRouter, Depends, HTTPException, Query
from app.services.inventory import get_node_relations from sqlalchemy.orm import Session
from app.services.store import RELATIONS
from app.database import get_db
from app.models import NetObjectModel, RelationModel
from app.schemas import (
EntityResponse, ListResponse, Relation,
RelationCreate, RelationUpdate,
)
from app.services.inventory import _to_relation
router = APIRouter(prefix='/relations', tags=['relations']) router = APIRouter(prefix='/relations', tags=['relations'])
@@ -14,24 +23,84 @@ def list_relations(
layer: str | None = None, layer: str | None = None,
type: str | None = Query(default=None), type: str | None = Query(default=None),
min_confidence: float | None = None, min_confidence: float | None = None,
) -> ListResponse[Relation]: status: str | None = None,
items = RELATIONS db: Session = Depends(get_db),
):
q = db.query(RelationModel)
if source_id: if source_id:
items = [r for r in items if r.source_id == source_id] q = q.filter(RelationModel.source_id == source_id)
if target_id: if target_id:
items = [r for r in items if r.target_id == target_id] q = q.filter(RelationModel.target_id == target_id)
if layer: if layer:
items = [r for r in items if r.layer == layer] q = q.filter(RelationModel.layer == layer)
if type: if type:
items = [r for r in items if r.type == type] q = q.filter(RelationModel.type == type)
if min_confidence is not None: if min_confidence is not None:
items = [r for r in items if r.confidence >= min_confidence] q = q.filter(RelationModel.confidence >= min_confidence)
return ListResponse(count=len(items), items=items) if status:
q = q.filter(RelationModel.status == status)
rows = q.all()
return ListResponse(count=len(rows), items=[_to_relation(r) for r in rows])
@router.get('/{relation_id}', response_model=EntityResponse[Relation]) @router.get('/{relation_id}', response_model=EntityResponse[Relation])
def get_relation(relation_id: str) -> EntityResponse[Relation]: def get_relation(relation_id: str, db: Session = Depends(get_db)):
for item in RELATIONS: row = db.query(RelationModel).filter(RelationModel.id == relation_id).first()
if item.id == relation_id: if not row:
return EntityResponse(item=item) raise HTTPException(status_code=404, detail='Relation not found')
raise HTTPException(status_code=404, detail='Relation not found') return EntityResponse(item=_to_relation(row))
@router.post('', response_model=EntityResponse[Relation], status_code=201)
def create_relation(body: RelationCreate, db: Session = Depends(get_db)):
for fk, val in [('source_id', body.source_id), ('target_id', body.target_id)]:
if not db.query(NetObjectModel).filter(NetObjectModel.id == val).first():
raise HTTPException(status_code=422, detail=f'{fk} {val!r} not found')
now = datetime.now(timezone.utc)
row = RelationModel(
id=f'rel_{uuid4().hex[:8]}',
type=body.type,
layer=body.layer,
source_id=body.source_id,
target_id=body.target_id,
direction=body.direction,
source=body.source,
confidence=body.confidence,
status=body.status,
attributes_json=json.dumps(body.attributes),
created_at=now,
updated_at=now,
)
db.add(row)
db.commit()
db.refresh(row)
return EntityResponse(item=_to_relation(row))
@router.patch('/{relation_id}', response_model=EntityResponse[Relation])
def update_relation(relation_id: str, body: RelationUpdate, db: Session = Depends(get_db)):
row = db.query(RelationModel).filter(RelationModel.id == relation_id).first()
if not row:
raise HTTPException(status_code=404, detail='Relation not found')
data = body.model_dump(exclude_unset=True)
for fk in ('source_id', 'target_id'):
if fk in data and data[fk]:
if not db.query(NetObjectModel).filter(NetObjectModel.id == data[fk]).first():
raise HTTPException(status_code=422, detail=f'{fk} {data[fk]!r} not found')
if 'attributes' in data:
data['attributes_json'] = json.dumps(data.pop('attributes'))
for field, value in data.items():
setattr(row, field, value)
row.updated_at = datetime.now(timezone.utc)
db.commit()
db.refresh(row)
return EntityResponse(item=_to_relation(row))
@router.delete('/{relation_id}', status_code=204)
def delete_relation(relation_id: str, db: Session = Depends(get_db)):
row = db.query(RelationModel).filter(RelationModel.id == relation_id).first()
if not row:
raise HTTPException(status_code=404, detail='Relation not found')
db.delete(row)
db.commit()