Home Applications iris-fhir-python-strategy

iris-fhir-python-strategy Status Unknown

InterSystems does not provide technical support for this project. Please contact its developer for the technical assistance.
0
0 reviews
0
Awards
571
Views
0
IPM installs
2
0
Details
Releases (1)
Reviews
Issues
Articles (1)
Python hooks on IRIS FHIR Repository/Facade

What's new in this version

Initial Release

InterSystems IRIS FHIR Python Strategy

A flexible Python-based strategy for customizing InterSystems IRIS FHIR Server behavior using decorators.

Overview

This project provides a bridge between the high-performance InterSystems IRIS FHIR Server and Python. It allows developers to customize FHIR operations (Create, Read, Update, Delete, Search) and implement business logic (Consent, Validation, OAuth) using familiar Python decorators.

Features

  • Full CRUD Hook Support: Pre-processing (on_before_) and post-processing (on_after_) hooks for all interactions.
  • Pythonic API: Use decorators like @fhir.on_before_create("Patient") to register handlers.
  • Consent Management: Implement fine-grained consent rules.
  • Custom Operations: Easily add $operations in Python.
  • Validation: Custom resource and bundle validation.
  • OAuth Customization: Hooks for token introspection and user context.

Prerequisites

Quick Start

  1. Clone the repository

    git clone https://github.com/grongierisc/iris-fhir-python-strategy.git
    cd iris-fhir-python-strategy
    
  2. Start the containers

    docker-compose up -d
    
  3. Verify Installation
    Access the FHIR metadata endpoint:

    curl http://localhost:8083/fhir/r4/metadata
    

Usage Guide

Defining Custom Logic

  1. Open examples/custom_decorators.py (or create your own module).
  2. Import the fhir registry from fhir_decorators.
  3. Decorate your functions to register them as handlers.

Detailed Examples

1. Validation Logic

Validate resources before they are saved to the database.

from fhir_decorators import fhir

@fhir.on_validate_resource("Patient")
def validate_patient(resource, is_in_transaction):
"""
Ensure specific rules for Patient resources.
Raises ValueError to reject the resource with a 400 Bad Request.
"""
# Rule: Check if 'identifier' exists
if "identifier" not in resource:
raise ValueError("Patient must have at least one identifier")

# Rule: Check for forbidden names
for name in resource.get("name", []):
    if name.get("family") == "Forbidden":
        raise ValueError("This family name is not allowed")

@fhir.on_validate_bundle
def validate_bundle(bundle, fhir_version):
"""
Apply rules to the entire bundle.
"""
if bundle.get("type") == "transaction":
if len(bundle.get("entry", [])) > 100:
raise ValueError("Transaction bundle too large (max 100 entries)")

2. Pre-Processing Hooks (Modification)

Modify the incoming resource or metadata before the server processes it.

@fhir.on_before_create("Observation")
def enrich_observation(service, request, body, timeout):
    """
    Automatically add a tag to all new Observations.
    """
    meta = body.setdefault("meta", {})
    tags = meta.setdefault("tag", [])
    tags.append({
        "system": "http://my-hospital.org/tags",
        "code": "auto-generated",
        "display": "Auto Generated"
    })

3. Post-Processing Hooks (Masking/Filtering)

Modify or filter the response after the database operation but before sending it to the client.

@fhir.on_after_read("Patient")
def mask_patient_data(resource):
    """
    Mask sensitive fields for non-admin users.
    Returns:
        True: Return the resource (modified or not).
        False: Hide the resource (client receives 404).
    """
    # Assuming user context is stored globally or passed
    user_role = "user" # Replace with actual context retrieval logic
if user_role != "admin":
    # Remove telecom info
    if "telecom" in resource:
        del resource["telecom"]
    
    # Obfuscate birth date
    if "birthDate" in resource:
        resource["birthDate"] = "1900-01-01"
        
return True

4. Custom Operations ($operation)

Implement custom FHIR operations using Python functions.

@fhir.operation(name="echo", scope="Instance", resource_type="Patient")
def echo_patient_operation(name, scope, body, service, request, response):
    """
    Implements POST /Patient/{id}/$echo
    """
    # Logic: Just reflect the input body and operation details
    response_payload = {
        "resourceType": "Parameters",
        "parameter": [
            {"name": "operation", "valueString": name},
            {"name": "received_body", "resource": body}
        ]
    }
# Set the response payload
# Note: 'response' is the IRIS response object wrapper
response.Json = response_payload
return response

5. Search Filtering (Row-Level Security)

Intercept search results to enforce fine-grained access control.

@fhir.on_after_search("Patient")
def filter_search_results(result_set, resource_type):
    """
    Iterate through search results and remove restricted items.
    'result_set' is an iris.HS.FHIRServer.Util.SearchResult object.
    """
    # Iterate over the result set
    result_set._SetIterator(0)
    while result_set._Next():
        # Get resource content or ID
        resource_id = result_set._Get("ResourceId")
    # Example validation logic
    if resource_id.startswith("restricted-"):
        # Mark this row as deleted so it is excluded from the Bundle
        result_set.MarkAsDeleted()
        result_set._SaveRow()

6. Customizing Capability Statement

Remove unsupported resources or add documentation.

@fhir.on_capability_statement
def customize_metadata(capability_statement):
    """
    Remove 'Account' resource from the metadata.
    """
    rest_def = capability_statement['rest'][0]
    resources = rest_def['resource']
# Filter out Account
rest_def['resource'] = [r for r in resources if r['type'] != 'Account']

