hubble-sdk-python 0.0.2__py3-none-any.whl

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.
Files changed (46) hide show
  1. hubble_sdk/__init__.py +43 -0
  2. hubble_sdk/_generated/__init__.py +8 -0
  3. hubble_sdk/_generated/api/__init__.py +1 -0
  4. hubble_sdk/_generated/api/default/__init__.py +1 -0
  5. hubble_sdk/_generated/api/default/attach_client_authorization.py +220 -0
  6. hubble_sdk/_generated/api/default/create_consent_link.py +197 -0
  7. hubble_sdk/_generated/api/default/exchange_token.py +186 -0
  8. hubble_sdk/_generated/api/default/export_patient_pdf.py +194 -0
  9. hubble_sdk/_generated/api/default/get_consent.py +166 -0
  10. hubble_sdk/_generated/api/default/list_consents.py +191 -0
  11. hubble_sdk/_generated/api/default/read_fhir.py +182 -0
  12. hubble_sdk/_generated/api/default/revoke_consent.py +167 -0
  13. hubble_sdk/_generated/client.py +282 -0
  14. hubble_sdk/_generated/errors.py +16 -0
  15. hubble_sdk/_generated/models/__init__.py +59 -0
  16. hubble_sdk/_generated/models/api_error.py +42 -0
  17. hubble_sdk/_generated/models/attach_client_authorization_body.py +112 -0
  18. hubble_sdk/_generated/models/attach_client_authorization_response_201.py +100 -0
  19. hubble_sdk/_generated/models/attach_client_authorization_response_201_status.py +8 -0
  20. hubble_sdk/_generated/models/consent.py +160 -0
  21. hubble_sdk/_generated/models/consent_error.py +50 -0
  22. hubble_sdk/_generated/models/consent_link_request.py +88 -0
  23. hubble_sdk/_generated/models/consent_list.py +56 -0
  24. hubble_sdk/_generated/models/consent_source.py +63 -0
  25. hubble_sdk/_generated/models/consent_source_status.py +10 -0
  26. hubble_sdk/_generated/models/consent_source_type.py +9 -0
  27. hubble_sdk/_generated/models/consent_status.py +12 -0
  28. hubble_sdk/_generated/models/device_authorization_request.py +142 -0
  29. hubble_sdk/_generated/models/device_authorization_response.py +90 -0
  30. hubble_sdk/_generated/models/fhir_resource.py +62 -0
  31. hubble_sdk/_generated/models/o_auth_error.py +54 -0
  32. hubble_sdk/_generated/models/o_auth_error_error.py +16 -0
  33. hubble_sdk/_generated/models/operation_outcome.py +75 -0
  34. hubble_sdk/_generated/models/operation_outcome_issue_item.py +61 -0
  35. hubble_sdk/_generated/models/patient_address.py +69 -0
  36. hubble_sdk/_generated/models/patient_demographics.py +109 -0
  37. hubble_sdk/_generated/models/provider_seed.py +53 -0
  38. hubble_sdk/_generated/models/token_request.py +81 -0
  39. hubble_sdk/_generated/models/token_request_grant_type.py +12 -0
  40. hubble_sdk/_generated/models/token_response.py +84 -0
  41. hubble_sdk/_generated/types.py +54 -0
  42. hubble_sdk/_hubble.py +379 -0
  43. hubble_sdk_python-0.0.2.dist-info/METADATA +89 -0
  44. hubble_sdk_python-0.0.2.dist-info/RECORD +46 -0
  45. hubble_sdk_python-0.0.2.dist-info/WHEEL +4 -0
  46. hubble_sdk_python-0.0.2.dist-info/licenses/LICENSE +21 -0
