hubble-sdk-python 0.3.1__tar.gz → 0.3.2__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 (81) hide show
  1. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/CHANGELOG.md +7 -0
  2. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/PKG-INFO +22 -1
  3. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/README.md +21 -0
  4. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_hubble.py +25 -10
  5. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/pyproject.toml +1 -1
  6. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/tests/test_hubble.py +59 -0
  7. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/.gitignore +0 -0
  8. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/LICENSE +0 -0
  9. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/config.yaml +0 -0
  10. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/__init__.py +0 -0
  11. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_generated/__init__.py +0 -0
  12. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_generated/api/__init__.py +0 -0
  13. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_generated/api/default/__init__.py +0 -0
  14. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_generated/api/default/attach_client_authorization.py +0 -0
  15. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_generated/api/default/create_consent.py +0 -0
  16. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_generated/api/default/create_consent_link.py +0 -0
  17. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_generated/api/default/exchange_token.py +0 -0
  18. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_generated/api/default/export_patient_fhir.py +0 -0
  19. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_generated/api/default/export_patient_pdf.py +0 -0
  20. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_generated/api/default/get_application_token.py +0 -0
  21. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_generated/api/default/get_consent.py +0 -0
  22. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_generated/api/default/get_patient_pdf_export.py +0 -0
  23. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_generated/api/default/list_consents.py +0 -0
  24. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_generated/api/default/read_fhir.py +0 -0
  25. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_generated/api/default/revoke_consent.py +0 -0
  26. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_generated/client.py +0 -0
  27. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_generated/errors.py +0 -0
  28. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_generated/models/__init__.py +0 -0
  29. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_generated/models/api_error.py +0 -0
  30. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_generated/models/application_token_error.py +0 -0
  31. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_generated/models/application_token_error_error.py +0 -0
  32. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_generated/models/application_token_request.py +0 -0
  33. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_generated/models/application_token_response.py +0 -0
  34. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_generated/models/attach_client_authorization_body.py +0 -0
  35. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_generated/models/client_supplied_discloser.py +0 -0
  36. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_generated/models/client_supplied_discloser_facility_type.py +0 -0
  37. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_generated/models/consent.py +0 -0
  38. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_generated/models/consent_error.py +0 -0
  39. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_generated/models/consent_link_request.py +0 -0
  40. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_generated/models/consent_list.py +0 -0
  41. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_generated/models/consent_source.py +0 -0
  42. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_generated/models/consent_source_status.py +0 -0
  43. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_generated/models/consent_source_type.py +0 -0
  44. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_generated/models/consent_status.py +0 -0
  45. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_generated/models/created_consent.py +0 -0
  46. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_generated/models/device_authorization_request.py +0 -0
  47. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_generated/models/device_authorization_response.py +0 -0
  48. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_generated/models/export_patient_fhir_prefer.py +0 -0
  49. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_generated/models/export_patient_fhir_response_202.py +0 -0
  50. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_generated/models/export_patient_fhir_response_202_status.py +0 -0
  51. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_generated/models/export_patient_pdf_combined.py +0 -0
  52. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_generated/models/export_patient_pdf_hubble_version.py +0 -0
  53. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_generated/models/export_patient_pdf_prefer.py +0 -0
  54. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_generated/models/export_patient_pdf_response_202.py +0 -0
  55. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_generated/models/export_patient_pdf_response_202_status.py +0 -0
  56. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_generated/models/facility_address.py +0 -0
  57. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_generated/models/facility_address_state.py +0 -0
  58. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_generated/models/fhir_resource.py +0 -0
  59. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_generated/models/get_patient_pdf_export_download.py +0 -0
  60. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_generated/models/get_patient_pdf_export_response_200.py +0 -0
  61. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_generated/models/get_patient_pdf_export_response_200_status.py +0 -0
  62. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_generated/models/o_auth_error.py +0 -0
  63. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_generated/models/o_auth_error_error.py +0 -0
  64. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_generated/models/operation_outcome.py +0 -0
  65. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_generated/models/operation_outcome_issue_item.py +0 -0
  66. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_generated/models/patient_address.py +0 -0
  67. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_generated/models/patient_address_state.py +0 -0
  68. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_generated/models/patient_demographics.py +0 -0
  69. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_generated/models/provider_seed.py +0 -0
  70. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_generated/models/token_request.py +0 -0
  71. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_generated/models/token_request_grant_type.py +0 -0
  72. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_generated/models/token_response.py +0 -0
  73. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_generated/types.py +0 -0
  74. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_params.py +0 -0
  75. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/_webhooks.py +0 -0
  76. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/hubble_sdk/py.typed +0 -0
  77. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/pydoc-markdown.yml +0 -0
  78. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/templates/property_templates/enum_property.py.jinja +0 -0
  79. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/templates/str_enum.py.jinja +0 -0
  80. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/tests/test_params.py +0 -0
  81. {hubble_sdk_python-0.3.1 → hubble_sdk_python-0.3.2}/tests/test_webhooks.py +0 -0
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.3.2](https://github.com/hubble-dot-ai/hubble-sdks/compare/hubble-sdk-python-v0.3.1...hubble-sdk-python-v0.3.2) (2026-11-05)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * expose combined PDF exports through high-level clients ([#85](https://github.com/hubble-dot-ai/hubble-sdks/issues/85)) ([1365b65](https://github.com/hubble-dot-ai/hubble-sdks/commit/1365b65248e81137b8f814cc895440e1ba009400))
9
+
3
10
  ## [0.3.1](https://github.com/hubble-dot-ai/hubble-sdks/compare/hubble-sdk-python-v0.3.0...hubble-sdk-python-v0.3.1) (2026-11-05)
4
11
 
5
12
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: hubble-sdk-python
3
- Version: 0.3.1
3
+ Version: 0.3.2
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
@@ -81,6 +81,27 @@ export = hub.export_pdf(consent_id, connection=connection_id) # one sour
81
81
 
82
82
  Reuse one `Hubble` client so its token is reused too.
83
83
 
84
+ ### Combined PDF exports
85
+
86
+ Pass `version`, `connection`, and `combined=True` to download one PDF containing
87
+ the source's summary and documents. The helper requests background generation,
88
+ waits for completion, and downloads the file.
89
+
90
+ ```python
91
+ export = hub.export_pdf(
92
+ consent_id,
93
+ version="2026-11-01",
94
+ connection=connection_id,
95
+ combined=True,
96
+ )
97
+ # export.content contains the PDF bytes; export.content_type is "application/pdf".
98
+ ```
99
+
100
+ `session.export_pdf` accepts the same options without the consent ID. With a
101
+ version and `combined` omitted or false, the result is a records archive (ZIP).
102
+ Calls without a version keep their existing behavior. `timeout` controls the
103
+ wait for background generation in seconds and defaults to ten minutes.
104
+
84
105
  ### The device flow
85
106
 
86
107
  Existing integrations can keep using the device flow, which waits for the
@@ -49,6 +49,27 @@ export = hub.export_pdf(consent_id, connection=connection_id) # one sour
49
49
 
50
50
  Reuse one `Hubble` client so its token is reused too.
51
51
 
52
+ ### Combined PDF exports
53
+
54
+ Pass `version`, `connection`, and `combined=True` to download one PDF containing
55
+ the source's summary and documents. The helper requests background generation,
56
+ waits for completion, and downloads the file.
57
+
58
+ ```python
59
+ export = hub.export_pdf(
60
+ consent_id,
61
+ version="2026-11-01",
62
+ connection=connection_id,
63
+ combined=True,
64
+ )
65
+ # export.content contains the PDF bytes; export.content_type is "application/pdf".
66
+ ```
67
+
68
+ `session.export_pdf` accepts the same options without the consent ID. With a
69
+ version and `combined` omitted or false, the result is a records archive (ZIP).
70
+ Calls without a version keep their existing behavior. `timeout` controls the
71
+ wait for background generation in seconds and defaults to ten minutes.
72
+
52
73
  ### The device flow
53
74
 
54
75
  Existing integrations can keep using the device flow, which waits for the
@@ -132,6 +132,19 @@ def _require_fhir_namespace(base_url: str, url: httpx.URL, shown: str) -> None:
132
132
  raise HubbleError(f"Invalid FHIR path: {shown!r}")
133
133
 
134
134
 
135
+ def _pdf_export_url(
136
+ base_url: str, connection: str | None, version: str | None, combined: bool
137
+ ) -> httpx.URL:
138
+ if combined and (not version or not connection):
139
+ raise HubbleError("A combined PDF requires version and connection.")
140
+ url = _fhir_url(base_url, "Patient/$pdf")
141
+ if connection is not None:
142
+ url = url.copy_set_param("connection", connection)
143
+ if combined:
144
+ url = url.copy_set_param("combined", "true")
145
+ return url
146
+
147
+
135
148
  def _pdf_export(
136
149
  resp: httpx.Response, file: httpx.Response | None = None, version: str | None = None
137
150
  ) -> PdfExport:
@@ -330,6 +343,7 @@ class Hubble:
330
343
  *,
331
344
  connection: str | None = None,
332
345
  version: str | None = None,
346
+ combined: bool = False,
333
347
  timeout: float = _EXPORT_TIMEOUT,
334
348
  ) -> PdfExport:
335
349
  """The patient's records as PDF via ``Patient/$pdf``: a ZIP of per-source
@@ -337,10 +351,10 @@ class Hubble:
337
351
 
338
352
  Pass ``version`` (a ``Hubble-Version``, such as ``"2026-11-01"``) for that
339
353
  version's export instead. It is built in the background, so this waits up
340
- to ``timeout`` seconds for it, then downloads it."""
341
- url = _fhir_url(self.base_url, "Patient/$pdf")
342
- if connection is not None:
343
- url = url.copy_set_param("connection", connection)
354
+ to ``timeout`` seconds for it, then downloads it. Set ``combined=True``
355
+ with ``version`` and ``connection`` for one PDF containing that source's
356
+ summary and documents instead of the archive. Defaults to false."""
357
+ url = _pdf_export_url(self.base_url, connection, version, combined)
344
358
  if version is None:
345
359
  return _pdf_export(self._consent_get(consent_id, url, None))
346
360
  return _versioned_pdf_export(
@@ -676,6 +690,7 @@ class PatientSession:
676
690
  *,
677
691
  connection: str | None = None,
678
692
  version: str | None = None,
693
+ combined: bool = False,
679
694
  timeout: float = _EXPORT_TIMEOUT,
680
695
  ) -> PdfExport:
681
696
  """Export the patient's records as PDF via ``Patient/$pdf``.
