Metadata-Version: 2.5
Name: hubble-sdk-python
Version: 0.2.0
Summary: Python client for the Hubble Patient Access API.
Project-URL: Homepage, https://github.com/hubble-dot-ai/hubble-sdks
Project-URL: Repository, https://github.com/hubble-dot-ai/hubble-sdks
Project-URL: Issues, https://github.com/hubble-dot-ai/hubble-sdks/issues
Author: Hubble
License-Expression: MIT
License-File: LICENSE
Keywords: health records,hubble,medical records,patient
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Healthcare Industry
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.11
Requires-Dist: attrs>=22.2.0
Requires-Dist: httpx<0.29.0,>=0.23.1
Requires-Dist: python-dateutil>=2.8.0
Provides-Extra: docs
Requires-Dist: pydoc-markdown<5,>=4.8; extra == 'docs'
Provides-Extra: test
Requires-Dist: pytest>=8.0; extra == 'test'
Requires-Dist: respx>=0.21; extra == 'test'
Description-Content-Type: text/markdown

# hubble-sdk-python

Python client for the Hubble Patient Access API: create patient authorization
links, exchange them for consent-scoped access tokens, and read FHIR R4 data.

```bash
pip install hubble-sdk-python
```

```python
from hubble_sdk import Hubble

hub = Hubble(client_id="hbl_client_prod_...", client_secret="hbl_prod_...")

# 1. Create a patient link and send the patient to it. Optionally prefill with
#    patient={"first_name": ...} demographics, providers=[{"name": ...}], and
#    the optional organization your verified Hubble organization represents.
link = hub.create_link(patient_ref="abc123", on_behalf_of_organization_name="Acme Health")
print(link.verification_uri_complete)

# 2. Wait for the patient to authorize (polls the token endpoint for you).
session = hub.wait_for_authorization(link)

# 3. Read the patient's FHIR data (Bearer token + auto-refresh handled for you).
bundle = session.everything()
observations = session.fhir("Observation", params={"category": "vital-signs"})
export = session.export_pdf()   # export.content bytes; export.sources_failed counts failed sources

# Consent management
for consent in hub.list_consents(status="active"):
    print(consent.id, consent.patient_ref)
hub.revoke_consent(session.consent_id)
```

## Receiving webhooks

Verify each delivery against your endpoint's signing secret (from the portal's
Webhooks page) and get the event back. Pass the **raw** body: a body that was
parsed and re-serialized will not verify.

```python
from hubble_sdk import WebhookSignatureError, construct_webhook_event

# e.g. Flask
@app.post("/hubble/webhooks")
def hubble_webhook():
    try:
        event = construct_webhook_event(
            request.get_data(), request.headers.get("Hubble-Signature"), WEBHOOK_SECRET
        )
    except WebhookSignatureError:
        return "bad signature", 400
    if event.type == "consent.source.available":
        print(event.consent_id, event.source.id)
    # newer event types may arrive; ignore what you don't handle
    return "", 204
```

In FastAPI, read the body with `await request.body()`. Deliveries are
at-least-once, so dedupe on `event.id`. Pass `secret` as a list to accept the old
and new secret during a rotation, and `tolerance` to change the default 5-minute
replay window.

## What the client handles for you

- **Auth per plane** — HTTP Basic (your app credentials) for consent management
  and the token endpoint; Bearer (the consent token) for FHIR reads. You never
  wire the schemes yourself.
- **Default base URL** — `https://portal.hubble.ai`. Override only with a trusted
  value; your access token is sent to it.
- **Device-flow polling** — `wait_for_authorization` honors the server's
  `interval` and `slow_down`.
- **Correct FHIR paths** — operation and sub-resource paths like
  `Patient/$everything` are sent unencoded.
- **Transparent refresh** — FHIR reads refresh and retry once on a 401.
- **FHIR responses** — reads return the raw FHIR R4 resource as a dict. For typed
  models, parse with the optional [`fhir.resources`](https://pypi.org/project/fhir.resources/)
  package: `from fhir.resources.bundle import Bundle; Bundle.model_validate(session.everything())`.

## Layout

`Hubble` / `PatientSession` and the webhook helpers are hand-written. The low-level client generated
from the OpenAPI contract with
[openapi-python-client](https://github.com/openapi-generators/openapi-python-client)
lives under `hubble_sdk._generated` (regenerated on every spec change;
never edited by hand). Reach into it only if you need raw operations. See the
repo root README for how to regenerate.