hubble_sdk/_hubble.py ADDED
@@ -0,0 +1,379 @@
1
+ """Hand-written ergonomics layer over the generated client.
2
+
3
+ The generated code under ``_generated`` is a faithful, low-level mapping of the
4
+ OpenAPI contract and is regenerated on every spec change; nothing here is
5
+ generated. This module is the surface users actually import: it defaults the
6
+ base URL, picks the right auth scheme per call (HTTP Basic for the control
7
+ plane, Bearer for the FHIR data plane), builds FHIR URLs correctly, polls the
8
+ device-authorization flow, and refreshes consent tokens transparently.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import base64
14
+ import time
15
+ from collections.abc import Callable, Mapping
16
+ from dataclasses import dataclass
17
+ from typing import Any, TypeVar
18
+ from urllib.parse import unquote
19
+
20
+ import httpx
21
+
22
+ from ._generated.api.default import (
23
+ create_consent_link,
24
+ exchange_token,
25
+ get_consent as _get_consent,
26
+ list_consents as _list_consents,
27
+ revoke_consent as _revoke_consent,
28
+ )
29
+ from ._generated.client import AuthenticatedClient
30
+ from ._generated.models import (
31
+ Consent,
32
+ ConsentLinkRequest,
33
+ ConsentList,
34
+ ConsentStatus,
35
+ DeviceAuthorizationResponse,
36
+ TokenRequest,
37
+ TokenRequestGrantType,
38
+ TokenResponse,
39
+ )
40
+ from ._generated.types import UNSET
41
+
42
+ DEFAULT_BASE_URL = "https://api.hubble.ai"
43
+ _DEVICE_CODE_GRANT = TokenRequestGrantType("urn:ietf:params:oauth:grant-type:device_code")
44
+ _REFRESH_GRANT = TokenRequestGrantType("refresh_token")
45
+ _PENDING = {"authorization_pending", "slow_down"}
46
+
47
+
48
+ def _err_code(parsed: Any) -> str | None:
49
+ code = getattr(parsed, "error", None)
50
+ return code.value if hasattr(code, "value") else code
51
+
52
+
53
+ _T = TypeVar("_T")
54
+
55
+
56
+ def _unwrap(result: Any, expected: type[_T], ctx: str) -> _T:
57
+ # The generated .sync() returns the success model, an error model, or None.
58
+ # Route calls through this so a non-success result can't be silently used.
59
+ if not isinstance(result, expected):
60
+ raise HubbleError(f"{ctx}: {result}")
61
+ return result
62
+
63
+
64
+ def _ensure_ok(resp: Any, ctx: str) -> None:
65
+ # For operations with no response body (e.g. a 204): assert a 2xx status.
66
+ if not 200 <= resp.status_code < 300:
67
+ raise HubbleError(f"{ctx}: HTTP {resp.status_code}")
68
+
69
+
70
+ class Hubble:
71
+ """Client for the Hubble Patient Access API.
72
+
73
+ Args:
74
+ client_id / client_secret: your application credentials.
75
+ base_url: defaults to the production host; override only with a trusted
76
+ value (your access tokens are sent to it).
77
+ """
78
+
79
+ def __init__(self, client_id: str, client_secret: str, base_url: str = DEFAULT_BASE_URL):
80
+ self.base_url = base_url.rstrip("/")
81
+ basic = base64.b64encode(f"{client_id}:{client_secret}".encode()).decode()
82
+ # Control plane authenticates with HTTP Basic.
83
+ self._control = AuthenticatedClient(base_url=self.base_url, token=basic, prefix="Basic")
84
+
85
+ # ── link flow (OAuth device authorization grant) ──────────────────────────
86
+
87
+ def create_link(
88
+ self,
89
+ *,
90
+ patient_ref: str | None = None,
91
+ patient: Mapping[str, Any] | None = None,
92
+ providers: list[Mapping[str, Any]] | None = None,
93
+ ) -> DeviceAuthorizationResponse:
94
+ """Start the patient link flow. Send the patient to the returned
95
+ ``verification_uri_complete``, then call `wait_for_authorization()`.
96
+
97
+ Optionally prefill the link. ``patient`` is a mapping of demographics
98
+ (``first_name``, ``last_name``, ``birth_date``, ``sex``, ``phone``,
99
+ ``email``, and a nested ``address``); ``providers`` is a list of provider
100
+ seeds (each ``{"name": ..., "phone"?: ...}``) to prefill the patient's
101
+ list. The patient can add, change, or remove any of it on the link page."""
102
+ body: dict[str, Any] = {}
103
+ if patient_ref is not None:
104
+ body["patient_ref"] = patient_ref
105
+ if patient is not None:
106
+ body["patient"] = dict(patient)
107
+ if providers is not None:
108
+ body["providers"] = [dict(p) for p in providers]
109
+ return _unwrap(
110
+ create_consent_link.sync(client=self._control, body=ConsentLinkRequest.from_dict(body)),
111
+ DeviceAuthorizationResponse,
112
+ "Could not create consent link",
113
+ )
114
+
115
+ def wait_for_authorization(
116
+ self,
117
+ link: DeviceAuthorizationResponse,
118
+ *,
119
+ timeout: float = 600.0,
120
+ on_refresh: RefreshCallback | None = None,
121
+ ) -> PatientSession:
122
+ """Poll the token endpoint until the patient authorizes, honoring the
123
+ server's ``interval`` and ``slow_down``. Returns a `PatientSession`.
124
+
125
+ Pass ``on_refresh`` to be notified when the returned session later rotates
126
+ its tokens, so you can persist the new values."""
127
+ deadline = time.monotonic() + timeout
128
+ interval = float(link.interval)
129
+ while True:
130
+ resp = exchange_token.sync_detailed(
131
+ client=self._control,
132
+ body=TokenRequest(grant_type=_DEVICE_CODE_GRANT, device_code=link.device_code),
133
+ )
134
+ if resp.status_code == 200 and isinstance(resp.parsed, TokenResponse):
135
+ return PatientSession._from_token(self, resp.parsed, on_refresh=on_refresh)
136
+ code = _err_code(resp.parsed)
137
+ if code not in _PENDING:
138
+ raise HubbleError(f"Authorization failed: {code or resp.status_code}")
139
+ if time.monotonic() >= deadline:
140
+ raise ConsentTimeout("Patient did not authorize before the timeout.")
141
+ if code == "slow_down":
142
+ interval += 5
143
+ time.sleep(interval)
144
+
145
+ # ── consent management (control plane) ────────────────────────────────────
146
+
147
+ def list_consents(
148
+ self, *, status: str | None = None, patient_ref: str | None = None
149
+ ) -> list[Consent]:
150
+ """List consents, optionally filtered by ``status`` or ``patient_ref``."""
151
+ result = _list_consents.sync(
152
+ client=self._control,
153
+ status=ConsentStatus(status) if status is not None else UNSET,
154
+ patient_ref=patient_ref if patient_ref is not None else UNSET,
155
+ )
156
+ # _unwrap raises on failure, so this never returns [] to mean "errored".
157
+ return list(_unwrap(result, ConsentList, "Could not list consents").consents)
158
+
159
+ def get_consent(self, consent_id: str) -> Consent:
160
+ """Fetch a single consent by id."""
161
+ return _unwrap(
162
+ _get_consent.sync(id=consent_id, client=self._control),
163
+ Consent,
164
+ f"Could not fetch consent {consent_id}",
165
+ )
166
+
167
+ def revoke_consent(self, consent_id: str) -> None:
168
+ """Revoke a consent. Idempotent; all its tokens stop working immediately."""
169
+ # 204 with no body — assert success rather than expecting data.
170
+ _ensure_ok(
171
+ _revoke_consent.sync_detailed(id=consent_id, client=self._control),
172
+ f"Could not revoke consent {consent_id}",
173
+ )
174
+
175
+ # ── reconstruct a session from stored tokens ──────────────────────────────
176
+
177
+ def session(
178
+ self,
179
+ access_token: str,
180
+ *,
181
+ refresh_token: str | None = None,
182
+ consent_id: str | None = None,
183
+ on_refresh: RefreshCallback | None = None,
184
+ ) -> PatientSession:
185
+ """Reconstruct a `PatientSession` from previously stored tokens. Pass
186
+ ``on_refresh`` to be notified when the session rotates its tokens, so you can
187
+ persist the new values."""
188
+ return PatientSession(
189
+ self,
190
+ access_token=access_token,
191
+ refresh_token=refresh_token,
192
+ consent_id=consent_id,
193
+ on_refresh=on_refresh,
194
+ )
195
+
196
+
197
+ class PatientSession:
198
+ """A consent-scoped session for reading one patient's FHIR data.
199
+
200
+ Holds the consent tokens and reads the FHIR data plane with a Bearer token,
201
+ refreshing transparently on expiry. Reconstruct a later session from stored
202
+ tokens with `Hubble.session()`.
203
+ """
204
+
205
+ def __init__(
206
+ self,
207
+ hubble: Hubble,
208
+ *,
209
+ access_token: str,
210
+ refresh_token: str | None = None,
211
+ consent_id: str | None = None,
212
+ on_refresh: RefreshCallback | None = None,
213
+ ):
214
+ self._hubble = hubble
215
+ self.access_token = access_token
216
+ self.refresh_token = refresh_token
217
+ self.consent_id = consent_id
218
+ #: Called with the rotated tokens whenever this session refreshes.
219
+ self.on_refresh = on_refresh
220
+ self._data = AuthenticatedClient(base_url=hubble.base_url, token=access_token) # Bearer
221
+
222
+ @classmethod
223
+ def _from_token(
224
+ cls, hubble: Hubble, tr: TokenResponse, on_refresh: RefreshCallback | None = None
225
+ ) -> PatientSession:
226
+ return cls(
227
+ hubble,
228
+ access_token=tr.access_token,
229
+ refresh_token=tr.refresh_token,
230
+ consent_id=tr.consent_id,
231
+ on_refresh=on_refresh,
232
+ )
233
+
234
+ # ── FHIR data plane (Bearer) ──────────────────────────────────────────────
235
+
236
+ def fhir(self, path: str, *, params: dict[str, Any] | None = None) -> dict[str, Any]:
237
+ """GET a FHIR path, e.g. ``"Patient/$everything"`` or ``"Observation"``.
238
+
239
+ Returns the raw FHIR R4 resource as JSON (a dict). To work with typed
240
+ models, parse it with the optional ``fhir.resources`` package, e.g.
241
+ ``from fhir.resources.bundle import Bundle; Bundle.model_validate(result)``.
242
+
243
+ The path is passed through httpx unencoded, so ``/`` and ``$`` survive
244
+ (unlike the raw generated client). Refreshes and retries once on 401."""
245
+ return self._fhir_get(path, params, retry=self.refresh_token is not None)
246
+
247
+ def everything(self, **params: Any) -> dict[str, Any]:
248
+ """Read the patient's full record via ``Patient/$everything`` (a FHIR Bundle)."""
249
+ return self.fhir("Patient/$everything", params=params or None)
250
+
251
+ def summary(self) -> dict[str, Any]:
252
+ """Read the patient summary via ``Patient/$summary`` (a FHIR Bundle)."""
253
+ return self.fhir("Patient/$summary")
254
+
255
+ def export_pdf(self, *, connection: str | None = None) -> PdfExport:
256
+ """Export the patient's records as PDF via ``Patient/$pdf``.
257
+
258
+ Returns a `PdfExport`: the raw body (a ZIP of per-source PDFs plus a
259
+ ``manifest.json`` marking each source complete/partial/failed, or a
260
+ single PDF when ``connection`` selects one source) together with the
261
+ per-source counts from the response headers, so you can tell how many
262
+ sources failed without opening the archive. Refreshes and retries once
263
+ on 401."""
264
+ params = {"connection": connection} if connection is not None else None
265
+ resp = self._fhir_request("Patient/$pdf", params, retry=self.refresh_token is not None)
266
+
267
+ def _count(header: str) -> int | None:
268
+ try:
269
+ return int(resp.headers[header])
270
+ except (KeyError, ValueError):
271
+ return None
272
+
273
+ return PdfExport(
274
+ content=resp.content,
275
+ content_type=resp.headers.get("content-type", ""),
276
+ sources_total=_count("X-Hubble-Sources-Total"),
277
+ sources_failed=_count("X-Hubble-Sources-Failed"),
278
+ resource_count=_count("X-Hubble-Resource-Count"),
279
+ )
280
+
281
+ def _fhir_get(self, path: str, params: dict[str, Any] | None, retry: bool) -> dict[str, Any]:
282
+ return self._fhir_request(path, params, retry).json()
283
+
284
+ def _fhir_request(
285
+ self, path: str, params: dict[str, Any] | None, retry: bool
286
+ ) -> httpx.Response:
287
+ # Guard against escaping the FHIR namespace (which would send the consent
288
+ # token there): URL resolution normalizes raw `..`, and decoding then
289
+ # catches percent-encoded (`%2e%2e`) segments a server might resolve.
290
+ url = httpx.URL(self._hubble.base_url).join(f"/api/v1/fhir/{path.lstrip('/')}")
291
+ if not url.path.startswith("/api/v1/fhir/") or ".." in unquote(url.path).split("/"):
292
+ raise HubbleError(f"Invalid FHIR path: {path!r}")
293
+ resp = self._data.get_httpx_client().get(url, params=params)
294
+ if resp.status_code == 401 and retry:
295
+ self.refresh()
296
+ return self._fhir_request(path, params, retry=False)
297
+ if not resp.is_success:
298
+ raise HubbleError(f"FHIR request failed: {resp.status_code}")
299
+ return resp
300
+
301
+ def refresh(self) -> None:
302
+ """Exchange the refresh token for a fresh access token (rotating)."""
303
+ if not self.refresh_token:
304
+ raise HubbleError("No refresh token available to refresh this session.")
305
+ result = _unwrap(
306
+ exchange_token.sync(
307
+ client=self._hubble._control,
308
+ body=TokenRequest(grant_type=_REFRESH_GRANT, refresh_token=self.refresh_token),
309
+ ),
310
+ TokenResponse,
311
+ "Token refresh failed",
312
+ )
313
+ self.access_token = result.access_token
314
+ self.refresh_token = result.refresh_token
315
+ # Close the old client's connection pool before replacing it with one
316
+ # carrying the new token, so rotating tokens does not leak connections.
317
+ self._data.get_httpx_client().close()
318
+ self._data = AuthenticatedClient(base_url=self._hubble.base_url, token=self.access_token)
319
+ # Notify the caller so it can persist the rotated tokens.
320
+ if self.on_refresh is not None:
321
+ self.on_refresh(
322
+ TokenSet(
323
+ access_token=self.access_token,
324
+ refresh_token=self.refresh_token,
325
+ consent_id=self.consent_id,
326
+ )
327
+ )
328
+
329
+ def close(self) -> None:
330
+ """Close the underlying HTTP connection pool."""
331
+ self._data.get_httpx_client().close()
332
+
333
+ def __enter__(self) -> PatientSession:
334
+ return self
335
+
336
+ def __exit__(self, *exc: Any) -> None:
337
+ self.close()
338
+
339
+
340
+ # ── callback types and errors ─────────────────────────────────────────────────
341
+
342
+
343
+ @dataclass(frozen=True)
344
+ class TokenSet:
345
+ """The rotating token set a session holds; passed to an ``on_refresh`` callback."""
346
+
347
+ access_token: str
348
+ refresh_token: str | None = None
349
+ consent_id: str | None = None
350
+
351
+
352
+ @dataclass(frozen=True)
353
+ class PdfExport:
354
+ """The result of `PatientSession.export_pdf`.
355
+
356
+ ``content`` is the raw body — a ZIP of per-source PDFs (with a
357
+ ``manifest.json`` naming each file and marking it complete/partial/failed),
358
+ or a single PDF when ``connection`` was set. The counts come from the
359
+ response headers; a non-zero ``sources_failed`` means some records are
360
+ missing."""
361
+
362
+ content: bytes
363
+ content_type: str
364
+ sources_total: int | None = None
365
+ sources_failed: int | None = None
366
+ resource_count: int | None = None
367
+
368
+
369
+ # Invoked whenever a session rotates its tokens (on refresh), so callers can
370
+ # persist the new values.
371
+ RefreshCallback = Callable[["TokenSet"], None]
372
+
373
+
374
+ class HubbleError(Exception):
375
+ """Base error for the Hubble client."""
376
+
377
+
378
+ class ConsentTimeout(HubbleError):
379
+ """The patient did not authorize before the timeout elapsed."""
@@ -0,0 +1,89 @@
1
+ Metadata-Version: 2.5
2
+ Name: hubble-sdk-python
3
+ Version: 0.0.2
4
+ Summary: Python client for the Hubble Patient Access API.
5
+ Project-URL: Homepage, https://github.com/hubble-dot-ai/hubble-sdks
6
+ Project-URL: Repository, https://github.com/hubble-dot-ai/hubble-sdks
7
+ Project-URL: Issues, https://github.com/hubble-dot-ai/hubble-sdks/issues
8
+ Author: Hubble
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: health records,hubble,medical records,patient
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: Healthcare Industry
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
20
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
+ Classifier: Typing :: Typed
22
+ Requires-Python: >=3.11
23
+ Requires-Dist: attrs>=22.2.0
24
+ Requires-Dist: httpx<0.29.0,>=0.23.1
25
+ Requires-Dist: python-dateutil>=2.8.0
26
+ Provides-Extra: docs
27
+ Requires-Dist: pydoc-markdown<5,>=4.8; extra == 'docs'
28
+ Provides-Extra: test
29
+ Requires-Dist: pytest>=8.0; extra == 'test'
30
+ Requires-Dist: respx>=0.21; extra == 'test'
31
+ Description-Content-Type: text/markdown
32
+
33
+ # hubble-sdk-python
34
+
35
+ Python client for the Hubble Patient Access API: create patient authorization
36
+ links, exchange them for consent-scoped access tokens, and read FHIR R4 data.
37
+
38
+ ```bash
39
+ pip install hubble-sdk-python
40
+ ```
41
+
42
+ ```python
43
+ from hubble_sdk import Hubble
44
+
45
+ hub = Hubble(client_id="hbl_client_prod_...", client_secret="hbl_prod_...")
46
+
47
+ # 1. Create a patient link and send the patient to it. Optionally prefill with
48
+ # patient={"first_name": ...} demographics and providers=[{"name": ...}].
49
+ link = hub.create_link(patient_ref="abc123")
50
+ print(link.verification_uri_complete)
51
+
52
+ # 2. Wait for the patient to authorize (polls the token endpoint for you).
53
+ session = hub.wait_for_authorization(link)
54
+
55
+ # 3. Read the patient's FHIR data (Bearer token + auto-refresh handled for you).
56
+ bundle = session.everything()
57
+ observations = session.fhir("Observation", params={"category": "vital-signs"})
58
+ export = session.export_pdf() # export.content bytes; export.sources_failed counts failed sources
59
+
60
+ # Consent management
61
+ for consent in hub.list_consents(status="active"):
62
+ print(consent.id, consent.patient_ref)
63
+ hub.revoke_consent(session.consent_id)
64
+ ```
65
+
66
+ ## What the client handles for you
67
+
68
+ - **Auth per plane** — HTTP Basic (your app credentials) for consent management
69
+ and the token endpoint; Bearer (the consent token) for FHIR reads. You never
70
+ wire the schemes yourself.
71
+ - **Default base URL** — `https://api.hubble.ai`. Override only with a trusted
72
+ value; your access token is sent to it.
73
+ - **Device-flow polling** — `wait_for_authorization` honors the server's
74
+ `interval` and `slow_down`.
75
+ - **Correct FHIR paths** — operation and sub-resource paths like
76
+ `Patient/$everything` are sent unencoded.
77
+ - **Transparent refresh** — FHIR reads refresh and retry once on a 401.
78
+ - **FHIR responses** — reads return the raw FHIR R4 resource as a dict. For typed
79
+ models, parse with the optional [`fhir.resources`](https://pypi.org/project/fhir.resources/)
80
+ package: `from fhir.resources.bundle import Bundle; Bundle.model_validate(session.everything())`.
81
+
82
+ ## Layout
83
+
84
+ `Hubble` / `PatientSession` are hand-written. The low-level client generated
85
+ from the OpenAPI contract with
86
+ [openapi-python-client](https://github.com/openapi-generators/openapi-python-client)
87
+ lives under `hubble_sdk._generated` (regenerated on every spec change;
88
+ never edited by hand). Reach into it only if you need raw operations. See the
89
+ repo root README for how to regenerate.
@@ -0,0 +1,46 @@
1
+ hubble_sdk/__init__.py,sha256=lyGGBoULJqdApAePja5KAOjHMMhM60F-XhvLXJR2dm4,1042
2
+ hubble_sdk/_hubble.py,sha256=JI6gF3AfgDn37NkkBXWkzUgJsTSVCF647da1N95TO4E,15489
3
+ hubble_sdk/_generated/__init__.py,sha256=vHzWGo0njcFBcxGMOUuCWyhYdJi_xldILX7pdrPzdYA,168
4
+ hubble_sdk/_generated/client.py,sha256=aXn2Tt52-cnyAkBJcNlIzxMx35J2g6FneRGuk_wlA40,12530
5
+ hubble_sdk/_generated/errors.py,sha256=gO8GBmKqmSNgAg-E5oT-oOyxztvp7V_6XG7OUTT15q0,546
6
+ hubble_sdk/_generated/types.py,sha256=0We4NPvhIYASRpQ3le41nmJeEAVm42-2VKdzlJ4Ogok,1343
7
+ hubble_sdk/_generated/api/__init__.py,sha256=zTSiG_ujSjAqWPyc435YXaX9XTlpMjiJWBbV-f-YtdA,45
8
+ hubble_sdk/_generated/api/default/__init__.py,sha256=5vd9uJWAjRqa9xzxzYkLD1yoZ12Ld_bAaNB5WX4fbE8,56
9
+ hubble_sdk/_generated/api/default/attach_client_authorization.py,sha256=ZeMTt7o3tVJlI2hJHZ3v7iz1H4DiLo33kYh64vY-SLE,6622
10
+ hubble_sdk/_generated/api/default/create_consent_link.py,sha256=88KZtzq0cJ9hLZpNQqzpj2GjR0rmxzHTSWvqmMCGFW8,6377
11
+ hubble_sdk/_generated/api/default/exchange_token.py,sha256=86ysMEnY2jOUItEHEv06zeszFe4k2vxjn4h0nzvR8zk,5268
12
+ hubble_sdk/_generated/api/default/export_patient_pdf.py,sha256=1EwGf-H1HMo0L0SwwzuTsml3lfWESexYwDQWLYClFus,6269
13
+ hubble_sdk/_generated/api/default/get_consent.py,sha256=UkH6LMTsx2Mmqg9xgkIymw08QuJvcu1DMzuevi6z4pM,3808
14
+ hubble_sdk/_generated/api/default/list_consents.py,sha256=LOcrDwTiRh4MV0-PigLYZWY1dkUfdyljNBA7d8zAa64,4906
15
+ hubble_sdk/_generated/api/default/read_fhir.py,sha256=1sny0rT0ToKQ1sCWciEASQCUH-fOv4RNvbS80U2x2BU,5026
16
+ hubble_sdk/_generated/api/default/revoke_consent.py,sha256=ocFu3svKMa-E2EKIPTE-JYZ7D0-ye9mR_XUNj58mwWE,3966
17
+ hubble_sdk/_generated/models/__init__.py,sha256=iQFE98ouXrDKUs23VmpQcQFr7Qkdheexnp-rVy8Pg84,2053
18
+ hubble_sdk/_generated/models/api_error.py,sha256=RrgLT6j31RWPbyMsgkFcu5DWaWi1aJooVL7C24gbGhw,780
19
+ hubble_sdk/_generated/models/attach_client_authorization_body.py,sha256=ur71Z-fbahmKaJ0r68fkpAvaAM2ShtR1xc2ypqeNmAY,3475
20
+ hubble_sdk/_generated/models/attach_client_authorization_response_201.py,sha256=5L2MNvzlYkpFFbt-oJI9fPf1r2oCZ9cIReqkcMn_0Wc,3220
21
+ hubble_sdk/_generated/models/attach_client_authorization_response_201_status.py,sha256=B8XSR9rNquad1znJZygM-U47W-HNFe6DEi0QPLUt504,171
22
+ hubble_sdk/_generated/models/consent.py,sha256=azEAKRMCeFAav3qcFhylMct1GnXQRmGcR8HJp9FwLcU,4949
23
+ hubble_sdk/_generated/models/consent_error.py,sha256=6FO1yRkKkpS7ENGBZeCExFqjugzhScYYDV-W9A4t47Y,1030
24
+ hubble_sdk/_generated/models/consent_link_request.py,sha256=I7GxVFnqwcrn-BNvzxZVjD1cUu-NEYhbe-tdn8MCyVI,2853
25
+ hubble_sdk/_generated/models/consent_list.py,sha256=y9oZYL7QU6UQvm4qf9J_RbvLQnjWZvydhbpX6GO-Ld4,1250
26
+ hubble_sdk/_generated/models/consent_source.py,sha256=3uj5-xud14iKW_jOO711CpnGsWFeGWs0LDALmg8kEJ0,1688
27
+ hubble_sdk/_generated/models/consent_source_status.py,sha256=AMApXkgwZN0j8DOmwYPBh2zinjXriHYM39yRGjN4u-4,198
28
+ hubble_sdk/_generated/models/consent_source_type.py,sha256=qfwhIElp1PQDuLgUdQryn-_aTDvZ8xkLUfIq237_MK4,166
29
+ hubble_sdk/_generated/models/consent_status.py,sha256=jXZ3_yQ-M0vvoVuvs3vBbLnGcWPXYF-5ERz144_48aA,232
30
+ hubble_sdk/_generated/models/device_authorization_request.py,sha256=fSilSwVylrWSzwLv_4LIGwh1kaIunr2pcdOx8sPZQpg,5138
31
+ hubble_sdk/_generated/models/device_authorization_response.py,sha256=LWsLmhthCw_eRO_DaYrVdFPR8XTU6ZfS95FZYR2-DrY,2627
32
+ hubble_sdk/_generated/models/fhir_resource.py,sha256=RWGpFkFgJVFdRleOKM8WTiFl4RFhM0wa_FjFVsaLvBI,1635
33
+ hubble_sdk/_generated/models/o_auth_error.py,sha256=mrAIGsI6Cxq1f_xHPXD53xwMS_OI6lEJZ_SXObpnmxc,1252
34
+ hubble_sdk/_generated/models/o_auth_error_error.py,sha256=lpg84xgoJ_hW8qaJr6CUJfBH1tsSKgeQfb7D1hStuIQ,474
35
+ hubble_sdk/_generated/models/operation_outcome.py,sha256=oGYvHr9__XZ3wOP_LADPH9P8CCSjtvppAz6AS40az9U,1877
36
+ hubble_sdk/_generated/models/operation_outcome_issue_item.py,sha256=wmV4n_E0880pwYchBbWH8f9PXRbR8atuzjK5Ep-54y0,1318
37
+ hubble_sdk/_generated/models/patient_address.py,sha256=cajHVQIuvGl83SkQNdgwvdZOzfWTYNo8hRW2azNmTec,1619
38
+ hubble_sdk/_generated/models/patient_demographics.py,sha256=igIRoU9SBKmnyEXjTJ_VIW-It19Z5y40Wn72wLZ6V5g,3000
39
+ hubble_sdk/_generated/models/provider_seed.py,sha256=EmvivHxaDLQzl5H64FeZ86mUyTTxxUZB5KfFhhqm218,1133
40
+ hubble_sdk/_generated/models/token_request.py,sha256=8ZOQVJIIPPTdtAUeuG8ADeQdz0QHrc0yyrR9pXXMjVM,2391
41
+ hubble_sdk/_generated/models/token_request_grant_type.py,sha256=1PyWEyd65JbKn_gDDXoSa808pjia0Xj3hmPk-67XNZg,317
42
+ hubble_sdk/_generated/models/token_response.py,sha256=HORaPEpLNuPdCtGMlR-CXt8sQEYQH6gEp0gNTpQoFeE,2121
43
+ hubble_sdk_python-0.0.2.dist-info/METADATA,sha256=--IFrEDXx-xbr52oV5KcRxBFP3xTxWU-esOlqJroq4E,3773
44
+ hubble_sdk_python-0.0.2.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
45
+ hubble_sdk_python-0.0.2.dist-info/licenses/LICENSE,sha256=S8zTUFi7pK2HOg-wSn6bFAWbNAO4Lt1UiyDu_vcqO6w,1063
46
+ hubble_sdk_python-0.0.2.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Hubble
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.