Skip to content

fhirpy client example #4

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ A collection of examples on top of Aidbox FHIR platform
- [Aidbox Notify via Custom Resources](aidbox-notify-via-custom-resources/)
- [Topic-Based Subscription to Kafka](aidbox-subscriptions-to-kafka/)
- [SMART App Launch with Aidbox and Keycloak](smart-app-launch/)
- [Aidbox Python Client](aidbox-with-python-sdk/)

## Documentation

Expand Down
14 changes: 14 additions & 0 deletions aidbox-with-python-sdk/.env.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
AIDBOX_LICENSE=

AIDBOX_DEV_MODE=true
AIDBOX_FHIR_VERSION=4.0.1
AIDBOX_FHIR_SCHEMA_VALIDATION=true
AIDBOX_FHIR_PACKAGES=hl7.fhir.r4.core#4.0.1

AIDBOX_CLIENT_SECRET=secret
AIDBOX_CLIENT_ID=root
AIDBOX_ADMIN_PASSWORD=password

BOX_AUTH_KEYS_SECRET=auth-key-secret
BOX_AUTH_KEYS_PRIVATE="-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEApbUYGNmCz1P8G0j/FFOjx1d5GNssJ/jj6xasSwTIbjjt6FtY\nCDw8o7hayOc/u8aUqXCGhK3JD2T9gtKv9/rV30w4YzmHhA8OOuLJE7tfh/PJA4Hn\n4i2JJ30BuoZ7rPTlTRGdc1FS3XFdmBQtnplEkJ7y8qbdrVme3Kbtn+BR1BdtgwSy\nbpNH2yqh3bb6PwpgNSMH7BIkBWL4A6QDpaFf1/9jSNE1vO25ssLC+bhFQNWLYriu\n+HogzEf9NWIrR2W29mI1QiA7wqvEuhg1yx38ylWD8GhCGL6+2QLKBYgp7DIGv6Uo\nTnqcVISatdQ51lVcCPmU6L1BhmcXVti6dWBI+wIDAQABAoIBAFKMOcJbTKpKvLq8\n7PErz1lFDpreyArrlmKsy0ydx9j8vCt1oY+MrmqisnsFk/7PaIxV9XUP+6qTFSUA\nHtAKYVOZLTfk10jmlSCpjCCrxWW9AISiSKkoJPyKbfuE9gRNhRMU9NoXB5Av4r+Z\nQbaRxJHE1OMjVCgAjr592786qJjd+shhY8ZLchrxctpBj6/4T2Rd4Q8ltyEV3hiy\noYaFVp9g332bFw7jZSuxgedZojNO6xPvbparTAgVDDwKB+CVUhuZ5EXWwemRvwoc\nYZM1UKPgtCqBZwm2GRv7s6XzJKBAZEMxcL7hS0RfijCe4MJcZlUCoM43Tf5XqDlT\nMmoXnPECgYEA4dkY/uqDLjJep5+4imRbceotxV2CZoJRQ0D85Ewu3tm9zdXhqL4p\n3XAOcNnqj7xBP3qkb/cXZumwdAIZns4kO1kw5hVQLX+xwMAJuravxp8sYJkx3CLO\noaOPNnlhGRv35fg4ZnoHHMO2C0wUmtSqsi6vE1EObYsIIFil58pI0NECgYEAu9SL\ne6AUCI/sdDlrTXQ8fdW8XSSJYPhZHqAvOAZfkeG4uuA2Qzxe8yUSES7z5V29futl\nWU7x+FWfqzkjh8qerviydAEFxVOpZ99ih9VB9dAwz3nX3OCoz3EUFmQGtTMxQmbo\nfW9sT4E6R7Hpa5jKnYvixk6u4p3aoEaZI4KeUAsCgYEA2OC3hiQBcN1h1Com9o7E\n2bF93qebT4EZNDI2J62Y3NvPztfy6S4j2cd/tpMtEnY/WgwV2Ic5a9RBZEWYAM4I\nMQ3HTUtuQSL8uRIwxaIlTeEQpnq2TKUINGRyZGdO/OPEvIwO7SmFpvOx30tiBgTv\nHkiCS1RtPHhkh1tZhirUneECgYAxNmARVQDKuYLXdM/jbEgJJD4FHXSNHqSi/I9C\nm5DgtQZkmCg/d4rdI+JW9Dlc6DGlFmHog2GskiqSfxcLFhB7gZeoAziS2fexynqT\nYlG06QZQ5fij24z/RP5hW3XSdgY7AqF5c/8p2Y7+h+PDmDXGD4esM6NoprlIcxbe\nkfOOvwKBgQCoOpkW+OWnxPLawmG/gv8+s5CsfOPUpURwAjltSXz9LXvsJmWQPQVG\np4sKEOJidYyt24YrIHi9/UEqRi+uuRQ4zCuXS6UjXftjAarPIPGkL/1S6B1Z91zg\nE5C0rXOvAlrvK09p4HGXLrwQxjrWt8R7rPvaD2yqVKLP4liFj8RMdg==\n-----END RSA PRIVATE KEY-----\n"
BOX_AUTH_KEYS_PUBLIC="-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApbUYGNmCz1P8G0j/FFOj\nx1d5GNssJ/jj6xasSwTIbjjt6FtYCDw8o7hayOc/u8aUqXCGhK3JD2T9gtKv9/rV\n30w4YzmHhA8OOuLJE7tfh/PJA4Hn4i2JJ30BuoZ7rPTlTRGdc1FS3XFdmBQtnplE\nkJ7y8qbdrVme3Kbtn+BR1BdtgwSybpNH2yqh3bb6PwpgNSMH7BIkBWL4A6QDpaFf\n1/9jSNE1vO25ssLC+bhFQNWLYriu+HogzEf9NWIrR2W29mI1QiA7wqvEuhg1yx38\nylWD8GhCGL6+2QLKBYgp7DIGv6UoTnqcVISatdQ51lVcCPmU6L1BhmcXVti6dWBI\n+wIDAQAB\n-----END PUBLIC KEY-----\n"
4 changes: 4 additions & 0 deletions aidbox-with-python-sdk/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.env
tmp/
.ruff_cache
*.pyc
17 changes: 17 additions & 0 deletions aidbox-with-python-sdk/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
.PHONY: format-fix lint

