hubble-sdk-python 0.2.0__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {hubble_sdk_python-0.2.0 → hubble_sdk_python-0.3.0}/CHANGELOG.md +47 -0
- {hubble_sdk_python-0.2.0 → hubble_sdk_python-0.3.0}/PKG-INFO +45 -16
- {hubble_sdk_python-0.2.0 → hubble_sdk_python-0.3.0}/README.md +44 -15
- {hubble_sdk_python-0.2.0 → hubble_sdk_python-0.3.0}/hubble_sdk/__init__.py +24 -4
- {hubble_sdk_python-0.2.0 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/api/default/attach_client_authorization.py +32 -8
- hubble_sdk_python-0.3.0/hubble_sdk/_generated/api/default/create_consent.py +193 -0
- {hubble_sdk_python-0.2.0 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/api/default/create_consent_link.py +8 -4
- {hubble_sdk_python-0.2.0 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/api/default/export_patient_fhir.py +31 -12
- {hubble_sdk_python-0.2.0 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/api/default/export_patient_pdf.py +36 -12
- hubble_sdk_python-0.3.0/hubble_sdk/_generated/api/default/get_application_token.py +194 -0
- {hubble_sdk_python-0.2.0 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/api/default/get_patient_pdf_export.py +47 -20
- {hubble_sdk_python-0.2.0 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/api/default/list_consents.py +16 -4
- {hubble_sdk_python-0.2.0 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/api/default/read_fhir.py +48 -13
- {hubble_sdk_python-0.2.0 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/models/__init__.py +20 -0
- hubble_sdk_python-0.3.0/hubble_sdk/_generated/models/application_token_error.py +54 -0
- hubble_sdk_python-0.3.0/hubble_sdk/_generated/models/application_token_error_error.py +31 -0
- hubble_sdk_python-0.3.0/hubble_sdk/_generated/models/application_token_request.py +72 -0
- hubble_sdk_python-0.3.0/hubble_sdk/_generated/models/application_token_response.py +69 -0
- {hubble_sdk_python-0.2.0 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/models/attach_client_authorization_body.py +1 -1
- hubble_sdk_python-0.3.0/hubble_sdk/_generated/models/client_supplied_discloser.py +107 -0
- hubble_sdk_python-0.3.0/hubble_sdk/_generated/models/client_supplied_discloser_facility_type.py +28 -0
- {hubble_sdk_python-0.2.0 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/models/consent.py +19 -10
- {hubble_sdk_python-0.2.0 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/models/consent_source.py +18 -3
- hubble_sdk_python-0.3.0/hubble_sdk/_generated/models/consent_source_status.py +30 -0
- hubble_sdk_python-0.3.0/hubble_sdk/_generated/models/consent_source_type.py +28 -0
- hubble_sdk_python-0.3.0/hubble_sdk/_generated/models/consent_status.py +31 -0
- hubble_sdk_python-0.3.0/hubble_sdk/_generated/models/created_consent.py +152 -0
- {hubble_sdk_python-0.2.0 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/models/device_authorization_request.py +2 -2
- hubble_sdk_python-0.3.0/hubble_sdk/_generated/models/export_patient_fhir_prefer.py +27 -0
- {hubble_sdk_python-0.2.0 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/models/export_patient_fhir_response_202.py +1 -1
- hubble_sdk_python-0.3.0/hubble_sdk/_generated/models/export_patient_fhir_response_202_status.py +28 -0
- hubble_sdk_python-0.3.0/hubble_sdk/_generated/models/export_patient_pdf_prefer.py +27 -0
- {hubble_sdk_python-0.2.0 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/models/export_patient_pdf_response_202.py +1 -1
- hubble_sdk_python-0.3.0/hubble_sdk/_generated/models/export_patient_pdf_response_202_status.py +28 -0
- hubble_sdk_python-0.3.0/hubble_sdk/_generated/models/facility_address.py +78 -0
- hubble_sdk_python-0.3.0/hubble_sdk/_generated/models/facility_address_state.py +77 -0
- hubble_sdk_python-0.3.0/hubble_sdk/_generated/models/get_patient_pdf_export_download.py +27 -0
- {hubble_sdk_python-0.2.0 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/models/get_patient_pdf_export_response_200.py +1 -1
- hubble_sdk_python-0.3.0/hubble_sdk/_generated/models/get_patient_pdf_export_response_200_status.py +27 -0
- {hubble_sdk_python-0.2.0 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/models/o_auth_error.py +1 -1
- hubble_sdk_python-0.3.0/hubble_sdk/_generated/models/o_auth_error_error.py +35 -0
- {hubble_sdk_python-0.2.0 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/models/patient_address.py +13 -5
- hubble_sdk_python-0.3.0/hubble_sdk/_generated/models/patient_address_state.py +77 -0
- {hubble_sdk_python-0.2.0 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/models/patient_demographics.py +1 -1
- {hubble_sdk_python-0.2.0 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/models/provider_seed.py +13 -1
- {hubble_sdk_python-0.2.0 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/models/token_request.py +1 -1
- hubble_sdk_python-0.3.0/hubble_sdk/_generated/models/token_request_grant_type.py +30 -0
- {hubble_sdk_python-0.2.0 → hubble_sdk_python-0.3.0}/hubble_sdk/_hubble.py +293 -48
- hubble_sdk_python-0.3.0/hubble_sdk/_params.py +86 -0
- {hubble_sdk_python-0.2.0 → hubble_sdk_python-0.3.0}/hubble_sdk/_webhooks.py +8 -1
- hubble_sdk_python-0.3.0/hubble_sdk/py.typed +0 -0
- {hubble_sdk_python-0.2.0 → hubble_sdk_python-0.3.0}/pydoc-markdown.yml +4 -3
- {hubble_sdk_python-0.2.0 → hubble_sdk_python-0.3.0}/pyproject.toml +1 -1
- hubble_sdk_python-0.3.0/templates/property_templates/enum_property.py.jinja +33 -0
- hubble_sdk_python-0.3.0/templates/str_enum.py.jinja +28 -0
- {hubble_sdk_python-0.2.0 → hubble_sdk_python-0.3.0}/tests/test_hubble.py +391 -12
- hubble_sdk_python-0.3.0/tests/test_params.py +68 -0
- {hubble_sdk_python-0.2.0 → hubble_sdk_python-0.3.0}/tests/test_webhooks.py +17 -0
- hubble_sdk_python-0.2.0/hubble_sdk/_generated/models/consent_source_status.py +0 -11
- hubble_sdk_python-0.2.0/hubble_sdk/_generated/models/consent_source_type.py +0 -9
- hubble_sdk_python-0.2.0/hubble_sdk/_generated/models/consent_status.py +0 -12
- hubble_sdk_python-0.2.0/hubble_sdk/_generated/models/export_patient_fhir_prefer.py +0 -8
- hubble_sdk_python-0.2.0/hubble_sdk/_generated/models/export_patient_fhir_response_202_status.py +0 -9
- hubble_sdk_python-0.2.0/hubble_sdk/_generated/models/export_patient_pdf_prefer.py +0 -8
- hubble_sdk_python-0.2.0/hubble_sdk/_generated/models/export_patient_pdf_response_202_status.py +0 -9
- hubble_sdk_python-0.2.0/hubble_sdk/_generated/models/get_patient_pdf_export_download.py +0 -8
- hubble_sdk_python-0.2.0/hubble_sdk/_generated/models/get_patient_pdf_export_response_200_status.py +0 -8
- hubble_sdk_python-0.2.0/hubble_sdk/_generated/models/o_auth_error_error.py +0 -16
- hubble_sdk_python-0.2.0/hubble_sdk/_generated/models/token_request_grant_type.py +0 -11
- {hubble_sdk_python-0.2.0 → hubble_sdk_python-0.3.0}/.gitignore +0 -0
- {hubble_sdk_python-0.2.0 → hubble_sdk_python-0.3.0}/LICENSE +0 -0
- {hubble_sdk_python-0.2.0 → hubble_sdk_python-0.3.0}/config.yaml +0 -0
- {hubble_sdk_python-0.2.0 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/__init__.py +0 -0
- {hubble_sdk_python-0.2.0 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/api/__init__.py +0 -0
- {hubble_sdk_python-0.2.0 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/api/default/__init__.py +0 -0
- {hubble_sdk_python-0.2.0 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/api/default/exchange_token.py +0 -0
- {hubble_sdk_python-0.2.0 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/api/default/get_consent.py +0 -0
- {hubble_sdk_python-0.2.0 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/api/default/revoke_consent.py +0 -0
- {hubble_sdk_python-0.2.0 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/client.py +0 -0
- {hubble_sdk_python-0.2.0 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/errors.py +0 -0
- {hubble_sdk_python-0.2.0 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/models/api_error.py +0 -0
- {hubble_sdk_python-0.2.0 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/models/consent_error.py +0 -0
- {hubble_sdk_python-0.2.0 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/models/consent_link_request.py +0 -0
- {hubble_sdk_python-0.2.0 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/models/consent_list.py +0 -0
- {hubble_sdk_python-0.2.0 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/models/device_authorization_response.py +0 -0
- {hubble_sdk_python-0.2.0 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/models/fhir_resource.py +0 -0
- {hubble_sdk_python-0.2.0 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/models/operation_outcome.py +0 -0
- {hubble_sdk_python-0.2.0 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/models/operation_outcome_issue_item.py +0 -0
- {hubble_sdk_python-0.2.0 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/models/token_response.py +0 -0
- {hubble_sdk_python-0.2.0 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/types.py +0 -0
|
@@ -1,5 +1,52 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.3.0](https://github.com/hubble-dot-ai/hubble-sdks/compare/hubble-sdk-python-v0.2.1...hubble-sdk-python-v0.3.0) (2026-10-27)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### ⚠ BREAKING CHANGES
|
|
7
|
+
|
|
8
|
+
* type the consent status filter as a literal ([#70](https://github.com/hubble-dot-ai/hubble-sdks/issues/70))
|
|
9
|
+
* each discloser needs an address (line1, city, state, postal_code) next to its name. The API now refuses a name-only discloser with a 400, and the TypeScript type no longer accepts one.
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* create consents without the device flow ([#66](https://github.com/hubble-dot-ai/hubble-sdks/issues/66)) ([f82bed9](https://github.com/hubble-dot-ai/hubble-sdks/commit/f82bed9d31192144cd592c25890639b1576bf2bd))
|
|
14
|
+
* read consents with an application access token ([#65](https://github.com/hubble-dot-ai/hubble-sdks/issues/65)) ([66f9703](https://github.com/hubble-dot-ai/hubble-sdks/commit/66f97031f9eef15bc39c2e5a8f6059c11c4bbf93))
|
|
15
|
+
* send facility details with each discloser when attaching an authorization ([#69](https://github.com/hubble-dot-ai/hubble-sdks/issues/69)) ([6d49b68](https://github.com/hubble-dot-ai/hubble-sdks/commit/6d49b68b49768efb5715cc7a3379ee0123ddd466))
|
|
16
|
+
* ship a py.typed marker so type checkers read the SDK's types ([#72](https://github.com/hubble-dot-ai/hubble-sdks/issues/72)) ([bf1f4b0](https://github.com/hubble-dot-ai/hubble-sdks/commit/bf1f4b0a0ecadc17d89e0eb477cbda98f9648aec))
|
|
17
|
+
* sync the spec for application tokens and POST /consents ([#64](https://github.com/hubble-dot-ai/hubble-sdks/issues/64)) ([8981d57](https://github.com/hubble-dot-ai/hubble-sdks/commit/8981d57c7dee236a1668effc477adbd77ef38ba3))
|
|
18
|
+
* type the consent status filter as a literal ([#70](https://github.com/hubble-dot-ai/hubble-sdks/issues/70)) ([fb7fd4b](https://github.com/hubble-dot-ai/hubble-sdks/commit/fb7fd4b26ba0e05874a01a307ca8e16a02000979))
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
### Bug Fixes
|
|
22
|
+
|
|
23
|
+
* harden application-token reads (dot ids, relative next links, shared token) ([#68](https://github.com/hubble-dot-ai/hubble-sdks/issues/68)) ([0d9d41e](https://github.com/hubble-dot-ai/hubble-sdks/commit/0d9d41e85f5d45f98a7cc08d7efb1ede6a1aabbd))
|
|
24
|
+
* send create_consent's body as given so a misspelt field is refused ([#73](https://github.com/hubble-dot-ai/hubble-sdks/issues/73)) ([559cf0f](https://github.com/hubble-dot-ai/hubble-sdks/commit/559cf0f7471f277f77d874f4454d5202d3a16e62))
|
|
25
|
+
* sync the consent status and error descriptions from the portal ([#62](https://github.com/hubble-dot-ai/hubble-sdks/issues/62)) ([2bd1606](https://github.com/hubble-dot-ai/hubble-sdks/commit/2bd160636fa5ccd8d5f324d0d69f29ab19ff57fc))
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
### Documentation
|
|
29
|
+
|
|
30
|
+
* keep the disclosers description in one argument entry ([#74](https://github.com/hubble-dot-ai/hubble-sdks/issues/74)) ([53e5163](https://github.com/hubble-dot-ai/hubble-sdks/commit/53e51634c5a29c0b3bd96dab06b8965f7b6cfdda))
|
|
31
|
+
|
|
32
|
+
## [0.2.1](https://github.com/hubble-dot-ai/hubble-sdks/compare/hubble-sdk-python-v0.2.0...hubble-sdk-python-v0.2.1) (2026-10-14)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
### Features
|
|
36
|
+
|
|
37
|
+
* echo provider_ref on sources from a seeded provider ([#57](https://github.com/hubble-dot-ai/hubble-sdks/issues/57)) ([dbfc576](https://github.com/hubble-dot-ai/hubble-sdks/commit/dbfc5763b2514aecbc520bd0406eeefedae1f6ac))
|
|
38
|
+
* mark Consent.error deprecated and optional ([#58](https://github.com/hubble-dot-ai/hubble-sdks/issues/58)) ([dacf748](https://github.com/hubble-dot-ai/hubble-sdks/commit/dacf7488d5d4b073b9781914d20bdd6897075c50))
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
### Bug Fixes
|
|
42
|
+
|
|
43
|
+
* accept enum values this SDK version does not know ([#56](https://github.com/hubble-dot-ai/hubble-sdks/issues/56)) ([45d69ad](https://github.com/hubble-dot-ai/hubble-sdks/commit/45d69ad44d67de470f122803e842a914b9342915))
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
### Documentation
|
|
47
|
+
|
|
48
|
+
* state the BYO attestation and who checks the authorization ([#54](https://github.com/hubble-dot-ai/hubble-sdks/issues/54)) ([7ddecdd](https://github.com/hubble-dot-ai/hubble-sdks/commit/7ddecddf1d1ffc61e92825aa202d5105c7647092))
|
|
49
|
+
|
|
3
50
|
## [0.2.0](https://github.com/hubble-dot-ai/hubble-sdks/compare/hubble-sdk-python-v0.1.0...hubble-sdk-python-v0.2.0) (2026-09-30)
|
|
4
51
|
|
|
5
52
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: hubble-sdk-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Python client for the Hubble Patient Access API.
|
|
5
5
|
Project-URL: Homepage, https://github.com/hubble-dot-ai/hubble-sdks
|
|
6
6
|
Project-URL: Repository, https://github.com/hubble-dot-ai/hubble-sdks
|
|
@@ -32,8 +32,8 @@ Description-Content-Type: text/markdown
|
|
|
32
32
|
|
|
33
33
|
# hubble-sdk-python
|
|
34
34
|
|
|
35
|
-
Python client for the Hubble Patient Access API: create patient
|
|
36
|
-
links,
|
|
35
|
+
Python client for the Hubble Patient Access API: create consents and patient
|
|
36
|
+
links, and read FHIR R4 data with your application's access token.
|
|
37
37
|
|
|
38
38
|
```bash
|
|
39
39
|
pip install hubble-sdk-python
|
|
@@ -44,24 +44,53 @@ from hubble_sdk import Hubble
|
|
|
44
44
|
|
|
45
45
|
hub = Hubble(client_id="hbl_client_prod_...", client_secret="hbl_prod_...")
|
|
46
46
|
|
|
47
|
-
# 1. Create a
|
|
47
|
+
# 1. Create a consent and send the patient its link. Optionally prefill with
|
|
48
48
|
# patient={"first_name": ...} demographics, providers=[{"name": ...}], and
|
|
49
49
|
# the optional organization your verified Hubble organization represents.
|
|
50
|
-
|
|
51
|
-
print(
|
|
52
|
-
|
|
53
|
-
# 2. Wait for the patient to authorize (polls the token endpoint for you).
|
|
54
|
-
session = hub.wait_for_authorization(link)
|
|
50
|
+
consent = hub.create_consent(patient_ref="abc123", on_behalf_of_organization_name="Acme Health")
|
|
51
|
+
print(consent.link_url) # returned only here; store it if you may resend it
|
|
55
52
|
|
|
56
|
-
#
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
export = session.export_pdf() # export.content bytes; export.sources_failed counts failed sources
|
|
53
|
+
# 2. Read the patient's FHIR data whenever you like (see below for every read).
|
|
54
|
+
summary = hub.summary(consent.id)
|
|
55
|
+
labs = hub.search(consent.id, "Observation", {"category": "laboratory"})
|
|
60
56
|
|
|
61
57
|
# Consent management
|
|
62
|
-
for
|
|
63
|
-
print(
|
|
64
|
-
hub.revoke_consent(
|
|
58
|
+
for c in hub.list_consents(status="active"):
|
|
59
|
+
print(c.id, c.patient_ref)
|
|
60
|
+
hub.revoke_consent(consent.id)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Reading with an application token
|
|
64
|
+
|
|
65
|
+
Read any consent in your environment by its id, without the device flow: while
|
|
66
|
+
the patient is still adding sources, after they finish, or after the link
|
|
67
|
+
expires. The client gets the application access token from `POST /api/v1/token`,
|
|
68
|
+
caches it and renews it for you, and names the consent on every request. One
|
|
69
|
+
`Hubble` client is safe to share across threads: they share the one token, and
|
|
70
|
+
only one thread fetches or renews it at a time.
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
obs = hub.read(consent_id, "Observation", "abc-123") # one resource
|
|
74
|
+
page = hub.search(consent_id, "Observation", {"category": "laboratory"}) # first page (a Bundle)
|
|
75
|
+
for resource in hub.iter_search(consent_id, "Observation", {"category": "laboratory"}):
|
|
76
|
+
... # every page, lazily
|
|
77
|
+
summary = hub.summary(consent_id)
|
|
78
|
+
record = hub.everything(consent_id, {"_count": 100})
|
|
79
|
+
export = hub.export_pdf(consent_id, connection=connection_id) # one source's PDF
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Reuse one `Hubble` client so its token is reused too.
|
|
83
|
+
|
|
84
|
+
### The device flow
|
|
85
|
+
|
|
86
|
+
Existing integrations can keep using the device flow, which waits for the
|
|
87
|
+
patient to finish and reads with a consent-scoped token:
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
link = hub.create_link(patient_ref="abc123")
|
|
91
|
+
print(link.verification_uri_complete)
|
|
92
|
+
session = hub.wait_for_authorization(link) # polls until the patient finishes
|
|
93
|
+
bundle = session.everything() # refreshes the consent token for you
|
|
65
94
|
```
|
|
66
95
|
|
|
67
96
|
## Receiving webhooks
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# hubble-sdk-python
|
|
2
2
|
|
|
3
|
-
Python client for the Hubble Patient Access API: create patient
|
|
4
|
-
links,
|
|
3
|
+
Python client for the Hubble Patient Access API: create consents and patient
|
|
4
|
+
links, and read FHIR R4 data with your application's access token.
|
|
5
5
|
|
|
6
6
|
```bash
|
|
7
7
|
pip install hubble-sdk-python
|
|
@@ -12,24 +12,53 @@ from hubble_sdk import Hubble
|
|
|
12
12
|
|
|
13
13
|
hub = Hubble(client_id="hbl_client_prod_...", client_secret="hbl_prod_...")
|
|
14
14
|
|
|
15
|
-
# 1. Create a
|
|
15
|
+
# 1. Create a consent and send the patient its link. Optionally prefill with
|
|
16
16
|
# patient={"first_name": ...} demographics, providers=[{"name": ...}], and
|
|
17
17
|
# the optional organization your verified Hubble organization represents.
|
|
18
|
-
|
|
19
|
-
print(
|
|
20
|
-
|
|
21
|
-
# 2. Wait for the patient to authorize (polls the token endpoint for you).
|
|
22
|
-
session = hub.wait_for_authorization(link)
|
|
18
|
+
consent = hub.create_consent(patient_ref="abc123", on_behalf_of_organization_name="Acme Health")
|
|
19
|
+
print(consent.link_url) # returned only here; store it if you may resend it
|
|
23
20
|
|
|
24
|
-
#
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
export = session.export_pdf() # export.content bytes; export.sources_failed counts failed sources
|
|
21
|
+
# 2. Read the patient's FHIR data whenever you like (see below for every read).
|
|
22
|
+
summary = hub.summary(consent.id)
|
|
23
|
+
labs = hub.search(consent.id, "Observation", {"category": "laboratory"})
|
|
28
24
|
|
|
29
25
|
# Consent management
|
|
30
|
-
for
|
|
31
|
-
print(
|
|
32
|
-
hub.revoke_consent(
|
|
26
|
+
for c in hub.list_consents(status="active"):
|
|
27
|
+
print(c.id, c.patient_ref)
|
|
28
|
+
hub.revoke_consent(consent.id)
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Reading with an application token
|
|
32
|
+
|
|
33
|
+
Read any consent in your environment by its id, without the device flow: while
|
|
34
|
+
the patient is still adding sources, after they finish, or after the link
|
|
35
|
+
expires. The client gets the application access token from `POST /api/v1/token`,
|
|
36
|
+
caches it and renews it for you, and names the consent on every request. One
|
|
37
|
+
`Hubble` client is safe to share across threads: they share the one token, and
|
|
38
|
+
only one thread fetches or renews it at a time.
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
obs = hub.read(consent_id, "Observation", "abc-123") # one resource
|
|
42
|
+
page = hub.search(consent_id, "Observation", {"category": "laboratory"}) # first page (a Bundle)
|
|
43
|
+
for resource in hub.iter_search(consent_id, "Observation", {"category": "laboratory"}):
|
|
44
|
+
... # every page, lazily
|
|
45
|
+
summary = hub.summary(consent_id)
|
|
46
|
+
record = hub.everything(consent_id, {"_count": 100})
|
|
47
|
+
export = hub.export_pdf(consent_id, connection=connection_id) # one source's PDF
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Reuse one `Hubble` client so its token is reused too.
|
|
51
|
+
|
|
52
|
+
### The device flow
|
|
53
|
+
|
|
54
|
+
Existing integrations can keep using the device flow, which waits for the
|
|
55
|
+
patient to finish and reads with a consent-scoped token:
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
link = hub.create_link(patient_ref="abc123")
|
|
59
|
+
print(link.verification_uri_complete)
|
|
60
|
+
session = hub.wait_for_authorization(link) # polls until the patient finishes
|
|
61
|
+
bundle = session.everything() # refreshes the consent token for you
|
|
33
62
|
```
|
|
34
63
|
|
|
35
64
|
## Receiving webhooks
|
|
@@ -5,10 +5,12 @@ Import the high-level client from here:
|
|
|
5
5
|
from hubble_sdk import Hubble
|
|
6
6
|
|
|
7
7
|
hub = Hubble(client_id="hbl_client_...", client_secret="hbl_prod_...")
|
|
8
|
-
|
|
9
|
-
print(
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
consent = hub.create_consent(patient_ref="abc123")
|
|
9
|
+
print(consent.link_url) # send the patient here
|
|
10
|
+
bundle = hub.everything(consent.id) # read whenever you like
|
|
11
|
+
|
|
12
|
+
The device flow (``create_link`` / ``wait_for_authorization``) still works for
|
|
13
|
+
existing integrations.
|
|
12
14
|
|
|
13
15
|
The generated low-level client lives under ``hubble_sdk._generated``
|
|
14
16
|
if you need direct access to raw operations and models.
|
|
@@ -17,6 +19,7 @@ if you need direct access to raw operations and models.
|
|
|
17
19
|
from ._generated.models import (
|
|
18
20
|
Consent,
|
|
19
21
|
ConsentStatus,
|
|
22
|
+
CreatedConsent,
|
|
20
23
|
DeviceAuthorizationResponse,
|
|
21
24
|
TokenResponse,
|
|
22
25
|
)
|
|
@@ -28,6 +31,15 @@ from ._hubble import (
|
|
|
28
31
|
PdfExport,
|
|
29
32
|
TokenSet,
|
|
30
33
|
)
|
|
34
|
+
from ._params import (
|
|
35
|
+
ConsentStatusFilter,
|
|
36
|
+
DiscloserParams,
|
|
37
|
+
FacilityAddressParams,
|
|
38
|
+
PatientAddressParams,
|
|
39
|
+
PatientParams,
|
|
40
|
+
ProviderSeedParams,
|
|
41
|
+
UsStateCode,
|
|
42
|
+
)
|
|
31
43
|
from ._webhooks import (
|
|
32
44
|
WEBHOOK_SIGNATURE_HEADER,
|
|
33
45
|
WebhookEvent,
|
|
@@ -43,6 +55,13 @@ __all__ = [
|
|
|
43
55
|
"ConsentTimeout",
|
|
44
56
|
"TokenSet",
|
|
45
57
|
"PdfExport",
|
|
58
|
+
"ConsentStatusFilter",
|
|
59
|
+
"PatientParams",
|
|
60
|
+
"PatientAddressParams",
|
|
61
|
+
"ProviderSeedParams",
|
|
62
|
+
"DiscloserParams",
|
|
63
|
+
"FacilityAddressParams",
|
|
64
|
+
"UsStateCode",
|
|
46
65
|
"construct_webhook_event",
|
|
47
66
|
"WebhookEvent",
|
|
48
67
|
"WebhookEventSource",
|
|
@@ -50,6 +69,7 @@ __all__ = [
|
|
|
50
69
|
"WEBHOOK_SIGNATURE_HEADER",
|
|
51
70
|
"Consent",
|
|
52
71
|
"ConsentStatus",
|
|
72
|
+
"CreatedConsent",
|
|
53
73
|
"DeviceAuthorizationResponse",
|
|
54
74
|
"TokenResponse",
|
|
55
75
|
]
|
|
@@ -98,8 +98,14 @@ def sync_detailed(
|
|
|
98
98
|
"""Attach a customer-supplied HIPAA authorization
|
|
99
99
|
|
|
100
100
|
Upload an already-signed HIPAA authorization for a consent and grant delegated retrieval from the
|
|
101
|
-
providers it names.
|
|
102
|
-
|
|
101
|
+
providers it names. Hubble keeps a copy of the signed authorization on file and, based on your
|
|
102
|
+
attestation, makes the consent `active` right away with no patient step. If your organization also
|
|
103
|
+
uses EHR sign-in, health plans, or the health information network, the consent stays `pending` until
|
|
104
|
+
the patient finishes the link. Requires the `byo_hipaa_authorization` entitlement on your
|
|
105
|
+
organization. Attaching an authorization is your organization's attestation that it holds a valid
|
|
106
|
+
HIPAA authorization signed by the patient, and that it covers the providers you name. Hubble does
|
|
107
|
+
not review the document or ask the patient to confirm it, so your organization is responsible for
|
|
108
|
+
checking that the authorization is correct before you attach it.
|
|
103
109
|
|
|
104
110
|
Args:
|
|
105
111
|
id (str):
|
|
@@ -134,8 +140,14 @@ def sync(
|
|
|
134
140
|
"""Attach a customer-supplied HIPAA authorization
|
|
135
141
|
|
|
136
142
|
Upload an already-signed HIPAA authorization for a consent and grant delegated retrieval from the
|
|
137
|
-
providers it names.
|
|
138
|
-
|
|
143
|
+
providers it names. Hubble keeps a copy of the signed authorization on file and, based on your
|
|
144
|
+
attestation, makes the consent `active` right away with no patient step. If your organization also
|
|
145
|
+
uses EHR sign-in, health plans, or the health information network, the consent stays `pending` until
|
|
146
|
+
the patient finishes the link. Requires the `byo_hipaa_authorization` entitlement on your
|
|
147
|
+
organization. Attaching an authorization is your organization's attestation that it holds a valid
|
|
148
|
+
HIPAA authorization signed by the patient, and that it covers the providers you name. Hubble does
|
|
149
|
+
not review the document or ask the patient to confirm it, so your organization is responsible for
|
|
150
|
+
checking that the authorization is correct before you attach it.
|
|
139
151
|
|
|
140
152
|
Args:
|
|
141
153
|
id (str):
|
|
@@ -165,8 +177,14 @@ async def asyncio_detailed(
|
|
|
165
177
|
"""Attach a customer-supplied HIPAA authorization
|
|
166
178
|
|
|
167
179
|
Upload an already-signed HIPAA authorization for a consent and grant delegated retrieval from the
|
|
168
|
-
providers it names.
|
|
169
|
-
|
|
180
|
+
providers it names. Hubble keeps a copy of the signed authorization on file and, based on your
|
|
181
|
+
attestation, makes the consent `active` right away with no patient step. If your organization also
|
|
182
|
+
uses EHR sign-in, health plans, or the health information network, the consent stays `pending` until
|
|
183
|
+
the patient finishes the link. Requires the `byo_hipaa_authorization` entitlement on your
|
|
184
|
+
organization. Attaching an authorization is your organization's attestation that it holds a valid
|
|
185
|
+
HIPAA authorization signed by the patient, and that it covers the providers you name. Hubble does
|
|
186
|
+
not review the document or ask the patient to confirm it, so your organization is responsible for
|
|
187
|
+
checking that the authorization is correct before you attach it.
|
|
170
188
|
|
|
171
189
|
Args:
|
|
172
190
|
id (str):
|
|
@@ -199,8 +217,14 @@ async def asyncio(
|
|
|
199
217
|
"""Attach a customer-supplied HIPAA authorization
|
|
200
218
|
|
|
201
219
|
Upload an already-signed HIPAA authorization for a consent and grant delegated retrieval from the
|
|
202
|
-
providers it names.
|
|
203
|
-
|
|
220
|
+
providers it names. Hubble keeps a copy of the signed authorization on file and, based on your
|
|
221
|
+
attestation, makes the consent `active` right away with no patient step. If your organization also
|
|
222
|
+
uses EHR sign-in, health plans, or the health information network, the consent stays `pending` until
|
|
223
|
+
the patient finishes the link. Requires the `byo_hipaa_authorization` entitlement on your
|
|
224
|
+
organization. Attaching an authorization is your organization's attestation that it holds a valid
|
|
225
|
+
HIPAA authorization signed by the patient, and that it covers the providers you name. Hubble does
|
|
226
|
+
not review the document or ask the patient to confirm it, so your organization is responsible for
|
|
227
|
+
checking that the authorization is correct before you attach it.
|
|
204
228
|
|
|
205
229
|
Args:
|
|
206
230
|
id (str):
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
from http import HTTPStatus
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
import httpx
|
|
5
|
+
|
|
6
|
+
from ... import errors
|
|
7
|
+
from ...client import AuthenticatedClient, Client
|
|
8
|
+
from ...models.api_error import ApiError
|
|
9
|
+
from ...models.consent_link_request import ConsentLinkRequest
|
|
10
|
+
from ...models.created_consent import CreatedConsent
|
|
11
|
+
from ...types import UNSET, Response, Unset
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _get_kwargs(
|
|
15
|
+
*,
|
|
16
|
+
body: ConsentLinkRequest | Unset = UNSET,
|
|
17
|
+
) -> dict[str, Any]:
|
|
18
|
+
headers: dict[str, Any] = {}
|
|
19
|
+
|
|
20
|
+
_kwargs: dict[str, Any] = {
|
|
21
|
+
"method": "post",
|
|
22
|
+
"url": "/api/v1/consents",
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if not isinstance(body, Unset):
|
|
26
|
+
_kwargs["json"] = body.to_dict()
|
|
27
|
+
|
|
28
|
+
headers["Content-Type"] = "application/json"
|
|
29
|
+
|
|
30
|
+
_kwargs["headers"] = headers
|
|
31
|
+
return _kwargs
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _parse_response(
|
|
35
|
+
*, client: AuthenticatedClient | Client, response: httpx.Response
|
|
36
|
+
) -> ApiError | CreatedConsent | None:
|
|
37
|
+
if response.status_code == 201:
|
|
38
|
+
response_201 = CreatedConsent.from_dict(response.json())
|
|
39
|
+
|
|
40
|
+
return response_201
|
|
41
|
+
|
|
42
|
+
if response.status_code == 400:
|
|
43
|
+
response_400 = ApiError.from_dict(response.json())
|
|
44
|
+
|
|
45
|
+
return response_400
|
|
46
|
+
|
|
47
|
+
if response.status_code == 401:
|
|
48
|
+
response_401 = ApiError.from_dict(response.json())
|
|
49
|
+
|
|
50
|
+
return response_401
|
|
51
|
+
|
|
52
|
+
if response.status_code == 403:
|
|
53
|
+
response_403 = ApiError.from_dict(response.json())
|
|
54
|
+
|
|
55
|
+
return response_403
|
|
56
|
+
|
|
57
|
+
if client.raise_on_unexpected_status:
|
|
58
|
+
raise errors.UnexpectedStatus(response.status_code, response.content)
|
|
59
|
+
else:
|
|
60
|
+
return None
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _build_response(
|
|
64
|
+
*, client: AuthenticatedClient | Client, response: httpx.Response
|
|
65
|
+
) -> Response[ApiError | CreatedConsent]:
|
|
66
|
+
return Response(
|
|
67
|
+
status_code=HTTPStatus(response.status_code),
|
|
68
|
+
content=response.content,
|
|
69
|
+
headers=response.headers,
|
|
70
|
+
parsed=_parse_response(client=client, response=response),
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def sync_detailed(
|
|
75
|
+
*,
|
|
76
|
+
client: AuthenticatedClient,
|
|
77
|
+
body: ConsentLinkRequest | Unset = UNSET,
|
|
78
|
+
) -> Response[ApiError | CreatedConsent]:
|
|
79
|
+
"""Create a consent
|
|
80
|
+
|
|
81
|
+
Creates a pending consent and its patient link, and returns the consent with `link_url`. Deliver the
|
|
82
|
+
link to the patient, then read the consent with an application access token from POST /api/v1/token
|
|
83
|
+
and `consent=<id>` on /api/v1/fhir. Every field is optional.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
body (ConsentLinkRequest | Unset):
|
|
87
|
+
|
|
88
|
+
Raises:
|
|
89
|
+
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
90
|
+
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
Response[ApiError | CreatedConsent]
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
kwargs = _get_kwargs(
|
|
97
|
+
body=body,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
response = client.get_httpx_client().request(
|
|
101
|
+
**kwargs,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
return _build_response(client=client, response=response)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def sync(
|
|
108
|
+
*,
|
|
109
|
+
client: AuthenticatedClient,
|
|
110
|
+
body: ConsentLinkRequest | Unset = UNSET,
|
|
111
|
+
) -> ApiError | CreatedConsent | None:
|
|
112
|
+
"""Create a consent
|
|
113
|
+
|
|
114
|
+
Creates a pending consent and its patient link, and returns the consent with `link_url`. Deliver the
|
|
115
|
+
link to the patient, then read the consent with an application access token from POST /api/v1/token
|
|
116
|
+
and `consent=<id>` on /api/v1/fhir. Every field is optional.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
body (ConsentLinkRequest | Unset):
|
|
120
|
+
|
|
121
|
+
Raises:
|
|
122
|
+
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
123
|
+
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
ApiError | CreatedConsent
|
|
127
|
+
"""
|
|
128
|
+
|
|
129
|
+
return sync_detailed(
|
|
130
|
+
client=client,
|
|
131
|
+
body=body,
|
|
132
|
+
).parsed
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
async def asyncio_detailed(
|
|
136
|
+
*,
|
|
137
|
+
client: AuthenticatedClient,
|
|
138
|
+
body: ConsentLinkRequest | Unset = UNSET,
|
|
139
|
+
) -> Response[ApiError | CreatedConsent]:
|
|
140
|
+
"""Create a consent
|
|
141
|
+
|
|
142
|
+
Creates a pending consent and its patient link, and returns the consent with `link_url`. Deliver the
|
|
143
|
+
link to the patient, then read the consent with an application access token from POST /api/v1/token
|
|
144
|
+
and `consent=<id>` on /api/v1/fhir. Every field is optional.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
body (ConsentLinkRequest | Unset):
|
|
148
|
+
|
|
149
|
+
Raises:
|
|
150
|
+
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
151
|
+
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
Response[ApiError | CreatedConsent]
|
|
155
|
+
"""
|
|
156
|
+
|
|
157
|
+
kwargs = _get_kwargs(
|
|
158
|
+
body=body,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
response = await client.get_async_httpx_client().request(**kwargs)
|
|
162
|
+
|
|
163
|
+
return _build_response(client=client, response=response)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
async def asyncio(
|
|
167
|
+
*,
|
|
168
|
+
client: AuthenticatedClient,
|
|
169
|
+
body: ConsentLinkRequest | Unset = UNSET,
|
|
170
|
+
) -> ApiError | CreatedConsent | None:
|
|
171
|
+
"""Create a consent
|
|
172
|
+
|
|
173
|
+
Creates a pending consent and its patient link, and returns the consent with `link_url`. Deliver the
|
|
174
|
+
link to the patient, then read the consent with an application access token from POST /api/v1/token
|
|
175
|
+
and `consent=<id>` on /api/v1/fhir. Every field is optional.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
body (ConsentLinkRequest | Unset):
|
|
179
|
+
|
|
180
|
+
Raises:
|
|
181
|
+
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
182
|
+
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
ApiError | CreatedConsent
|
|
186
|
+
"""
|
|
187
|
+
|
|
188
|
+
return (
|
|
189
|
+
await asyncio_detailed(
|
|
190
|
+
client=client,
|
|
191
|
+
body=body,
|
|
192
|
+
)
|
|
193
|
+
).parsed
|
|
@@ -77,7 +77,8 @@ def sync_detailed(
|
|
|
77
77
|
Mints a pending consent and a one-time patient link. Send the patient to
|
|
78
78
|
`verification_uri_complete`, then poll the token endpoint with `device_code` until they authorize.
|
|
79
79
|
Accepts a nested JSON body (recommended) or the flat RFC 8628 form encoding; both prefill the same
|
|
80
|
-
consent.
|
|
80
|
+
consent. To read with an application access token instead, create the consent with POST
|
|
81
|
+
/api/v1/consents, which has no device code.
|
|
81
82
|
|
|
82
83
|
Args:
|
|
83
84
|
body (ConsentLinkRequest | Unset):
|
|
@@ -112,7 +113,8 @@ def sync(
|
|
|
112
113
|
Mints a pending consent and a one-time patient link. Send the patient to
|
|
113
114
|
`verification_uri_complete`, then poll the token endpoint with `device_code` until they authorize.
|
|
114
115
|
Accepts a nested JSON body (recommended) or the flat RFC 8628 form encoding; both prefill the same
|
|
115
|
-
consent.
|
|
116
|
+
consent. To read with an application access token instead, create the consent with POST
|
|
117
|
+
/api/v1/consents, which has no device code.
|
|
116
118
|
|
|
117
119
|
Args:
|
|
118
120
|
body (ConsentLinkRequest | Unset):
|
|
@@ -142,7 +144,8 @@ async def asyncio_detailed(
|
|
|
142
144
|
Mints a pending consent and a one-time patient link. Send the patient to
|
|
143
145
|
`verification_uri_complete`, then poll the token endpoint with `device_code` until they authorize.
|
|
144
146
|
Accepts a nested JSON body (recommended) or the flat RFC 8628 form encoding; both prefill the same
|
|
145
|
-
consent.
|
|
147
|
+
consent. To read with an application access token instead, create the consent with POST
|
|
148
|
+
/api/v1/consents, which has no device code.
|
|
146
149
|
|
|
147
150
|
Args:
|
|
148
151
|
body (ConsentLinkRequest | Unset):
|
|
@@ -175,7 +178,8 @@ async def asyncio(
|
|
|
175
178
|
Mints a pending consent and a one-time patient link. Send the patient to
|
|
176
179
|
`verification_uri_complete`, then poll the token endpoint with `device_code` until they authorize.
|
|
177
180
|
Accepts a nested JSON body (recommended) or the flat RFC 8628 form encoding; both prefill the same
|
|
178
|
-
consent.
|
|
181
|
+
consent. To read with an application access token instead, create the consent with POST
|
|
182
|
+
/api/v1/consents, which has no device code.
|
|
179
183
|
|
|
180
184
|
Args:
|
|
181
185
|
body (ConsentLinkRequest | Unset):
|