From bd1b7dd0a687a9bbda9f5c460aafa32646d54ec6 Mon Sep 17 00:00:00 2001 From: Vadim Laletin Date: Sat, 28 Mar 2026 17:07:45 +0100 Subject: [PATCH 1/5] Handle BaseFHIRError in handlers --- aidbox_python_sdk/handlers.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/aidbox_python_sdk/handlers.py b/aidbox_python_sdk/handlers.py index 227ec25..0104f81 100644 --- a/aidbox_python_sdk/handlers.py +++ b/aidbox_python_sdk/handlers.py @@ -3,7 +3,7 @@ from typing import Any from aiohttp import web -from fhirpy.base.exceptions import OperationOutcome +from fhirpy.base.exceptions import OperationOutcome, BaseFHIRError from . import app_keys as ak @@ -48,6 +48,8 @@ async def operation(request: web.Request, data: dict[str, Any]): return result except OperationOutcome as exc: return web.json_response(exc.resource, status=422) + except BaseFHIRError as exc: + return web.json_response(str(exc), status=422) TYPES = { From d78978ef4d5dcebc25cb1f5a81f8218eaf08d159 Mon Sep 17 00:00:00 2001 From: Vadim Laletin Date: Sat, 28 Mar 2026 17:12:02 +0100 Subject: [PATCH 2/5] Handle JSON and non-JSON BaseFHIRError --- aidbox_python_sdk/handlers.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/aidbox_python_sdk/handlers.py b/aidbox_python_sdk/handlers.py index 0104f81..607d369 100644 --- a/aidbox_python_sdk/handlers.py +++ b/aidbox_python_sdk/handlers.py @@ -49,7 +49,11 @@ async def operation(request: web.Request, data: dict[str, Any]): except OperationOutcome as exc: return web.json_response(exc.resource, status=422) except BaseFHIRError as exc: - return web.json_response(str(exc), status=422) + try: + payload = json.loads(str(exc)) + return web.json_response(payload, status=422) + except (json.JSONDecodeError, TypeError): + return web.Response(text=str(exc), status=422, content_type='text/plain') TYPES = { From 846fd72cab00b1ab2639ffe1fae849c08f3d3ceb Mon Sep 17 00:00:00 2001 From: Vadim Laletin Date: Sat, 28 Mar 2026 17:12:27 +0100 Subject: [PATCH 3/5] Bump to 0.2.1 --- aidbox_python_sdk/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aidbox_python_sdk/__init__.py b/aidbox_python_sdk/__init__.py index 3e3bd5c..f0e91f8 100644 --- a/aidbox_python_sdk/__init__.py +++ b/aidbox_python_sdk/__init__.py @@ -1,5 +1,5 @@ __title__ = "aidbox-python-sdk" -__version__ = "0.2.0" +__version__ = "0.2.1" __author__ = "beda.software" __license__ = "None" __copyright__ = "Copyright 2024 beda.software" From 4aa0fc8198fb61fda46d2d1a49f890f82d685ffe Mon Sep 17 00:00:00 2001 From: Vadim Laletin Date: Sat, 28 Mar 2026 17:46:34 +0100 Subject: [PATCH 4/5] Add tests --- aidbox_python_sdk/handlers.py | 13 +++++----- main.py | 45 +++++++++++++++++++++++++++++++++-- tests/test_sdk.py | 21 +++++++++++++++- 3 files changed, 70 insertions(+), 9 deletions(-) diff --git a/aidbox_python_sdk/handlers.py b/aidbox_python_sdk/handlers.py index 607d369..9e23cfe 100644 --- a/aidbox_python_sdk/handlers.py +++ b/aidbox_python_sdk/handlers.py @@ -1,9 +1,10 @@ import asyncio +import json import logging from typing import Any from aiohttp import web -from fhirpy.base.exceptions import OperationOutcome, BaseFHIRError +from fhirpy.base.exceptions import BaseFHIRError, OperationOutcome from . import app_keys as ak @@ -53,7 +54,7 @@ async def operation(request: web.Request, data: dict[str, Any]): payload = json.loads(str(exc)) return web.json_response(payload, status=422) except (json.JSONDecodeError, TypeError): - return web.Response(text=str(exc), status=422, content_type='text/plain') + return web.Response(text=str(exc), status=422, content_type="text/plain") TYPES = { @@ -65,10 +66,10 @@ async def operation(request: web.Request, data: dict[str, Any]): @routes.post("/aidbox") async def dispatch(request): logger.debug("Dispatch new request %s %s", request.method, request.url) - json = await request.json() - if "type" in json and json["type"] in TYPES: - logger.debug("Dispatch to `%s` handler", json["type"]) - return await TYPES[json["type"]](request, json) + data = await request.json() + if "type" in data and data["type"] in TYPES: + logger.debug("Dispatch to `%s` handler", data["type"]) + return await TYPES[data["type"]](request, data) req = { "method": request.method, "url": str(request.url), diff --git a/main.py b/main.py index 62ab231..d3202fb 100644 --- a/main.py +++ b/main.py @@ -1,9 +1,11 @@ import asyncio +import json import logging from datetime import datetime import coloredlogs from aiohttp import web +from fhirpy.base.exceptions import BaseFHIRError, OperationOutcome from sqlalchemy.sql.expression import select from aidbox_python_sdk.db import DBProxy @@ -119,7 +121,7 @@ async def daily_patient_report(operation, request): GET /Patient/$weekly-report GET /Patient/$daily-report """ - patients = request.app["client"].resources("Patient") + patients = request["app"]["client"].resources("Patient") async for p in patients: logging.debug(p.serialize()) logging.debug("`daily_patient_report` operation handler") @@ -133,7 +135,7 @@ async def get_app_ids(db: DBProxy): return await db.alchemy(select(app.c.id)) -@routes.get("/db_tests") +@routes.get("/db-tests") async def db_tests(request): db = request.app["db"] @@ -151,3 +153,42 @@ async def db_tests(request): ) async def observation_custom_op(operation, request): return {"message": "Observation custom operation response"} + + +@sdk.operation( + ["POST"], + ["$operation-outcome-test"], +) +async def operation_outcome_test_op(operation, request): + raise OperationOutcome(reason="test reason") + + +@sdk.operation( + ["POST"], + ["$base-fhir-error-json-test"], +) +async def base_fhir_error_json_test_op(operation, request): + raise BaseFHIRError( + json.dumps( + { + "resourceType": "OperationOutcome", + "id": "not-found", + "text": {"status": "generated", "div": "Resource Patient/id not found"}, + "issue": [ + { + "severity": "fatal", + "code": "not-found", + "diagnostics": "Resource Patient/id not found", + } + ], + } + ) + ) + + +@sdk.operation( + ["POST"], + ["$base-fhir-error-text-test"], +) +async def base_fhir_error_text_test_op(operation, request): + raise BaseFHIRError("plain") diff --git a/tests/test_sdk.py b/tests/test_sdk.py index 9bb83ef..fc01a35 100644 --- a/tests/test_sdk.py +++ b/tests/test_sdk.py @@ -4,6 +4,7 @@ import pytest from fhirpathpy import evaluate +from fhirpy.base.exceptions import OperationOutcome import main from aidbox_python_sdk.db import DBProxy @@ -182,10 +183,28 @@ async def test_aidbox_db_fixture(client, aidbox_db: DBProxy, safe_db): """ Test that aidbox_db fixture works with isolated DB Proxy from app's instance """ - response = await client.get("/db_tests") + response = await client.get("/db-tests") assert response.status == 200 json = await response.json() assert json == [{"id": "app-test"}] app_ids = await main.get_app_ids(aidbox_db) assert app_ids == [{"id": "app-test"}] + + +async def test_operation_base_fhir_error_json_test_op(aidbox_client): + with pytest.raises(OperationOutcome) as exc: + await aidbox_client.execute("/$base-fhir-error-json-test") + assert exc.value.resource.get("issue")[0].get("diagnostics") == "Resource Patient/id not found" + + +async def test_operation_base_fhir_error_text_test_op(aidbox_client): + with pytest.raises(OperationOutcome) as exc: + await aidbox_client.execute("/$base-fhir-error-text-test") + assert exc.value.resource.get("issue")[0].get("diagnostics") == "plain" + + +async def test_operation_outcome_test_op(aidbox_client): + with pytest.raises(OperationOutcome) as exc: + await aidbox_client.execute("/$operation-outcome-test") + assert exc.value.resource.get("issue")[0].get("diagnostics") == "test reason" From da8a29a3cc6521de409c0729e41f53cfd38f0401 Mon Sep 17 00:00:00 2001 From: Vadim Laletin Date: Sat, 28 Mar 2026 17:47:31 +0100 Subject: [PATCH 5/5] Update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8747b00..fd54b58 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.2.1 + +- Handle BaseFHIRError exceptions from fhir-py in operation handlers + ## 0.2.0 - Make pytest_plugin confiugurable via `aidbox_create_app` pytest settings for create_app function