all: format-fix lint

format-fix:
ruff format

lint:
ruff check

mypy:
mypy --strict *.py

pyright:
pyright *.py

type-check: mypy pyright
225 changes: 225 additions & 0 deletions aidbox-with-python-sdk/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
# Python SDK Example


This example shows how to use the [fhir-py client](https://github.com/beda-software/fhir-py "fhir-py") with an Aidbox instance, demonstrating the Python SDK client in action.

In the example we demonstrate a several ways to implement import patient data from CSV file in Python. We describe it in the following steps:

1. Setup Aidbox Instance
1. Install [fhir-py](https://github.com/beda-software/fhir-py).
1. Use [fhir-py](https://github.com/beda-software/fhir-py):
1. with raw data (dictionaries and lists),
1. with manually defined dataclasses for FHIR Resources,
1. with generated dataclasses for FHIR Resources by [fhir-py-types](https://github.com/beda-software/fhir-py-types) from StructureDefinitions
1. (recommended) with generated dataclasses for FHIR Resources by [fhir-schema-codegen](https://github.com/fhir-schema/fhir-schema-codegen) from FHIR Schemas.



## Setting up Aidbox Instance

Let's run an Aidbox instance on your computer.

``` shell
cp .env.tpt .env
# manually add license to .env
docker compose up
```



## Using fhir-py Client with Raw Data

#### Installation

Create new python project and add fhir-py client to your dependencies

``` shell
poetry add fhirpy@2.0.15
```

*Note that this example uses `SyncFHIRClient`. However, you can also use `AsyncFHIRClient``. For more details, refer to the [fhir-py's documentation](https://github.com/beda-software/fhir-py/README.md)*

#### Example

This is a basic example demonstrating the creation of a new patient. Refer to the example files in each section for more detailed examples.

``` python
from fhirpy import SyncFHIRClient

# create FHIR client instance
client = SyncFHIRClient(
url="http://localhost:8888/",
authorization="Basic cm9vdDpzZWNyZXQ="
)

# create patient resource
patient = client.resource(
"Patient",
name=[
{
"given": ["Sam"],
"family": "Brown",
}
],
gender="male",
)

# save patient resource
patient.save()
```

See full CSV import example: [fhirpy_raw_data.py](fhirpy_raw_data.py)



## Option 1. Write Custom Data Models for fhir-py Client

Fhir-py client allows you to write and use [custom data models](https://github.com/beda-software/fhir-py?tab=readme-ov-file#data-models). The following example demonstrates how custom data models can be defined and used in your project.

This option is offered primarily for educational purposes, to showcase possibilities and improve general understanding. However, we discourage using this method for manually defining data models, as it is both error-prone and cumbersome.

#### Requirements

Data model classes need to meet two requirements:

- Be iterable (implemented by inheritance from `dict`).
- Implement `ResourceProtocol` interface (defined in fhir-py).

Usage dataclasses for FHIR Resource representation allow programmers to use autocomplete and type checking. This approach allows user to have full control (including custom validations and hiding unused resource elements) but requires to write a lot of boilerplate code.

#### Example


``` python
from dataclasses import dataclass, field, asdict
from typing import Optional

from fhirpy import SyncFHIRClient
from fhirpy.base.resource_protocol import ResourceProtocol

# create FHIR client instance
client = SyncFHIRClient(
url="http://localhost:8888/",
authorization="Basic cm9vdDpzZWNyZXQ="
dump_resource=asdict
)

@dataclass
class Base(ResourceProtocol, dict):
id: Optional[str] = None
meta: Optional[Meta] = None

@dataclass
class Patient(Base):
resourceType: str = "Patient"
active: Optional[bool] = None
gender: Optional[str] = None

# create patient resource
patient = Patient(active=True, gender="unknown")

# save patient resource
client.save(patient)
```

See full CSV import example: [fhirpy_custom_models.py](fhirpy_custom_models.py)



## Option 2. Using fhir-py-types

In this example, we demonstrate how to use data models generated by [fhir-py-types generator](https://github.com/beda-software/fhir-py-types "fhir-py-types"), which simplifies working with data models by providing ready-to-use, pre-defined structures aligned with FHIR specifications.

The simplest option to get pre-defined data models is install ready-to-use `fhirpy-types-r4b` or `fhirpy-types-r5` data model sets from PyPi.

#### Installation

``` shell
poetry add fhirpy-types-r4b@0.1.1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And we need to explain how to generate dataclasses. Ideally, it will be nice to have similar structure for that and next section.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I got it right you mean generating data models with fhirpy-types. But the idea here is that you do not have to generate them manually, since there are pre-baked packages in PyPi.

```

#### Example

``` python
from pydantic import BaseModel
from fhirpy import SyncFHIRClient
from fhirpy_types_r4b import HumanName, Patient

# create FHIR client instance
client = SyncFHIRClient(
url="http://localhost:8888/",
authorization="Basic cm9vdDpzZWNyZXQ=",
dump_resource=BaseModel.model_dump
)

# create patient resource
patient = Patient(
name=[
HumanName(
given=["Sam"],
family="Brown")
],
gender="unknown"
)

# save patient resource
client.save(patient)
```

See CSV import example: [fhirpy_fhirtypes.py](fhirpy_fhirtypes.py)

## Option 3. Using Types Generated by fhir-schema-codegen

Another option for obtaining ready-to-use data models aligned with FHIR specification is to use data models generated by [fhir-schema-codegen](https://github.com/fhir-schema/fhir-schema-codegen).

At the first step you'll need to install generator

```shell
# install fhir-schema-codegen
npm install -g @fhirschema/codegen
```

Then use it for generating python classes.

```sh
# generate FHIR data models
fscg generate --generator python --output generated --package hl7.fhir.r4.core:4.0.1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we also generate classes for our custom resources? E.g. AidboxSubscriptionTopic?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean extending example with custom resources to highlight the advantages of the fhir-schema-codegen over the fhirpy-types package? I think it's mainly a question for fhir-schema-codegen. We could add this later, as soon as it becomes possible to generate custom resources from Aidbox instance.

The same applies to other features: I propose enhancing this example gradually, in line with the evolution of fhir-schema-codegen.

```

After running the generation command, you'll see a new `generated` directory that contains the data models.

#### Example


``` python
from typing import List, Optional

from pydantic import BaseModel
from fhirpy import SyncFHIRClient

from utils import read_csv_as_dict, now
from generated.patient import Patient, HumanName

# create FHIR client instance
client = SyncFHIRClient(
url="http://localhost:8888/",
authorization="Basic cm9vdDpzZWNyZXQ=",
dump_resource=BaseModel.model_dump
)

# create patient resource
patient = Patient(
name=[
HumanName(
given=["Sam"],
family="Brown"
)
],
gender="unknown"
)

# save patient resource
client.create(patient)
```

See full CSV import example: [fhirpy_fhir_schema_codegen.py](fhirpy_fhir_schema_codegen.py)
4 changes: 4 additions & 0 deletions aidbox-with-python-sdk/data/patients.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
given,family,gender,weight
John,Doe,male,83
Alice,Doe,female,63
Charly,Doe,male,22
44 changes: 44 additions & 0 deletions aidbox-with-python-sdk/docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
volumes:
subs-pgdata:
name: subs-pgdata
subs-kafka-data:
name: subs-kafka-data
services:

aidbox-db:
image: healthsamurai/aidboxdb:16.1
pull_policy: always
ports:
- "5438:5432"
volumes:
- "subs-pgdata:/data:delegated"
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: aidbox

aidbox:
image: healthsamurai/aidboxone:edge
pull_policy: always
depends_on: ["aidbox-db"]
volumes:
- "./tmp:/tmp"
ports:
- "8888:8888"
environment:
PGPORT: 5432
PGHOST: aidbox-db
PGHOSTPORT: 5438
PGUSER: postgres
PGPASSWORD: postgres
PGDATABASE: aidbox
AIDBOX_BASE_URL: http://localhost:8888
AIDBOX_PORT: 8888
env_file:
- .env
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8888/health"]
interval: 30s
timeout: 10s
retries: 5
start_period: 30s
Loading