Coverage for src/lumen/api/admin/packages.py: 88%

32 statements  

« prev     ^ index     » next       coverage.py v7.16.0, created at 2026-08-31 18:46 +0000

1""" 

2This router implements endpoints for administrative package management. 

3It allows uploading and deleting FHIR packages and triggers DB package loader routines. 

4""" 

5 

6from fastapi import APIRouter, HTTPException, UploadFile, status 

7from fastapi.responses import JSONResponse 

8 

9from lumen.admin.loader import delete_package, get_installed_packages, load_package_tgz 

10from lumen.api.deps import TenantDep 

11from lumen.db.pool import get_pool 

12 

13router = APIRouter() 

14 

15 

16@router.post("/packages", status_code=status.HTTP_201_CREATED) 

17async def upload_package( 

18 tenant_id: TenantDep, 

19 file: UploadFile, 

20) -> JSONResponse: 

21 """Upload and install a FHIR NPM package (.tgz).""" 

22 if not file.filename or not file.filename.endswith(".tgz"): 

23 raise HTTPException( 

24 status_code=status.HTTP_400_BAD_REQUEST, 

25 detail="Only .tgz package files are supported.", 

26 ) 

27 

28 file_bytes = await file.read() 

29 

30 pool = get_pool() 

31 async with pool.acquire() as conn: 

32 try: 

33 # We wrap the entire package import in a single transaction 

34 async with conn.transaction(): 

35 results = await load_package_tgz(conn, tenant_id, file_bytes) 

36 except Exception as e: 

37 raise HTTPException( 

38 status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, 

39 detail=f"Failed to process package: {e!s}", 

40 ) from e 

41 

42 return JSONResponse(content=results, status_code=status.HTTP_201_CREATED) 

43 

44 

45@router.get("/packages") 

46async def list_packages(tenant_id: TenantDep) -> JSONResponse: 

47 """List all installed FHIR packages for the current tenant.""" 

48 pool = get_pool() 

49 async with pool.acquire() as conn: 

50 packages = await get_installed_packages(conn, tenant_id) 

51 

52 return JSONResponse( 

53 content={"installed_packages": packages}, 

54 status_code=status.HTTP_200_OK, 

55 ) 

56 

57 

58@router.delete("/packages/{package_id:path}", status_code=status.HTTP_204_NO_CONTENT) 

59async def remove_package( 

60 tenant_id: TenantDep, 

61 package_id: str, 

62) -> None: 

63 """Uninstall a FHIR package and delete all its conformance resources.""" 

64 pool = get_pool() 

65 async with pool.acquire() as conn, conn.transaction(): 

66 deleted_count = await delete_package(conn, tenant_id, package_id) 

67 

68 if deleted_count == 0: 

69 raise HTTPException( 

70 status_code=status.HTTP_404_NOT_FOUND, 

71 detail=f"Package '{package_id}' not found or no resources were deleted.", 

72 )