hubble-sdk-python 0.2.1__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.1 → hubble_sdk_python-0.3.0}/CHANGELOG.md +29 -0
- {hubble_sdk_python-0.2.1 → hubble_sdk_python-0.3.0}/PKG-INFO +45 -16
- {hubble_sdk_python-0.2.1 → hubble_sdk_python-0.3.0}/README.md +44 -15
- {hubble_sdk_python-0.2.1 → hubble_sdk_python-0.3.0}/hubble_sdk/__init__.py +24 -4
- hubble_sdk_python-0.3.0/hubble_sdk/_generated/api/default/create_consent.py +193 -0
- {hubble_sdk_python-0.2.1 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/api/default/create_consent_link.py +8 -4
- {hubble_sdk_python-0.2.1 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/api/default/export_patient_fhir.py +31 -12
- {hubble_sdk_python-0.2.1 → 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.1 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/api/default/get_patient_pdf_export.py +47 -20
- {hubble_sdk_python-0.2.1 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/api/default/list_consents.py +16 -4
- {hubble_sdk_python-0.2.1 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/api/default/read_fhir.py +48 -13
- {hubble_sdk_python-0.2.1 → 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.1 → 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.1 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/models/consent.py +5 -4
- hubble_sdk_python-0.3.0/hubble_sdk/_generated/models/created_consent.py +152 -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.2.1 → 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.1 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/models/patient_demographics.py +1 -1
- {hubble_sdk_python-0.2.1 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/models/provider_seed.py +2 -1
- {hubble_sdk_python-0.2.1 → hubble_sdk_python-0.3.0}/hubble_sdk/_hubble.py +281 -41
- hubble_sdk_python-0.3.0/hubble_sdk/_params.py +86 -0
- hubble_sdk_python-0.3.0/hubble_sdk/py.typed +0 -0
- {hubble_sdk_python-0.2.1 → hubble_sdk_python-0.3.0}/pydoc-markdown.yml +4 -3
- {hubble_sdk_python-0.2.1 → hubble_sdk_python-0.3.0}/pyproject.toml +1 -1
- {hubble_sdk_python-0.2.1 → hubble_sdk_python-0.3.0}/tests/test_hubble.py +326 -9
- hubble_sdk_python-0.3.0/tests/test_params.py +68 -0
- {hubble_sdk_python-0.2.1 → hubble_sdk_python-0.3.0}/.gitignore +0 -0
- {hubble_sdk_python-0.2.1 → hubble_sdk_python-0.3.0}/LICENSE +0 -0
- {hubble_sdk_python-0.2.1 → hubble_sdk_python-0.3.0}/config.yaml +0 -0
- {hubble_sdk_python-0.2.1 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/__init__.py +0 -0
- {hubble_sdk_python-0.2.1 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/api/__init__.py +0 -0
- {hubble_sdk_python-0.2.1 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/api/default/__init__.py +0 -0
- {hubble_sdk_python-0.2.1 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/api/default/attach_client_authorization.py +0 -0
- {hubble_sdk_python-0.2.1 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/api/default/exchange_token.py +0 -0
- {hubble_sdk_python-0.2.1 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/api/default/get_consent.py +0 -0
- {hubble_sdk_python-0.2.1 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/api/default/revoke_consent.py +0 -0
- {hubble_sdk_python-0.2.1 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/client.py +0 -0
- {hubble_sdk_python-0.2.1 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/errors.py +0 -0
- {hubble_sdk_python-0.2.1 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/models/api_error.py +0 -0
- {hubble_sdk_python-0.2.1 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/models/consent_error.py +0 -0
- {hubble_sdk_python-0.2.1 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/models/consent_link_request.py +0 -0
- {hubble_sdk_python-0.2.1 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/models/consent_list.py +0 -0
- {hubble_sdk_python-0.2.1 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/models/consent_source.py +0 -0
- {hubble_sdk_python-0.2.1 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/models/consent_source_status.py +0 -0
- {hubble_sdk_python-0.2.1 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/models/consent_source_type.py +0 -0
- {hubble_sdk_python-0.2.1 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/models/consent_status.py +0 -0
- {hubble_sdk_python-0.2.1 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/models/device_authorization_request.py +0 -0
- {hubble_sdk_python-0.2.1 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/models/device_authorization_response.py +0 -0
- {hubble_sdk_python-0.2.1 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/models/export_patient_fhir_prefer.py +0 -0
- {hubble_sdk_python-0.2.1 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/models/export_patient_fhir_response_202.py +0 -0
- {hubble_sdk_python-0.2.1 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/models/export_patient_fhir_response_202_status.py +0 -0
- {hubble_sdk_python-0.2.1 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/models/export_patient_pdf_prefer.py +0 -0
- {hubble_sdk_python-0.2.1 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/models/export_patient_pdf_response_202.py +0 -0
- {hubble_sdk_python-0.2.1 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/models/export_patient_pdf_response_202_status.py +0 -0
- {hubble_sdk_python-0.2.1 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/models/fhir_resource.py +0 -0
- {hubble_sdk_python-0.2.1 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/models/get_patient_pdf_export_download.py +0 -0
- {hubble_sdk_python-0.2.1 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/models/get_patient_pdf_export_response_200.py +0 -0
- {hubble_sdk_python-0.2.1 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/models/get_patient_pdf_export_response_200_status.py +0 -0
- {hubble_sdk_python-0.2.1 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/models/o_auth_error.py +0 -0
- {hubble_sdk_python-0.2.1 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/models/o_auth_error_error.py +0 -0
- {hubble_sdk_python-0.2.1 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/models/operation_outcome.py +0 -0
- {hubble_sdk_python-0.2.1 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/models/operation_outcome_issue_item.py +0 -0
- {hubble_sdk_python-0.2.1 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/models/token_request.py +0 -0
- {hubble_sdk_python-0.2.1 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/models/token_request_grant_type.py +0 -0
- {hubble_sdk_python-0.2.1 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/models/token_response.py +0 -0
- {hubble_sdk_python-0.2.1 → hubble_sdk_python-0.3.0}/hubble_sdk/_generated/types.py +0 -0
- {hubble_sdk_python-0.2.1 → hubble_sdk_python-0.3.0}/hubble_sdk/_webhooks.py +0 -0
- {hubble_sdk_python-0.2.1 → hubble_sdk_python-0.3.0}/templates/property_templates/enum_property.py.jinja +0 -0
- {hubble_sdk_python-0.2.1 → hubble_sdk_python-0.3.0}/templates/str_enum.py.jinja +0 -0
- {hubble_sdk_python-0.2.1 → hubble_sdk_python-0.3.0}/tests/test_webhooks.py +0 -0
|
@@ -1,5 +1,34 @@
|
|
|
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
|
+
|
|
3
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)
|
|
4
33
|
|
|
5
34
|
|
|
@@ -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
|
]
|
|
@@ -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):
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from http import HTTPStatus
|
|
2
2
|
from typing import Any
|
|
3
|
+
from uuid import UUID
|
|
3
4
|
|
|
4
5
|
import httpx
|
|
5
6
|
|
|
@@ -14,6 +15,7 @@ from ...types import UNSET, Response, Unset
|
|
|
14
15
|
|
|
15
16
|
def _get_kwargs(
|
|
16
17
|
*,
|
|
18
|
+
consent: UUID | Unset = UNSET,
|
|
17
19
|
field_count: int | Unset = UNSET,
|
|
18
20
|
field_offset: int | Unset = UNSET,
|
|
19
21
|
prefer: ExportPatientFhirPrefer | Unset = UNSET,
|
|
@@ -24,6 +26,11 @@ def _get_kwargs(
|
|
|
24
26
|
|
|
25
27
|
params: dict[str, Any] = {}
|
|
26
28
|
|
|
29
|
+
json_consent: str | Unset = UNSET
|
|
30
|
+
if not isinstance(consent, Unset):
|
|
31
|
+
json_consent = str(consent)
|
|
32
|
+
params["consent"] = json_consent
|
|
33
|
+
|
|
27
34
|
params["_count"] = field_count
|
|
28
35
|
|
|
29
36
|
params["_offset"] = field_offset
|
|
@@ -103,6 +110,7 @@ def _build_response(
|
|
|
103
110
|
def sync_detailed(
|
|
104
111
|
*,
|
|
105
112
|
client: AuthenticatedClient,
|
|
113
|
+
consent: UUID | Unset = UNSET,
|
|
106
114
|
field_count: int | Unset = UNSET,
|
|
107
115
|
field_offset: int | Unset = UNSET,
|
|
108
116
|
prefer: ExportPatientFhirPrefer | Unset = UNSET,
|
|
@@ -113,11 +121,12 @@ def sync_detailed(
|
|
|
113
121
|
query parameters to prepare health-records.json in the background. The file is a FHIR collection
|
|
114
122
|
Bundle containing the captured provider sources, including each source's Patient and warnings for
|
|
115
123
|
incomplete sources. It has no paging links, search metadata or total field, and is not a Bulk Data
|
|
116
|
-
$export/NDJSON response. Poll Content-Location with the same
|
|
117
|
-
|
|
118
|
-
|
|
124
|
+
$export/NDJSON response. Poll Content-Location with the same token no sooner than Retry-After for a
|
|
125
|
+
303 download redirect. An organization can hold 20 exports in flight at once; a repeat request for
|
|
126
|
+
one already running reuses it. Payer records are not included.
|
|
119
127
|
|
|
120
128
|
Args:
|
|
129
|
+
consent (UUID | Unset):
|
|
121
130
|
field_count (int | Unset):
|
|
122
131
|
field_offset (int | Unset):
|
|
123
132
|
prefer (ExportPatientFhirPrefer | Unset):
|
|
@@ -131,6 +140,7 @@ def sync_detailed(
|
|
|
131
140
|
"""
|
|
132
141
|
|
|
133
142
|
kwargs = _get_kwargs(
|
|
143
|
+
consent=consent,
|
|
134
144
|
field_count=field_count,
|
|
135
145
|
field_offset=field_offset,
|
|
136
146
|
prefer=prefer,
|
|
@@ -146,6 +156,7 @@ def sync_detailed(
|
|
|
146
156
|
def sync(
|
|
147
157
|
*,
|
|
148
158
|
client: AuthenticatedClient,
|
|
159
|
+
consent: UUID | Unset = UNSET,
|
|
149
160
|
field_count: int | Unset = UNSET,
|
|
150
161
|
field_offset: int | Unset = UNSET,
|
|
151
162
|
prefer: ExportPatientFhirPrefer | Unset = UNSET,
|
|
@@ -156,11 +167,12 @@ def sync(
|
|
|
156
167
|
query parameters to prepare health-records.json in the background. The file is a FHIR collection
|
|
157
168
|
Bundle containing the captured provider sources, including each source's Patient and warnings for
|
|
158
169
|
incomplete sources. It has no paging links, search metadata or total field, and is not a Bulk Data
|
|
159
|
-
$export/NDJSON response. Poll Content-Location with the same
|
|
160
|
-
|
|
161
|
-
|
|
170
|
+
$export/NDJSON response. Poll Content-Location with the same token no sooner than Retry-After for a
|
|
171
|
+
303 download redirect. An organization can hold 20 exports in flight at once; a repeat request for
|
|
172
|
+
one already running reuses it. Payer records are not included.
|
|
162
173
|
|
|
163
174
|
Args:
|
|
175
|
+
consent (UUID | Unset):
|
|
164
176
|
field_count (int | Unset):
|
|
165
177
|
field_offset (int | Unset):
|
|
166
178
|
prefer (ExportPatientFhirPrefer | Unset):
|
|
@@ -175,6 +187,7 @@ def sync(
|
|
|
175
187
|
|
|
176
188
|
return sync_detailed(
|
|
177
189
|
client=client,
|
|
190
|
+
consent=consent,
|
|
178
191
|
field_count=field_count,
|
|
179
192
|
field_offset=field_offset,
|
|
180
193
|
prefer=prefer,
|
|
@@ -184,6 +197,7 @@ def sync(
|
|
|
184
197
|
async def asyncio_detailed(
|
|
185
198
|
*,
|
|
186
199
|
client: AuthenticatedClient,
|
|
200
|
+
consent: UUID | Unset = UNSET,
|
|
187
201
|
field_count: int | Unset = UNSET,
|
|
188
202
|
field_offset: int | Unset = UNSET,
|
|
189
203
|
prefer: ExportPatientFhirPrefer | Unset = UNSET,
|
|
@@ -194,11 +208,12 @@ async def asyncio_detailed(
|
|
|
194
208
|
query parameters to prepare health-records.json in the background. The file is a FHIR collection
|
|
195
209
|
Bundle containing the captured provider sources, including each source's Patient and warnings for
|
|
196
210
|
incomplete sources. It has no paging links, search metadata or total field, and is not a Bulk Data
|
|
197
|
-
$export/NDJSON response. Poll Content-Location with the same
|
|
198
|
-
|
|
199
|
-
|
|
211
|
+
$export/NDJSON response. Poll Content-Location with the same token no sooner than Retry-After for a
|
|
212
|
+
303 download redirect. An organization can hold 20 exports in flight at once; a repeat request for
|
|
213
|
+
one already running reuses it. Payer records are not included.
|
|
200
214
|
|
|
201
215
|
Args:
|
|
216
|
+
consent (UUID | Unset):
|
|
202
217
|
field_count (int | Unset):
|
|
203
218
|
field_offset (int | Unset):
|
|
204
219
|
prefer (ExportPatientFhirPrefer | Unset):
|
|
@@ -212,6 +227,7 @@ async def asyncio_detailed(
|
|
|
212
227
|
"""
|
|
213
228
|
|
|
214
229
|
kwargs = _get_kwargs(
|
|
230
|
+
consent=consent,
|
|
215
231
|
field_count=field_count,
|
|
216
232
|
field_offset=field_offset,
|
|
217
233
|
prefer=prefer,
|
|
@@ -225,6 +241,7 @@ async def asyncio_detailed(
|
|
|
225
241
|
async def asyncio(
|
|
226
242
|
*,
|
|
227
243
|
client: AuthenticatedClient,
|
|
244
|
+
consent: UUID | Unset = UNSET,
|
|
228
245
|
field_count: int | Unset = UNSET,
|
|
229
246
|
field_offset: int | Unset = UNSET,
|
|
230
247
|
prefer: ExportPatientFhirPrefer | Unset = UNSET,
|
|
@@ -235,11 +252,12 @@ async def asyncio(
|
|
|
235
252
|
query parameters to prepare health-records.json in the background. The file is a FHIR collection
|
|
236
253
|
Bundle containing the captured provider sources, including each source's Patient and warnings for
|
|
237
254
|
incomplete sources. It has no paging links, search metadata or total field, and is not a Bulk Data
|
|
238
|
-
$export/NDJSON response. Poll Content-Location with the same
|
|
239
|
-
|
|
240
|
-
|
|
255
|
+
$export/NDJSON response. Poll Content-Location with the same token no sooner than Retry-After for a
|
|
256
|
+
303 download redirect. An organization can hold 20 exports in flight at once; a repeat request for
|
|
257
|
+
one already running reuses it. Payer records are not included.
|
|
241
258
|
|
|
242
259
|
Args:
|
|
260
|
+
consent (UUID | Unset):
|
|
243
261
|
field_count (int | Unset):
|
|
244
262
|
field_offset (int | Unset):
|
|
245
263
|
prefer (ExportPatientFhirPrefer | Unset):
|
|
@@ -255,6 +273,7 @@ async def asyncio(
|
|
|
255
273
|
return (
|
|
256
274
|
await asyncio_detailed(
|
|
257
275
|
client=client,
|
|
276
|
+
consent=consent,
|
|
258
277
|
field_count=field_count,
|
|
259
278
|
field_offset=field_offset,
|
|
260
279
|
prefer=prefer,
|