hubble-sdk-python 0.0.3__tar.gz → 0.1.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.
Files changed (51) hide show
  1. {hubble_sdk_python-0.0.3 → hubble_sdk_python-0.1.0}/CHANGELOG.md +13 -0
  2. {hubble_sdk_python-0.0.3 → hubble_sdk_python-0.1.0}/PKG-INFO +4 -3
  3. {hubble_sdk_python-0.0.3 → hubble_sdk_python-0.1.0}/README.md +3 -2
  4. {hubble_sdk_python-0.0.3 → hubble_sdk_python-0.1.0}/hubble_sdk/_generated/api/default/attach_client_authorization.py +19 -16
  5. {hubble_sdk_python-0.0.3 → hubble_sdk_python-0.1.0}/hubble_sdk/_generated/client.py +9 -11
  6. {hubble_sdk_python-0.0.3 → hubble_sdk_python-0.1.0}/hubble_sdk/_generated/models/__init__.py +0 -8
  7. {hubble_sdk_python-0.0.3 → hubble_sdk_python-0.1.0}/hubble_sdk/_generated/models/consent.py +0 -25
  8. {hubble_sdk_python-0.0.3 → hubble_sdk_python-0.1.0}/hubble_sdk/_generated/models/consent_link_request.py +12 -0
  9. {hubble_sdk_python-0.0.3 → hubble_sdk_python-0.1.0}/hubble_sdk/_generated/models/consent_source_status.py +2 -2
  10. {hubble_sdk_python-0.0.3 → hubble_sdk_python-0.1.0}/hubble_sdk/_generated/models/consent_source_type.py +2 -2
  11. {hubble_sdk_python-0.0.3 → hubble_sdk_python-0.1.0}/hubble_sdk/_generated/models/consent_status.py +2 -2
  12. {hubble_sdk_python-0.0.3 → hubble_sdk_python-0.1.0}/hubble_sdk/_generated/models/device_authorization_request.py +21 -0
  13. {hubble_sdk_python-0.0.3 → hubble_sdk_python-0.1.0}/hubble_sdk/_generated/models/o_auth_error_error.py +2 -2
  14. {hubble_sdk_python-0.0.3 → hubble_sdk_python-0.1.0}/hubble_sdk/_generated/models/operation_outcome.py +3 -1
  15. {hubble_sdk_python-0.0.3 → hubble_sdk_python-0.1.0}/hubble_sdk/_generated/models/patient_address.py +9 -0
  16. {hubble_sdk_python-0.0.3 → hubble_sdk_python-0.1.0}/hubble_sdk/_generated/models/token_request_grant_type.py +2 -2
  17. {hubble_sdk_python-0.0.3 → hubble_sdk_python-0.1.0}/hubble_sdk/_hubble.py +66 -2
  18. {hubble_sdk_python-0.0.3 → hubble_sdk_python-0.1.0}/pyproject.toml +1 -1
  19. {hubble_sdk_python-0.0.3 → hubble_sdk_python-0.1.0}/tests/test_hubble.py +100 -2
  20. hubble_sdk_python-0.0.3/hubble_sdk/_generated/models/attach_client_authorization_response_201.py +0 -100
  21. hubble_sdk_python-0.0.3/hubble_sdk/_generated/models/attach_client_authorization_response_201_status.py +0 -8
  22. {hubble_sdk_python-0.0.3 → hubble_sdk_python-0.1.0}/.gitignore +0 -0
  23. {hubble_sdk_python-0.0.3 → hubble_sdk_python-0.1.0}/LICENSE +0 -0
  24. {hubble_sdk_python-0.0.3 → hubble_sdk_python-0.1.0}/config.yaml +0 -0
  25. {hubble_sdk_python-0.0.3 → hubble_sdk_python-0.1.0}/hubble_sdk/__init__.py +0 -0
  26. {hubble_sdk_python-0.0.3 → hubble_sdk_python-0.1.0}/hubble_sdk/_generated/__init__.py +0 -0
  27. {hubble_sdk_python-0.0.3 → hubble_sdk_python-0.1.0}/hubble_sdk/_generated/api/__init__.py +0 -0
  28. {hubble_sdk_python-0.0.3 → hubble_sdk_python-0.1.0}/hubble_sdk/_generated/api/default/__init__.py +0 -0
  29. {hubble_sdk_python-0.0.3 → hubble_sdk_python-0.1.0}/hubble_sdk/_generated/api/default/create_consent_link.py +0 -0
  30. {hubble_sdk_python-0.0.3 → hubble_sdk_python-0.1.0}/hubble_sdk/_generated/api/default/exchange_token.py +0 -0
  31. {hubble_sdk_python-0.0.3 → hubble_sdk_python-0.1.0}/hubble_sdk/_generated/api/default/export_patient_pdf.py +0 -0
  32. {hubble_sdk_python-0.0.3 → hubble_sdk_python-0.1.0}/hubble_sdk/_generated/api/default/get_consent.py +0 -0
  33. {hubble_sdk_python-0.0.3 → hubble_sdk_python-0.1.0}/hubble_sdk/_generated/api/default/list_consents.py +0 -0
  34. {hubble_sdk_python-0.0.3 → hubble_sdk_python-0.1.0}/hubble_sdk/_generated/api/default/read_fhir.py +0 -0
  35. {hubble_sdk_python-0.0.3 → hubble_sdk_python-0.1.0}/hubble_sdk/_generated/api/default/revoke_consent.py +0 -0
  36. {hubble_sdk_python-0.0.3 → hubble_sdk_python-0.1.0}/hubble_sdk/_generated/errors.py +0 -0
  37. {hubble_sdk_python-0.0.3 → hubble_sdk_python-0.1.0}/hubble_sdk/_generated/models/api_error.py +0 -0
  38. {hubble_sdk_python-0.0.3 → hubble_sdk_python-0.1.0}/hubble_sdk/_generated/models/attach_client_authorization_body.py +0 -0
  39. {hubble_sdk_python-0.0.3 → hubble_sdk_python-0.1.0}/hubble_sdk/_generated/models/consent_error.py +0 -0
  40. {hubble_sdk_python-0.0.3 → hubble_sdk_python-0.1.0}/hubble_sdk/_generated/models/consent_list.py +0 -0
  41. {hubble_sdk_python-0.0.3 → hubble_sdk_python-0.1.0}/hubble_sdk/_generated/models/consent_source.py +0 -0
  42. {hubble_sdk_python-0.0.3 → hubble_sdk_python-0.1.0}/hubble_sdk/_generated/models/device_authorization_response.py +0 -0
  43. {hubble_sdk_python-0.0.3 → hubble_sdk_python-0.1.0}/hubble_sdk/_generated/models/fhir_resource.py +0 -0
  44. {hubble_sdk_python-0.0.3 → hubble_sdk_python-0.1.0}/hubble_sdk/_generated/models/o_auth_error.py +0 -0
  45. {hubble_sdk_python-0.0.3 → hubble_sdk_python-0.1.0}/hubble_sdk/_generated/models/operation_outcome_issue_item.py +0 -0
  46. {hubble_sdk_python-0.0.3 → hubble_sdk_python-0.1.0}/hubble_sdk/_generated/models/patient_demographics.py +0 -0
  47. {hubble_sdk_python-0.0.3 → hubble_sdk_python-0.1.0}/hubble_sdk/_generated/models/provider_seed.py +0 -0
  48. {hubble_sdk_python-0.0.3 → hubble_sdk_python-0.1.0}/hubble_sdk/_generated/models/token_request.py +0 -0
  49. {hubble_sdk_python-0.0.3 → hubble_sdk_python-0.1.0}/hubble_sdk/_generated/models/token_response.py +0 -0
  50. {hubble_sdk_python-0.0.3 → hubble_sdk_python-0.1.0}/hubble_sdk/_generated/types.py +0 -0
  51. {hubble_sdk_python-0.0.3 → hubble_sdk_python-0.1.0}/pydoc-markdown.yml +0 -0
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.1.0](https://github.com/hubble-dot-ai/hubble-sdks/compare/hubble-sdk-python-v0.0.3...hubble-sdk-python-v0.1.0) (2026-09-02)
4
+
5
+
6
+ ### ⚠ BREAKING CHANGES
7
+
8
+ * `Consent.granted_at` is removed. Callers reading it must derive authorization time another way (e.g. status transitions).
9
+
10
+ ### Features
11
+
12
+ * add attach_authorization for BYO HIPAA authorization ([#41](https://github.com/hubble-dot-ai/hubble-sdks/issues/41)) ([66baf11](https://github.com/hubble-dot-ai/hubble-sdks/commit/66baf1192291b31cb03e325be44f1e6c5e4371f3))
13
+ * drop granted_at from Consent ([#44](https://github.com/hubble-dot-ai/hubble-sdks/issues/44)) ([cb61be8](https://github.com/hubble-dot-ai/hubble-sdks/commit/cb61be8dc6e8c4784e30332cb4e31317f773ec3b))
14
+ * support represented organizations in SDK links ([#45](https://github.com/hubble-dot-ai/hubble-sdks/issues/45)) ([430a1d0](https://github.com/hubble-dot-ai/hubble-sdks/commit/430a1d022f75ea645d27b0b7ebf87b910f1c51a9))
15
+
3
16
  ## [0.0.3](https://github.com/hubble-dot-ai/hubble-sdks/compare/hubble-sdk-python-v0.0.2...hubble-sdk-python-v0.0.3) (2026-08-23)
4
17
 
5
18
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: hubble-sdk-python
3
- Version: 0.0.3
3
+ Version: 0.1.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
@@ -45,8 +45,9 @@ from hubble_sdk import Hubble
45
45
  hub = Hubble(client_id="hbl_client_prod_...", client_secret="hbl_prod_...")
46
46
 
47
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")
48
+ # patient={"first_name": ...} demographics, providers=[{"name": ...}], and
49
+ # the optional organization your verified Hubble organization represents.
50
+ link = hub.create_link(patient_ref="abc123", on_behalf_of_organization_name="Acme Health")
50
51
  print(link.verification_uri_complete)
51
52
 
52
53
  # 2. Wait for the patient to authorize (polls the token endpoint for you).
@@ -13,8 +13,9 @@ from hubble_sdk import Hubble
13
13
  hub = Hubble(client_id="hbl_client_prod_...", client_secret="hbl_prod_...")
14
14
 
15
15
  # 1. Create a patient link and send the patient to it. Optionally prefill with
16
- # patient={"first_name": ...} demographics and providers=[{"name": ...}].
17
- link = hub.create_link(patient_ref="abc123")
16
+ # patient={"first_name": ...} demographics, providers=[{"name": ...}], and
17
+ # the optional organization your verified Hubble organization represents.
18
+ link = hub.create_link(patient_ref="abc123", on_behalf_of_organization_name="Acme Health")
18
19
  print(link.verification_uri_complete)
19
20
 
20
21
  # 2. Wait for the patient to authorize (polls the token endpoint for you).
@@ -8,9 +8,7 @@ from ... import errors
8
8
  from ...client import AuthenticatedClient, Client
9
9
  from ...models.api_error import ApiError
10
10
  from ...models.attach_client_authorization_body import AttachClientAuthorizationBody
11
- from ...models.attach_client_authorization_response_201 import (
12
- AttachClientAuthorizationResponse201,
13
- )
11
+ from ...models.consent import Consent
14
12
  from ...types import Response
15
13
 
16
14
 
@@ -38,11 +36,11 @@ def _get_kwargs(
38
36
 
39
37
  def _parse_response(
40
38
  *, client: AuthenticatedClient | Client, response: httpx.Response
41
- ) -> ApiError | AttachClientAuthorizationResponse201 | None:
42
- if response.status_code == 201:
43
- response_201 = AttachClientAuthorizationResponse201.from_dict(response.json())
39
+ ) -> ApiError | Consent | None:
40
+ if response.status_code == 200:
41
+ response_200 = Consent.from_dict(response.json())
44
42
 
45
- return response_201
43
+ return response_200
46
44
 
47
45
  if response.status_code == 400:
48
46
  response_400 = ApiError.from_dict(response.json())
@@ -64,6 +62,11 @@ def _parse_response(
64
62
 
65
63
  return response_404
66
64
 
65
+ if response.status_code == 409:
66
+ response_409 = ApiError.from_dict(response.json())
67
+
68
+ return response_409
69
+
67
70
  if response.status_code == 413:
68
71
  response_413 = ApiError.from_dict(response.json())
69
72
 
@@ -77,7 +80,7 @@ def _parse_response(
77
80
 
78
81
  def _build_response(
79
82
  *, client: AuthenticatedClient | Client, response: httpx.Response
80
- ) -> Response[ApiError | AttachClientAuthorizationResponse201]:
83
+ ) -> Response[ApiError | Consent]:
81
84
  return Response(
82
85
  status_code=HTTPStatus(response.status_code),
83
86
  content=response.content,
@@ -91,7 +94,7 @@ def sync_detailed(
91
94
  *,
92
95
  client: AuthenticatedClient,
93
96
  body: AttachClientAuthorizationBody,
94
- ) -> Response[ApiError | AttachClientAuthorizationResponse201]:
97
+ ) -> Response[ApiError | Consent]:
95
98
  """Attach a customer-supplied HIPAA authorization
96
99
 
97
100
  Upload an already-signed HIPAA authorization for a consent and grant delegated retrieval from the
@@ -107,7 +110,7 @@ def sync_detailed(
107
110
  httpx.TimeoutException: If the request takes longer than Client.timeout.
108
111
 
109
112
  Returns:
110
- Response[ApiError | AttachClientAuthorizationResponse201]
113
+ Response[ApiError | Consent]
111
114
  """
112
115
 
113
116
  kwargs = _get_kwargs(
@@ -127,7 +130,7 @@ def sync(
127
130
  *,
128
131
  client: AuthenticatedClient,
129
132
  body: AttachClientAuthorizationBody,
130
- ) -> ApiError | AttachClientAuthorizationResponse201 | None:
133
+ ) -> ApiError | Consent | None:
131
134
  """Attach a customer-supplied HIPAA authorization
132
135
 
133
136
  Upload an already-signed HIPAA authorization for a consent and grant delegated retrieval from the
@@ -143,7 +146,7 @@ def sync(
143
146
  httpx.TimeoutException: If the request takes longer than Client.timeout.
144
147
 
145
148
  Returns:
146
- ApiError | AttachClientAuthorizationResponse201
149
+ ApiError | Consent
147
150
  """
148
151
 
149
152
  return sync_detailed(
@@ -158,7 +161,7 @@ async def asyncio_detailed(
158
161
  *,
159
162
  client: AuthenticatedClient,
160
163
  body: AttachClientAuthorizationBody,
161
- ) -> Response[ApiError | AttachClientAuthorizationResponse201]:
164
+ ) -> Response[ApiError | Consent]:
162
165
  """Attach a customer-supplied HIPAA authorization
163
166
 
164
167
  Upload an already-signed HIPAA authorization for a consent and grant delegated retrieval from the
@@ -174,7 +177,7 @@ async def asyncio_detailed(
174
177
  httpx.TimeoutException: If the request takes longer than Client.timeout.
175
178
 
176
179
  Returns:
177
- Response[ApiError | AttachClientAuthorizationResponse201]
180
+ Response[ApiError | Consent]
178
181
  """
179
182
 
180
183
  kwargs = _get_kwargs(
@@ -192,7 +195,7 @@ async def asyncio(
192
195
  *,
193
196
  client: AuthenticatedClient,
194
197
  body: AttachClientAuthorizationBody,
195
- ) -> ApiError | AttachClientAuthorizationResponse201 | None:
198
+ ) -> ApiError | Consent | None:
196
199
  """Attach a customer-supplied HIPAA authorization
197
200
 
198
201
  Upload an already-signed HIPAA authorization for a consent and grant delegated retrieval from the
@@ -208,7 +211,7 @@ async def asyncio(
208
211
  httpx.TimeoutException: If the request takes longer than Client.timeout.
209
212
 
210
213
  Returns:
211
- ApiError | AttachClientAuthorizationResponse201
214
+ ApiError | Consent
212
215
  """
213
216
 
214
217
  return (
@@ -1,5 +1,5 @@
1
1
  import ssl
2
- from typing import Any
2
+ from typing import Any, Self
3
3
 
4
4
  import httpx
5
5
  from attrs import define, evolve, field
@@ -73,7 +73,7 @@ class Client:
73
73
  self._async_client.timeout = timeout
74
74
  return evolve(self, timeout=timeout)
75
75
 
76
- def set_httpx_client(self, client: httpx.Client) -> "Client":
76
+ def set_httpx_client(self, client: httpx.Client) -> Self:
77
77
  """Manually set the underlying httpx.Client
78
78
 
79
79
  **NOTE**: This will override any other settings on the client, including cookies, headers, and timeout.
@@ -95,7 +95,7 @@ class Client:
95
95
  )
96
96
  return self._client
97
97
 
98
- def __enter__(self) -> "Client":
98
+ def __enter__(self) -> Self:
99
99
  """Enter a context manager for self.client—you cannot enter twice (see httpx docs)"""
100
100
  self.get_httpx_client().__enter__()
101
101
  return self
@@ -104,7 +104,7 @@ class Client:
104
104
  """Exit a context manager for internal httpx.Client (see httpx docs)"""
105
105
  self.get_httpx_client().__exit__(*args, **kwargs)
106
106
 
107
- def set_async_httpx_client(self, async_client: httpx.AsyncClient) -> "Client":
107
+ def set_async_httpx_client(self, async_client: httpx.AsyncClient) -> Self:
108
108
  """Manually set the underlying httpx.AsyncClient
109
109
 
110
110
  **NOTE**: This will override any other settings on the client, including cookies, headers, and timeout.
@@ -126,7 +126,7 @@ class Client:
126
126
  )
127
127
  return self._async_client
128
128
 
129
- async def __aenter__(self) -> "Client":
129
+ async def __aenter__(self) -> Self:
130
130
  """Enter a context manager for underlying httpx.AsyncClient—you cannot enter twice (see httpx docs)"""
131
131
  await self.get_async_httpx_client().__aenter__()
132
132
  return self
@@ -211,7 +211,7 @@ class AuthenticatedClient:
211
211
  self._async_client.timeout = timeout
212
212
  return evolve(self, timeout=timeout)
213
213
 
214
- def set_httpx_client(self, client: httpx.Client) -> "AuthenticatedClient":
214
+ def set_httpx_client(self, client: httpx.Client) -> Self:
215
215
  """Manually set the underlying httpx.Client
216
216
 
217
217
  **NOTE**: This will override any other settings on the client, including cookies, headers, and timeout.
@@ -236,7 +236,7 @@ class AuthenticatedClient:
236
236
  )
237
237
  return self._client
238
238
 
239
- def __enter__(self) -> "AuthenticatedClient":
239
+ def __enter__(self) -> Self:
240
240
  """Enter a context manager for self.client—you cannot enter twice (see httpx docs)"""
241
241
  self.get_httpx_client().__enter__()
242
242
  return self
@@ -245,9 +245,7 @@ class AuthenticatedClient:
245
245
  """Exit a context manager for internal httpx.Client (see httpx docs)"""
246
246
  self.get_httpx_client().__exit__(*args, **kwargs)
247
247
 
248
- def set_async_httpx_client(
249
- self, async_client: httpx.AsyncClient
250
- ) -> "AuthenticatedClient":
248
+ def set_async_httpx_client(self, async_client: httpx.AsyncClient) -> Self:
251
249
  """Manually set the underlying httpx.AsyncClient
252
250
 
253
251
  **NOTE**: This will override any other settings on the client, including cookies, headers, and timeout.
@@ -272,7 +270,7 @@ class AuthenticatedClient:
272
270
  )
273
271
  return self._async_client
274
272
 
275
- async def __aenter__(self) -> "AuthenticatedClient":
273
+ async def __aenter__(self) -> Self:
276
274
  """Enter a context manager for underlying httpx.AsyncClient—you cannot enter twice (see httpx docs)"""
277
275
  await self.get_async_httpx_client().__aenter__()
278
276
  return self
@@ -2,12 +2,6 @@
2
2
 
3
3
  from .api_error import ApiError
4
4
  from .attach_client_authorization_body import AttachClientAuthorizationBody
5
- from .attach_client_authorization_response_201 import (
6
- AttachClientAuthorizationResponse201,
7
- )
8
- from .attach_client_authorization_response_201_status import (
9
- AttachClientAuthorizationResponse201Status,
10
- )
11
5
  from .consent import Consent
12
6
  from .consent_error import ConsentError
13
7
  from .consent_link_request import ConsentLinkRequest
@@ -33,8 +27,6 @@ from .token_response import TokenResponse
33
27
  __all__ = (
34
28
  "ApiError",
35
29
  "AttachClientAuthorizationBody",
36
- "AttachClientAuthorizationResponse201",
37
- "AttachClientAuthorizationResponse201Status",
38
30
  "Consent",
39
31
  "ConsentError",
40
32
  "ConsentLinkRequest",
@@ -24,7 +24,6 @@ class Consent:
24
24
  status (ConsentStatus):
25
25
  patient_ref (None | str): Your own reference for the patient, echoed back from consent creation.
26
26
  created_at (datetime.datetime): When the consent was created.
27
- granted_at (datetime.datetime | None): When the patient authorized. Null until active.
28
27
  expires_at (datetime.datetime): When a pending link expires, or when the grant lapses.
29
28
  sources (list[ConsentSource]): Each source on the consent and its progress (live vs one-time,
30
29
  available/pending/failed).
@@ -35,7 +34,6 @@ class Consent:
35
34
  status: ConsentStatus
36
35
  patient_ref: None | str
37
36
  created_at: datetime.datetime
38
- granted_at: datetime.datetime | None
39
37
  expires_at: datetime.datetime
40
38
  sources: list[ConsentSource]
41
39
  error: ConsentError | None
@@ -52,12 +50,6 @@ class Consent:
52
50
 
53
51
  created_at = self.created_at.isoformat()
54
52
 
55
- granted_at: None | str
56
- if isinstance(self.granted_at, datetime.datetime):
57
- granted_at = self.granted_at.isoformat()
58
- else:
59
- granted_at = self.granted_at
60
-
61
53
  expires_at = self.expires_at.isoformat()
62
54
 
63
55
  sources = []
@@ -79,7 +71,6 @@ class Consent:
79
71
  "status": status,
80
72
  "patient_ref": patient_ref,
81
73
  "created_at": created_at,
82
- "granted_at": granted_at,
83
74
  "expires_at": expires_at,
84
75
  "sources": sources,
85
76
  "error": error,
@@ -107,21 +98,6 @@ class Consent:
107
98
 
108
99
  created_at = datetime.datetime.fromisoformat(d.pop("created_at"))
109
100
 
110
- def _parse_granted_at(data: object) -> datetime.datetime | None:
111
- if data is None:
112
- return data
113
- try:
114
- if not isinstance(data, str):
115
- raise TypeError()
116
- granted_at_type_0 = datetime.datetime.fromisoformat(data)
117
-
118
- return granted_at_type_0
119
- except (TypeError, ValueError, AttributeError, KeyError):
120
- pass
121
- return cast(datetime.datetime | None, data)
122
-
123
- granted_at = _parse_granted_at(d.pop("granted_at"))
124
-
125
101
  expires_at = datetime.datetime.fromisoformat(d.pop("expires_at"))
126
102
 
127
103
  sources = []
@@ -151,7 +127,6 @@ class Consent:
151
127
  status=status,
152
128
  patient_ref=patient_ref,
153
129
  created_at=created_at,
154
- granted_at=granted_at,
155
130
  expires_at=expires_at,
156
131
  sources=sources,
157
132
  error=error,
@@ -23,11 +23,14 @@ class ConsentLinkRequest:
23
23
  patient (PatientDemographics | Unset):
24
24
  providers (list[ProviderSeed] | Unset): Provider seeds to prefill the patient's list. Seeded by name (freeform);
25
25
  the patient can add, change, or remove them on the link page.
26
+ on_behalf_of_organization_name (str | Unset): Organization the verified requester is acting on behalf of for
27
+ this link.
26
28
  """
27
29
 
28
30
  patient_ref: str | Unset = UNSET
29
31
  patient: PatientDemographics | Unset = UNSET
30
32
  providers: list[ProviderSeed] | Unset = UNSET
33
+ on_behalf_of_organization_name: str | Unset = UNSET
31
34
 
32
35
  def to_dict(self) -> dict[str, Any]:
33
36
  patient_ref = self.patient_ref
@@ -43,6 +46,8 @@ class ConsentLinkRequest:
43
46
  providers_item = providers_item_data.to_dict()
44
47
  providers.append(providers_item)
45
48
 
49
+ on_behalf_of_organization_name = self.on_behalf_of_organization_name
50
+
46
51
  field_dict: dict[str, Any] = {}
47
52
 
48
53
  field_dict.update({})
@@ -52,6 +57,10 @@ class ConsentLinkRequest:
52
57
  field_dict["patient"] = patient
53
58
  if providers is not UNSET:
54
59
  field_dict["providers"] = providers
60
+ if on_behalf_of_organization_name is not UNSET:
61
+ field_dict["on_behalf_of_organization_name"] = (
62
+ on_behalf_of_organization_name
63
+ )
55
64
 
56
65
  return field_dict
57
66
 
@@ -79,10 +88,13 @@ class ConsentLinkRequest:
79
88
 
80
89
  providers.append(providers_item)
81
90
 
91
+ on_behalf_of_organization_name = d.pop("on_behalf_of_organization_name", UNSET)
92
+
82
93
  consent_link_request = cls(
83
94
  patient_ref=patient_ref,
84
95
  patient=patient,
85
96
  providers=providers,
97
+ on_behalf_of_organization_name=on_behalf_of_organization_name,
86
98
  )
87
99
 
88
100
  return consent_link_request
@@ -1,7 +1,7 @@
1
- from enum import Enum
1
+ from enum import StrEnum
2
2
 
3
3
 
4
- class ConsentSourceStatus(str, Enum):
4
+ class ConsentSourceStatus(StrEnum):
5
5
  AVAILABLE = "available"
6
6
  FAILED = "failed"
7
7
  PENDING = "pending"
@@ -1,7 +1,7 @@
1
- from enum import Enum
1
+ from enum import StrEnum
2
2
 
3
3
 
4
- class ConsentSourceType(str, Enum):
4
+ class ConsentSourceType(StrEnum):
5
5
  LIVE = "live"
6
6
  ONE_TIME = "one_time"
7
7
 
@@ -1,7 +1,7 @@
1
- from enum import Enum
1
+ from enum import StrEnum
2
2
 
3
3
 
4
- class ConsentStatus(str, Enum):
4
+ class ConsentStatus(StrEnum):
5
5
  ACTIVE = "active"
6
6
  ERROR = "error"
7
7
  EXPIRED = "expired"
@@ -20,6 +20,7 @@ class DeviceAuthorizationRequest:
20
20
  patient_birth_date (str | Unset): Patient's date of birth, ISO YYYY-MM-DD.
21
21
  patient_sex (str | Unset): Patient's sex.
22
22
  patient_address_line1 (str | Unset): Patient's street address.
23
+ patient_address_line2 (str | Unset): Patient's street address, second line (apt, suite, unit).
23
24
  patient_city (str | Unset): Patient's city.
24
25
  patient_state (str | Unset): Patient's state or region.
25
26
  patient_postal_code (str | Unset): Patient's postal code.
@@ -27,6 +28,8 @@ class DeviceAuthorizationRequest:
27
28
  patient_email (str | Unset): Patient's email, used as the signer identity.
28
29
  providers (str | Unset): JSON array of provider seeds to prefill the patient's list, each `{ "name", "phone"?
29
30
  }`. Seeded by name (freeform); the patient can add, change, or remove them on the link page.
31
+ on_behalf_of_organization_name (str | Unset): Organization the verified requester is acting on behalf of for
32
+ this link.
30
33
  """
31
34
 
32
35
  patient_ref: str | Unset = UNSET
@@ -35,12 +38,14 @@ class DeviceAuthorizationRequest:
35
38
  patient_birth_date: str | Unset = UNSET
36
39
  patient_sex: str | Unset = UNSET
37
40
  patient_address_line1: str | Unset = UNSET
41
+ patient_address_line2: str | Unset = UNSET
38
42
  patient_city: str | Unset = UNSET
39
43
  patient_state: str | Unset = UNSET
40
44
  patient_postal_code: str | Unset = UNSET
41
45
  patient_phone: str | Unset = UNSET
42
46
  patient_email: str | Unset = UNSET
43
47
  providers: str | Unset = UNSET
48
+ on_behalf_of_organization_name: str | Unset = UNSET
44
49
 
45
50
  def to_dict(self) -> dict[str, Any]:
46
51
  patient_ref = self.patient_ref
@@ -55,6 +60,8 @@ class DeviceAuthorizationRequest:
55
60
 
56
61
  patient_address_line1 = self.patient_address_line1
57
62
 
63
+ patient_address_line2 = self.patient_address_line2
64
+
58
65
  patient_city = self.patient_city
59
66
 
60
67
  patient_state = self.patient_state
@@ -67,6 +74,8 @@ class DeviceAuthorizationRequest:
67
74
 
68
75
  providers = self.providers
69
76
 
77
+ on_behalf_of_organization_name = self.on_behalf_of_organization_name
78
+
70
79
  field_dict: dict[str, Any] = {}
71
80
 
72
81
  field_dict.update({})
@@ -82,6 +91,8 @@ class DeviceAuthorizationRequest:
82
91
  field_dict["patient_sex"] = patient_sex
83
92
  if patient_address_line1 is not UNSET:
84
93
  field_dict["patient_address_line1"] = patient_address_line1
94
+ if patient_address_line2 is not UNSET:
95
+ field_dict["patient_address_line2"] = patient_address_line2
85
96
  if patient_city is not UNSET:
86
97
  field_dict["patient_city"] = patient_city
87
98
  if patient_state is not UNSET:
@@ -94,6 +105,10 @@ class DeviceAuthorizationRequest:
94
105
  field_dict["patient_email"] = patient_email
95
106
  if providers is not UNSET:
96
107
  field_dict["providers"] = providers
108
+ if on_behalf_of_organization_name is not UNSET:
109
+ field_dict["on_behalf_of_organization_name"] = (
110
+ on_behalf_of_organization_name
111
+ )
97
112
 
98
113
  return field_dict
99
114
 
@@ -112,6 +127,8 @@ class DeviceAuthorizationRequest:
112
127
 
113
128
  patient_address_line1 = d.pop("patient_address_line1", UNSET)
114
129
 
130
+ patient_address_line2 = d.pop("patient_address_line2", UNSET)
131
+
115
132
  patient_city = d.pop("patient_city", UNSET)
116
133
 
117
134
  patient_state = d.pop("patient_state", UNSET)
@@ -124,6 +141,8 @@ class DeviceAuthorizationRequest:
124
141
 
125
142
  providers = d.pop("providers", UNSET)
126
143
 
144
+ on_behalf_of_organization_name = d.pop("on_behalf_of_organization_name", UNSET)
145
+
127
146
  device_authorization_request = cls(
128
147
  patient_ref=patient_ref,
129
148
  patient_first_name=patient_first_name,
@@ -131,12 +150,14 @@ class DeviceAuthorizationRequest:
131
150
  patient_birth_date=patient_birth_date,
132
151
  patient_sex=patient_sex,
133
152
  patient_address_line1=patient_address_line1,
153
+ patient_address_line2=patient_address_line2,
134
154
  patient_city=patient_city,
135
155
  patient_state=patient_state,
136
156
  patient_postal_code=patient_postal_code,
137
157
  patient_phone=patient_phone,
138
158
  patient_email=patient_email,
139
159
  providers=providers,
160
+ on_behalf_of_organization_name=on_behalf_of_organization_name,
140
161
  )
141
162
 
142
163
  return device_authorization_request
@@ -1,7 +1,7 @@
1
- from enum import Enum
1
+ from enum import StrEnum
2
2
 
3
3
 
4
- class OAuthErrorError(str, Enum):
4
+ class OAuthErrorError(StrEnum):
5
5
  ACCESS_DENIED = "access_denied"
6
6
  AUTHORIZATION_PENDING = "authorization_pending"
7
7
  EXPIRED_TOKEN = "expired_token"
@@ -51,7 +51,9 @@ class OperationOutcome:
51
51
 
52
52
  @classmethod
53
53
  def from_dict(cls, src_dict: Mapping[str, Any]) -> Self:
54
- from ..models.operation_outcome_issue_item import OperationOutcomeIssueItem
54
+ from ..models.operation_outcome_issue_item import (
55
+ OperationOutcomeIssueItem,
56
+ )
55
57
 
56
58
  d = dict(src_dict)
57
59
  resource_type = cast(Literal["OperationOutcome"], d.pop("resourceType"))
@@ -15,12 +15,14 @@ class PatientAddress:
15
15
  """
16
16
  Attributes:
17
17
  line1 (str | Unset): Street address.
18
+ line2 (str | Unset): Street address, second line (apt, suite, unit).
18
19
  city (str | Unset): City.
19
20
  state (str | Unset): State or region.
20
21
  postal_code (str | Unset): Postal code.
21
22
  """
22
23
 
23
24
  line1: str | Unset = UNSET
25
+ line2: str | Unset = UNSET
24
26
  city: str | Unset = UNSET
25
27
  state: str | Unset = UNSET
26
28
  postal_code: str | Unset = UNSET
@@ -28,6 +30,8 @@ class PatientAddress:
28
30
  def to_dict(self) -> dict[str, Any]:
29
31
  line1 = self.line1
30
32
 
33
+ line2 = self.line2
34
+
31
35
  city = self.city
32
36
 
33
37
  state = self.state
@@ -39,6 +43,8 @@ class PatientAddress:
39
43
  field_dict.update({})
40
44
  if line1 is not UNSET:
41
45
  field_dict["line1"] = line1
46
+ if line2 is not UNSET:
47
+ field_dict["line2"] = line2
42
48
  if city is not UNSET:
43
49
  field_dict["city"] = city
44
50
  if state is not UNSET:
@@ -53,6 +59,8 @@ class PatientAddress:
53
59
  d = dict(src_dict)
54
60
  line1 = d.pop("line1", UNSET)
55
61
 
62
+ line2 = d.pop("line2", UNSET)
63
+
56
64
  city = d.pop("city", UNSET)
57
65
 
58
66
  state = d.pop("state", UNSET)
@@ -61,6 +69,7 @@ class PatientAddress:
61
69
 
62
70
  patient_address = cls(
63
71
  line1=line1,
72
+ line2=line2,
64
73
  city=city,
65
74
  state=state,
66
75
  postal_code=postal_code,
@@ -1,7 +1,7 @@
1
- from enum import Enum
1
+ from enum import StrEnum
2
2
 
3
3
 
4
- class TokenRequestGrantType(str, Enum):
4
+ class TokenRequestGrantType(StrEnum):
5
5
  AUTHORIZATION_CODE = "authorization_code"
6
6
  REFRESH_TOKEN = "refresh_token"
7
7
  URNIETFPARAMSOAUTHGRANT_TYPEDEVICE_CODE = (
@@ -11,15 +11,20 @@ device-authorization flow, and refreshes consent tokens transparently.
11
11
  from __future__ import annotations
12
12
 
13
13
  import base64
14
+ import json
14
15
  import time
15
16
  from collections.abc import Callable, Mapping
16
17
  from dataclasses import dataclass
18
+ from datetime import datetime
19
+ from io import BytesIO
20
+ from pathlib import Path
17
21
  from typing import Any, TypeVar
18
22
  from urllib.parse import unquote
19
23
 
20
24
  import httpx
21
25
 
22
26
  from ._generated.api.default import (
27
+ attach_client_authorization as _attach_client_authorization,
23
28
  create_consent_link,
24
29
  exchange_token,
25
30
  get_consent as _get_consent,
@@ -28,6 +33,7 @@ from ._generated.api.default import (
28
33
  )
29
34
  from ._generated.client import AuthenticatedClient
30
35
  from ._generated.models import (
36
+ AttachClientAuthorizationBody,
31
37
  Consent,
32
38
  ConsentLinkRequest,
33
39
  ConsentList,
@@ -37,7 +43,7 @@ from ._generated.models import (
37
43
  TokenRequestGrantType,
38
44
  TokenResponse,
39
45
  )
40
- from ._generated.types import UNSET
46
+ from ._generated.types import UNSET, File
41
47
 
42
48
  DEFAULT_BASE_URL = "https://portal.hubble.ai"
43
49
  _DEVICE_CODE_GRANT = TokenRequestGrantType("urn:ietf:params:oauth:grant-type:device_code")
@@ -88,13 +94,16 @@ class Hubble:
88
94
  self,
89
95
  *,
90
96
  patient_ref: str | None = None,
97
+ on_behalf_of_organization_name: str | None = None,
91
98
  patient: Mapping[str, Any] | None = None,
92
99
  providers: list[Mapping[str, Any]] | None = None,
93
100
  ) -> DeviceAuthorizationResponse:
94
101
  """Start the patient link flow. Send the patient to the returned
95
102
  ``verification_uri_complete``, then call `wait_for_authorization()`.
96
103
 
97
- Optionally prefill the link. ``patient`` is a mapping of demographics
104
+ Optionally prefill the link. ``on_behalf_of_organization_name`` identifies
105
+ the organization your verified Hubble organization represents for this
106
+ link. ``patient`` is a mapping of demographics
98
107
  (``first_name``, ``last_name``, ``birth_date``, ``sex``, ``phone``,
99
108
  ``email``, and a nested ``address``); ``providers`` is a list of provider
100
109
  seeds (each ``{"name": ..., "phone"?: ...}``) to prefill the patient's
@@ -102,6 +111,8 @@ class Hubble:
102
111
  body: dict[str, Any] = {}
103
112
  if patient_ref is not None:
104
113
  body["patient_ref"] = patient_ref
114
+ if on_behalf_of_organization_name is not None:
115
+ body["on_behalf_of_organization_name"] = on_behalf_of_organization_name
105
116
  if patient is not None:
106
117
  body["patient"] = dict(patient)
107
118
  if providers is not None:
@@ -172,6 +183,59 @@ class Hubble:
172
183
  f"Could not revoke consent {consent_id}",
173
184
  )
174
185
 
186
+ def attach_authorization(
187
+ self,
188
+ consent_id: str,
189
+ *,
190
+ authorization: bytes | str | Path,
191
+ disclosers: list[Mapping[str, str]],
192
+ expires_at: str | datetime | None = None,
193
+ content_type: str = "application/pdf",
194
+ ) -> Consent:
195
+ """Attach a customer-supplied, already-signed HIPAA authorization to a
196
+ consent and grant delegated retrieval from the providers it names.
197
+
198
+ The signed artifact is the evidence, so the consent is granted at once
199
+ with no patient step, and the granted `Consent` is returned — each
200
+ discloser appears on it as a one-time source, ``pending`` until its
201
+ records are retrieved. Requires the ``byo_hipaa_authorization``
202
+ entitlement on your organization (a caller without it gets a `HubbleError`
203
+ wrapping the API's 403).
204
+
205
+ Args:
206
+ authorization: the signed file to upload, as raw ``bytes`` or a
207
+ filesystem path (``str``/``Path``) to read it from.
208
+ disclosers: the providers to disclose from — a list of mappings, each
209
+ with a ``name`` (e.g. a provider named General Hospital),
210
+ serialized to the JSON array the API expects.
211
+ expires_at: the authorization's asserted expiration (ISO 8601 string
212
+ or ``datetime``); omit if it does not expire.
213
+ """
214
+ if isinstance(authorization, (str, Path)):
215
+ path = Path(authorization)
216
+ data = path.read_bytes()
217
+ upload_name = path.name
218
+ else:
219
+ data = authorization
220
+ upload_name = "authorization"
221
+
222
+ expires = (
223
+ datetime.fromisoformat(expires_at) if isinstance(expires_at, str) else expires_at
224
+ )
225
+ # The multipart part must carry a non-empty filename or the server parses
226
+ # it as a text field instead of a file. The value is transport-only — the
227
+ # API stores the artifact under its own id and never reads this name.
228
+ body = AttachClientAuthorizationBody(
229
+ authorization=File(payload=BytesIO(data), file_name=upload_name, mime_type=content_type),
230
+ disclosers=json.dumps([dict(d) for d in disclosers]),
231
+ expires_at=expires if expires is not None else UNSET,
232
+ )
233
+ return _unwrap(
234
+ _attach_client_authorization.sync(id=consent_id, client=self._control, body=body),
235
+ Consent,
236
+ f"Could not attach authorization to consent {consent_id}",
237
+ )
238
+
175
239
  # ── reconstruct a session from stored tokens ──────────────────────────────
176
240
 
177
241
  def session(
@@ -3,7 +3,7 @@
3
3
  # none); this file and the build config are not.
4
4
  [project]
5
5
  name = "hubble-sdk-python"
6
- version = "0.0.3"
6
+ version = "0.1.0"
7
7
  description = "Python client for the Hubble Patient Access API."
8
8
  readme = "README.md"
9
9
  requires-python = ">=3.11"
@@ -15,7 +15,14 @@ import pytest
15
15
  import respx
16
16
 
17
17
  from hubble_sdk import ConsentTimeout, Hubble, HubbleError, TokenSet
18
- from hubble_sdk._generated.models import DeviceAuthorizationResponse
18
+ from hubble_sdk._generated.models import (
19
+ ConsentLinkRequest,
20
+ DeviceAuthorizationRequest,
21
+ DeviceAuthorizationResponse,
22
+ PatientDemographics,
23
+ ProviderSeed,
24
+ )
25
+ from hubble_sdk._generated.types import UNSET
19
26
 
20
27
  BASE = "https://portal.hubble.ai"
21
28
  BASIC = "Basic " + base64.b64encode(b"id:sec").decode()
@@ -41,7 +48,6 @@ CONSENT_DICT = {
41
48
  "status": "active",
42
49
  "patient_ref": "abc",
43
50
  "created_at": "2026-01-01T00:00:00Z",
44
- "granted_at": "2026-01-01T00:05:00Z",
45
51
  "expires_at": "2026-06-01T00:00:00Z",
46
52
  "sources": [{"provider": "General Hospital", "type": "live", "status": "available"}],
47
53
  "error": None,
@@ -67,6 +73,7 @@ def test_create_link_posts_json_with_basic_auth_and_nested_prefill():
67
73
 
68
74
  result = hub().create_link(
69
75
  patient_ref="abc",
76
+ on_behalf_of_organization_name="Acme Health",
70
77
  patient={"first_name": "Jane", "address": {"city": "Springfield"}},
71
78
  providers=[{"name": "Dr. Smith", "phone": "555-0100"}],
72
79
  )
@@ -77,11 +84,30 @@ def test_create_link_posts_json_with_basic_auth_and_nested_prefill():
77
84
  assert req.headers["Content-Type"].startswith("application/json")
78
85
  sent = json.loads(req.content)
79
86
  assert sent["patient_ref"] == "abc"
87
+ assert sent["on_behalf_of_organization_name"] == "Acme Health"
80
88
  assert sent["patient"]["first_name"] == "Jane"
81
89
  assert sent["patient"]["address"]["city"] == "Springfield"
82
90
  assert sent["providers"] == [{"name": "Dr. Smith", "phone": "555-0100"}]
83
91
 
84
92
 
93
+ def test_generated_request_models_preserve_existing_positional_arguments():
94
+ patient = PatientDemographics(first_name="Jane")
95
+ provider = ProviderSeed(name="Dr. Smith")
96
+
97
+ # These were valid positional calls before the represented-organization
98
+ # field was added. It must remain last in generated models so none of the
99
+ # existing arguments silently changes meaning.
100
+ link_request = ConsentLinkRequest("abc", patient, [provider])
101
+ form_request = DeviceAuthorizationRequest("abc", "Jane", "Rivera")
102
+
103
+ assert link_request.patient is patient
104
+ assert link_request.providers == [provider]
105
+ assert link_request.on_behalf_of_organization_name is UNSET
106
+ assert form_request.patient_first_name == "Jane"
107
+ assert form_request.patient_last_name == "Rivera"
108
+ assert form_request.on_behalf_of_organization_name is UNSET
109
+
110
+
85
111
  @respx.mock
86
112
  def test_create_link_raises_with_context_on_error():
87
113
  respx.post(f"{BASE}/api/v1/consents/oauth/device_authorization").mock(
@@ -136,6 +162,78 @@ def test_revoke_consent_surfaces_failure():
136
162
  hub().revoke_consent("cns_x")
137
163
 
138
164
 
165
+ GRANT_CONSENT_DICT = {
166
+ "id": "cns_1",
167
+ "status": "active",
168
+ "patient_ref": "abc",
169
+ "created_at": "2026-01-01T00:00:00Z",
170
+ "expires_at": "2027-01-01T00:00:00Z",
171
+ "sources": [
172
+ {"provider": "General Hospital", "type": "one_time", "status": "pending"},
173
+ {"provider": "City Clinic", "type": "one_time", "status": "pending"},
174
+ ],
175
+ "error": None,
176
+ }
177
+
178
+
179
+ @respx.mock
180
+ def test_attach_authorization_posts_multipart_and_returns_consent():
181
+ route = respx.post(f"{BASE}/api/v1/consents/cns_1/authorization").mock(
182
+ return_value=httpx.Response(200, json=GRANT_CONSENT_DICT)
183
+ )
184
+
185
+ consent = hub().attach_authorization(
186
+ "cns_1",
187
+ authorization=b"%PDF-1.7 signed",
188
+ disclosers=[{"name": "General Hospital"}, {"name": "City Clinic"}],
189
+ expires_at="2027-01-01T00:00:00",
190
+ )
191
+
192
+ # Returns the granted consent, with each discloser as a pending one-time source.
193
+ assert consent.id == "cns_1"
194
+ assert consent.status.value == "active"
195
+ assert [(s.provider, s.type_.value, s.status.value) for s in consent.sources] == [
196
+ ("General Hospital", "one_time", "pending"),
197
+ ("City Clinic", "one_time", "pending"),
198
+ ]
199
+
200
+ req = route.calls.last.request
201
+ assert req.headers["Authorization"] == BASIC
202
+ assert req.headers["Content-Type"].startswith("multipart/form-data")
203
+ body = req.content
204
+ assert b'name="authorization"' in body
205
+ assert b"%PDF-1.7 signed" in body
206
+ # disclosers travels as a JSON array string.
207
+ assert b'[{"name": "General Hospital"}, {"name": "City Clinic"}]' in body
208
+ assert b"2027-01-01T00:00:00" in body
209
+
210
+
211
+ @respx.mock
212
+ def test_attach_authorization_reads_a_file_path(tmp_path):
213
+ route = respx.post(f"{BASE}/api/v1/consents/cns_1/authorization").mock(
214
+ return_value=httpx.Response(200, json=GRANT_CONSENT_DICT)
215
+ )
216
+ pdf = tmp_path / "signed-auth.pdf"
217
+ pdf.write_bytes(b"%PDF-1.7 from disk")
218
+
219
+ hub().attach_authorization("cns_1", authorization=pdf, disclosers=[{"name": "X"}])
220
+
221
+ body = route.calls.last.request.content
222
+ assert b"%PDF-1.7 from disk" in body
223
+ # The path's name becomes the upload filename.
224
+ assert b'filename="signed-auth.pdf"' in body
225
+
226
+
227
+ @respx.mock
228
+ def test_attach_authorization_surfaces_not_entitled_403():
229
+ respx.post(f"{BASE}/api/v1/consents/cns_1/authorization").mock(
230
+ return_value=httpx.Response(403, json={"error": "not enabled for this"})
231
+ )
232
+
233
+ with pytest.raises(HubbleError, match="Could not attach authorization to consent cns_1"):
234
+ hub().attach_authorization("cns_1", authorization=b"x", disclosers=[{"name": "X"}])
235
+
236
+
139
237
  def test_base_url_trailing_slash_is_normalized():
140
238
  assert Hubble("id", "sec", base_url="https://staging.hubble.ai/").base_url == (
141
239
  "https://staging.hubble.ai"
@@ -1,100 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from collections.abc import Mapping
4
- from typing import Any, Self, TypeVar, cast
5
-
6
- from attrs import define as _attrs_define
7
- from attrs import field as _attrs_field
8
-
9
- from ..models.attach_client_authorization_response_201_status import (
10
- AttachClientAuthorizationResponse201Status,
11
- )
12
- from ..types import UNSET, Unset
13
-
14
- T = TypeVar("T", bound="AttachClientAuthorizationResponse201")
15
-
16
-
17
- @_attrs_define
18
- class AttachClientAuthorizationResponse201:
19
- """
20
- Attributes:
21
- consent_id (str | Unset):
22
- status (AttachClientAuthorizationResponse201Status | Unset):
23
- authorization_id (str | Unset):
24
- requests (list[str] | Unset):
25
- """
26
-
27
- consent_id: str | Unset = UNSET
28
- status: AttachClientAuthorizationResponse201Status | Unset = UNSET
29
- authorization_id: str | Unset = UNSET
30
- requests: list[str] | Unset = UNSET
31
- additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
32
-
33
- def to_dict(self) -> dict[str, Any]:
34
- consent_id = self.consent_id
35
-
36
- status: str | Unset = UNSET
37
- if not isinstance(self.status, Unset):
38
- status = self.status.value
39
-
40
- authorization_id = self.authorization_id
41
-
42
- requests: list[str] | Unset = UNSET
43
- if not isinstance(self.requests, Unset):
44
- requests = self.requests
45
-
46
- field_dict: dict[str, Any] = {}
47
- field_dict.update(self.additional_properties)
48
- field_dict.update({})
49
- if consent_id is not UNSET:
50
- field_dict["consent_id"] = consent_id
51
- if status is not UNSET:
52
- field_dict["status"] = status
53
- if authorization_id is not UNSET:
54
- field_dict["authorization_id"] = authorization_id
55
- if requests is not UNSET:
56
- field_dict["requests"] = requests
57
-
58
- return field_dict
59
-
60
- @classmethod
61
- def from_dict(cls, src_dict: Mapping[str, Any]) -> Self:
62
- d = dict(src_dict)
63
- consent_id = d.pop("consent_id", UNSET)
64
-
65
- _status = d.pop("status", UNSET)
66
- status: AttachClientAuthorizationResponse201Status | Unset
67
- if isinstance(_status, Unset):
68
- status = UNSET
69
- else:
70
- status = AttachClientAuthorizationResponse201Status(_status)
71
-
72
- authorization_id = d.pop("authorization_id", UNSET)
73
-
74
- requests = cast(list[str], d.pop("requests", UNSET))
75
-
76
- attach_client_authorization_response_201 = cls(
77
- consent_id=consent_id,
78
- status=status,
79
- authorization_id=authorization_id,
80
- requests=requests,
81
- )
82
-
83
- attach_client_authorization_response_201.additional_properties = d
84
- return attach_client_authorization_response_201
85
-
86
- @property
87
- def additional_keys(self) -> list[str]:
88
- return list(self.additional_properties.keys())
89
-
90
- def __getitem__(self, key: str) -> Any:
91
- return self.additional_properties[key]
92
-
93
- def __setitem__(self, key: str, value: Any) -> None:
94
- self.additional_properties[key] = value
95
-
96
- def __delitem__(self, key: str) -> None:
97
- del self.additional_properties[key]
98
-
99
- def __contains__(self, key: str) -> bool:
100
- return key in self.additional_properties
@@ -1,8 +0,0 @@
1
- from enum import Enum
2
-
3
-
4
- class AttachClientAuthorizationResponse201Status(str, Enum):
5
- GRANTED = "granted"
6
-
7
- def __str__(self) -> str:
8
- return str(self.value)