Pyrofile
FHIR validation built for Python pipelines.
Validate FHIR R4 JSON resources, Bundles, folders, or live server search results. Pyrofile goes beyond JSON schema checks: it enforces structure, profiles, terminology, references, Bundle rules, and FHIRPath invariants, then writes reproducible evidence you can compare across runs.
The current official HL7 fhir-test-cases matrix run evaluates 165 supported R4 JSON cases: 152 have the same pass/fail result as the reference validator (92.1%), including 61/61 profile-module cases. These are selected-case agreement metrics, not a claim of complete validator parity. Validation evidence output inspired by MedVertical Records.
Why Pyrofile
| Deterministic reports | Every run gets a runId, issue fingerprints, config snapshot, and content hash |
| Evidence on disk | JSON report, OperationOutcome, CI summary, and manifest under evidence/<run-id>/ |
| Drift detection | Compare two runs for new, resolved, and changed validation issues |
| Profile-aware | Load StructureDefinitions from local files, folders, .tgz packages, or remote URLs |
| Automation-ready | Machine-readable --agent-output, rule catalog, and structured logs to stderr |
[!NOTE] Pyrofile is a working validator with real validation depth covering structure, profiles, terminology, extensions, references, and Bundle semantics. See HL7 parity for current coverage and Current limitations for known gaps.
Features
Validation inputs
- Single resource file, Bundle, folder of JSON files, or resources fetched from a FHIR server
- Incremental folder validation with
--changed-from(only re-validate changed files, keep reference context)
Structure and datatypes
- JSON validity,
resourceType, cardinality, choice elements, unknown elements - Primitive and complex datatype checks for common R4 clinical resources
- Contained resources, modifier extensions, and extension shape validation
Profiles and packages
- Enforced profiles from config and
meta.profile - FHIR NPM package resolution into a local cache (
package-fetch) - Snapshot + differential overlay for effective StructureDefinition elements
- Profile cardinality, fixed/pattern values, bindings, invariants, slicing (value/pattern/exists discriminators), and extension definitions
Terminology and references
- Local CodeSystem / ValueSet membership from packages (
terminology.mode:off,local,strict) - Reference resolution across contained resources, Bundle
fullUrl, relative, absolute, and conditional references
Project rules and conformance
- Configurable custom rules (identifier systems, local reference resolution, Bundle resource types, and more)
- Conformance fixtures asserting PASS/WARN/FAIL plus expected issues or OperationOutcome-shaped expectations
Output
- Console summary, JSON report, OperationOutcome-compatible JSON, CI summary text
--agent-outputfor a single JSON object with top issues, rule hints, and evidence pathcompareandexport-evidencecommands for drift workflows
Installation
Requirements: Python 3.11+
After the first release under the new name, install Pyrofile from PyPI:
pip install pyrofile-validator
Until that release is published, install the current version directly from GitHub:
pip install "pyrofile-validator @ git+https://github.com/N3RDMJ/pyrofile.git"
For development, clone the repository and use uv:
git clone https://github.com/N3RDMJ/pyrofile.git
cd pyrofile
uv sync
This creates .venv, installs locked dependencies from uv.lock, and installs Pyrofile in editable mode with dev tools (pytest, mypy, build).
Migrating from pyfhircheck
Version 0.3.0 adopts the Pyrofile name across every public integration surface:
| Before | Now |
|---|---|
Distribution: pyfhircheck |
pyrofile-validator |
CLI: pyfhircheck |
pyrofile |
Python imports: pyfhircheck.* |
pyrofile_validator.* |
Environment variables: PYFHIRCHECK_* |
PYROFILE_* |
Cache directory: .pyfhircheck/ |
.pyrofile/ |
Evidence schemas: pyfhircheck.* |
pyrofile.* |
Configuration example: examples/pyfhircheck.json |
examples/pyrofile.json |
This is an intentional breaking rename; update imports, commands, environment variables, and cached paths together.
[!TIP] Without uv, use
pip install -e ".[dev]"— the project stays compatible with standard PEP 517 tooling.
Build a wheel locally:
uv sync
uv run python -m build
Quick start
# Validate a resource
pyrofile file examples/valid-patient.json
# Validate with config (profiles, terminology, custom rules)
pyrofile file examples/valid-patient.json -c examples/pyrofile.json
# Fail CI on validation errors with structured outputs
pyrofile folder path/to/resources -c pyrofile.json \
--json-output report.json \
--ci-summary-output ci-summary.txt
Exit codes
| Code | Meaning |
|---|---|
0 |
Validation passed, or warnings below ciFailureThreshold |
1 |
Validation failed (errors, or warnings when threshold is warning) |
2 |
Config, evidence, package, or runtime error |
CLI reference
| Command | Description |
|---|---|
file <path> |
Validate one FHIR JSON resource |
bundle <path> |
Validate one Bundle resource |
folder <path> |
Validate all *.json files in a directory |
scan <url> |
Validate resources returned by configured searches (not a server conformance claim) |
backend install|status|doctor |
Manage the optional pinned HL7 Validator backend |
validate-config |
Check a config file without validating resources |
package-fetch |
Resolve configured FHIR packages into the local cache |
conformance <path> |
Run expected PASS/WARN/FAIL fixture cases |
compare <before> <after> |
Diff two evidence runs |
export-evidence <run> <dest> |
Copy an evidence run to another directory |
rules |
Print the machine-readable validation rule catalog |
explain <code> |
Explain a validation rule code |
Common options (on file, bundle, folder, scan)
-c, --config PATH Validator config JSON
--json-output PATH Write full validation report JSON
--operation-outcome-output PATH Write OperationOutcome-compatible JSON
--ci-summary-output PATH Write one-line CI summary
--agent-output Single machine-readable JSON object on stdout
--max-issues N Limit issues in console/agent output
--fail-fast Show only the first issue
--changed-from RUN Validate only files changed since a prior evidence run
--log-level LEVEL Structured logs to stderr (DEBUG|INFO|WARNING|ERROR)
--backend native|hl7 Explicit validation backend (no fallback)
Examples
pyrofile file examples/invalid-patient.json --json-output report.json
pyrofile bundle examples/bundle.json -c examples/pyrofile.json
pyrofile scan https://hapi.fhir.org/baseR4 -c examples/pyrofile.json
pyrofile package-fetch -c examples/pyrofile.json
pyrofile conformance examples/conformance
pyrofile compare evidence/run-a evidence/run-b --fail-on-new-errors
pyrofile explain datatype.invalid --json
Python library
Validate resources returned by a live FHIR R4 API with bounded pagination, retry handling, and explicit scan-completeness metadata:
import os
from pyrofile_validator import ApiScanOptions, scan_api
report = scan_api(
"https://example.org/fhir",
options=ApiScanOptions(
resource_types=("Patient", "Observation"),
search_params={"Patient": {"active": True}},
auth=lambda: {"Authorization": f"Bearer {os.environ['FHIR_TOKEN']}"},
timeout_seconds=20,
max_pages=25,
max_resources=2_000,
),
)
if not report.api_scan or not report.api_scan.complete:
print(f"Incomplete scan: {report.api_scan.stop_reason if report.api_scan else 'unknown'}")
for issue in report.errors:
print(issue.code, issue.path, issue.message)
When resource_types is omitted, pyrofile discovers the server's advertised resource types from /metadata. It accepts FHIR R4 servers, follows same-origin Bundle.link[relation=next] URLs, retries transient HTTP failures, and records redacted request diagnostics in report.api_scan. complete means only that configured searches were fully covered; it never asserts server conformance.
Run explicit API scenarios with typed builders or a versioned JSON pack:
from pyrofile_validator import ApiTestOptions, load_builtin_api_test_plan, run_api_tests
api_report = run_api_tests(
"https://example.org/fhir",
load_builtin_api_test_plan("base-r4"),
options=ApiTestOptions(
inputs={"resourceType": "Patient", "resourceId": "p1", "versionId": "1"}
),
)
print(api_report.status, api_report.passed, api_report.failed, api_report.skipped)
Built-in packs are base-r4 and read-only isik3-basismodul. Missing identifiers produce SKIP, never PASS. Requests are read-only unless the scenario declares mutating=True and runtime execution also sets allow_mutation=True.
Validate an in-memory resource with the lower-level validator:
from pyrofile_validator import Validator, ValidationReport
from pyrofile_validator.config import ValidatorConfig
config = ValidatorConfig.load("pyrofile.json")
validator = Validator(config)
patient = {
"resourceType": "Patient",
"id": "example",
"gender": "female",
}
report: ValidationReport = validator.validate_resource(patient)
print(report.status.value) # PASS | WARN | FAIL
print(len(report.errors)) # error count
print(report.to_dict()["runId"]) # correlation / evidence id
Public exports also include ApiScanSummary, ApiScanStopReason, RequestDiagnostic, typed exceptions (ConfigError, PackageError, EvidenceError, …), and ValidationIssue.
Configuration
See examples/pyrofile.json for a working config.
| Field | Purpose |
|---|---|
fhirVersion |
FHIR version (4.0.1 / R4) |
backend |
native (default) or explicit hl7 |
packages |
NPM package id + version to resolve before validation |
packageCacheDir |
Local cache for resolved .tgz packages |
localPackagePaths |
Local StructureDefinition JSON, folders, or .tgz files |
remotePackageSources |
Remote .tgz package URLs |
profiles |
Enforced profile URLs per resource type |
terminology.mode |
off, local, or strict |
terminology.endpoint |
Explicit remote FHIR terminology base URL |
terminology.bearerTokenEnv |
Environment variable containing a bearer token |
ciFailureThreshold |
Fail CI on error (default) or warning |
customRules |
Project-specific rule settings |
evidenceOutputDir |
Where validation evidence is persisted |
scanResourceTypes |
Resource types covered by scan |
Load config from a dict in code:
config = ValidatorConfig.load_dict({"fhirVersion": "4.0.1", "terminology": {"mode": "local"}})
Evidence and drift
Every validation run writes a directory under evidence/<run-id>/:
evidence/<run-id>/
├── manifest.json # run metadata and file index
├── report.json # full validation report
├── operation-outcome.json # OperationOutcome-compatible issues
├── ci-summary.txt # one-line PASS/FAIL summary
├── config.json # config snapshot used for the run
└── inputs.json # input file content hashes
Compare two runs:
pyrofile compare evidence/run-a evidence/run-b --fail-on-new-errors
The diff reports new errors, resolved issues, severity changes, and config/profile/terminology drift.
CI integration
pyrofile folder fhir-resources -c pyrofile.json \
--json-output validation-report.json \
--operation-outcome-output operation-outcome.json \
--ci-summary-output validation-summary.txt
Use exit code 1 to fail the pipeline when validation errors are present. Set "ciFailureThreshold": "warning" in config if warnings should also fail CI.
[!TIP] Pair
--changed-fromwith evidence from a previous run to validate only modified files while keeping unchanged resources available for reference resolution.
Agent and automation output
For LLM agents and CI parsers, use --agent-output to emit a single JSON object (pyrofile.agent-output.v1) with status, truncated top issues (including rule hints and fingerprints), and the evidence path.
pyrofile file patient.json --agent-output --max-issues 5
pyrofile rules # machine-readable rule catalog
pyrofile explain profile.required --json
Observability
Structured JSON logs are written to stderr (stdout stays clean for --agent-output and piped JSON).
export PYROFILE_LOG_LEVEL=INFO # default: WARNING
export PYROFILE_LOG_FORMAT=json # or console
pyrofile file patient.json --log-level INFO
Logs include correlation IDs, run timing, package download retries, and validation summaries.
Conformance fixtures
Fixture files assert expected validation outcomes. Minimal case:
{
"expectedStatus": "PASS",
"resource": {"resourceType": "Patient", "id": "p1", "gender": "female"}
}
Issue-level expectations:
{
"expectedStatus": "FAIL",
"expectedIssues": [
{"severity": "error", "code": "datatype.invalid", "path": "Patient.id"}
],
"resource": {"resourceType": "Patient", "id": "bad id"}
}
expectedOperationOutcome.issue is also accepted for OperationOutcome-compatible matching. Run fixtures with:
pyrofile conformance examples/conformance
Development
uv sync
uv run pytest tests/ -v
uv run mypy src/pyrofile_validator/
uv run python -m build
GitHub Actions CI runs lint, type check, tests, and build on every push and PR. See .github/workflows/ci.yml.
To publish a release, configure PyPI trusted publishing for the pypi GitHub environment, update pyrofile_validator.__version__, and publish a GitHub release tagged with either the version (for example 0.3.0) or a v prefix (v0.3.0). The publish workflow verifies the tag, runs the test suite, builds the distributions, and publishes them without a long-lived API token.
HL7 parity
pyrofile is tested against the official HL7 fhir-test-cases conformance suite. Each test case includes a FHIR resource and the expected HAPI validator outcome.
| Metric | Value |
|---|---|
| Manifest cases recorded | 972 |
| Supported R4 JSON cases evaluated | 165 |
| Matches (same pass/fail as the reference validator) | 152 |
| Mismatches | 13 |
| Explicitly skipped with a reason | 807 |
| Profile-module matches | 61/61 |
| Selected-case pass/fail agreement | 92.1% |
Run the parity suite yourself:
from pathlib import Path
from pyrofile_validator.parity.hl7_runner import run_hl7_test_cases, format_hl7_report
report = run_hl7_test_cases(Path("/tmp/fhir-test-cases"), Path(".pyrofile/packages"))
print(format_hl7_report(report))
The percentage is selected-case pass/fail agreement, not feature coverage or full validator parity. Coverage matrices keep every manifest case visible as evaluated or skipped with a reason and also compare normalized severity, category, and location.
Optional HL7 backend
The native R4 backend remains the default. Install the pinned, checksum-verified 6.10.0 Java artifact explicitly before selecting it:
pyrofile backend install
pyrofile backend doctor
pyrofile file patient.json --backend hl7
Validation never downloads the JAR and never falls back between backends.
Current limitations
[!WARNING] Native validation remains R4 JSON only. Differential snapshot, slicing/reslicing, FHIRPath, narrative, and terminology semantics continue to be measured against the pinned HL7 backend; XML resource input and R4B/R5 native engines are out of scope.
Roadmap
See docs/parity-roadmap.md for parity gates and current status against HAPI / HL7 validator behavior.
Trademark
HL7, FHIR and the FHIR [FLAME DESIGN] are registered trademarks of Health Level Seven International and their use does not constitute endorsement by HL7.
Release files for pyrofile-validator 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pyrofile_validator-0.3.0.tar.gz | 127.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pyrofile_validator-0.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 240.4 kB
Release files / pyrofile_validator-0.3.0.tar.gz
| Download URL | pyrofile_validator-0.3.0.tar.gz |
|---|---|
| Size | 127.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
ce6597c73d3a003441f969b06b2e5194cb2f0edfcaae2674f1555eb52733d2f8
|
|
BLAKE2b-256 checksum How to use checksums |
15e81c9c586477a4279ebfea621ee680d13e06d01c3196bb5f8e2c349365417a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 9, 2026.
Transparency logRelease files / pyrofile_validator-0.3.0-py3-none-any.whl
| Download URL | pyrofile_validator-0.3.0-py3-none-any.whl |
|---|---|
| Size | 113.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b67ebcaedb09e9f7b557bb536d220f828bac52e6830718bbd993a1475da4308e
|
|
BLAKE2b-256 checksum How to use checksums |
60896f065d1f0100ef482235b22b2ba9120b5897fd1191c5c3822fc92f78b3c6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 9, 2026.
Transparency log