feat(crud): full CRUD for Relation — wire to SQLAlchemy, drop store.py dep
CI / basic-check (push) Has been cancelled
CI / basic-check (push) Has been cancelled
This commit is contained in:
@@ -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()
|
||||||
|
|||||||
Reference in New Issue
Block a user