FHIR REST
fyron.fhir.rest is the API-facing FHIR client for bounded searches, pagination, row-wise cohort lookups, FHIRPath-style extraction, and server write-back. Use it when your data source is a live FHIR endpoint and you want inspectable pandas.DataFrame outputs rather than opaque client objects.
When To Use It
- query resources such as
Patient,Observation,Condition,Procedure, orImagingStudy, - page through search results with explicit limits,
- run row-wise queries from a cohort table,
- flatten or project returned resources into analysis columns,
- create/update resources or submit reviewed transaction bundles.
Use FHIR SQL when the same data is already available in a relational warehouse.
Core Concepts
| Concept | Meaning in Fyron |
|---|---|
resource_type | The FHIR resource name, such as Patient, Observation, Condition, or ImagingStudy. |
params | Ordinary FHIR search parameters sent to the server, for example _count, code, subject, or date. |
max_pages | Safety limit for Bundle pagination. Use a small value during exploration and -1 only for deliberate full extracts. |
fhir_paths | Explicit field projection from nested FHIR JSON into DataFrame columns. |
| FHIRPath preset | Reusable list of (column_name, fhir_path) pairs for common resources. |
process_function | Custom Python function for resource-to-row transformation when simple paths are not enough. |
column_map | Mapping from FHIR search parameter names to columns in an input cohort table. |
deduplicate_by | Column or columns used to remove repeated resources after partitioned extraction. |
Most read functions return pandas.DataFrame outputs. Write functions return FHIRWriteResult objects with request status and server response information.
API Contract
| Topic | Contract |
|---|---|
| Input shape | FHIR resource type plus explicit search parameters; row-wise helpers also accept a cohort DataFrame. |
| Required columns | search_df_from and related helpers require the columns referenced by column_map. |
| Return shape | Read helpers return pandas.DataFrame; lower-level fetch helpers return FHIR bundles/resources; write helpers return FHIRWriteResult. |
| Saved artifacts | Optional CSV/JSON outputs are caller-managed; CLI workflows can write result tables and logs. |
| Failure modes | Missing mapped columns, authentication errors, server errors, invalid FHIRPath extraction, pagination limits, or write-back rejection. |
Research Decision: Extraction Safety
| Decision | Practical guidance |
|---|---|
| Pagination | Keep max_pages low during exploration; remove or expand only for planned extracts. |
| Partitioning | Use search_df_partitioned for broad date ranges or servers with result caps. |
| Deduplication | Set deduplicate_by when overlapping date windows can return the same resource. |
| FHIRPath | Use fhir_paths for explicit columns instead of ad hoc nested JSON parsing. |
| Write-back | Treat create/update/transaction calls as reviewed outputs, not exploration steps. |
Required Inputs
| Input | Needed for | Notes |
|---|---|---|
base_url | all REST queries | May come from FHIR_BASE_URL. |
resource_type | searches | FHIR resource name, e.g. Observation. |
| query parameters | filtered searches | Use ordinary FHIR search params. |
Optional Inputs
| Input | Use |
|---|---|
auth, basic_auth, bearer_token | authenticated servers |
max_pages | safe development limits |
fhir_paths | explicit field projection |
get_fhir_path_preset(...) | reusable default projections for common resources |
process_function | custom resource-to-row logic |
cache_ttl_seconds | repeat query caching |
Minimal Example
from fyron import FHIRRestClient
client = FHIRRestClient("https://hapi.fhir.org/baseR4")
patients = client.search_df(
"Patient",
params={"_count": 25, "_sort": "_id"},
max_pages=1,
)search_df is the right starting point when one FHIR query should become one DataFrame. Keep _count and max_pages explicit so the extract is reproducible.
Clinical Example
observations = client.search_df_from(
cohort,
resource_type="Observation",
column_map={"subject": "patient_id"},
params={"code": "http://loinc.org|718-7"},
fhir_paths=[
("observation_id", "id"),
("patient_ref", "subject.reference"),
("value", "valueQuantity.value"),
("unit", "valueQuantity.unit"),
],
max_pages=1,
)search_df_from is for row-wise cohort lookups. In this example, each cohort row contributes a subject search parameter from its patient_id column. This is useful when you already have a reviewed cohort and want to retrieve related resources.
If extracted Condition or Procedure resources contain ICD-10, OPS, medication, or local code strings, use DataFrame Operations to derive broad code indicators or date intervals before cohort modeling.
FHIRPath Extraction
FHIRPath extraction is the most readable way to turn nested FHIR JSON into a stable analysis table when the desired values are direct resource fields. In Fyron, fhir_paths accepts either plain path strings or named tuples:
fhir_paths=[
("condition_id", "id"),
("patient_reference", "subject.reference"),
("diagnosis_date", "recordedDate"),
("icd_code", "code.coding.code"),
]Named tuples are preferred because the first value becomes the DataFrame column name. This keeps downstream cohort joins, Table 1 creation, and model feature tables readable.
FHIRPath results follow a simple table contract:
| FHIRPath result | DataFrame value |
|---|---|
| no value | missing value |
| one value | scalar |
| multiple values | list |
Use FHIRPath for direct projections. Use process_function when you need logic such as choosing one coding system from many, exploding components into separate rows, unit conversion, extension parsing, or local phenotype rules.
FHIRPath Extraction Presets
Fyron includes copyable defaults for common FHIR R4 resources. They are intended as starting points, not as hidden clinical assumptions. Review the resulting columns against your server profile and remove or add paths before freezing an analysis.
from fyron import FHIRRestClient, get_fhir_path_preset
client = FHIRRestClient("https://fhir.example.org/r4")
paths = get_fhir_path_preset("Observation")
paths.append(("local_lab_flag", "extension.where(url = 'https://example.org/fhir/lab-flag').valueCode"))
labs = client.search_df(
"Observation",
params={"code": "http://loinc.org|718-7", "_count": 500},
max_pages=2,
fhir_paths=paths,
)Available presets:
| Resource | Typical use | Important columns |
|---|---|---|
Patient | demographics and patient identifiers | patient_id, identifier_value, gender, birth_date, death fields |
Encounter | visit timelines and encounter context | encounter_id, patient_reference, class_code, period_start, period_end |
Condition | diagnoses and clinical phenotypes | condition_id, patient_reference, code, recorded_date, onset_datetime |
Observation | labs, vitals, scores, components | observation_id, patient_reference, code, effective_datetime, value, unit |
Procedure | operations and interventions | procedure_id, patient_reference, code, performed date/period |
DiagnosticReport | reports and linked observations | report_id, patient_reference, code, issued, result_reference, conclusion |
ImagingStudy | imaging cohorts and DICOM joins | study_instance_uid, series_instance_uid, series_modality, series_description |
MedicationStatement | medication exposure tables | medication code/display, effective period, assertion date |
ServiceRequest | orders and requested procedures | request id, patient, code, authored date, requester |
Specimen | biospecimen timelines | specimen id, patient, collection date/period, body site |
DocumentReference | clinical notes and document artifacts | document id, patient, type, date, content URL/title |
The raw preset dictionary is also available when you want to inspect or document it:
from fyron.fhir import FHIR_PATH_PRESETS
FHIR_PATH_PRESETS["ImagingStudy"]Preset Examples By Resource
Use Patient for the first cohort identity table:
patients = client.search_df(
"Patient",
params={"_count": 500, "_sort": "_id"},
max_pages=1,
fhir_paths=get_fhir_path_preset("Patient"),
)Use Condition to build diagnosis cohorts:
conditions = client.search_df_partitioned(
"Condition",
partition_param="recorded-date",
start="2020-01-01",
end="2026-01-01",
params={"code": "http://hl7.org/fhir/sid/icd-10|C34", "_count": 500},
fhir_paths=get_fhir_path_preset("Condition"),
deduplicate_by="condition_id",
)Use ImagingStudy when you need StudyInstanceUID and SeriesInstanceUID for DICOM download or BOA merges:
imaging = client.search_df_from(
cohort,
resource_type="ImagingStudy",
column_map={"subject": "patient_id"},
param_prefix={"subject": "Patient/"},
fhir_paths=get_fhir_path_preset("ImagingStudy"),
include_column_map_columns=True,
max_pages=1,
)For high-stakes analyses, save the exact paths beside the extracted CSV as provenance. This makes it possible to reproduce why a column exists and which FHIR field populated it.
Function Guide
| Function | Use it when | Output |
|---|---|---|
search_df | One FHIR search should become one DataFrame. | DataFrame or dict of DataFrames when a processing function splits outputs. |
search_df_from | A cohort table should drive one query per row. | Combined result DataFrame with optional source-row metadata. |
search_df_partitioned | A broad search may hit server result caps. | Merged DataFrame with partition trace columns. |
get_fhir_path_preset | You want a reviewed starting projection for a common resource. | list of (column_name, fhir_path) pairs. |
query_df / query_df_from | You need compatibility aliases for older code. | Same as search aliases. |
build_partition_params | You want to preview date windows before running. | list of parameter dictionaries. |
create_resource / update_resource | You need single-resource write-back. | FHIRWriteResult. |
submit_bundle | You need transaction-bundle write-back. | FHIRWriteResult. |
Parameter Notes
| Parameter | Practical guidance |
|---|---|
params | Values are passed as FHIR search params. Use server-supported names from the CapabilityStatement. |
_count | Controls server page size, not total rows. Combine with max_pages during development. |
max_pages | Use 1 for smoke tests; use -1 only when you intentionally want all pages. |
fhir_paths | Prefer named tuples like ("diagnosis_date", "recordedDate") so output columns are readable. |
process_function | Use when values require custom logic, multiple rows per resource, or more robust coding extraction. |
include_source_columns | Use row-wise queries to preserve cohort identifiers beside extracted FHIR data. |
Sequenced Search For Large Result Sets
Some FHIR servers enforce hard result caps, commonly around 10,000 resources. When a query reaches that cap, ordinary Bundle next links may stop even though more matching resources exist. This is a server-side search behavior; the remedy is to split the search into smaller, non-overlapping windows and run those windows one after another.
Fyron calls this partitioned search in the API. Conceptually, it is a sequenced FHIR REST extraction: build date windows, query each window, merge the rows, and optionally de-duplicate by the resource ID.
Use search_df_partitioned when a resource supports a date-like search parameter such as recorded-date, date, authored, issued, started, or _lastUpdated.
Minimal Sequenced Example
from fyron import FHIRRestClient
client = FHIRRestClient("https://fhir.example.org/r4")
conditions = client.search_df_partitioned(
"Condition",
partition_param="recorded-date",
start="2020-01-01",
end="2024-01-01",
frequency="YS",
params={"_count": 500, "code": "C34,C34.9"},
max_pages_per_partition=-1,
)This creates yearly windows:
recorded-date=ge2020-01-01&recorded-date=lt2021-01-01
recorded-date=ge2021-01-01&recorded-date=lt2022-01-01
recorded-date=ge2022-01-01&recorded-date=lt2023-01-01
recorded-date=ge2023-01-01&recorded-date=lt2024-01-01Clinical Condition Extraction
conditions = client.search_df_partitioned(
"Condition",
partition_param="recorded-date",
start="2018-01-01",
end="2026-01-01",
frequency="YS",
params={
"_count": 500,
"identifier": "https://example.org/system|",
"code": "C34,C34.0,C34.1,C34.2,C34.3,C34.4,C34.5,C34.8,C34.9",
},
fhir_paths=[
("condition_id", "id"),
("patient_reference", "subject.reference"),
("encounter_reference", "encounter.reference"),
("diagnosis_date", "recordedDate"),
("icd_code", "code.coding[0].code"),
],
deduplicate_by="condition_id",
show_progress=True,
)Partitioned search adds _sort=_id by default when no sort is provided, uses valid repeated FHIR date bounds like recorded-date=ge2020-01-01&recorded-date=lt2021-01-01, and adds _fyron_partition_start / _fyron_partition_end columns for traceability.
Monthly Or Fixed-Day Windows
Use monthly windows when yearly windows still hit the server cap:
monthly_observations = client.search_df_partitioned(
"Observation",
partition_param="date",
start="2023-01-01",
end="2024-01-01",
frequency="MS",
params={"_count": 500, "code": "http://loinc.org|718-7"},
deduplicate_by="id",
)Use fixed-day windows when you want predictable partition size:
weekly_reports = client.search_df_partitioned(
"DiagnosticReport",
partition_param="issued",
start="2023-01-01",
end="2024-01-01",
window_days=7,
params={"_count": 200},
)De-Duplication
Use deduplicate_by when partitions may overlap or when a server returns the same resource in adjacent windows because of date precision:
conditions = client.search_df_partitioned(
"Condition",
partition_param="recorded-date",
start="2018-01-01",
end="2026-01-01",
frequency="YS",
params={"code": "C34,C34.9"},
fhir_paths=[
("condition_id", "id"),
("patient_reference", "subject.reference"),
("diagnosis_date", "recordedDate"),
],
deduplicate_by="condition_id",
)Parameter Reference
| Parameter | Required | Meaning |
|---|---|---|
resource_type | yes | FHIR resource type, e.g. Condition, Observation, DiagnosticReport. |
partition_param | yes | Date-like FHIR search parameter used to split the query. |
start, end | yes | Inclusive lower bound and exclusive upper bound for the overall search. |
params | no | Ordinary FHIR search parameters reused for every partition. |
frequency | no | Pandas-style frequency such as YS for yearly or MS for monthly windows. |
window_days | no | Fixed-size windows in days; overrides frequency when set. |
max_pages_per_partition | no | Page limit for each partition. Use -1 for all pages within every window. |
deduplicate_by | no | Column name or columns used to drop duplicate rows after merging. |
include_partition_columns | no | Adds _fyron_partition_start and _fyron_partition_end; default is True. |
parallel_fetch | no | Fetch partition windows in parallel when the client has multiple processes and suitable auth. |
auto_sort | no | Adds _sort=_id by default for stable pagination. |
Preview Windows Before Running
Preview the query windows before running a large extraction:
from fyron.fhir import build_partition_params
windows = build_partition_params(
{"_count": 500, "code": "C34,C34.9"},
partition_param="recorded-date",
start="2018-01-01",
end="2026-01-01",
frequency="YS",
)The output is a list of ordinary parameter dictionaries. The partition bounds are represented internally as repeated FHIR parameters so Fyron sends a valid query string instead of a comma-joined date expression.
Choosing The Right Partition Parameter
| Resource | Typical parameter | Notes |
|---|---|---|
Condition | recorded-date | Good for diagnosis/cohort extraction when supported by the server. |
Observation | date | Often maps to effective time. |
DiagnosticReport | issued or date | Server support varies; inspect the CapabilityStatement. |
Procedure | date | Useful for operation/procedure cohorts. |
ImagingStudy | started | Useful for imaging cohort discovery. |
| Any resource | _lastUpdated | Good for incremental synchronization, not always equivalent to clinical event time. |
When Not To Use It
Do not use partitioning as a substitute for correct clinical filters. It is a transport strategy for large result sets. If every monthly or weekly partition still reaches the server cap, narrow the clinical query, use a smaller window_days, or ask the FHIR server administrator whether the endpoint supports asynchronous bulk export.
CLI Workflow
fyron fhir-rest \
--base-url "$FHIR_BASE_URL" \
--resource Observation \
--input cohort.csv \
--column-map subject=patient_id \
--param 'code=http://loinc.org|718-7' \
--max-pages 1 \
--output observations.csvFunction Table
| Function | Required | Optional | Returns |
|---|---|---|---|
FHIRRestClient(...) | base_url or FHIR_BASE_URL | auth, timeout, cache, logging | client |
search_df(resource_type, ...) | resource type | params, max pages | DataFrame |
search_df_partitioned(...) | resource type, partition param, start, end | frequency, window days, de-duplication | DataFrame |
search_df_from(df, ...) | input table, resource type, column map | params, paths, metadata | DataFrame |
query_df(...) / query_df_from(...) | same as search aliases | compatibility names for existing code | |
build_partition_params(...) | params, partition param, start, end | frequency, window days | list of parameter dicts |
create_resource(resource, ...) | FHIR resource dict | resource type override | FHIRWriteResult |
update_resource(resource, ...) | FHIR resource dict | id/type override | FHIRWriteResult |
submit_bundle(bundle) | transaction bundle | none | FHIRWriteResult |
Return Values
| Output | Meaning |
|---|---|
| DataFrame rows | Usually one row per returned resource, unless a custom processor emits multiple rows. |
| FHIR path columns | Values projected from nested FHIR JSON; missing paths become missing table values. |
_fyron_partition_start, _fyron_partition_end | Trace columns added by partitioned search when enabled. |
FHIRWriteResult | Write-back status, response payload, and request context for review/logging. |
Common Pitfalls
- Keep
max_pagesduring development so exploratory queries do not pull a whole server. - If a server caps broad searches, partition by date instead of relying on endless pagination.
- FHIR references often need
Patient/<id>rather than a bare ID; inspect your server behavior. collectionbundles are for local review; submittransactionbundles to servers.- Fyron validates structure lightly. Production write-back should still use server/profile validation.
Related Modules
- FHIR for the overview.
- FHIR Mapping for CSV/DataFrame-to-FHIR bundles.
- Core I/O for
.env, table, Teable, and S3 helpers.