@@ -689,11 +704,11 @@ class PatientSession:
689
704
 
690
705
  Pass ``version`` (a ``Hubble-Version``, such as ``"2026-11-01"``) for that
691
706
  version's export instead. It is built in the background, so this waits up
692
- to ``timeout`` seconds for it, then downloads it."""
707
+ to ``timeout`` seconds for it, then downloads it. Set ``combined=True``
708
+ with ``version`` and ``connection`` for one PDF containing that source's
709
+ summary and documents instead of the archive. Defaults to false."""
693
710
  retry = self.refresh_token is not None
694
- url = _fhir_url(self._hubble.base_url, "Patient/$pdf")
695
- if connection is not None:
696
- url = url.copy_set_param("connection", connection)
711
+ url = _pdf_export_url(self._hubble.base_url, connection, version, combined)
697
712
  if version is None:
698
713
  return _pdf_export(self._fhir_request(url, retry))
699
714
  return _versioned_pdf_export(
@@ -779,8 +794,8 @@ class PdfExport:
779
794
  ``content`` is the raw body — a ZIP of per-source PDFs (with a
780
795
  ``manifest.json`` naming each file and marking it complete/partial/failed),
781
796
  or a single PDF when ``connection`` was set. With a ``version``, it is that
782
- version's export, described in the API reference. The counts come from the
783
- response headers; a non-zero ``sources_failed`` means some records are
797
+ version's archive, or one PDF if ``combined=True`` was requested. The counts
798
+ come from the response headers; a non-zero ``sources_failed`` means some records are
784
799
  missing. ``version`` is the ``Hubble-Version`` served, or ``None`` when none
785
800
  was asked for."""
786
801
 
@@ -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.3.1"
6
+ version = "0.3.2"
7
7
  description = "Python client for the Hubble Patient Access API."
8
8
  readme = "README.md"
9
9
  requires-python = ">=3.11"
@@ -493,6 +493,64 @@ def _versioned_export_routes(poll_url: str = "/api/v1/fhir/exports/exp_1"):
493
493
  return start, poll, download
494
494
 
495
495
 
496
+ @respx.mock
497
+ @pytest.mark.parametrize("application", [True, False])
498
+ def test_combined_pdf_download_through_high_level_client(application):
499
+ if application:
500
+ _token_route(return_value=httpx.Response(200, json=APP_TOKEN))
501
+ start, poll, download = _versioned_export_routes()
502
+ download.mock(return_value=httpx.Response(
503
+ 200, content=b"%PDF combined", headers={"Content-Type": "application/pdf"}
504
+ ))
505
+ client = hub() if application else hub().session("cat_abc")
506
+ args = ("cns_1",) if application else ()
507
+
508
+ result = client.export_pdf(*args, version=VERSION, connection="conn_1", combined=True)
509
+
510
+ request = start.calls.last.request
511
+ assert request.url.params["combined"] == "true"
512
+ assert request.url.params["connection"] == "conn_1"
513
+ assert request.headers["Hubble-Version"] == VERSION
514
+ assert request.headers["Prefer"] == "respond-async"
515
+ assert request.headers["Authorization"] == ("Bearer app_tok_1" if application else "Bearer cat_abc")
516
+ assert poll.call_count == 2
517
+ if application:
518
+ assert request.url.params["consent"] == "cns_1"
519
+ assert all(c.request.url.params["consent"] == "cns_1" for c in poll.calls)
520
+ assert "Authorization" not in download.calls.last.request.headers
521
+ assert result.content == b"%PDF combined"
522
+ assert result.content_type == "application/pdf"
523
+ assert result.version == VERSION
524
+
525
+
526
+ @respx.mock
527
+ @pytest.mark.parametrize("application", [True, False])
528
+ def test_combined_false_preserves_archive_behavior(application):
529
+ if application:
530
+ _token_route(return_value=httpx.Response(200, json=APP_TOKEN))
531
+ start, _, _ = _versioned_export_routes()
532
+ client = hub() if application else hub().session("cat_abc")
533
+ args = ("cns_1",) if application else ()
534
+
535
+ result = client.export_pdf(*args, version=VERSION, combined=False)
536
+
537
+ assert "combined" not in start.calls.last.request.url.params
538
+ assert result.content_type == "application/zip"
539
+
540
+
541
+ @respx.mock
542
+ @pytest.mark.parametrize("application", [True, False])
543
+ @pytest.mark.parametrize("options", [
544
+ {}, {"connection": "conn_1"}, {"version": VERSION}, {"version": VERSION, "connection": ""},
545
+ ])
546
+ def test_combined_requires_version_and_connection_before_any_request(application, options):
547
+ client = hub() if application else hub().session("cat_abc")
548
+ args = ("cns_1",) if application else ()
549
+ with pytest.raises(HubbleError, match="A combined PDF requires version and connection"):
550
+ client.export_pdf(*args, combined=True, **options)
551
+ assert not respx.calls
552
+
553
+
496
554
  @respx.mock
497
555
  def test_export_pdf_with_a_version_polls_then_downloads_without_the_token():
498
556
  start, poll, download = _versioned_export_routes()
@@ -504,6 +562,7 @@ def test_export_pdf_with_a_version_polls_then_downloads_without_the_token():
504
562
  assert started.headers["Hubble-Version"] == VERSION
505
563
  assert started.headers["Prefer"] == "respond-async"
506
564
  assert started.url.params["connection"] == "epic"
565
+ assert "combined" not in started.url.params
507
566
  assert poll.call_count == 2
508
567
  assert poll.calls.last.request.headers["Authorization"] == "Bearer cat_abc"
509
568
  assert "Authorization" not in download.calls.last.request.headers