diff --git a/CHANGELOG.md b/CHANGELOG.md index 5980f01..e33e583 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.15 + +- Add compliance_params (fhirCore, fhirUrl, fhirResource) as operation parameter + ## 0.1.14 - Fix db innitialization crash for fhirschema diff --git a/Pipfile b/Pipfile index 1b3cb03..4352153 100644 --- a/Pipfile +++ b/Pipfile @@ -20,8 +20,7 @@ autohooks-plugin-ruff = "~=23.11.0" autohooks-plugin-black = "~=23.7.0" coloredlogs = "*" ruff = "*" - -# dependencies for python 3.8 exceptiongroup = "~=1.2.1" tomli = "~=2.0.1" async-timeout = "~=4.0.3" +fhirpathpy = "==2.0.0" diff --git a/Pipfile.lock b/Pipfile.lock index b0ce2d9..01fcedc 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "efdbc2aa1548d4c58ebf4b20ef03ebc381518d2d137f66cffa5c37ef4d3c426c" + "sha256": "7924ec9def93de969de0093f33ecf2704a0787dc8007d807645b862a7abbf482" }, "pipfile-spec": 6, "requires": {}, @@ -128,6 +128,13 @@ "markers": "python_version >= '3.9'", "version": "==1.3.2" }, + "antlr4-python3-runtime": { + "hashes": [ + "sha256:909b647e1d2fc2b70180ac586df3933e38919c85f98ccc656a96cd3f25ef3916", + "sha256:fe3835eb8d33daece0e799090eda89719dbccee7aa39ef94eed3818cafa5a7e8" + ], + "version": "==4.13.2" + }, "attrs": { "hashes": [ "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", @@ -242,6 +249,15 @@ "markers": "python_version >= '3.7'", "version": "==3.4.1" }, + "fhirpathpy": { + "hashes": [ + "sha256:0f73ec9a09111ef1a79440786a9e5ee3ebbd31f3da80d26d87b82250279d5c58", + "sha256:52bf689262a8bccfdc26175e11bb9fda51495845487623a4470fc6f0c8f4cffc" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==2.0.0" + }, "fhirpy": { "hashes": [ "sha256:b95675cea7925671b3548cdc35dce3d17820fda278589fd87f46b1dbbbd33b52", @@ -606,6 +622,14 @@ "markers": "python_version >= '3.8'", "version": "==0.20.0" }, + "python-dateutil": { + "hashes": [ + "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", + "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", + "version": "==2.9.0.post0" + }, "pytz": { "hashes": [ "sha256:89dd22dca55b46eac6eda23b2d72721bf1bdfef212645d81513ef5d03038de57", @@ -621,6 +645,14 @@ "markers": "python_version >= '3.8'", "version": "==2.32.3" }, + "six": { + "hashes": [ + "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", + "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", + "version": "==1.17.0" + }, "sqlalchemy": { "hashes": [ "sha256:018ee97c558b499b58935c5a152aeabf6d36b3d55d91656abeb6d93d663c0c4c", @@ -905,6 +937,13 @@ "markers": "python_version >= '3.9'", "version": "==1.3.2" }, + "antlr4-python3-runtime": { + "hashes": [ + "sha256:909b647e1d2fc2b70180ac586df3933e38919c85f98ccc656a96cd3f25ef3916", + "sha256:fe3835eb8d33daece0e799090eda89719dbccee7aa39ef94eed3818cafa5a7e8" + ], + "version": "==4.13.2" + }, "anyio": { "hashes": [ "sha256:1d9fe889df5212298c0c0723fa20479d1b94883a2df44bd3897aa91083316f7a", @@ -1089,6 +1128,15 @@ "markers": "python_version >= '3.7'", "version": "==1.2.2" }, + "fhirpathpy": { + "hashes": [ + "sha256:0f73ec9a09111ef1a79440786a9e5ee3ebbd31f3da80d26d87b82250279d5c58", + "sha256:52bf689262a8bccfdc26175e11bb9fda51495845487623a4470fc6f0c8f4cffc" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==2.0.0" + }, "frozenlist": { "hashes": [ "sha256:000a77d6034fbad9b6bb880f7ec073027908f1b40254b5d6f26210d2dab1240e", diff --git a/aidbox_python_sdk/__init__.py b/aidbox_python_sdk/__init__.py index 4b971c5..e87600f 100644 --- a/aidbox_python_sdk/__init__.py +++ b/aidbox_python_sdk/__init__.py @@ -1,5 +1,5 @@ __title__ = "aidbox-python-sdk" -__version__ = "0.1.14" +__version__ = "0.1.15" __author__ = "beda.software" __license__ = "None" __copyright__ = "Copyright 2024 beda.software" diff --git a/aidbox_python_sdk/sdk.py b/aidbox_python_sdk/sdk.py index 0411c2e..5ad46c7 100644 --- a/aidbox_python_sdk/sdk.py +++ b/aidbox_python_sdk/sdk.py @@ -1,11 +1,13 @@ import asyncio import logging +from typing import Optional import jsonschema from fhirpy.base.exceptions import OperationOutcome from .aidboxpy import AsyncAidboxClient from .db_migrations import sdk_migrations +from .types import Compliance logger = logging.getLogger("aidbox_sdk") @@ -151,6 +153,7 @@ def operation( # noqa: PLR0913 access_policy=None, request_schema=None, timeout=None, + compliance: Optional[Compliance] = None, ): if public and access_policy is not None: raise ValueError("Operation might be public or have access policy, not both") @@ -184,6 +187,7 @@ def wrapped_func(operation, request): "method": method, "path": path, **({"timeout": timeout} if timeout else {}), + **(compliance if compliance else {}), } self._operation_handlers[operation_id] = wrapped_func if public is True: diff --git a/aidbox_python_sdk/types.py b/aidbox_python_sdk/types.py index 13c4b61..6b84813 100644 --- a/aidbox_python_sdk/types.py +++ b/aidbox_python_sdk/types.py @@ -1,8 +1,13 @@ -from typing import Any +from typing import Any, List from aiohttp import web from typing_extensions import TypedDict +class Compliance(TypedDict, total=True): + fhirUrl: str + fhirCode: str + fhirResource: List[str] + SDKOperationRequest = TypedDict( "SDKOperationRequest", {"app": web.Application, "params": dict, "route-params": dict, "headers": dict, "resource": Any}, diff --git a/docker-compose.yaml b/docker-compose.yaml index d7a7a63..a416189 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,45 +1,31 @@ version: '3.1' services: - devbox-db: - image: "healthsamurai/aidboxdb:14.5" + aidbox-db: + image: "healthsamurai/aidboxdb:16.1" + env_file: + - ./envs/db ports: - - "5434:5432" - environment: - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - POSTGRES_DB: devbox - devbox: - image: "healthsamurai/aidboxone:latest" + - 5432:5432 + healthcheck: + test: ["CMD", "pg_isready", "-U", "postgres"] + interval: 5s + timeout: 5s + retries: 10 + aidbox: + image: ${AIDBOX_PROJECT_IMAGE} + build: example/aidbox-project/ depends_on: - - devbox-db - links: - - "devbox-db:database" - ports: - - "8080:8080" + aidbox-db: + condition: service_healthy env_file: - - env_tests + - ./envs/aidbox environment: - PGHOST: database - PGDATABASE: devbox - PGPORT: 5432 - PGUSER: postgres - PGPASSWORD: postgres - AIDBOX_CONFIG: /var/config/config.edn AIDBOX_LICENSE: ${AIDBOX_LICENSE} - volumes: - - ./config:/var/config - devbox-healthcheck: - image: curlimages/curl - entrypoint: /bin/sleep 10000 - links: - - devbox - depends_on: - - devbox healthcheck: - test: curl --fail http://devbox:8080/__healthcheck || exit 1 - interval: 1s - timeout: 20s - retries: 100 + test: curl --fail http://localhost:8080/health || exit 1 + interval: 5s + timeout: 30s + retries: 50 app: build: context: . @@ -47,13 +33,10 @@ services: PYTHON_VERSION: ${PYTHON:-3.11} command: ["pipenv", "run", "pytest"] depends_on: - devbox-healthcheck: - condition: - service_healthy - links: - - devbox + aidbox: + condition: service_healthy env_file: - - env_tests + - ./envs/backend ports: - "8081:8081" volumes: diff --git a/envs/aidbox b/envs/aidbox new file mode 100644 index 0000000..c56edc5 --- /dev/null +++ b/envs/aidbox @@ -0,0 +1,19 @@ +AIDBOX_STDOUT_PRETTY=all +AIDBOX_CLIENT_ID=root +AIDBOX_CLIENT_SECRET=secret +AIDBOX_BASE_URL=http://aidbox:8080 +AIDBOX_PORT=8080 +AIDBOX_FHIR_VERSION=4.0.0 + +PGHOST=aidbox-db +PGPORT=5432 +PGDATABASE=aidbox-tests +PGUSER=postgres +PGPASSWORD=postgres + +BOX_PROJECT_GIT_TARGET__PATH=/aidbox-project +AIDBOX_ZEN_PATHS=path:package-dir:/aidbox-project +AIDBOX_ZEN_ENTRYPOINT=main/box +AIDBOX_DEV_MODE=false +AIDBOX_ZEN_DEV_MODE=false +AIDBOX_COMPLIANCE=enabled diff --git a/envs/backend b/envs/backend new file mode 100644 index 0000000..e5507b2 --- /dev/null +++ b/envs/backend @@ -0,0 +1,10 @@ +APP_INIT_CLIENT_ID=root +APP_INIT_CLIENT_SECRET=secret +APP_INIT_URL=http://aidbox:8080 + +APP_ID=backend-test +APP_SECRET=secret +APP_URL=http://backend:8081 +APP_PORT=8081 +AIO_PORT=8081 +AIO_HOST=0.0.0.0 diff --git a/envs/db b/envs/db new file mode 100644 index 0000000..24b18d5 --- /dev/null +++ b/envs/db @@ -0,0 +1,3 @@ +POSTGRES_USER=postgres +POSTGRES_PASSWORD=postgres +POSTGRES_DB=aidbox-tests \ No newline at end of file diff --git a/main.py b/main.py index 1f6e621..0c1a4e3 100644 --- a/main.py +++ b/main.py @@ -160,3 +160,15 @@ async def db_tests(request): result = await db.alchemy(statement) logging.debug("Result:\n%s", result) return web.json_response({}) + + +@sdk.operation( + ["POST"], + ["Observation", "observation-custom-op"], + compliance={ + "fhirCode": "observation-custom-op", + "fhirUrl": "http://test.com", + "fhirResource": ["Observation"], + }) +async def observation_custom_op(operation, request): + return {"message": "Observation custom operation response"} diff --git a/run_test.sh b/run_test.sh index 90dd509..c57db78 100755 --- a/run_test.sh +++ b/run_test.sh @@ -1,3 +1,16 @@ -#!/bin/bash +#!/bin/sh +if [ -f "envs/aidbox" ]; then + export `cat envs/aidbox` +fi + +if [ -z "${AIDBOX_LICENSE}" ]; then + echo "AIDBOX_LICENSE is required to run tests" + exit 1 +fi + + +docker compose -f docker-compose.yaml pull --quiet docker compose -f docker-compose.yaml up --exit-code-from app app + +exit $? diff --git a/tests/test_sdk.py b/tests/test_sdk.py index 9df704e..88ab8f1 100644 --- a/tests/test_sdk.py +++ b/tests/test_sdk.py @@ -3,10 +3,38 @@ from unittest import mock import pytest +from fhirpathpy import evaluate import main +@pytest.mark.asyncio +@pytest.mark.parametrize( + ("expression", "expected"), + [ + ( + "CapabilityStatement.rest.operation.where(definition='http://test.com').count()", + 1, + ), + ( + "CapabilityStatement.rest.operation.where(definition='http://test.com').first().name", + "observation-custom-op", + ), + ( + "CapabilityStatement.rest.resource.where(type='Observation').operation.where(definition='http://test.com').count()", + 1, + ), + ( + "CapabilityStatement.rest.resource.where(type='Observation').operation.where(definition='http://test.com').first().name", + "observation-custom-op", + ), + ], +) +async def test_operation_with_compliance_params(aidbox_client, expression, expected): + response = await aidbox_client.execute("fhir/metadata", method="GET") + assert evaluate(response, expression, {})[0] == expected + + @pytest.mark.asyncio() async def test_health_check(client): resp = await client.get("/health")