return capability_statement

Available Decorators

Execution Pipeline

For any given FHIR request, decorators execute in the following sequence:

  1. @fhir.on_before_request: Global pre-processing (Logs, Auth).
  2. @fhir.on_before_create (or read/update/delete/search): Interaction-specific pre-processing.
  3. Database Operation: The core FHIR server processing (Saving/Retrieving).
  4. @fhir.on_after_create (or read/update/delete/search): Interaction-specific post-processing.
  5. @fhir.on_after_request: Global post-processing (Cleanup).
graph LR
    A[Global Before Request] --> B[Specific Before Interaction]
    B --> C((Database))
    C --> D[Specific After Interaction]
    D --> E[Global After Request]

Interaction Hooks

These hooks allow you to intercept and modify standard FHIR interactions.

Execution Order Rule: When multiple handlers are registered for the same interaction (e.g., global, specific, and wildcard), they execute in this strict order:

  1. Global Handlers: @fhir.hook() (No arguments)
  2. Specific Resource: @fhir.hook("Patient")
  3. Wildcard Handlers: @fhir.hook("*")

Pre-Process Hooks

Runs before database operation. Signature: def handler(fhir_service, fhir_request, body, timeout):

  • @fhir.on_before_create(resource_type)
  • @fhir.on_before_read(resource_type)
  • @fhir.on_before_update(resource_type)
  • @fhir.on_before_delete(resource_type)
  • @fhir.on_before_search(resource_type)

Post-Process Hooks

Runs after database operation.

  • @fhir.on_after_create(resource_type)
    • Signature: def handler(fhir_service, fhir_request, fhir_response, body):
  • @fhir.on_after_update(resource_type)
    • Signature: def handler(fhir_service, fhir_request, fhir_response, body):
  • @fhir.on_after_delete(resource_type)
    • Signature: def handler(fhir_service, fhir_request, fhir_response, body):
  • @fhir.on_after_read(resource_type)
    • Signature: def handler(resource): -> Returns bool (False to hide/404)
  • @fhir.on_after_search(resource_type)
    • Signature: def handler(result_set, resource_type):

Note: resource_type is optional. If omitted, applies to all types.

Global Request Hooks

  • @fhir.on_before_request
    • Runs before any interaction. Useful for logging or setting up user context.
    • Triggers: Before @fhir.on_before_{interaction}.
    • Signature: def handler(fhir_service, fhir_request, body, timeout):
  • @fhir.on_after_request
    • Runs after any interaction sequence. Useful for cleanup.
    • Triggers: After @fhir.on_after_{interaction}.
    • Signature: def handler(fhir_service, fhir_request, fhir_response, body):

Capability Statement

  • @fhir.on_capability_statement
    • Customize the server’s CapabilityStatement (Metadata).
    • Signature: def handler(capability_statement): -> Returns dict

Custom Operations

  • @fhir.operation(name, scope, resource_type)
    • Implement custom FHIR operations (e.g., $diff).
    • Signature: def handler(operation_name, operation_scope, body, fhir_service, fhir_request, fhir_response):

OAuth & Security

  • @fhir.oauth_get_user_info
    • Extract user info from token.
    • Signature: def handler(username, roles): -> Returns dict
  • @fhir.oauth_get_introspection
    • Customize token introspection.
    • Signature: def handler(): -> Returns dict
  • @fhir.consent(resource_type)
    • Implement consent logic.
    • Signature: def handler(resource): -> Returns bool
  • @fhir.oauth_verify_resource_id(resource_type)
    • Verify access by Resource ID.
    • Signature: def handler(resource_type, resource_id, required_privilege): -> Returns bool
  • @fhir.oauth_verify_resource_content(resource_type)
    • Verify access by Resource Content.
    • Signature: def handler(resource_dict, required_privilege, allow_shared): -> Returns bool
  • @fhir.oauth_verify_search(resource_type)
    • Verify access for Search parameters.
    • Signature: def handler(resource_type, compartment_type, compartment_id, parameters, required_privilege): -> Returns bool
  • @fhir.oauth_verify_system_level
    • Verify system administration privileges.
    • Signature: def handler(): -> Returns bool

Validation

  • @fhir.on_validate_resource(resource_type)
    • Custom logic to validate a resource.
    • Signature: def handler(resource, is_in_transaction):
  • @fhir.on_validate_bundle
    • Custom logic to validate a bundle.
    • Signature: def handler(bundle, fhir_version):

Configuration

The strategy is configured via environment variables in docker-compose.yml:

  • FHIR_CUSTOMIZATION_MODULE: The Python module to load (default: examples.custom_decorators).
  • FHIR_CUSTOMIZATION_PATH: Path to the Python code (default: /irisdev/app/).

Architecture

  1. Interactions.cls: The ObjectScript class that intercepts FHIR requests.
  2. fhir_decorators.py: The Python registry that manages hooks.
  3. Your Module: The Python code where you define handlers.

When a request arrives (e.g., POST /Patient), IRIS calls Interactions.cls, which looks up the registered Python handler for on_before_create and executes it.

For detailed implementation of the ObjectScript to Python bridge, see src/cls/FHIR/Python/Interactions.cls and src/cls/FHIR/Python/Helper.cls.

Made with
Version
1.0.007 Jul, 2023
Ideas portal
Category
Technology Example
Works with
InterSystems IRIS for Health
First published
07 Jul, 2023
Last edited
07 Jul, 2023
Last checked by moderator
14 Nov, 2025Impossible to Test