From a1de5c203813703bc576016e086eb4aee99ca74d Mon Sep 17 00:00:00 2001
From: "fern-api[bot]" <115122769+fern-api[bot]@users.noreply.github.com>
Date: Fri, 4 Sep 2026 16:36:23 +0000
Subject: [PATCH 1/4] [fern-generated] Update SDK
Generated by Fern
CLI Version: unknown
Generators:
- fernapi/fern-python-sdk: 5.18.1
---
.fern/metadata.json | 6 +-
poetry.lock | 258 +--
pyproject.toml | 5 +-
reference.md | 1173 +++++++++-
src/phenoml/__init__.py | 3 +
src/phenoml/client.py | 19 +
src/phenoml/core/client_wrapper.py | 2 +-
src/phenoml/lang2fhir_batch/__init__.py | 93 +
src/phenoml/lang2fhir_batch/client.py | 898 ++++++++
.../lang2fhir_batch/errors/__init__.py | 59 +
.../errors/bad_request_error.py | 10 +
.../errors/client_closed_request_error.py | 10 +
.../lang2fhir_batch/errors/conflict_error.py | 10 +
.../errors/content_too_large_error.py | 10 +
.../errors/gateway_timeout_error.py | 10 +
.../errors/internal_server_error.py | 10 +
.../lang2fhir_batch/errors/not_found_error.py | 10 +
.../errors/unauthorized_error.py | 10 +
src/phenoml/lang2fhir_batch/raw_client.py | 2001 +++++++++++++++++
src/phenoml/lang2fhir_batch/types/__init__.py | 65 +
.../lang2fhir_batch/types/batch_counts.py | 27 +
.../lang2fhir_batch/types/batch_error.py | 40 +
.../types/batch_item_status.py | 64 +
.../types/batch_item_status_status.py | 5 +
.../lang2fhir_batch/types/batch_job.py | 73 +
.../lang2fhir_batch/types/batch_job_status.py | 5 +
.../types/job_detail_response.py | 36 +
.../types/job_list_response.py | 33 +
.../types/results_page_response.py | 33 +
.../types/upload_item_response.py | 40 +
src/phenoml/profiles/__init__.py | 21 +-
src/phenoml/profiles/client.py | 19 +
src/phenoml/profiles/errors/__init__.py | 11 +-
src/phenoml/profiles/errors/conflict_error.py | 10 +
src/phenoml/profiles/profiles/client.py | 142 +-
src/phenoml/profiles/profiles/raw_client.py | 85 +-
src/phenoml/profiles/types/__init__.py | 6 +
.../profiles/types/profile_get_response.py | 6 +-
.../profiles/types/profile_list_response.py | 2 +-
src/phenoml/profiles/types/profile_summary.py | 42 +-
.../types/profile_version_create_request.py | 8 +
.../types/profile_version_list_response.py | 20 +
src/phenoml/profiles/versions/__init__.py | 4 +
src/phenoml/profiles/versions/client.py | 392 ++++
src/phenoml/profiles/versions/raw_client.py | 874 +++++++
tests/wire/test_lang2FhirBatch.py | 78 +
tests/wire/test_profiles_profiles.py | 32 +-
tests/wire/test_profiles_versions.py | 44 +
wiremock/wiremock-mappings.json | 375 ++-
49 files changed, 6944 insertions(+), 245 deletions(-)
create mode 100644 src/phenoml/lang2fhir_batch/__init__.py
create mode 100644 src/phenoml/lang2fhir_batch/client.py
create mode 100644 src/phenoml/lang2fhir_batch/errors/__init__.py
create mode 100644 src/phenoml/lang2fhir_batch/errors/bad_request_error.py
create mode 100644 src/phenoml/lang2fhir_batch/errors/client_closed_request_error.py
create mode 100644 src/phenoml/lang2fhir_batch/errors/conflict_error.py
create mode 100644 src/phenoml/lang2fhir_batch/errors/content_too_large_error.py
create mode 100644 src/phenoml/lang2fhir_batch/errors/gateway_timeout_error.py
create mode 100644 src/phenoml/lang2fhir_batch/errors/internal_server_error.py
create mode 100644 src/phenoml/lang2fhir_batch/errors/not_found_error.py
create mode 100644 src/phenoml/lang2fhir_batch/errors/unauthorized_error.py
create mode 100644 src/phenoml/lang2fhir_batch/raw_client.py
create mode 100644 src/phenoml/lang2fhir_batch/types/__init__.py
create mode 100644 src/phenoml/lang2fhir_batch/types/batch_counts.py
create mode 100644 src/phenoml/lang2fhir_batch/types/batch_error.py
create mode 100644 src/phenoml/lang2fhir_batch/types/batch_item_status.py
create mode 100644 src/phenoml/lang2fhir_batch/types/batch_item_status_status.py
create mode 100644 src/phenoml/lang2fhir_batch/types/batch_job.py
create mode 100644 src/phenoml/lang2fhir_batch/types/batch_job_status.py
create mode 100644 src/phenoml/lang2fhir_batch/types/job_detail_response.py
create mode 100644 src/phenoml/lang2fhir_batch/types/job_list_response.py
create mode 100644 src/phenoml/lang2fhir_batch/types/results_page_response.py
create mode 100644 src/phenoml/lang2fhir_batch/types/upload_item_response.py
create mode 100644 src/phenoml/profiles/errors/conflict_error.py
create mode 100644 src/phenoml/profiles/types/profile_version_create_request.py
create mode 100644 src/phenoml/profiles/types/profile_version_list_response.py
create mode 100644 src/phenoml/profiles/versions/__init__.py
create mode 100644 src/phenoml/profiles/versions/client.py
create mode 100644 src/phenoml/profiles/versions/raw_client.py
create mode 100644 tests/wire/test_lang2FhirBatch.py
create mode 100644 tests/wire/test_profiles_versions.py
diff --git a/.fern/metadata.json b/.fern/metadata.json
index 50a967f..1241008 100644
--- a/.fern/metadata.json
+++ b/.fern/metadata.json
@@ -1,5 +1,5 @@
{
- "cliVersion": "5.106.0",
+ "cliVersion": "5.114.0",
"generatorName": "fernapi/fern-python-sdk",
"generatorVersion": "5.18.1",
"generatorConfig": {
@@ -8,10 +8,10 @@
"enabled": true
}
},
- "originGitCommit": "4a08550f5db230949c7423d0ce5aa7055e8f0d65",
+ "originGitCommit": "e2cfa711e7e5ad04621efc35d12dcfd7d060040d",
"originGitCommitIsDirty": true,
"invokedBy": "ci",
"requestedVersion": "AUTO",
"ciProvider": "unknown",
- "sdkVersion": "16.11.0"
+ "sdkVersion": "0.0.0.dev0"
}
\ No newline at end of file
diff --git a/poetry.lock b/poetry.lock
index c3a2b77..435fa6a 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -188,20 +188,20 @@ files = [
[[package]]
name = "anyio"
-version = "4.14.2"
+version = "4.15.0"
description = "High-level concurrency and networking framework on top of asyncio or Trio"
optional = false
python-versions = ">=3.10"
groups = ["main"]
files = [
- {file = "anyio-4.14.2-py3-none-any.whl", hash = "sha256:9f505dda5ac9f0c8309b5e8bd445a8c2bf7246f3ce950121e45ea15bc41d1494"},
- {file = "anyio-4.14.2.tar.gz", hash = "sha256:cfa139f3ed1a23ee8f88a145ddb5ac7605b8bbfd8592baacd7ce3d8bb4313c7f"},
+ {file = "anyio-4.15.0-py3-none-any.whl", hash = "sha256:7ecd9937369ffce8bba0b5ccb9b3a9507b101b0ed50256aecfbab27e6c2acb99"},
+ {file = "anyio-4.15.0.tar.gz", hash = "sha256:b5c620ed540725e2579c31b17bb995b3bf02c9281c9cace04c7d186380bab85e"},
]
[package.dependencies]
exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""}
idna = ">=2.8"
-typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""}
+typing_extensions = {version = ">=4.16.0", markers = "python_version < \"3.15\""}
[package.extras]
trio = ["trio (>=0.32.0)"]
@@ -1118,19 +1118,19 @@ files = [
[[package]]
name = "pydantic"
-version = "2.13.4"
+version = "2.13.5"
description = "Data validation using Python type hints"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
- {file = "pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba"},
- {file = "pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6"},
+ {file = "pydantic-2.13.5-py3-none-any.whl", hash = "sha256:346a034f080da3755d8e9cb5e00e8b07de1d39e4f6e2c87d8ab7cafa0b269a73"},
+ {file = "pydantic-2.13.5.tar.gz", hash = "sha256:51a9c5f7b2f8e636f04c6cada605d9b6a3bf1348fdf945a3d8869b19bba0ee08"},
]
[package.dependencies]
annotated-types = ">=0.6.0"
-pydantic-core = "2.46.4"
+pydantic-core = "2.46.5"
typing-extensions = ">=4.14.1"
typing-inspection = ">=0.4.2"
@@ -1140,132 +1140,132 @@ timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows
[[package]]
name = "pydantic-core"
-version = "2.46.4"
+version = "2.46.5"
description = "Core functionality for Pydantic validation and serialization"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
- {file = "pydantic_core-2.46.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:a396dcc17e5a0b164dbe026896245a4fa9ff402edca1dff0be3d53a517f74de4"},
- {file = "pydantic_core-2.46.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:da4b951fe36dc7c3a1ccb4e3cd1747c3542b8c9ceede8fc86cae054e764485f5"},
- {file = "pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb63e0198ca18aad131c089b9204c23079c3afa95487e561f4c522d519e55aba"},
- {file = "pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f47286a97f0bc9b8859519809077b91b2cefe4ae47fcbf5e466a009c1c5d742b"},
- {file = "pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:905a0ed8ea6f2d61c1738835f99b699348d7857379083e5fc497fa0c967a407c"},
- {file = "pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea793e075b70290d89d8142074262885d3f7da19634845135751bd6344f73b50"},
- {file = "pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:395aebd9183f9d112f569aeb5b2214d1a10a33bec8456447f7fbdfa51d38d4cd"},
- {file = "pydantic_core-2.46.4-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:b078afbc25f3a1436c7a1d2cd3e322497ee99615ba97c563566fdf46aff1ee01"},
- {file = "pydantic_core-2.46.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f747929cf940cddb5b3668a390056ddd5ba2e5010615ea2dcf4f9c4f3ab8791d"},
- {file = "pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:daa27d92c36f24388fe3ad306b174781c747627f134452e4f128ea00ce1fe8c4"},
- {file = "pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:19e51f073cd3df251856a8a4189fbdf1de4012c3ebacfb1884f94f1eb406079f"},
- {file = "pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1747f85cee84c26985853c6f3d9bd3e75da5212912443fa111c113b9c246f39"},
- {file = "pydantic_core-2.46.4-cp310-cp310-win32.whl", hash = "sha256:2f84c03c8607173d16b5a854ec68a2f9079ae03237a54fb506d13af47e1d018d"},
- {file = "pydantic_core-2.46.4-cp310-cp310-win_amd64.whl", hash = "sha256:8358a950c8909158e3df31538a7e4edc2d7265a7c54b47f0864d9e5bae9dcebf"},
- {file = "pydantic_core-2.46.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0e96592440881c74a213e5ad528e2b24d3d4f940de2766bed9010ab1d9e51594"},
- {file = "pydantic_core-2.46.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0d65b8c354be7fb5f720c3caa8bc940bc2d20ce749c8e06135f07f8ed95dd7c"},
- {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bfb192b3f4b9e8a89b6277b6ce787564f62cfd272055f6e685726b111dc7826"},
- {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9037063db01f09b09e237c282b6792bd4da634b5402c4e7f0c61effed7701a04"},
- {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc010ab034c8c7452522748bf937df58020d256ccae0874463d1f4d01758af8e"},
- {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5dac79fa1614d1e06ca695109c6105923bd9c7d1d6c918d4e637b7e6b32fd3"},
- {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9fa868638bf362d3d138ea55829cefb3d5f4b0d7f142234382a15e2485dbec4"},
- {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:17299feefe090f2caa5b8e37222bb5f663e4935a8bfa6931d4102e5df1a9f398"},
- {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4c63ebc82684aa89d9a3bcbd13d515b3be44250dc68dd3bd81526c1cb31286c3"},
- {file = "pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:aaa2a54443eff1950ba5ddc6b6ccda0d9c84a364276a62f969bdf2a390650848"},
- {file = "pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:18e5ceec2ab67e6d5f1a9085e5a24c9c4e2ac4545730bfe668680bca05e555f3"},
- {file = "pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a0f62d0a58f4e7da165457e995725421e0064f2255d8eccebc49f41bbc23b109"},
- {file = "pydantic_core-2.46.4-cp311-cp311-win32.whl", hash = "sha256:041bde0a48fd37cf71cab1c9d56d3e8625a3793fef1f7dd232b3ff37e978ecda"},
- {file = "pydantic_core-2.46.4-cp311-cp311-win_amd64.whl", hash = "sha256:6f2eeda33a839975441c86a4119e1383c50b47faf0cbb5176985565c6bb02c33"},
- {file = "pydantic_core-2.46.4-cp311-cp311-win_arm64.whl", hash = "sha256:14f4c5d6db102bd796a627bbb3a17b4cf4574b9ae861d8b7c9a9661c6dd3362d"},
- {file = "pydantic_core-2.46.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2"},
- {file = "pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f"},
- {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7"},
- {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a233125ac121aa3ffba9a2b59edfc4a985a76092dc8279586ab4b71390875e7"},
- {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b712b53160b79a5850310b912a5ef8e57e56947c8ad690c227f5c9d7e561712"},
- {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9401557acd873c3a7f3eb9383edef8ac4968f9510e340f4808d427e75667e7b4"},
- {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:926c9541b14b12b1681dca8a0b75feb510b06c6341b70a8e500c2fdcff837cce"},
- {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:56cb4851bcaf3d117eddcef4fe66afd750a50274b0da8e22be256d10e5611987"},
- {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c68fcd102d71ea85c5b2dfac3f4f8476eff42a9e078fd5faefff6d145063536b"},
- {file = "pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b2f69dec1725e79a012d920df1707de5caf7ed5e08f3be4435e25803efc47458"},
- {file = "pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8d0820e8192167f80d88d64038e609c31452eeca865b4e1d9950a27a4609b00b"},
- {file = "pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fbdb89b3e1c94a30cc5edfce477c6e6a5dc4d8f84665b455c27582f211a1c72c"},
- {file = "pydantic_core-2.46.4-cp312-cp312-win32.whl", hash = "sha256:9aa768456404a8bf48a4406685ac2bec8e72b62c69313734fa3b73cf33b3a894"},
- {file = "pydantic_core-2.46.4-cp312-cp312-win_amd64.whl", hash = "sha256:e9c26f834c65f5752f3f06cb08cb86a913ceb7274d0db6e267808a708b46bc89"},
- {file = "pydantic_core-2.46.4-cp312-cp312-win_arm64.whl", hash = "sha256:4fc73cb559bdb54b1134a706a2802a4cddd27a0633f5abb7e53056268751ac6a"},
- {file = "pydantic_core-2.46.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008"},
- {file = "pydantic_core-2.46.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4"},
- {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76"},
- {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3"},
- {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76"},
- {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4"},
- {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a"},
- {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262"},
- {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e"},
- {file = "pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd"},
- {file = "pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be"},
- {file = "pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d"},
- {file = "pydantic_core-2.46.4-cp313-cp313-win32.whl", hash = "sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb"},
- {file = "pydantic_core-2.46.4-cp313-cp313-win_amd64.whl", hash = "sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292"},
- {file = "pydantic_core-2.46.4-cp313-cp313-win_arm64.whl", hash = "sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d"},
- {file = "pydantic_core-2.46.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:428e04521a40150c85216fc8b85e8d39fece235a9cf5e383761238c7fa9b96fb"},
- {file = "pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23ace664830ee0bfe014a0c7bc248b1f7f25ed7ad103852c317624a1083af462"},
- {file = "pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce5c1d2a8b27468f433ca974829c44060b8097eedc39933e3c206a90ee49c4a9"},
- {file = "pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7283d57845ecf5a163403eb0702dfc220cc4fbdd18919cb5ccea4f95ee1cdab4"},
- {file = "pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8daafc69c93ee8a0204506a3b6b30f586ef54028f52aeeeb5c4cfc5184fd5914"},
- {file = "pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2213145bcc2ba85884d0ac63d222fece9209678f77b9b4d76f054c561adb28"},
- {file = "pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b"},
- {file = "pydantic_core-2.46.4-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:c1b3f518abeca3aa13c712fd202306e145abf59a18b094a6bafb2d2bbf59192c"},
- {file = "pydantic_core-2.46.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a7dd0b3ee80d90150e3495a3a13ac34dbcbfd4f012996a6a1d8900e91b5c0fb"},
- {file = "pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:3fb702cd90b0446a3a1c5e470bfa0dd23c0233b676a9099ddcc964fa6ca13898"},
- {file = "pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b8458003118a712e66286df6a707db01c52c0f52f7db8e4a38f0da1d3b94fc4e"},
- {file = "pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:372429a130e469c9cd698925ce5fc50940b7a1336b0d82038e63d5bbc4edc519"},
- {file = "pydantic_core-2.46.4-cp314-cp314-win32.whl", hash = "sha256:85bb3611ff1802f3ee7fdd7dbff26b56f343fb432d57a4728fdd49b6ef35e2f4"},
- {file = "pydantic_core-2.46.4-cp314-cp314-win_amd64.whl", hash = "sha256:811ff8e9c313ab425368bcbb36e5c4ebd7108c2bbf4e4089cfbb0b01eff63fac"},
- {file = "pydantic_core-2.46.4-cp314-cp314-win_arm64.whl", hash = "sha256:bfec22eab3c8cc2ceec0248aec886624116dc079afa027ecc8ad4a7e62010f8a"},
- {file = "pydantic_core-2.46.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:af8244b2bef6aaad6d92cda81372de7f8c8d36c9f0c3ea36e827c60e7d9467a0"},
- {file = "pydantic_core-2.46.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a4330cdbc57162e4b3aa303f588ba752257694c9c9be3e7ebb11b4aca659b5d"},
- {file = "pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c61fc04a3d840155ff08e475a04809278972fe6aef51e2720554e96367e34b"},
- {file = "pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c50f2528cf200c5eed56faf3f4e22fcd5f38c157a8b78576e6ba3168ec35f000"},
- {file = "pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cbe8b01f948de4286c74cdd6c667aceb38f5c1e26f0693b3983d9d74887c65e"},
- {file = "pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:617d7e2ca7dcb8c5cf6bcb8c59b8832c94b36196bbf1cbd1bfb56ed341905edd"},
- {file = "pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7027560ee92211647d0d34e3f7cd6f50da56399d26a9c8ad0da286d3869a53f3"},
- {file = "pydantic_core-2.46.4-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:f99626688942fb746e545232e7726926f3be91b5975f8b55327665fafda991c7"},
- {file = "pydantic_core-2.46.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff"},
- {file = "pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:97e7cf2be5c77b7d1a9713a05605d49460d02c6078d38d8bef3cbe323c548424"},
- {file = "pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:3bf92c5d0e00fefaab325a4d27828fe6b6e2a21848686b5b60d2d9eeb09d76c6"},
- {file = "pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:3ecbc122d18468d06ca279dc26a8c2e2d5acb10943bb35e36ae92096dc3b5565"},
- {file = "pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02"},
- {file = "pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5"},
- {file = "pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596"},
- {file = "pydantic_core-2.46.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:fd8b3d9fd264be37976686c7f65cd52a83f5e84f4bfd2adf9c1d469676bbb6ae"},
- {file = "pydantic_core-2.46.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9f444c499b3eefd3a92e348059471ea0c3a6e303d9c1cec09fa748fd9f895201"},
- {file = "pydantic_core-2.46.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3447661d99f75a3683a4cf5c87da72f2161964611864dbbeac7fbb118bb4bfc0"},
- {file = "pydantic_core-2.46.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8b9bab013d1c7a79d3501ff86d0bc9c31bf587db4551677b96bec07df78c6b15"},
- {file = "pydantic_core-2.46.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d995260fdf4e1db774581b4900e0f832abe3c7c84996726bbc161b19c8f29e76"},
- {file = "pydantic_core-2.46.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f13a646d65d09fbf1bc6b3a9635d30095c8e7e5cc419ff35ecc563c5fd04cd49"},
- {file = "pydantic_core-2.46.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432c179df7874eeb73307aad2df0755e1ae0efa61ff0ea89b93e194411ae3928"},
- {file = "pydantic_core-2.46.4-cp39-cp39-manylinux_2_31_riscv64.whl", hash = "sha256:e68b7a074f65a2fd746c52a7ce6142ab7006074ac269ace0c25cd8ba171f8066"},
- {file = "pydantic_core-2.46.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4a05d69cba51d852c5c3e92758653245a50c0b646ced0cf05bd793ed592839d6"},
- {file = "pydantic_core-2.46.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:228ee9bae8bef5b1e97ec58302f80357c37199e0d0a99174e138d28e6957b9d9"},
- {file = "pydantic_core-2.46.4-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:10e17cbb10a330363733efc4d7c4d0dd827ac0909b8f6a6542298fed1ea62f29"},
- {file = "pydantic_core-2.46.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:91a06d2e259ecfbd8c901d70c3c507900458498142b3026a296b7de4d1322cc9"},
- {file = "pydantic_core-2.46.4-cp39-cp39-win32.whl", hash = "sha256:d80ee3d731373b24cebbc10d689ca4ee1875caf0d5703a245db18efd4dd37fc1"},
- {file = "pydantic_core-2.46.4-cp39-cp39-win_amd64.whl", hash = "sha256:3be77f45df024d789a672ae34f8b06fb346c4f9f46ea714956660ea4862e89ac"},
- {file = "pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:14d4edf427bdcf950a8a02d7cb44a08614388dd6e1bdcbf4f67504fa7887da9c"},
- {file = "pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:0ce40cd7b21210e99342afafbd4d0f76d784eb5b1d60f3bdc566be4983c6c73b"},
- {file = "pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90884113d8b48f760e9587002789ddd741e76ab9f89518cd1e43b1f1a52ec44b"},
- {file = "pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66ce7632c22d837c95301830e111ad0128a32b8207533b60896a96c4915192ea"},
- {file = "pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7"},
- {file = "pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df"},
- {file = "pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526"},
- {file = "pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0"},
- {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0c563b08bca408dc7f65f700633d8442fffb2421fc47b8101377e9fd65051ff0"},
- {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:db06ffe51636ffe9ca531fe9023dd64bdd794be8754cb5df57c5498ae5b518a7"},
- {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:133878133d271ade3d41d1bfb2a45ec38dbdbda40bc065921c6b04e4630127e2"},
- {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9bc519fbf2b7578398853d815009ae5e4d4603d12f4e3f91da8c06852d3da3e9"},
- {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c7a7bd4e39e8e4c12c39cd480356842b6a8a06e41b23a55a5e3e191718838ddf"},
- {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:d396ec2b979760aaf3218e76c24e65bd0aca24983298653b3a9d7a45f9e47b30"},
- {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:86e1a4418c6cd97d60c95c71164158eaf7324fae7b0923264016baa993eba6fc"},
- {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:d51026d73fcfd93610abc7b27789c26b313920fcfb20e27462d74a7f8b06e983"},
- {file = "pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1"},
+ {file = "pydantic_core-2.46.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:657b40d6240c0a7b6a64b30f22d1e3aa631c7e846c621b0c0f6d1d75e2e15ea6"},
+ {file = "pydantic_core-2.46.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ecb42011e12ee19cafbc312887cbf3546959fe02fbad44f272d4be5baa997615"},
+ {file = "pydantic_core-2.46.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4dedce55295becb61921e386b99d4f2706045306e7fa52249a33004c837379fb"},
+ {file = "pydantic_core-2.46.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9f47b8a949e60f027f0aa0a6f6c7b7e9c55cbf4380d10b344e282fa4e7ab1e1b"},
+ {file = "pydantic_core-2.46.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:200aa3dc9f8d54f0754f43247c0bad0999fdcfbfd2488384dd44f37279271fe6"},
+ {file = "pydantic_core-2.46.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6d30e1a4f138b8951063e9a394752a9179b51da288ffa507b1e659222f4c1793"},
+ {file = "pydantic_core-2.46.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:850a08d167dde16db8702c274f320c7be9d7da6f6dff2b58b18f9e815bd94f5b"},
+ {file = "pydantic_core-2.46.5-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:c3471e5c4a949c26ec00a77f01df59096aa9495877de76fd60a980f8ee6be461"},
+ {file = "pydantic_core-2.46.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3a3e26b6a8274211bddee2d0e4d0d42778f17a34510f49d2ec44b58abfc41736"},
+ {file = "pydantic_core-2.46.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:fc5d783bd4a2387e97b8a2d5ec781cfb92b3d893bf82370548e99db5915935d3"},
+ {file = "pydantic_core-2.46.5-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:356c8368cbc321050b169595683a2e1d63413b1e0e2868b330af9fc14c616d3f"},
+ {file = "pydantic_core-2.46.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:eb7d8d0e5886a89a55d2eef490e272fa965a9d57c6b29a5b5088a7997ec2cad1"},
+ {file = "pydantic_core-2.46.5-cp310-cp310-win32.whl", hash = "sha256:4d44cf99ddebf875f9b68cc267aa684c99b7b44fe63ee1cac4ec163807290069"},
+ {file = "pydantic_core-2.46.5-cp310-cp310-win_amd64.whl", hash = "sha256:1e5aad1220a1192c42341c8fd4a8686657e73ab2a920c970bdc4de334fe3193d"},
+ {file = "pydantic_core-2.46.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a1dee1b804ff4d11c663636cf15d2ea47e9f79cd56c033fb1cbf08924842a48f"},
+ {file = "pydantic_core-2.46.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d625a186a65201c23a9e3b8ed9c47e90a026e03256608cc91851c6709096844f"},
+ {file = "pydantic_core-2.46.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f8507560a9284e1370bb048ed4282012fbef4e8d109875b95e884d228552061"},
+ {file = "pydantic_core-2.46.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f93c5fe914d75fbec9a49209b00da5f08e9e467d69da2b1510c81940cfd10be"},
+ {file = "pydantic_core-2.46.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aca6c767f552b21b10f774aeac128e828eafb796adfa1b666a18bf6321453c3a"},
+ {file = "pydantic_core-2.46.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:701b2e04b560eeb4bddf7a25ab8ca476176e34fdbd9a0e18196f0d12d4685f0b"},
+ {file = "pydantic_core-2.46.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49776eab08766a08dfff7012f8b422dcd7e25e43b316eedf0477c24fcfa84b7c"},
+ {file = "pydantic_core-2.46.5-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:a2468d93d181667a7abd66e1b64bb9f76f361b0fef8faddf687456453576f5ee"},
+ {file = "pydantic_core-2.46.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:53feb344243bb9510a9dec7bf3cf1b64d88a98af5dc7872a5160465f8b198c8e"},
+ {file = "pydantic_core-2.46.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:cd5214352ae68f3b5e9af7768bdc5253695ee069675db3480518420b3be881f2"},
+ {file = "pydantic_core-2.46.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:9432f3598db432cb51c5b37fdbf29a60fcccc79e30d37a05022776a6bc4ab689"},
+ {file = "pydantic_core-2.46.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8feeac04b5794e513e710af2f9c87d49f31a6dc47967bb264a1fed61a8989bec"},
+ {file = "pydantic_core-2.46.5-cp311-cp311-win32.whl", hash = "sha256:892a881d5f68c2b9ea304b7a6c2c60d9343df578a311b0f86b94bc8f1ffe8129"},
+ {file = "pydantic_core-2.46.5-cp311-cp311-win_amd64.whl", hash = "sha256:40375c2d05acec10323e45dfe2077ac44bc74659008614af5069034e2cfc781c"},
+ {file = "pydantic_core-2.46.5-cp311-cp311-win_arm64.whl", hash = "sha256:28a6a556cd3b6066bea827857f9d9cce027c96f776e512f544a581f9e42161f8"},
+ {file = "pydantic_core-2.46.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b9fe6fb92520e3fd61f2e49000b6911b188824f089b75973ea06d6267f0b476d"},
+ {file = "pydantic_core-2.46.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a39ac25a9a2fa4072efdb429833c4a4c8009a51ff9eea3eeae131713cd27991e"},
+ {file = "pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fdc8b93a41521988916eeaa271173fcca7fa0803d62f87675aac8dcec1c8e29"},
+ {file = "pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b98134087d9de723658d17a42c7d0da8d6e2ef08015dee7dc93889047315f5e4"},
+ {file = "pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e652ab17569c94bff5475520f907b7148b8c24036a8ebbe5cf7cf7493d28579a"},
+ {file = "pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d925f3d9afd05a8c0fb3a1031463a8d59ebe5e2afad297e29c78be19e13b4e62"},
+ {file = "pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0fc5be0abd4a407e200d844b404e33639a554e7bd0d448e7b9ae181be4789ac2"},
+ {file = "pydantic_core-2.46.5-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:816ff0a6550ffc06c098ccd2e0698600f9aa7da192a79eaa6f9af504a35db869"},
+ {file = "pydantic_core-2.46.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c7ea57fc63aa7da93a1bd2d644e6577befae10c52c4e36377635eea1056a74f5"},
+ {file = "pydantic_core-2.46.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:efd62a42486f1bda5d24cb4f63d15a3c7768375fe83d36f9417b4ad7a2fb20b3"},
+ {file = "pydantic_core-2.46.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:2bc9419666990c06d7397831f2126a1ecc3594aaa3ff7de5bf2d066802f4e07b"},
+ {file = "pydantic_core-2.46.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:18a09e1e1011b462f2e32774f25859ef1223d5c2b0546a633cf56654710721e0"},
+ {file = "pydantic_core-2.46.5-cp312-cp312-win32.whl", hash = "sha256:5cb482e9e84c851f4e623fe4acc1ced89168cf1fe18f7089db4548c8f5bbb65b"},
+ {file = "pydantic_core-2.46.5-cp312-cp312-win_amd64.whl", hash = "sha256:5e81740c09e310f5aa5cbd3e434a01c154d4bef93241c7877b39f211d2b78ba8"},
+ {file = "pydantic_core-2.46.5-cp312-cp312-win_arm64.whl", hash = "sha256:f7b0ec93a2893de856652154d73b7ba622f26fa97726487dcac373de5f4c6084"},
+ {file = "pydantic_core-2.46.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:b7ca9034437b6022f941f4857459562ee00a560b97e7cce8a0ec5a74fc6766e0"},
+ {file = "pydantic_core-2.46.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f332f0e72a5a0400141f830744e141bf9f97917878dbe968669e8a7fefea78ff"},
+ {file = "pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:193375f3548919d3f0b60936ca113ada3e38f264f91b9b8e0508efaad57be931"},
+ {file = "pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:79bdfa52f843137045b2d081cc05c120ba6665d29b7559c2c47690906f39279f"},
+ {file = "pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:24922243639cbdac66c75fcb6fd6495a9cb52b213d62f9a0d16f0310b1ff8038"},
+ {file = "pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c76fe65e607be28c7fd4d56fc3c42b1583aa058ce3408b7ad0fd540171d31f9f"},
+ {file = "pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f7b393a8b3da82f5c1fc0751e6d01ac6c55b93c18226a60bdfba4a724efafd1"},
+ {file = "pydantic_core-2.46.5-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:7ac031912d54f3d83ef3b3eb98dfabc1608802e2202263d25957eeed40b94761"},
+ {file = "pydantic_core-2.46.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:837b396ca3d7b74091ca623f6cbd8351bd42d670a79c2683e79fb089f06a2de5"},
+ {file = "pydantic_core-2.46.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:5ee239d575f80b08eca11f6e20f90c4c695de7825c67eefe6091fbf20dda648e"},
+ {file = "pydantic_core-2.46.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:e80675d75ae2cd14372cb65cad5400d9347a3d3f6c13000183f22dfd027283ed"},
+ {file = "pydantic_core-2.46.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:9c4b71f10dd532fb7a5cbc8f58707779e64f03a258c2bf8bfbaecfcd9970b519"},
+ {file = "pydantic_core-2.46.5-cp313-cp313-win32.whl", hash = "sha256:97bf8de4d541598c94a59344eeb988a94c08ff76b5723c41f6567ec18c7892ea"},
+ {file = "pydantic_core-2.46.5-cp313-cp313-win_amd64.whl", hash = "sha256:15f4a94963c95accac15b7b657bb177d3ad82bb90b0d0526d9a9b85079925db5"},
+ {file = "pydantic_core-2.46.5-cp313-cp313-win_arm64.whl", hash = "sha256:d22a945598fb91236b4dd793a6e42e4f3dd7740bb5aace5ebd7d4c08d13bb575"},
+ {file = "pydantic_core-2.46.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:c1c43ad4339643d70ebb8124e1305a7dab423001eff58bb41a0f731adbc98355"},
+ {file = "pydantic_core-2.46.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1a353f84de772f423b5ffb11d7ae352fbbef0f446f3c0b0af0f8236d7233606e"},
+ {file = "pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5086029a57366b8cf81b130a43908738095c270c21a8d7f0e8bdfdb89718e2f3"},
+ {file = "pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:46c25dda9d092a06c08db76ffe0a197107904d0dfac653f7d5306bbcd6d6119c"},
+ {file = "pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:37ea7b83c935e5b0d68c9449b82651accf78a10828b2c02b2f2d9e9496446c21"},
+ {file = "pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e64e88d5585bea9ce95861079de72006c7fa6d3df4e3a3b65ba31eb979c15c9f"},
+ {file = "pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54d510bac3ee52247af28ed4bb18a1e799f040ac60fd2bf5ccd4c92f1fbe786f"},
+ {file = "pydantic_core-2.46.5-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:a2a5e1d0ff29adddc9f6d6821a66302e4493f8ca898b715b6b1182c2c201ea0a"},
+ {file = "pydantic_core-2.46.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:03b9666e41e35d8909852ba191a0607520f81b74eaf12ccf8737005dbb313821"},
+ {file = "pydantic_core-2.46.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:a91c17edf6eea2402cb5457b4c89e99bc5ed1004aa34c4adf1d4258c1a5c22c2"},
+ {file = "pydantic_core-2.46.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b49924c73a235e969511bf2aabdff3beebf9820931f646c80274d5d780010c47"},
+ {file = "pydantic_core-2.46.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:2cbd9a5eff05e51c447c34dfa4632145b26b09120cf04bd0c871e44c1a5e1c9a"},
+ {file = "pydantic_core-2.46.5-cp314-cp314-win32.whl", hash = "sha256:2d5d76654becf5efd62c9e51c3756c67b49498b0c9a40884934c40807adbd074"},
+ {file = "pydantic_core-2.46.5-cp314-cp314-win_amd64.whl", hash = "sha256:fa10ef4112775900e7a0661068635eb67b2ab824fbde764de6e0e21982a93db0"},
+ {file = "pydantic_core-2.46.5-cp314-cp314-win_arm64.whl", hash = "sha256:045ab3b6d308439e32b81cc173bba5b9018bc6ed896afd0c65b3b009b1699af5"},
+ {file = "pydantic_core-2.46.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8816f3d218beb4b787de5c9759c259b8fa61f9dec42dc7811f320a33771778b7"},
+ {file = "pydantic_core-2.46.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:bce57638e08ac148e5778cce7feb968307a727d66f8e2274a543d0cf0c9ad6a3"},
+ {file = "pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:976e1128455aa595ea04c79ccfedff1aaeab96ee013fcc916bed120c4f0ad94f"},
+ {file = "pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b891faeedeafba41b2983e5001a81b6a915b69544c7e7570d1989ce1c36ac7"},
+ {file = "pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5f194189415698233dd1114a093a9b56e61e2c57e11b469be3b0506f46f0771c"},
+ {file = "pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:82a36973cf8a2ef5406f4fe2edbf8ed0c99629535d959e0b100c76a32535a111"},
+ {file = "pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdbb78909f52b981d3b2d56b97328d71eb0b974c36bd77c920123a7ebb192829"},
+ {file = "pydantic_core-2.46.5-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:52e24eacdb536cade636aa90fb851835222becff8484b7001fdc78cb0290f2aa"},
+ {file = "pydantic_core-2.46.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:37ae34309d7bd8c0d61ab839668058f2a7962ea1fc51d105d2db228fe0618034"},
+ {file = "pydantic_core-2.46.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:0cdbada856a1c69a7624a64d3d9aefe79300bd6ef827b43a4f265010b9b55184"},
+ {file = "pydantic_core-2.46.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:545f26c504b27c3758439a5e6d9349931f0a04f855668d5fe323c89e82300a38"},
+ {file = "pydantic_core-2.46.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:ff218293c9c806138dca139765e3b067621be52bcd93cdc14c7711be7ddc90a9"},
+ {file = "pydantic_core-2.46.5-cp314-cp314t-win32.whl", hash = "sha256:97cf3eb53a8cccacf9d46686a0926186c9bfb5574f2ed66d3639d5fe117cd3a9"},
+ {file = "pydantic_core-2.46.5-cp314-cp314t-win_amd64.whl", hash = "sha256:d2f9fc07a8042a8f95925b35c4f04f469707c981fc33245b6ca187cf5d2dd290"},
+ {file = "pydantic_core-2.46.5-cp314-cp314t-win_arm64.whl", hash = "sha256:acf8a67ba51f4ca9ddbd0e6b3000a65ac51ab734661778b3e7ba64d99a710f2f"},
+ {file = "pydantic_core-2.46.5-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c583b927a8838dab890706a6fa7573fbb8b70e24000ef9f7238e2d6f6435a5ed"},
+ {file = "pydantic_core-2.46.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cdc8b74ecc48c0cb1e9607a05ec4e9e88db60a19ffcc9a1d5f9088ede40c8dc0"},
+ {file = "pydantic_core-2.46.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b10e3e8fd7ddc2bd915848a2768e44c15b22936f1cc54c462ad1164deb02655"},
+ {file = "pydantic_core-2.46.5-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f077d0b97ab11fa7dcc633fca53515f290bca8a8a633e966d5b6d1879d9ed01a"},
+ {file = "pydantic_core-2.46.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7b0fc826b16c55e561e5d2a0c5c77b051ba1d92808118c4e4b5390f5e0cf191d"},
+ {file = "pydantic_core-2.46.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef3fbbf161dc9351a2fe0422e51b129f9e97e42385bd0320b309c15f7d287dd8"},
+ {file = "pydantic_core-2.46.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:978e7b97d4824b5be09c69fb70507cbde3b0323fc147332ca40a94d9a6a0ebbf"},
+ {file = "pydantic_core-2.46.5-cp39-cp39-manylinux_2_31_riscv64.whl", hash = "sha256:9b68938dd5b0c783d88ff8e2dcc69451b5eb936fe212d516b21b9d5567f6d464"},
+ {file = "pydantic_core-2.46.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:771cf63ae0b1b50dd22e5f3e3549fab5f3f4ff1635d352a9e1a97fe01c7b2e64"},
+ {file = "pydantic_core-2.46.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7c6be839a5a8312626b32029a415644a0846b420bc8b52b95b28cd92da162168"},
+ {file = "pydantic_core-2.46.5-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:895395f8918627b04efb1ad2a4cf605387143300ba03304cd1dfa6d03f5e095e"},
+ {file = "pydantic_core-2.46.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:fc8515076c11f3cfdf4fb142dcca0fe384b1230a3b5415458ac84f3e0903ec13"},
+ {file = "pydantic_core-2.46.5-cp39-cp39-win32.whl", hash = "sha256:3d2652072b2d774947ba5cf78a9e59644ac62ee572daf6dd2e1dfe905e15b2b7"},
+ {file = "pydantic_core-2.46.5-cp39-cp39-win_amd64.whl", hash = "sha256:3aa166e99c4f2985407fb8714aebede877ecb5455cf321b606adca926d30d5a0"},
+ {file = "pydantic_core-2.46.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:c14ad3bdc85ee7f318742c457ca3968a92126d144b15721c759033bfb06296c2"},
+ {file = "pydantic_core-2.46.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:0bddb4020d8f04175865ccd17eff3040874fc11fb593f424edb452653b4b947c"},
+ {file = "pydantic_core-2.46.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2471fd51c61c610e1dcf7de44d7299283661654d11264ab4802b303368d69c47"},
+ {file = "pydantic_core-2.46.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b10ec717381bdbfafef34607824db4c91de69ff085e4fca3b2af91b4fa17e68a"},
+ {file = "pydantic_core-2.46.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:013d6f3483d81e02e7c328831808f336c8596ee33b4bd4026b9ffb1e960b8942"},
+ {file = "pydantic_core-2.46.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:e9c134bb666dd54b778b9fc0d2b50cbb7f979b9e3716f26a88c9ab3b6fc1dd0f"},
+ {file = "pydantic_core-2.46.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:347ec774390c87326a2e4929d58d3f7e8763a104d5d35f4cd595a4c952366433"},
+ {file = "pydantic_core-2.46.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e24d8f05fa2d28513d94e877e9c75ad66175376209b3977f916e240e623193c"},
+ {file = "pydantic_core-2.46.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ab4b66edffb32d9e951efb3814bd104b8367a7501b81b955cacb5726d897389f"},
+ {file = "pydantic_core-2.46.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:337639ba62a11acde6ef3aeb08c8ea755f8ef1fe5e513356c0f36a2b0d7568b0"},
+ {file = "pydantic_core-2.46.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:413a717a410d0c817ef5b786a059415550b3794e1d0c2abffd9efb93a3d9f7b4"},
+ {file = "pydantic_core-2.46.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1e449def1945a462c464331254e5a44fca7c3b4f9aedf59ec2f50f8066dd8e25"},
+ {file = "pydantic_core-2.46.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:a445486499897b88a7d6c310c88ed64dd37b1b59bfd7ae9107490bbb362f47d6"},
+ {file = "pydantic_core-2.46.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:2d330aaba8621b1edcec8ae2c4050f63b84ccf6d98723a8f212e9684713abf0e"},
+ {file = "pydantic_core-2.46.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b6acfb46a814762367fb7ba0828b0a17d441b92ce249a0e007474c9072662dda"},
+ {file = "pydantic_core-2.46.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:d0a24b40877af2de4950252be9d21eaf7fb07660f3c2cae1f56c6b599ada5266"},
+ {file = "pydantic_core-2.46.5.tar.gz", hash = "sha256:10416c15b8839ecc4ef4d0885da76da6fd0f67333a0eb8aff6d93c4b8f2910fc"},
]
[package.dependencies]
diff --git a/pyproject.toml b/pyproject.toml
index f599fde..cea8b20 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@ dynamic = ["version"]
[tool.poetry]
name = "phenoml"
-version = "16.11.0"
+version = "0.0.0.dev0"
description = ""
readme = "README.md"
authors = []
@@ -31,9 +31,6 @@ classifiers = [
packages = [
{ include = "phenoml", from = "src"}
]
-include = [
- { path = "src/phenoml/openapi/openapi.json", format = ["sdist", "wheel"] }
-]
[tool.poetry.urls]
Repository = 'https://github.com/phenoml/phenoml-python-sdk'
diff --git a/reference.md b/reference.md
index 0c4d8d9..565e109 100644
--- a/reference.md
+++ b/reference.md
@@ -5427,6 +5427,730 @@ File type is auto-detected from content magic bytes.
+
+
+
+
+## Lang2FhirBatch
+client.lang2fhir_batch.list(...) -> JobListResponse
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Returns a page of the instance's batch jobs, newest first, without
+per-job counts. Jobs are shared across the instance's credentials, so
+this lists every batch job on the instance, not just the calling
+credential's.
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from phenoml import PhenomlClient
+from phenoml.environment import PhenomlClientEnvironment
+
+client = PhenomlClient(
+ client_id="",
+ client_secret="",
+ environment=PhenomlClientEnvironment.DEFAULT,
+)
+
+client.lang2fhir_batch.list(
+ cursor="cursor",
+ limit=1,
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**cursor:** `typing.Optional[str]` — Opaque pagination cursor from a previous page's next_cursor.
+
+
+
+
+
+-
+
+**limit:** `typing.Optional[int]` — Page size. Defaults to 20; values above 100 are clamped to 100.
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.lang2fhir_batch.create(...) -> BatchJob
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Opens an empty batch job. Items arrive on later upload calls and the set
+is sealed at finalize.
+
+Supplying `request_id` makes the create idempotent on that token: a
+retried submit whose response was lost returns the original job rather
+than opening a second one. This dedupe is scoped to the calling
+credential.
+
+An instance may hold at most 4 active (pending or processing) jobs at
+once; a create past that limit returns `409`. The limit is instance-wide
+— jobs are shared across the instance's credentials — so another
+credential's jobs count against it.
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from phenoml import PhenomlClient
+from phenoml.environment import PhenomlClientEnvironment
+
+client = PhenomlClient(
+ client_id="",
+ client_secret="",
+ environment=PhenomlClientEnvironment.DEFAULT,
+)
+
+client.lang2fhir_batch.create(
+ request_id="submit-2025-09-02-batch-001",
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**request_id:** `typing.Optional[str]`
+
+Optional client idempotency token. A retried create with the same
+token returns the original job instead of opening a second one.
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.lang2fhir_batch.upload_item(...) -> UploadItemResponse
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Stores one item of a job from a multipart upload. A batch's items arrive
+one per request. The item carries **either** a `document` extraction
+(whose input file rides as raw bytes in the `file` part) **or** a
+`create` extraction (JSON only, no file).
+
+The upload enforces these rules:
+- Set **exactly one** of `document` or `create`. Setting both, or
+ neither, is a `400`.
+- When `document` is set, `file` is **required** — it supplies the
+ document's binary content (PDF or image).
+- When `create` is set, `file` is **forbidden** — a create item carries
+ no file.
+- `document` and `create` must each be a JSON **object**.
+
+Only the item's structure is checked here: the fields inside `document`
+or `create` are not validated at upload. A body that is well-formed JSON
+but not a valid request for its endpoint is still accepted with `202`
+and fails later during processing, recorded as an item `error`. A
+wrong-typed field the endpoint cannot decode fails as `invalid_input`; a
+body that decodes but the pipeline rejects (for example, a missing
+required field) fails as `processing_failed`.
+
+Supplying `request_id` makes the upload idempotent on that token. A
+re-upload under the same token overwrites the same item rather than
+adding a second, so a client that lost an upload's response can safely
+re-send it. The response's `deduplicated` is `true` only when the
+re-uploaded payload matches the one already stored; a same-token upload
+with a changed payload overwrites in place and returns `false`.
+
+Set a `request_id` on **every** upload: re-sending under the same token
+is the only way to repair a lost or incomplete upload, including the one
+a finalize `409` reports. Without one, a re-send adds a new item instead
+of replacing the missing one, and the job cannot be finalized.
+
+Uploads are rejected once the job has been finalized (`409`), once it
+holds its 500-item limit (`409`), or when the item is too large (`413` —
+see the raw-file limit in the API description).
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from phenoml import PhenomlClient
+from phenoml.environment import PhenomlClientEnvironment
+
+client = PhenomlClient(
+ client_id="",
+ client_secret="",
+ environment=PhenomlClientEnvironment.DEFAULT,
+)
+
+client.lang2fhir_batch.upload_item(
+ job_id="job_id",
+ file="example_file",
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**job_id:** `str`
+
+
+
+
+
+-
+
+**document:** `typing.Optional[typing.Dict[str, typing.Any]]`
+
+The JSON body of `POST /lang2fhir/document/multi`, **without**
+its base64 `content` field — the uploaded `file` supplies the
+content. Accepts that endpoint's fields (`version`, `provider`,
+`patient_reference`, `implementation_guide`, `detection_effort`,
+`validation_method`, `config`). This is the **multi**-resource
+body: it has no single-`resource` field, and the item's result
+is a `DocumentMultiResponse` (a Bundle of resources). Mutually
+exclusive with `create`; requires `file`.
+
+
+
+
+
+-
+
+**create:** `typing.Optional[typing.Dict[str, typing.Any]]`
+
+The JSON body of `POST /lang2fhir/create/multi`. Accepts that
+endpoint's fields (`text`, `version`, `provider`,
+`patient_reference`, `implementation_guide`, `detection_effort`,
+`validation_method`, `resource_review`). This is the
+**multi**-resource body: it has no single-`resource` field, and
+the item's result is a `CreateMultiResponse` (a Bundle of
+resources). Mutually exclusive with `document`; must **not** be
+accompanied by a `file`.
+
+
+
+
+
+-
+
+**file:** `typing.Optional[core.File]`
+
+The document's binary content (PDF, PNG, JPEG, or TIFF).
+Required with `document`; forbidden with `create`.
+
+
+
+
+
+-
+
+**request_id:** `typing.Optional[str]`
+
+Optional idempotency token (max 256 bytes). Re-uploading under
+the same token overwrites the same item instead of adding a
+new one. The token is scoped to this job; the same token in
+another job is independent and creates a separate item.
+
+
+
+
+
+-
+
+**id:** `typing.Optional[str]`
+
+Optional caller-supplied correlation label (max 512 bytes),
+echoed back on status and result listings so you can match the
+server's item_id to your own record.
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.lang2fhir_batch.finalize(...) -> BatchJob
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Seals the job's item set and starts processing. Takes no request body.
+Finalize is idempotent: a retried finalize succeeds again.
+
+If a previous upload did not complete, finalize returns a `409`; re-send
+the missing upload (with the same `request_id`), then finalize.
+Finalizing a job with no items is a `400`.
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from phenoml import PhenomlClient
+from phenoml.environment import PhenomlClientEnvironment
+
+client = PhenomlClient(
+ client_id="",
+ client_secret="",
+ environment=PhenomlClientEnvironment.DEFAULT,
+)
+
+client.lang2fhir_batch.finalize(
+ job_id="job_id",
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**job_id:** `str`
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.lang2fhir_batch.get(...) -> JobDetailResponse
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Returns a job's record, its per-status item counts, and one page of
+per-item statuses.
+
+Items are listed in a stable order that is not upload order and is the
+same across pages. Match each entry to your own records by its `id`
+(your correlation label) or `item_id` (from the upload response),
+never by position.
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from phenoml import PhenomlClient
+from phenoml.environment import PhenomlClientEnvironment
+
+client = PhenomlClient(
+ client_id="",
+ client_secret="",
+ environment=PhenomlClientEnvironment.DEFAULT,
+)
+
+client.lang2fhir_batch.get(
+ job_id="job_id",
+ cursor="cursor",
+ limit=1,
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**job_id:** `str`
+
+
+
+
+
+-
+
+**cursor:** `typing.Optional[str]` — Opaque pagination cursor from a previous page's next_cursor.
+
+
+
+
+
+-
+
+**limit:** `typing.Optional[int]` — Page size for the item-status page. Defaults to 20; values above 100 are clamped to 100.
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.lang2fhir_batch.get_results(...) -> ResultsPageResponse
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+A lighter status page. Returns the same per-item status entries as
+`GET /lang2fhir/batch/{job_id}`, but without the job record or counts,
+and the entries carry `result_size` rather than any result content. Use
+each entry's `item_id` to fetch that item's result from
+`GET /lang2fhir/batch/{job_id}/results/{item_id}`.
+
+Entries are listed in a stable order that is not upload order and is
+the same across pages. Match each entry to your own records by its `id`
+(your correlation label) or `item_id` (from the upload response),
+never by position.
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from phenoml import PhenomlClient
+from phenoml.environment import PhenomlClientEnvironment
+
+client = PhenomlClient(
+ client_id="",
+ client_secret="",
+ environment=PhenomlClientEnvironment.DEFAULT,
+)
+
+client.lang2fhir_batch.get_results(
+ job_id="job_id",
+ cursor="cursor",
+ limit=1,
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**job_id:** `str`
+
+
+
+
+
+-
+
+**cursor:** `typing.Optional[str]` — Opaque pagination cursor from a previous page's next_cursor.
+
+
+
+
+
+-
+
+**limit:** `typing.Optional[int]` — Page size. Defaults to 20; values above 100 are clamped to 100.
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.lang2fhir_batch.get_result(...) -> typing.Dict[str, typing.Any]
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Streams one item's stored result bytes verbatim as `application/json`.
+The body is the response the item's synchronous multi endpoint would have
+returned — a `DocumentMultiResponse` for a document item or a
+`CreateMultiResponse` for a create item.
+
+Only a succeeded item has a result: an item that has not succeeded
+(pending, processing, or failed) is a `409`, and a result that has
+expired is a `404`.
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from phenoml import PhenomlClient
+from phenoml.environment import PhenomlClientEnvironment
+
+client = PhenomlClient(
+ client_id="",
+ client_secret="",
+ environment=PhenomlClientEnvironment.DEFAULT,
+)
+
+client.lang2fhir_batch.get_result(
+ job_id="job_id",
+ item_id="item_id",
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**job_id:** `str`
+
+
+
+
+
+-
+
+**item_id:** `str`
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
@@ -5436,22 +6160,194 @@ File type is auto-detected from content magic bytes.
-
-#### 📝 Description
+#### 📝 Description
+
+
+-
+
+
+-
+
+Returns metadata for every custom (uploaded) FHIR profile on this
+instance, across all implementation guides. The full StructureDefinition
+JSON is omitted from each entry; fetch a single profile by id to retrieve it.
+
+The `url` query parameter filters by canonical URL. The canonical URL is the
+stable key other platform features use to reference a profile (FHIR's
+`meta.profile`, `baseDefinition`), since StructureDefinition ids are only
+unique within a package. An unpinned `url` filter returns metadata for
+the profile's current StructureDefinition. Pinned `url|version` filters
+resolve a retained version when present; otherwise they can fall back to
+the profile's current StructureDefinition, whose content can change
+through the profile update endpoint. A non-matching filter returns an
+empty list, not a 404.
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from phenoml import PhenomlClient
+from phenoml.environment import PhenomlClientEnvironment
+
+client = PhenomlClient(
+ client_id="",
+ client_secret="",
+ environment=PhenomlClientEnvironment.DEFAULT,
+)
+
+client.profiles.profiles.list(
+ url="http://phenoml.com/fhir/StructureDefinition/custom-patient|1.0.0",
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**url:** `typing.Optional[str]` — Filter by canonical URL. Accepts the FHIR pinned form `url|version`; without a version pin, returns the profile's current StructureDefinition metadata.
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.profiles.profiles.create(...) -> ProfileSummary
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Creates a custom profile from a FHIR StructureDefinition supplied as a JSON
+object. Metadata such as version, resource type, and url is read from the
+StructureDefinition; the lowercase StructureDefinition id becomes the
+profile's lookup key. When id is omitted, a random UUID is assigned.
+Optionally group the profile under a named implementation guide.
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from phenoml import PhenomlClient
+from phenoml.environment import PhenomlClientEnvironment
+
+client = PhenomlClient(
+ client_id="",
+ client_secret="",
+ environment=PhenomlClientEnvironment.DEFAULT,
+)
+
+client.profiles.profiles.create(
+ structure_definition={
+ "resourceType": "StructureDefinition",
+ "id": "custom-patient",
+ "url": "http://phenoml.com/fhir/StructureDefinition/custom-patient",
+ "name": "CustomPatient",
+ "status": "active",
+ "fhirVersion": "4.0.1",
+ "kind": "resource",
+ "abstract": False,
+ "type": "Patient",
+ "baseDefinition": "http://hl7.org/fhir/StructureDefinition/Patient",
+ "derivation": "constraint",
+ "snapshot": {"element": [{"id": "Patient", "path": "Patient", "min": 0, "max": "*"}]}
+ },
+ implementation_guide="acme-cardiology",
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**request:** `ProfileUploadRequest`
+
+
+
-
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.profiles.profiles.get(...) -> ProfileGetResponse
-
-Returns metadata for every custom (uploaded) FHIR profile on this
-instance, across all implementation guides. The full StructureDefinition
-JSON is omitted from each entry; fetch a single profile by id to retrieve it.
+#### 📝 Description
-The `url` query parameter filters by canonical URL. The canonical URL is the
-stable key other platform features use to reference a profile (FHIR's
-`meta.profile`, `baseDefinition`), since StructureDefinition ids are only
-unique within a package. A non-matching filter returns an empty list, not a 404.
+
+-
+
+
+-
+
+Returns a single custom profile by id, including its full StructureDefinition
+JSON.
@@ -5475,8 +6371,8 @@ client = PhenomlClient(
environment=PhenomlClientEnvironment.DEFAULT,
)
-client.profiles.profiles.list(
- url="http://phenoml.com/fhir/StructureDefinition/custom-patient|1.0.0",
+client.profiles.profiles.get(
+ id="custom-patient",
)
```
@@ -5493,7 +6389,7 @@ client.profiles.profiles.list(
-
-**url:** `typing.Optional[str]` — Filter by canonical URL. Accepts the FHIR pinned form `url|version` (split on the last `|`); the bare form matches the current version.
+**id:** `str` — The lowercase StructureDefinition id of the custom profile.
@@ -5513,7 +6409,7 @@ client.profiles.profiles.list(
-client.profiles.profiles.create(...) -> ProfileSummary
+client.profiles.profiles.update(...) -> ProfileSummary
-
@@ -5525,12 +6421,16 @@ client.profiles.profiles.list(
-
-Creates a custom profile from a FHIR StructureDefinition supplied as a JSON
-object. Metadata such as version, resource type, and url is read from the
-StructureDefinition; the lowercase StructureDefinition id becomes the
-profile's lookup key. When id is omitted, a random UUID is assigned. Code
-system configuration is auto-extracted from the snapshot. Optionally group
-the profile under a named implementation guide.
+Replaces an existing custom profile with a new StructureDefinition. The
+`id` path parameter is authoritative: if the StructureDefinition includes
+an `id` it must match the path parameter, and if it omits one the path
+parameter is used. The FHIR resource type of the profile cannot change.
+When `implementation_guide` is omitted, the profile keeps its existing
+implementation guide. A retained version string is allowed only when
+re-submitting the profile's current version with an unchanged
+StructureDefinition; otherwise it returns a conflict. While the profile
+has retained versions, its
+canonical URL cannot be changed.
@@ -5554,10 +6454,23 @@ client = PhenomlClient(
environment=PhenomlClientEnvironment.DEFAULT,
)
-client.profiles.profiles.create(
+client.profiles.profiles.update(
+ id="custom-patient",
structure_definition={
- "key": "value"
+ "resourceType": "StructureDefinition",
+ "id": "custom-patient",
+ "url": "http://phenoml.com/fhir/StructureDefinition/custom-patient",
+ "name": "CustomPatient",
+ "status": "active",
+ "fhirVersion": "4.0.1",
+ "kind": "resource",
+ "abstract": False,
+ "type": "Patient",
+ "baseDefinition": "http://hl7.org/fhir/StructureDefinition/Patient",
+ "derivation": "constraint",
+ "snapshot": {"element": [{"id": "Patient", "path": "Patient", "min": 0, "max": "*"}]}
},
+ implementation_guide="acme-cardiology",
)
```
@@ -5574,6 +6487,14 @@ client.profiles.profiles.create(
-
+**id:** `str` — The lowercase StructureDefinition id of the custom profile.
+
+
+
+
+
+-
+
**request:** `ProfileUploadRequest`
@@ -5594,7 +6515,7 @@ client.profiles.profiles.create(
-client.profiles.profiles.get(...) -> ProfileGetResponse
+client.profiles.profiles.delete(...)
-
@@ -5606,7 +6527,9 @@ client.profiles.profiles.create(
-
-Returns a single custom profile by id, including its full StructureDefinition JSON.
+Permanently deletes a custom profile by id. This also deletes all retained
+versions for that profile so the canonical URL can be reused by a later
+upload.
@@ -5630,7 +6553,7 @@ client = PhenomlClient(
environment=PhenomlClientEnvironment.DEFAULT,
)
-client.profiles.profiles.get(
+client.profiles.profiles.delete(
id="custom-patient",
)
@@ -5668,7 +6591,8 @@ client.profiles.profiles.get(
-client.profiles.profiles.update(...) -> ProfileSummary
+## Profiles Versions
+client.profiles.versions.list(...) -> ProfileVersionListResponse
-
@@ -5680,14 +6604,7 @@ client.profiles.profiles.get(
-
-Replaces an existing custom profile with a new StructureDefinition. The
-`id` path parameter is authoritative: if the StructureDefinition includes
-an `id` it must match the path parameter, and if it omits one the path
-parameter is used. The FHIR resource type of the profile cannot change.
-Code system configuration is
-re-derived from the new StructureDefinition. When `implementation_guide` is
-omitted, the profile keeps its existing implementation guide. The instance
-stores a single version per canonical URL, so this replaces it in place.
+Returns retained versions for a custom profile.
@@ -5711,9 +6628,94 @@ client = PhenomlClient(
environment=PhenomlClientEnvironment.DEFAULT,
)
-client.profiles.profiles.update(
+client.profiles.versions.list(
id="custom-patient",
- structure_definition={
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**id:** `str` — The lowercase StructureDefinition id of the custom profile.
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.profiles.versions.create(...) -> ProfileSummary
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Adds an immutable StructureDefinition version to a custom profile. If
+the profile does not exist, it is created from the submitted version.
+The StructureDefinition must include a non-empty `version`; its
+canonical URL and resource type must match the profile when one already
+exists. If it includes an `id`, that id must match the path parameter;
+if it omits `id`, the path parameter is used. Profiles created through
+this endpoint are grouped under `custom`. Posting the profile's current
+StructureDefinition unchanged retains it as a version.
+Version strings may contain letters, numbers, and the punctuation
+characters `.`, `_`, `~`, `+`, and `-`; they cannot be exactly `.` or
+`..`. Each profile can retain up to 250 versions; delete old
+versions before adding more.
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from phenoml import PhenomlClient
+from phenoml.environment import PhenomlClientEnvironment
+
+client = PhenomlClient(
+ client_id="",
+ client_secret="",
+ environment=PhenomlClientEnvironment.DEFAULT,
+)
+
+client.profiles.versions.create(
+ id="custom-patient",
+ request={
"key": "value"
},
)
@@ -5740,7 +6742,7 @@ client.profiles.profiles.update(
-
-**request:** `ProfileUploadRequest`
+**request:** `ProfileVersionCreateRequest`
@@ -5760,7 +6762,7 @@ client.profiles.profiles.update(
-client.profiles.profiles.delete(...)
+client.profiles.versions.get(...) -> ProfileGetResponse
-
@@ -5772,7 +6774,9 @@ client.profiles.profiles.update(
-
-Permanently deletes a custom profile by id.
+Returns metadata and the full StructureDefinition for one retained
+version. The returned StructureDefinition's id is the profile id. The
+path version is the authored `StructureDefinition.version` value.
@@ -5796,8 +6800,93 @@ client = PhenomlClient(
environment=PhenomlClientEnvironment.DEFAULT,
)
-client.profiles.profiles.delete(
+client.profiles.versions.get(
+ id="custom-patient",
+ version="2.0.0",
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**id:** `str` — The lowercase StructureDefinition id of the custom profile.
+
+
+
+
+
+-
+
+**version:** `str` — The authored StructureDefinition.version. It may contain letters, numbers, and the punctuation characters `.`, `_`, `~`, `+`, and `-`; it cannot be exactly `.` or `..`.
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.profiles.versions.delete(...)
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Deletes one retained version from a custom profile. The path
+version is the authored `StructureDefinition.version` value.
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from phenoml import PhenomlClient
+from phenoml.environment import PhenomlClientEnvironment
+
+client = PhenomlClient(
+ client_id="",
+ client_secret="",
+ environment=PhenomlClientEnvironment.DEFAULT,
+)
+
+client.profiles.versions.delete(
id="custom-patient",
+ version="2.0.0",
)
```
@@ -5822,6 +6911,14 @@ client.profiles.profiles.delete(
-
+**version:** `str` — The authored StructureDefinition.version. It may contain letters, numbers, and the punctuation characters `.`, `_`, `~`, `+`, and `-`; it cannot be exactly `.` or `..`.
+
+
+
+
+
+-
+
**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
diff --git a/src/phenoml/__init__.py b/src/phenoml/__init__.py
index 73ba5cf..29dc6a0 100644
--- a/src/phenoml/__init__.py
+++ b/src/phenoml/__init__.py
@@ -16,6 +16,7 @@
fhir_provider,
implementation_guides,
lang2fhir,
+ lang2fhir_batch,
profiles,
summary,
tools,
@@ -42,6 +43,7 @@
"fhir_provider": ".fhir_provider",
"implementation_guides": ".implementation_guides",
"lang2fhir": ".lang2fhir",
+ "lang2fhir_batch": ".lang2fhir_batch",
"profiles": ".profiles",
"summary": ".summary",
"tools": ".tools",
@@ -87,6 +89,7 @@ def __dir__():
"fhir_provider",
"implementation_guides",
"lang2fhir",
+ "lang2fhir_batch",
"profiles",
"summary",
"tools",
diff --git a/src/phenoml/client.py b/src/phenoml/client.py
index 4ab4b4f..87cfa7e 100644
--- a/src/phenoml/client.py
+++ b/src/phenoml/client.py
@@ -22,6 +22,7 @@
from .fhir_provider.client import AsyncFhirProviderClient, FhirProviderClient
from .implementation_guides.client import AsyncImplementationGuidesClient, ImplementationGuidesClient
from .lang2fhir.client import AsyncLang2FhirClient, Lang2FhirClient
+ from .lang2fhir_batch.client import AsyncLang2FhirBatchClient, Lang2FhirBatchClient
from .profiles.client import AsyncProfilesClient, ProfilesClient
from .summary.client import AsyncSummaryClient, SummaryClient
from .tools.client import AsyncToolsClient, ToolsClient
@@ -218,6 +219,7 @@ def __init__(
self._fhir_provider: typing.Optional[FhirProviderClient] = None
self._implementation_guides: typing.Optional[ImplementationGuidesClient] = None
self._lang2fhir: typing.Optional[Lang2FhirClient] = None
+ self._lang2fhir_batch: typing.Optional[Lang2FhirBatchClient] = None
self._profiles: typing.Optional[ProfilesClient] = None
self._summary: typing.Optional[SummaryClient] = None
self._tools: typing.Optional[ToolsClient] = None
@@ -296,6 +298,14 @@ def lang2fhir(self):
self._lang2fhir = Lang2FhirClient(client_wrapper=self._client_wrapper)
return self._lang2fhir
+ @property
+ def lang2fhir_batch(self):
+ if self._lang2fhir_batch is None:
+ from .lang2fhir_batch.client import Lang2FhirBatchClient # noqa: E402
+
+ self._lang2fhir_batch = Lang2FhirBatchClient(client_wrapper=self._client_wrapper)
+ return self._lang2fhir_batch
+
@property
def profiles(self):
if self._profiles is None:
@@ -541,6 +551,7 @@ def __init__(
self._fhir_provider: typing.Optional[AsyncFhirProviderClient] = None
self._implementation_guides: typing.Optional[AsyncImplementationGuidesClient] = None
self._lang2fhir: typing.Optional[AsyncLang2FhirClient] = None
+ self._lang2fhir_batch: typing.Optional[AsyncLang2FhirBatchClient] = None
self._profiles: typing.Optional[AsyncProfilesClient] = None
self._summary: typing.Optional[AsyncSummaryClient] = None
self._tools: typing.Optional[AsyncToolsClient] = None
@@ -619,6 +630,14 @@ def lang2fhir(self):
self._lang2fhir = AsyncLang2FhirClient(client_wrapper=self._client_wrapper)
return self._lang2fhir
+ @property
+ def lang2fhir_batch(self):
+ if self._lang2fhir_batch is None:
+ from .lang2fhir_batch.client import AsyncLang2FhirBatchClient # noqa: E402
+
+ self._lang2fhir_batch = AsyncLang2FhirBatchClient(client_wrapper=self._client_wrapper)
+ return self._lang2fhir_batch
+
@property
def profiles(self):
if self._profiles is None:
diff --git a/src/phenoml/core/client_wrapper.py b/src/phenoml/core/client_wrapper.py
index bba596d..c03a243 100644
--- a/src/phenoml/core/client_wrapper.py
+++ b/src/phenoml/core/client_wrapper.py
@@ -38,7 +38,7 @@ def get_headers(self) -> typing.Dict[str, str]:
"X-Fern-Runtime": f"python/{platform.python_version()}",
"X-Fern-Platform": f"{platform.system().lower()}/{platform.release()}",
"X-Fern-SDK-Name": "phenoml",
- "X-Fern-SDK-Version": "16.11.0",
+ "X-Fern-SDK-Version": "0.0.0.dev0",
**(self.get_custom_headers() or {}),
}
token = self._get_token()
diff --git a/src/phenoml/lang2fhir_batch/__init__.py b/src/phenoml/lang2fhir_batch/__init__.py
new file mode 100644
index 0000000..71aa4d2
--- /dev/null
+++ b/src/phenoml/lang2fhir_batch/__init__.py
@@ -0,0 +1,93 @@
+# This file was auto-generated by Fern from our API Definition.
+
+# isort: skip_file
+
+import typing
+from importlib import import_module
+
+if typing.TYPE_CHECKING:
+ from .types import (
+ BatchCounts,
+ BatchError,
+ BatchItemStatus,
+ BatchItemStatusStatus,
+ BatchJob,
+ BatchJobStatus,
+ JobDetailResponse,
+ JobListResponse,
+ ResultsPageResponse,
+ UploadItemResponse,
+ )
+ from .errors import (
+ BadRequestError,
+ ClientClosedRequestError,
+ ConflictError,
+ ContentTooLargeError,
+ GatewayTimeoutError,
+ InternalServerError,
+ NotFoundError,
+ UnauthorizedError,
+ )
+_dynamic_imports: typing.Dict[str, str] = {
+ "BadRequestError": ".errors",
+ "BatchCounts": ".types",
+ "BatchError": ".types",
+ "BatchItemStatus": ".types",
+ "BatchItemStatusStatus": ".types",
+ "BatchJob": ".types",
+ "BatchJobStatus": ".types",
+ "ClientClosedRequestError": ".errors",
+ "ConflictError": ".errors",
+ "ContentTooLargeError": ".errors",
+ "GatewayTimeoutError": ".errors",
+ "InternalServerError": ".errors",
+ "JobDetailResponse": ".types",
+ "JobListResponse": ".types",
+ "NotFoundError": ".errors",
+ "ResultsPageResponse": ".types",
+ "UnauthorizedError": ".errors",
+ "UploadItemResponse": ".types",
+}
+
+
+def __getattr__(attr_name: str) -> typing.Any:
+ module_name = _dynamic_imports.get(attr_name)
+ if module_name is None:
+ raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}")
+ try:
+ module = import_module(module_name, __package__)
+ if module_name == f".{attr_name}":
+ return module
+ else:
+ return getattr(module, attr_name)
+ except ImportError as e:
+ raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e
+ except AttributeError as e:
+ raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e
+
+
+def __dir__():
+ lazy_attrs = list(_dynamic_imports.keys())
+ return sorted(lazy_attrs)
+
+
+__all__ = [
+ "BadRequestError",
+ "BatchCounts",
+ "BatchError",
+ "BatchItemStatus",
+ "BatchItemStatusStatus",
+ "BatchJob",
+ "BatchJobStatus",
+ "ClientClosedRequestError",
+ "ConflictError",
+ "ContentTooLargeError",
+ "GatewayTimeoutError",
+ "InternalServerError",
+ "JobDetailResponse",
+ "JobListResponse",
+ "NotFoundError",
+ "ResultsPageResponse",
+ "UnauthorizedError",
+ "UploadItemResponse",
+]
diff --git a/src/phenoml/lang2fhir_batch/client.py b/src/phenoml/lang2fhir_batch/client.py
new file mode 100644
index 0000000..2d95deb
--- /dev/null
+++ b/src/phenoml/lang2fhir_batch/client.py
@@ -0,0 +1,898 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+from .. import core
+from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper
+from ..core.request_options import RequestOptions
+from .raw_client import AsyncRawLang2FhirBatchClient, RawLang2FhirBatchClient
+from .types.batch_job import BatchJob
+from .types.job_detail_response import JobDetailResponse
+from .types.job_list_response import JobListResponse
+from .types.results_page_response import ResultsPageResponse
+from .types.upload_item_response import UploadItemResponse
+
+# this is used as the default value for optional parameters
+OMIT = typing.cast(typing.Any, ...)
+
+
+class Lang2FhirBatchClient:
+ def __init__(self, *, client_wrapper: SyncClientWrapper):
+ self._raw_client = RawLang2FhirBatchClient(client_wrapper=client_wrapper)
+
+ @property
+ def with_raw_response(self) -> RawLang2FhirBatchClient:
+ """
+ Retrieves a raw implementation of this client that returns raw responses.
+
+ Returns
+ -------
+ RawLang2FhirBatchClient
+ """
+ return self._raw_client
+
+ def list(
+ self,
+ *,
+ cursor: typing.Optional[str] = None,
+ limit: typing.Optional[int] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> JobListResponse:
+ """
+ Returns a page of the instance's batch jobs, newest first, without
+ per-job counts. Jobs are shared across the instance's credentials, so
+ this lists every batch job on the instance, not just the calling
+ credential's.
+
+ Parameters
+ ----------
+ cursor : typing.Optional[str]
+ Opaque pagination cursor from a previous page's next_cursor.
+
+ limit : typing.Optional[int]
+ Page size. Defaults to 20; values above 100 are clamped to 100.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ JobListResponse
+ A page of batch jobs
+
+ Examples
+ --------
+ from phenoml import PhenomlClient
+
+ client = PhenomlClient(
+ client_id="YOUR_CLIENT_ID",
+ client_secret="YOUR_CLIENT_SECRET",
+ )
+ client.lang2fhir_batch.list(
+ cursor="cursor",
+ limit=1,
+ )
+ """
+ _response = self._raw_client.list(cursor=cursor, limit=limit, request_options=request_options)
+ return _response.data
+
+ def create(
+ self, *, request_id: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None
+ ) -> BatchJob:
+ """
+ Opens an empty batch job. Items arrive on later upload calls and the set
+ is sealed at finalize.
+
+ Supplying `request_id` makes the create idempotent on that token: a
+ retried submit whose response was lost returns the original job rather
+ than opening a second one. This dedupe is scoped to the calling
+ credential.
+
+ An instance may hold at most 4 active (pending or processing) jobs at
+ once; a create past that limit returns `409`. The limit is instance-wide
+ — jobs are shared across the instance's credentials — so another
+ credential's jobs count against it.
+
+ Parameters
+ ----------
+ request_id : typing.Optional[str]
+ Optional client idempotency token. A retried create with the same
+ token returns the original job instead of opening a second one.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ BatchJob
+ Batch job created (or an idempotent replay of an existing job)
+
+ Examples
+ --------
+ from phenoml import PhenomlClient
+
+ client = PhenomlClient(
+ client_id="YOUR_CLIENT_ID",
+ client_secret="YOUR_CLIENT_SECRET",
+ )
+ client.lang2fhir_batch.create(
+ request_id="submit-2025-09-02-batch-001",
+ )
+ """
+ _response = self._raw_client.create(request_id=request_id, request_options=request_options)
+ return _response.data
+
+ def upload_item(
+ self,
+ job_id: str,
+ *,
+ document: typing.Optional[typing.Dict[str, typing.Any]] = OMIT,
+ create: typing.Optional[typing.Dict[str, typing.Any]] = OMIT,
+ file: typing.Optional[core.File] = OMIT,
+ request_id: typing.Optional[str] = OMIT,
+ id: typing.Optional[str] = OMIT,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> UploadItemResponse:
+ """
+ Stores one item of a job from a multipart upload. A batch's items arrive
+ one per request. The item carries **either** a `document` extraction
+ (whose input file rides as raw bytes in the `file` part) **or** a
+ `create` extraction (JSON only, no file).
+
+ The upload enforces these rules:
+ - Set **exactly one** of `document` or `create`. Setting both, or
+ neither, is a `400`.
+ - When `document` is set, `file` is **required** — it supplies the
+ document's binary content (PDF or image).
+ - When `create` is set, `file` is **forbidden** — a create item carries
+ no file.
+ - `document` and `create` must each be a JSON **object**.
+
+ Only the item's structure is checked here: the fields inside `document`
+ or `create` are not validated at upload. A body that is well-formed JSON
+ but not a valid request for its endpoint is still accepted with `202`
+ and fails later during processing, recorded as an item `error`. A
+ wrong-typed field the endpoint cannot decode fails as `invalid_input`; a
+ body that decodes but the pipeline rejects (for example, a missing
+ required field) fails as `processing_failed`.
+
+ Supplying `request_id` makes the upload idempotent on that token. A
+ re-upload under the same token overwrites the same item rather than
+ adding a second, so a client that lost an upload's response can safely
+ re-send it. The response's `deduplicated` is `true` only when the
+ re-uploaded payload matches the one already stored; a same-token upload
+ with a changed payload overwrites in place and returns `false`.
+
+ Set a `request_id` on **every** upload: re-sending under the same token
+ is the only way to repair a lost or incomplete upload, including the one
+ a finalize `409` reports. Without one, a re-send adds a new item instead
+ of replacing the missing one, and the job cannot be finalized.
+
+ Uploads are rejected once the job has been finalized (`409`), once it
+ holds its 500-item limit (`409`), or when the item is too large (`413` —
+ see the raw-file limit in the API description).
+
+ Parameters
+ ----------
+ job_id : str
+
+ document : typing.Optional[typing.Dict[str, typing.Any]]
+ The JSON body of `POST /lang2fhir/document/multi`, **without**
+ its base64 `content` field — the uploaded `file` supplies the
+ content. Accepts that endpoint's fields (`version`, `provider`,
+ `patient_reference`, `implementation_guide`, `detection_effort`,
+ `validation_method`, `config`). This is the **multi**-resource
+ body: it has no single-`resource` field, and the item's result
+ is a `DocumentMultiResponse` (a Bundle of resources). Mutually
+ exclusive with `create`; requires `file`.
+
+ create : typing.Optional[typing.Dict[str, typing.Any]]
+ The JSON body of `POST /lang2fhir/create/multi`. Accepts that
+ endpoint's fields (`text`, `version`, `provider`,
+ `patient_reference`, `implementation_guide`, `detection_effort`,
+ `validation_method`, `resource_review`). This is the
+ **multi**-resource body: it has no single-`resource` field, and
+ the item's result is a `CreateMultiResponse` (a Bundle of
+ resources). Mutually exclusive with `document`; must **not** be
+ accompanied by a `file`.
+
+ file : typing.Optional[core.File]
+ See core.File for more documentation
+
+ request_id : typing.Optional[str]
+ Optional idempotency token (max 256 bytes). Re-uploading under
+ the same token overwrites the same item instead of adding a
+ new one. The token is scoped to this job; the same token in
+ another job is independent and creates a separate item.
+
+ id : typing.Optional[str]
+ Optional caller-supplied correlation label (max 512 bytes),
+ echoed back on status and result listings so you can match the
+ server's item_id to your own record.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ UploadItemResponse
+ Item accepted
+
+ Examples
+ --------
+ from phenoml import PhenomlClient
+
+ client = PhenomlClient(
+ client_id="YOUR_CLIENT_ID",
+ client_secret="YOUR_CLIENT_SECRET",
+ )
+ client.lang2fhir_batch.upload_item(
+ job_id="job_id",
+ )
+ """
+ _response = self._raw_client.upload_item(
+ job_id,
+ document=document,
+ create=create,
+ file=file,
+ request_id=request_id,
+ id=id,
+ request_options=request_options,
+ )
+ return _response.data
+
+ def finalize(self, job_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> BatchJob:
+ """
+ Seals the job's item set and starts processing. Takes no request body.
+ Finalize is idempotent: a retried finalize succeeds again.
+
+ If a previous upload did not complete, finalize returns a `409`; re-send
+ the missing upload (with the same `request_id`), then finalize.
+ Finalizing a job with no items is a `400`.
+
+ Parameters
+ ----------
+ job_id : str
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ BatchJob
+ Job finalized and processing started
+
+ Examples
+ --------
+ from phenoml import PhenomlClient
+
+ client = PhenomlClient(
+ client_id="YOUR_CLIENT_ID",
+ client_secret="YOUR_CLIENT_SECRET",
+ )
+ client.lang2fhir_batch.finalize(
+ job_id="job_id",
+ )
+ """
+ _response = self._raw_client.finalize(job_id, request_options=request_options)
+ return _response.data
+
+ def get(
+ self,
+ job_id: str,
+ *,
+ cursor: typing.Optional[str] = None,
+ limit: typing.Optional[int] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> JobDetailResponse:
+ """
+ Returns a job's record, its per-status item counts, and one page of
+ per-item statuses.
+
+ Items are listed in a stable order that is not upload order and is the
+ same across pages. Match each entry to your own records by its `id`
+ (your correlation label) or `item_id` (from the upload response),
+ never by position.
+
+ Parameters
+ ----------
+ job_id : str
+
+ cursor : typing.Optional[str]
+ Opaque pagination cursor from a previous page's next_cursor.
+
+ limit : typing.Optional[int]
+ Page size for the item-status page. Defaults to 20; values above 100 are clamped to 100.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ JobDetailResponse
+ The job record, counts, and a page of item statuses
+
+ Examples
+ --------
+ from phenoml import PhenomlClient
+
+ client = PhenomlClient(
+ client_id="YOUR_CLIENT_ID",
+ client_secret="YOUR_CLIENT_SECRET",
+ )
+ client.lang2fhir_batch.get(
+ job_id="job_id",
+ cursor="cursor",
+ limit=1,
+ )
+ """
+ _response = self._raw_client.get(job_id, cursor=cursor, limit=limit, request_options=request_options)
+ return _response.data
+
+ def get_results(
+ self,
+ job_id: str,
+ *,
+ cursor: typing.Optional[str] = None,
+ limit: typing.Optional[int] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> ResultsPageResponse:
+ """
+ A lighter status page. Returns the same per-item status entries as
+ `GET /lang2fhir/batch/{job_id}`, but without the job record or counts,
+ and the entries carry `result_size` rather than any result content. Use
+ each entry's `item_id` to fetch that item's result from
+ `GET /lang2fhir/batch/{job_id}/results/{item_id}`.
+
+ Entries are listed in a stable order that is not upload order and is
+ the same across pages. Match each entry to your own records by its `id`
+ (your correlation label) or `item_id` (from the upload response),
+ never by position.
+
+ Parameters
+ ----------
+ job_id : str
+
+ cursor : typing.Optional[str]
+ Opaque pagination cursor from a previous page's next_cursor.
+
+ limit : typing.Optional[int]
+ Page size. Defaults to 20; values above 100 are clamped to 100.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ ResultsPageResponse
+ A page of per-item result statuses
+
+ Examples
+ --------
+ from phenoml import PhenomlClient
+
+ client = PhenomlClient(
+ client_id="YOUR_CLIENT_ID",
+ client_secret="YOUR_CLIENT_SECRET",
+ )
+ client.lang2fhir_batch.get_results(
+ job_id="job_id",
+ cursor="cursor",
+ limit=1,
+ )
+ """
+ _response = self._raw_client.get_results(job_id, cursor=cursor, limit=limit, request_options=request_options)
+ return _response.data
+
+ def get_result(
+ self, job_id: str, item_id: str, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> typing.Dict[str, typing.Any]:
+ """
+ Streams one item's stored result bytes verbatim as `application/json`.
+ The body is the response the item's synchronous multi endpoint would have
+ returned — a `DocumentMultiResponse` for a document item or a
+ `CreateMultiResponse` for a create item.
+
+ Only a succeeded item has a result: an item that has not succeeded
+ (pending, processing, or failed) is a `409`, and a result that has
+ expired is a `404`.
+
+ Parameters
+ ----------
+ job_id : str
+
+ item_id : str
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ typing.Dict[str, typing.Any]
+ The item's raw result JSON
+
+ Examples
+ --------
+ from phenoml import PhenomlClient
+
+ client = PhenomlClient(
+ client_id="YOUR_CLIENT_ID",
+ client_secret="YOUR_CLIENT_SECRET",
+ )
+ client.lang2fhir_batch.get_result(
+ job_id="job_id",
+ item_id="item_id",
+ )
+ """
+ _response = self._raw_client.get_result(job_id, item_id, request_options=request_options)
+ return _response.data
+
+
+class AsyncLang2FhirBatchClient:
+ def __init__(self, *, client_wrapper: AsyncClientWrapper):
+ self._raw_client = AsyncRawLang2FhirBatchClient(client_wrapper=client_wrapper)
+
+ @property
+ def with_raw_response(self) -> AsyncRawLang2FhirBatchClient:
+ """
+ Retrieves a raw implementation of this client that returns raw responses.
+
+ Returns
+ -------
+ AsyncRawLang2FhirBatchClient
+ """
+ return self._raw_client
+
+ async def list(
+ self,
+ *,
+ cursor: typing.Optional[str] = None,
+ limit: typing.Optional[int] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> JobListResponse:
+ """
+ Returns a page of the instance's batch jobs, newest first, without
+ per-job counts. Jobs are shared across the instance's credentials, so
+ this lists every batch job on the instance, not just the calling
+ credential's.
+
+ Parameters
+ ----------
+ cursor : typing.Optional[str]
+ Opaque pagination cursor from a previous page's next_cursor.
+
+ limit : typing.Optional[int]
+ Page size. Defaults to 20; values above 100 are clamped to 100.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ JobListResponse
+ A page of batch jobs
+
+ Examples
+ --------
+ import asyncio
+
+ from phenoml import AsyncPhenomlClient
+
+ client = AsyncPhenomlClient(
+ client_id="YOUR_CLIENT_ID",
+ client_secret="YOUR_CLIENT_SECRET",
+ )
+
+
+ async def main() -> None:
+ await client.lang2fhir_batch.list(
+ cursor="cursor",
+ limit=1,
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.list(cursor=cursor, limit=limit, request_options=request_options)
+ return _response.data
+
+ async def create(
+ self, *, request_id: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None
+ ) -> BatchJob:
+ """
+ Opens an empty batch job. Items arrive on later upload calls and the set
+ is sealed at finalize.
+
+ Supplying `request_id` makes the create idempotent on that token: a
+ retried submit whose response was lost returns the original job rather
+ than opening a second one. This dedupe is scoped to the calling
+ credential.
+
+ An instance may hold at most 4 active (pending or processing) jobs at
+ once; a create past that limit returns `409`. The limit is instance-wide
+ — jobs are shared across the instance's credentials — so another
+ credential's jobs count against it.
+
+ Parameters
+ ----------
+ request_id : typing.Optional[str]
+ Optional client idempotency token. A retried create with the same
+ token returns the original job instead of opening a second one.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ BatchJob
+ Batch job created (or an idempotent replay of an existing job)
+
+ Examples
+ --------
+ import asyncio
+
+ from phenoml import AsyncPhenomlClient
+
+ client = AsyncPhenomlClient(
+ client_id="YOUR_CLIENT_ID",
+ client_secret="YOUR_CLIENT_SECRET",
+ )
+
+
+ async def main() -> None:
+ await client.lang2fhir_batch.create(
+ request_id="submit-2025-09-02-batch-001",
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.create(request_id=request_id, request_options=request_options)
+ return _response.data
+
+ async def upload_item(
+ self,
+ job_id: str,
+ *,
+ document: typing.Optional[typing.Dict[str, typing.Any]] = OMIT,
+ create: typing.Optional[typing.Dict[str, typing.Any]] = OMIT,
+ file: typing.Optional[core.File] = OMIT,
+ request_id: typing.Optional[str] = OMIT,
+ id: typing.Optional[str] = OMIT,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> UploadItemResponse:
+ """
+ Stores one item of a job from a multipart upload. A batch's items arrive
+ one per request. The item carries **either** a `document` extraction
+ (whose input file rides as raw bytes in the `file` part) **or** a
+ `create` extraction (JSON only, no file).
+
+ The upload enforces these rules:
+ - Set **exactly one** of `document` or `create`. Setting both, or
+ neither, is a `400`.
+ - When `document` is set, `file` is **required** — it supplies the
+ document's binary content (PDF or image).
+ - When `create` is set, `file` is **forbidden** — a create item carries
+ no file.
+ - `document` and `create` must each be a JSON **object**.
+
+ Only the item's structure is checked here: the fields inside `document`
+ or `create` are not validated at upload. A body that is well-formed JSON
+ but not a valid request for its endpoint is still accepted with `202`
+ and fails later during processing, recorded as an item `error`. A
+ wrong-typed field the endpoint cannot decode fails as `invalid_input`; a
+ body that decodes but the pipeline rejects (for example, a missing
+ required field) fails as `processing_failed`.
+
+ Supplying `request_id` makes the upload idempotent on that token. A
+ re-upload under the same token overwrites the same item rather than
+ adding a second, so a client that lost an upload's response can safely
+ re-send it. The response's `deduplicated` is `true` only when the
+ re-uploaded payload matches the one already stored; a same-token upload
+ with a changed payload overwrites in place and returns `false`.
+
+ Set a `request_id` on **every** upload: re-sending under the same token
+ is the only way to repair a lost or incomplete upload, including the one
+ a finalize `409` reports. Without one, a re-send adds a new item instead
+ of replacing the missing one, and the job cannot be finalized.
+
+ Uploads are rejected once the job has been finalized (`409`), once it
+ holds its 500-item limit (`409`), or when the item is too large (`413` —
+ see the raw-file limit in the API description).
+
+ Parameters
+ ----------
+ job_id : str
+
+ document : typing.Optional[typing.Dict[str, typing.Any]]
+ The JSON body of `POST /lang2fhir/document/multi`, **without**
+ its base64 `content` field — the uploaded `file` supplies the
+ content. Accepts that endpoint's fields (`version`, `provider`,
+ `patient_reference`, `implementation_guide`, `detection_effort`,
+ `validation_method`, `config`). This is the **multi**-resource
+ body: it has no single-`resource` field, and the item's result
+ is a `DocumentMultiResponse` (a Bundle of resources). Mutually
+ exclusive with `create`; requires `file`.
+
+ create : typing.Optional[typing.Dict[str, typing.Any]]
+ The JSON body of `POST /lang2fhir/create/multi`. Accepts that
+ endpoint's fields (`text`, `version`, `provider`,
+ `patient_reference`, `implementation_guide`, `detection_effort`,
+ `validation_method`, `resource_review`). This is the
+ **multi**-resource body: it has no single-`resource` field, and
+ the item's result is a `CreateMultiResponse` (a Bundle of
+ resources). Mutually exclusive with `document`; must **not** be
+ accompanied by a `file`.
+
+ file : typing.Optional[core.File]
+ See core.File for more documentation
+
+ request_id : typing.Optional[str]
+ Optional idempotency token (max 256 bytes). Re-uploading under
+ the same token overwrites the same item instead of adding a
+ new one. The token is scoped to this job; the same token in
+ another job is independent and creates a separate item.
+
+ id : typing.Optional[str]
+ Optional caller-supplied correlation label (max 512 bytes),
+ echoed back on status and result listings so you can match the
+ server's item_id to your own record.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ UploadItemResponse
+ Item accepted
+
+ Examples
+ --------
+ import asyncio
+
+ from phenoml import AsyncPhenomlClient
+
+ client = AsyncPhenomlClient(
+ client_id="YOUR_CLIENT_ID",
+ client_secret="YOUR_CLIENT_SECRET",
+ )
+
+
+ async def main() -> None:
+ await client.lang2fhir_batch.upload_item(
+ job_id="job_id",
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.upload_item(
+ job_id,
+ document=document,
+ create=create,
+ file=file,
+ request_id=request_id,
+ id=id,
+ request_options=request_options,
+ )
+ return _response.data
+
+ async def finalize(self, job_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> BatchJob:
+ """
+ Seals the job's item set and starts processing. Takes no request body.
+ Finalize is idempotent: a retried finalize succeeds again.
+
+ If a previous upload did not complete, finalize returns a `409`; re-send
+ the missing upload (with the same `request_id`), then finalize.
+ Finalizing a job with no items is a `400`.
+
+ Parameters
+ ----------
+ job_id : str
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ BatchJob
+ Job finalized and processing started
+
+ Examples
+ --------
+ import asyncio
+
+ from phenoml import AsyncPhenomlClient
+
+ client = AsyncPhenomlClient(
+ client_id="YOUR_CLIENT_ID",
+ client_secret="YOUR_CLIENT_SECRET",
+ )
+
+
+ async def main() -> None:
+ await client.lang2fhir_batch.finalize(
+ job_id="job_id",
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.finalize(job_id, request_options=request_options)
+ return _response.data
+
+ async def get(
+ self,
+ job_id: str,
+ *,
+ cursor: typing.Optional[str] = None,
+ limit: typing.Optional[int] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> JobDetailResponse:
+ """
+ Returns a job's record, its per-status item counts, and one page of
+ per-item statuses.
+
+ Items are listed in a stable order that is not upload order and is the
+ same across pages. Match each entry to your own records by its `id`
+ (your correlation label) or `item_id` (from the upload response),
+ never by position.
+
+ Parameters
+ ----------
+ job_id : str
+
+ cursor : typing.Optional[str]
+ Opaque pagination cursor from a previous page's next_cursor.
+
+ limit : typing.Optional[int]
+ Page size for the item-status page. Defaults to 20; values above 100 are clamped to 100.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ JobDetailResponse
+ The job record, counts, and a page of item statuses
+
+ Examples
+ --------
+ import asyncio
+
+ from phenoml import AsyncPhenomlClient
+
+ client = AsyncPhenomlClient(
+ client_id="YOUR_CLIENT_ID",
+ client_secret="YOUR_CLIENT_SECRET",
+ )
+
+
+ async def main() -> None:
+ await client.lang2fhir_batch.get(
+ job_id="job_id",
+ cursor="cursor",
+ limit=1,
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.get(job_id, cursor=cursor, limit=limit, request_options=request_options)
+ return _response.data
+
+ async def get_results(
+ self,
+ job_id: str,
+ *,
+ cursor: typing.Optional[str] = None,
+ limit: typing.Optional[int] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> ResultsPageResponse:
+ """
+ A lighter status page. Returns the same per-item status entries as
+ `GET /lang2fhir/batch/{job_id}`, but without the job record or counts,
+ and the entries carry `result_size` rather than any result content. Use
+ each entry's `item_id` to fetch that item's result from
+ `GET /lang2fhir/batch/{job_id}/results/{item_id}`.
+
+ Entries are listed in a stable order that is not upload order and is
+ the same across pages. Match each entry to your own records by its `id`
+ (your correlation label) or `item_id` (from the upload response),
+ never by position.
+
+ Parameters
+ ----------
+ job_id : str
+
+ cursor : typing.Optional[str]
+ Opaque pagination cursor from a previous page's next_cursor.
+
+ limit : typing.Optional[int]
+ Page size. Defaults to 20; values above 100 are clamped to 100.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ ResultsPageResponse
+ A page of per-item result statuses
+
+ Examples
+ --------
+ import asyncio
+
+ from phenoml import AsyncPhenomlClient
+
+ client = AsyncPhenomlClient(
+ client_id="YOUR_CLIENT_ID",
+ client_secret="YOUR_CLIENT_SECRET",
+ )
+
+
+ async def main() -> None:
+ await client.lang2fhir_batch.get_results(
+ job_id="job_id",
+ cursor="cursor",
+ limit=1,
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.get_results(
+ job_id, cursor=cursor, limit=limit, request_options=request_options
+ )
+ return _response.data
+
+ async def get_result(
+ self, job_id: str, item_id: str, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> typing.Dict[str, typing.Any]:
+ """
+ Streams one item's stored result bytes verbatim as `application/json`.
+ The body is the response the item's synchronous multi endpoint would have
+ returned — a `DocumentMultiResponse` for a document item or a
+ `CreateMultiResponse` for a create item.
+
+ Only a succeeded item has a result: an item that has not succeeded
+ (pending, processing, or failed) is a `409`, and a result that has
+ expired is a `404`.
+
+ Parameters
+ ----------
+ job_id : str
+
+ item_id : str
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ typing.Dict[str, typing.Any]
+ The item's raw result JSON
+
+ Examples
+ --------
+ import asyncio
+
+ from phenoml import AsyncPhenomlClient
+
+ client = AsyncPhenomlClient(
+ client_id="YOUR_CLIENT_ID",
+ client_secret="YOUR_CLIENT_SECRET",
+ )
+
+
+ async def main() -> None:
+ await client.lang2fhir_batch.get_result(
+ job_id="job_id",
+ item_id="item_id",
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.get_result(job_id, item_id, request_options=request_options)
+ return _response.data
diff --git a/src/phenoml/lang2fhir_batch/errors/__init__.py b/src/phenoml/lang2fhir_batch/errors/__init__.py
new file mode 100644
index 0000000..00ddd1d
--- /dev/null
+++ b/src/phenoml/lang2fhir_batch/errors/__init__.py
@@ -0,0 +1,59 @@
+# This file was auto-generated by Fern from our API Definition.
+
+# isort: skip_file
+
+import typing
+from importlib import import_module
+
+if typing.TYPE_CHECKING:
+ from .bad_request_error import BadRequestError
+ from .client_closed_request_error import ClientClosedRequestError
+ from .conflict_error import ConflictError
+ from .content_too_large_error import ContentTooLargeError
+ from .gateway_timeout_error import GatewayTimeoutError
+ from .internal_server_error import InternalServerError
+ from .not_found_error import NotFoundError
+ from .unauthorized_error import UnauthorizedError
+_dynamic_imports: typing.Dict[str, str] = {
+ "BadRequestError": ".bad_request_error",
+ "ClientClosedRequestError": ".client_closed_request_error",
+ "ConflictError": ".conflict_error",
+ "ContentTooLargeError": ".content_too_large_error",
+ "GatewayTimeoutError": ".gateway_timeout_error",
+ "InternalServerError": ".internal_server_error",
+ "NotFoundError": ".not_found_error",
+ "UnauthorizedError": ".unauthorized_error",
+}
+
+
+def __getattr__(attr_name: str) -> typing.Any:
+ module_name = _dynamic_imports.get(attr_name)
+ if module_name is None:
+ raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}")
+ try:
+ module = import_module(module_name, __package__)
+ if module_name == f".{attr_name}":
+ return module
+ else:
+ return getattr(module, attr_name)
+ except ImportError as e:
+ raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e
+ except AttributeError as e:
+ raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e
+
+
+def __dir__():
+ lazy_attrs = list(_dynamic_imports.keys())
+ return sorted(lazy_attrs)
+
+
+__all__ = [
+ "BadRequestError",
+ "ClientClosedRequestError",
+ "ConflictError",
+ "ContentTooLargeError",
+ "GatewayTimeoutError",
+ "InternalServerError",
+ "NotFoundError",
+ "UnauthorizedError",
+]
diff --git a/src/phenoml/lang2fhir_batch/errors/bad_request_error.py b/src/phenoml/lang2fhir_batch/errors/bad_request_error.py
new file mode 100644
index 0000000..412bae7
--- /dev/null
+++ b/src/phenoml/lang2fhir_batch/errors/bad_request_error.py
@@ -0,0 +1,10 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+from ...core.api_error import ApiError
+
+
+class BadRequestError(ApiError):
+ def __init__(self, body: typing.Any, headers: typing.Optional[typing.Dict[str, str]] = None):
+ super().__init__(status_code=400, headers=headers, body=body)
diff --git a/src/phenoml/lang2fhir_batch/errors/client_closed_request_error.py b/src/phenoml/lang2fhir_batch/errors/client_closed_request_error.py
new file mode 100644
index 0000000..fce97ba
--- /dev/null
+++ b/src/phenoml/lang2fhir_batch/errors/client_closed_request_error.py
@@ -0,0 +1,10 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+from ...core.api_error import ApiError
+
+
+class ClientClosedRequestError(ApiError):
+ def __init__(self, body: typing.Any, headers: typing.Optional[typing.Dict[str, str]] = None):
+ super().__init__(status_code=499, headers=headers, body=body)
diff --git a/src/phenoml/lang2fhir_batch/errors/conflict_error.py b/src/phenoml/lang2fhir_batch/errors/conflict_error.py
new file mode 100644
index 0000000..abe1a9f
--- /dev/null
+++ b/src/phenoml/lang2fhir_batch/errors/conflict_error.py
@@ -0,0 +1,10 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+from ...core.api_error import ApiError
+
+
+class ConflictError(ApiError):
+ def __init__(self, body: typing.Any, headers: typing.Optional[typing.Dict[str, str]] = None):
+ super().__init__(status_code=409, headers=headers, body=body)
diff --git a/src/phenoml/lang2fhir_batch/errors/content_too_large_error.py b/src/phenoml/lang2fhir_batch/errors/content_too_large_error.py
new file mode 100644
index 0000000..5aa39e3
--- /dev/null
+++ b/src/phenoml/lang2fhir_batch/errors/content_too_large_error.py
@@ -0,0 +1,10 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+from ...core.api_error import ApiError
+
+
+class ContentTooLargeError(ApiError):
+ def __init__(self, body: typing.Any, headers: typing.Optional[typing.Dict[str, str]] = None):
+ super().__init__(status_code=413, headers=headers, body=body)
diff --git a/src/phenoml/lang2fhir_batch/errors/gateway_timeout_error.py b/src/phenoml/lang2fhir_batch/errors/gateway_timeout_error.py
new file mode 100644
index 0000000..19acf1d
--- /dev/null
+++ b/src/phenoml/lang2fhir_batch/errors/gateway_timeout_error.py
@@ -0,0 +1,10 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+from ...core.api_error import ApiError
+
+
+class GatewayTimeoutError(ApiError):
+ def __init__(self, body: typing.Any, headers: typing.Optional[typing.Dict[str, str]] = None):
+ super().__init__(status_code=504, headers=headers, body=body)
diff --git a/src/phenoml/lang2fhir_batch/errors/internal_server_error.py b/src/phenoml/lang2fhir_batch/errors/internal_server_error.py
new file mode 100644
index 0000000..6df0c1d
--- /dev/null
+++ b/src/phenoml/lang2fhir_batch/errors/internal_server_error.py
@@ -0,0 +1,10 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+from ...core.api_error import ApiError
+
+
+class InternalServerError(ApiError):
+ def __init__(self, body: typing.Any, headers: typing.Optional[typing.Dict[str, str]] = None):
+ super().__init__(status_code=500, headers=headers, body=body)
diff --git a/src/phenoml/lang2fhir_batch/errors/not_found_error.py b/src/phenoml/lang2fhir_batch/errors/not_found_error.py
new file mode 100644
index 0000000..a01562a
--- /dev/null
+++ b/src/phenoml/lang2fhir_batch/errors/not_found_error.py
@@ -0,0 +1,10 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+from ...core.api_error import ApiError
+
+
+class NotFoundError(ApiError):
+ def __init__(self, body: typing.Any, headers: typing.Optional[typing.Dict[str, str]] = None):
+ super().__init__(status_code=404, headers=headers, body=body)
diff --git a/src/phenoml/lang2fhir_batch/errors/unauthorized_error.py b/src/phenoml/lang2fhir_batch/errors/unauthorized_error.py
new file mode 100644
index 0000000..c990122
--- /dev/null
+++ b/src/phenoml/lang2fhir_batch/errors/unauthorized_error.py
@@ -0,0 +1,10 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+from ...core.api_error import ApiError
+
+
+class UnauthorizedError(ApiError):
+ def __init__(self, body: typing.Any, headers: typing.Optional[typing.Dict[str, str]] = None):
+ super().__init__(status_code=401, headers=headers, body=body)
diff --git a/src/phenoml/lang2fhir_batch/raw_client.py b/src/phenoml/lang2fhir_batch/raw_client.py
new file mode 100644
index 0000000..2c5dd25
--- /dev/null
+++ b/src/phenoml/lang2fhir_batch/raw_client.py
@@ -0,0 +1,2001 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import json
+import typing
+from json.decoder import JSONDecodeError
+
+from .. import core
+from ..core.api_error import ApiError
+from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper
+from ..core.http_response import AsyncHttpResponse, HttpResponse
+from ..core.jsonable_encoder import encode_path_param, jsonable_encoder
+from ..core.parse_error import ParsingError
+from ..core.pydantic_utilities import parse_obj_as
+from ..core.request_options import RequestOptions
+from .errors.bad_request_error import BadRequestError
+from .errors.client_closed_request_error import ClientClosedRequestError
+from .errors.conflict_error import ConflictError
+from .errors.content_too_large_error import ContentTooLargeError
+from .errors.gateway_timeout_error import GatewayTimeoutError
+from .errors.internal_server_error import InternalServerError
+from .errors.not_found_error import NotFoundError
+from .errors.unauthorized_error import UnauthorizedError
+from .types.batch_job import BatchJob
+from .types.job_detail_response import JobDetailResponse
+from .types.job_list_response import JobListResponse
+from .types.results_page_response import ResultsPageResponse
+from .types.upload_item_response import UploadItemResponse
+from pydantic import ValidationError
+
+# this is used as the default value for optional parameters
+OMIT = typing.cast(typing.Any, ...)
+
+
+class RawLang2FhirBatchClient:
+ def __init__(self, *, client_wrapper: SyncClientWrapper):
+ self._client_wrapper = client_wrapper
+
+ def list(
+ self,
+ *,
+ cursor: typing.Optional[str] = None,
+ limit: typing.Optional[int] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> HttpResponse[JobListResponse]:
+ """
+ Returns a page of the instance's batch jobs, newest first, without
+ per-job counts. Jobs are shared across the instance's credentials, so
+ this lists every batch job on the instance, not just the calling
+ credential's.
+
+ Parameters
+ ----------
+ cursor : typing.Optional[str]
+ Opaque pagination cursor from a previous page's next_cursor.
+
+ limit : typing.Optional[int]
+ Page size. Defaults to 20; values above 100 are clamped to 100.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[JobListResponse]
+ A page of batch jobs
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ "lang2fhir/batch",
+ method="GET",
+ params={
+ "cursor": cursor,
+ "limit": limit,
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ JobListResponse,
+ parse_obj_as(
+ type_=JobListResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 499:
+ raise ClientClosedRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 504:
+ raise GatewayTimeoutError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ def create(
+ self, *, request_id: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None
+ ) -> HttpResponse[BatchJob]:
+ """
+ Opens an empty batch job. Items arrive on later upload calls and the set
+ is sealed at finalize.
+
+ Supplying `request_id` makes the create idempotent on that token: a
+ retried submit whose response was lost returns the original job rather
+ than opening a second one. This dedupe is scoped to the calling
+ credential.
+
+ An instance may hold at most 4 active (pending or processing) jobs at
+ once; a create past that limit returns `409`. The limit is instance-wide
+ — jobs are shared across the instance's credentials — so another
+ credential's jobs count against it.
+
+ Parameters
+ ----------
+ request_id : typing.Optional[str]
+ Optional client idempotency token. A retried create with the same
+ token returns the original job instead of opening a second one.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[BatchJob]
+ Batch job created (or an idempotent replay of an existing job)
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ "lang2fhir/batch",
+ method="POST",
+ json={
+ "request_id": request_id,
+ },
+ headers={
+ "content-type": "application/json",
+ },
+ request_options=request_options,
+ omit=OMIT,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ BatchJob,
+ parse_obj_as(
+ type_=BatchJob, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 409:
+ raise ConflictError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 499:
+ raise ClientClosedRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 504:
+ raise GatewayTimeoutError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ def upload_item(
+ self,
+ job_id: str,
+ *,
+ document: typing.Optional[typing.Dict[str, typing.Any]] = OMIT,
+ create: typing.Optional[typing.Dict[str, typing.Any]] = OMIT,
+ file: typing.Optional[core.File] = OMIT,
+ request_id: typing.Optional[str] = OMIT,
+ id: typing.Optional[str] = OMIT,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> HttpResponse[UploadItemResponse]:
+ """
+ Stores one item of a job from a multipart upload. A batch's items arrive
+ one per request. The item carries **either** a `document` extraction
+ (whose input file rides as raw bytes in the `file` part) **or** a
+ `create` extraction (JSON only, no file).
+
+ The upload enforces these rules:
+ - Set **exactly one** of `document` or `create`. Setting both, or
+ neither, is a `400`.
+ - When `document` is set, `file` is **required** — it supplies the
+ document's binary content (PDF or image).
+ - When `create` is set, `file` is **forbidden** — a create item carries
+ no file.
+ - `document` and `create` must each be a JSON **object**.
+
+ Only the item's structure is checked here: the fields inside `document`
+ or `create` are not validated at upload. A body that is well-formed JSON
+ but not a valid request for its endpoint is still accepted with `202`
+ and fails later during processing, recorded as an item `error`. A
+ wrong-typed field the endpoint cannot decode fails as `invalid_input`; a
+ body that decodes but the pipeline rejects (for example, a missing
+ required field) fails as `processing_failed`.
+
+ Supplying `request_id` makes the upload idempotent on that token. A
+ re-upload under the same token overwrites the same item rather than
+ adding a second, so a client that lost an upload's response can safely
+ re-send it. The response's `deduplicated` is `true` only when the
+ re-uploaded payload matches the one already stored; a same-token upload
+ with a changed payload overwrites in place and returns `false`.
+
+ Set a `request_id` on **every** upload: re-sending under the same token
+ is the only way to repair a lost or incomplete upload, including the one
+ a finalize `409` reports. Without one, a re-send adds a new item instead
+ of replacing the missing one, and the job cannot be finalized.
+
+ Uploads are rejected once the job has been finalized (`409`), once it
+ holds its 500-item limit (`409`), or when the item is too large (`413` —
+ see the raw-file limit in the API description).
+
+ Parameters
+ ----------
+ job_id : str
+
+ document : typing.Optional[typing.Dict[str, typing.Any]]
+ The JSON body of `POST /lang2fhir/document/multi`, **without**
+ its base64 `content` field — the uploaded `file` supplies the
+ content. Accepts that endpoint's fields (`version`, `provider`,
+ `patient_reference`, `implementation_guide`, `detection_effort`,
+ `validation_method`, `config`). This is the **multi**-resource
+ body: it has no single-`resource` field, and the item's result
+ is a `DocumentMultiResponse` (a Bundle of resources). Mutually
+ exclusive with `create`; requires `file`.
+
+ create : typing.Optional[typing.Dict[str, typing.Any]]
+ The JSON body of `POST /lang2fhir/create/multi`. Accepts that
+ endpoint's fields (`text`, `version`, `provider`,
+ `patient_reference`, `implementation_guide`, `detection_effort`,
+ `validation_method`, `resource_review`). This is the
+ **multi**-resource body: it has no single-`resource` field, and
+ the item's result is a `CreateMultiResponse` (a Bundle of
+ resources). Mutually exclusive with `document`; must **not** be
+ accompanied by a `file`.
+
+ file : typing.Optional[core.File]
+ See core.File for more documentation
+
+ request_id : typing.Optional[str]
+ Optional idempotency token (max 256 bytes). Re-uploading under
+ the same token overwrites the same item instead of adding a
+ new one. The token is scoped to this job; the same token in
+ another job is independent and creates a separate item.
+
+ id : typing.Optional[str]
+ Optional caller-supplied correlation label (max 512 bytes),
+ echoed back on status and result listings so you can match the
+ server's item_id to your own record.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[UploadItemResponse]
+ Item accepted
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"lang2fhir/batch/{encode_path_param(job_id)}/items",
+ method="POST",
+ data={
+ "request_id": request_id,
+ "id": id,
+ },
+ files={
+ **(
+ {"document": (None, json.dumps(jsonable_encoder(document)), "application/json")}
+ if document is not OMIT
+ else {}
+ ),
+ **(
+ {"create": (None, json.dumps(jsonable_encoder(create)), "application/json")}
+ if create is not OMIT
+ else {}
+ ),
+ **({"file": file} if file is not None else {}),
+ },
+ request_options=request_options,
+ omit=OMIT,
+ force_multipart=True,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ UploadItemResponse,
+ parse_obj_as(
+ type_=UploadItemResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 409:
+ raise ConflictError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 413:
+ raise ContentTooLargeError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 499:
+ raise ClientClosedRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 504:
+ raise GatewayTimeoutError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ def finalize(
+ self, job_id: str, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> HttpResponse[BatchJob]:
+ """
+ Seals the job's item set and starts processing. Takes no request body.
+ Finalize is idempotent: a retried finalize succeeds again.
+
+ If a previous upload did not complete, finalize returns a `409`; re-send
+ the missing upload (with the same `request_id`), then finalize.
+ Finalizing a job with no items is a `400`.
+
+ Parameters
+ ----------
+ job_id : str
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[BatchJob]
+ Job finalized and processing started
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"lang2fhir/batch/{encode_path_param(job_id)}/finalize",
+ method="POST",
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ BatchJob,
+ parse_obj_as(
+ type_=BatchJob, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 409:
+ raise ConflictError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 499:
+ raise ClientClosedRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 504:
+ raise GatewayTimeoutError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ def get(
+ self,
+ job_id: str,
+ *,
+ cursor: typing.Optional[str] = None,
+ limit: typing.Optional[int] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> HttpResponse[JobDetailResponse]:
+ """
+ Returns a job's record, its per-status item counts, and one page of
+ per-item statuses.
+
+ Items are listed in a stable order that is not upload order and is the
+ same across pages. Match each entry to your own records by its `id`
+ (your correlation label) or `item_id` (from the upload response),
+ never by position.
+
+ Parameters
+ ----------
+ job_id : str
+
+ cursor : typing.Optional[str]
+ Opaque pagination cursor from a previous page's next_cursor.
+
+ limit : typing.Optional[int]
+ Page size for the item-status page. Defaults to 20; values above 100 are clamped to 100.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[JobDetailResponse]
+ The job record, counts, and a page of item statuses
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"lang2fhir/batch/{encode_path_param(job_id)}",
+ method="GET",
+ params={
+ "cursor": cursor,
+ "limit": limit,
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ JobDetailResponse,
+ parse_obj_as(
+ type_=JobDetailResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 499:
+ raise ClientClosedRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 504:
+ raise GatewayTimeoutError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ def get_results(
+ self,
+ job_id: str,
+ *,
+ cursor: typing.Optional[str] = None,
+ limit: typing.Optional[int] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> HttpResponse[ResultsPageResponse]:
+ """
+ A lighter status page. Returns the same per-item status entries as
+ `GET /lang2fhir/batch/{job_id}`, but without the job record or counts,
+ and the entries carry `result_size` rather than any result content. Use
+ each entry's `item_id` to fetch that item's result from
+ `GET /lang2fhir/batch/{job_id}/results/{item_id}`.
+
+ Entries are listed in a stable order that is not upload order and is
+ the same across pages. Match each entry to your own records by its `id`
+ (your correlation label) or `item_id` (from the upload response),
+ never by position.
+
+ Parameters
+ ----------
+ job_id : str
+
+ cursor : typing.Optional[str]
+ Opaque pagination cursor from a previous page's next_cursor.
+
+ limit : typing.Optional[int]
+ Page size. Defaults to 20; values above 100 are clamped to 100.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[ResultsPageResponse]
+ A page of per-item result statuses
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"lang2fhir/batch/{encode_path_param(job_id)}/results",
+ method="GET",
+ params={
+ "cursor": cursor,
+ "limit": limit,
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ ResultsPageResponse,
+ parse_obj_as(
+ type_=ResultsPageResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 499:
+ raise ClientClosedRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 504:
+ raise GatewayTimeoutError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ def get_result(
+ self, job_id: str, item_id: str, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> HttpResponse[typing.Dict[str, typing.Any]]:
+ """
+ Streams one item's stored result bytes verbatim as `application/json`.
+ The body is the response the item's synchronous multi endpoint would have
+ returned — a `DocumentMultiResponse` for a document item or a
+ `CreateMultiResponse` for a create item.
+
+ Only a succeeded item has a result: an item that has not succeeded
+ (pending, processing, or failed) is a `409`, and a result that has
+ expired is a `404`.
+
+ Parameters
+ ----------
+ job_id : str
+
+ item_id : str
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[typing.Dict[str, typing.Any]]
+ The item's raw result JSON
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"lang2fhir/batch/{encode_path_param(job_id)}/results/{encode_path_param(item_id)}",
+ method="GET",
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ typing.Dict[str, typing.Any],
+ parse_obj_as(
+ type_=typing.Dict[str, typing.Any], # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 409:
+ raise ConflictError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 499:
+ raise ClientClosedRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 504:
+ raise GatewayTimeoutError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+
+class AsyncRawLang2FhirBatchClient:
+ def __init__(self, *, client_wrapper: AsyncClientWrapper):
+ self._client_wrapper = client_wrapper
+
+ async def list(
+ self,
+ *,
+ cursor: typing.Optional[str] = None,
+ limit: typing.Optional[int] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AsyncHttpResponse[JobListResponse]:
+ """
+ Returns a page of the instance's batch jobs, newest first, without
+ per-job counts. Jobs are shared across the instance's credentials, so
+ this lists every batch job on the instance, not just the calling
+ credential's.
+
+ Parameters
+ ----------
+ cursor : typing.Optional[str]
+ Opaque pagination cursor from a previous page's next_cursor.
+
+ limit : typing.Optional[int]
+ Page size. Defaults to 20; values above 100 are clamped to 100.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[JobListResponse]
+ A page of batch jobs
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ "lang2fhir/batch",
+ method="GET",
+ params={
+ "cursor": cursor,
+ "limit": limit,
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ JobListResponse,
+ parse_obj_as(
+ type_=JobListResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 499:
+ raise ClientClosedRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 504:
+ raise GatewayTimeoutError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ async def create(
+ self, *, request_id: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None
+ ) -> AsyncHttpResponse[BatchJob]:
+ """
+ Opens an empty batch job. Items arrive on later upload calls and the set
+ is sealed at finalize.
+
+ Supplying `request_id` makes the create idempotent on that token: a
+ retried submit whose response was lost returns the original job rather
+ than opening a second one. This dedupe is scoped to the calling
+ credential.
+
+ An instance may hold at most 4 active (pending or processing) jobs at
+ once; a create past that limit returns `409`. The limit is instance-wide
+ — jobs are shared across the instance's credentials — so another
+ credential's jobs count against it.
+
+ Parameters
+ ----------
+ request_id : typing.Optional[str]
+ Optional client idempotency token. A retried create with the same
+ token returns the original job instead of opening a second one.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[BatchJob]
+ Batch job created (or an idempotent replay of an existing job)
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ "lang2fhir/batch",
+ method="POST",
+ json={
+ "request_id": request_id,
+ },
+ headers={
+ "content-type": "application/json",
+ },
+ request_options=request_options,
+ omit=OMIT,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ BatchJob,
+ parse_obj_as(
+ type_=BatchJob, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 409:
+ raise ConflictError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 499:
+ raise ClientClosedRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 504:
+ raise GatewayTimeoutError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ async def upload_item(
+ self,
+ job_id: str,
+ *,
+ document: typing.Optional[typing.Dict[str, typing.Any]] = OMIT,
+ create: typing.Optional[typing.Dict[str, typing.Any]] = OMIT,
+ file: typing.Optional[core.File] = OMIT,
+ request_id: typing.Optional[str] = OMIT,
+ id: typing.Optional[str] = OMIT,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AsyncHttpResponse[UploadItemResponse]:
+ """
+ Stores one item of a job from a multipart upload. A batch's items arrive
+ one per request. The item carries **either** a `document` extraction
+ (whose input file rides as raw bytes in the `file` part) **or** a
+ `create` extraction (JSON only, no file).
+
+ The upload enforces these rules:
+ - Set **exactly one** of `document` or `create`. Setting both, or
+ neither, is a `400`.
+ - When `document` is set, `file` is **required** — it supplies the
+ document's binary content (PDF or image).
+ - When `create` is set, `file` is **forbidden** — a create item carries
+ no file.
+ - `document` and `create` must each be a JSON **object**.
+
+ Only the item's structure is checked here: the fields inside `document`
+ or `create` are not validated at upload. A body that is well-formed JSON
+ but not a valid request for its endpoint is still accepted with `202`
+ and fails later during processing, recorded as an item `error`. A
+ wrong-typed field the endpoint cannot decode fails as `invalid_input`; a
+ body that decodes but the pipeline rejects (for example, a missing
+ required field) fails as `processing_failed`.
+
+ Supplying `request_id` makes the upload idempotent on that token. A
+ re-upload under the same token overwrites the same item rather than
+ adding a second, so a client that lost an upload's response can safely
+ re-send it. The response's `deduplicated` is `true` only when the
+ re-uploaded payload matches the one already stored; a same-token upload
+ with a changed payload overwrites in place and returns `false`.
+
+ Set a `request_id` on **every** upload: re-sending under the same token
+ is the only way to repair a lost or incomplete upload, including the one
+ a finalize `409` reports. Without one, a re-send adds a new item instead
+ of replacing the missing one, and the job cannot be finalized.
+
+ Uploads are rejected once the job has been finalized (`409`), once it
+ holds its 500-item limit (`409`), or when the item is too large (`413` —
+ see the raw-file limit in the API description).
+
+ Parameters
+ ----------
+ job_id : str
+
+ document : typing.Optional[typing.Dict[str, typing.Any]]
+ The JSON body of `POST /lang2fhir/document/multi`, **without**
+ its base64 `content` field — the uploaded `file` supplies the
+ content. Accepts that endpoint's fields (`version`, `provider`,
+ `patient_reference`, `implementation_guide`, `detection_effort`,
+ `validation_method`, `config`). This is the **multi**-resource
+ body: it has no single-`resource` field, and the item's result
+ is a `DocumentMultiResponse` (a Bundle of resources). Mutually
+ exclusive with `create`; requires `file`.
+
+ create : typing.Optional[typing.Dict[str, typing.Any]]
+ The JSON body of `POST /lang2fhir/create/multi`. Accepts that
+ endpoint's fields (`text`, `version`, `provider`,
+ `patient_reference`, `implementation_guide`, `detection_effort`,
+ `validation_method`, `resource_review`). This is the
+ **multi**-resource body: it has no single-`resource` field, and
+ the item's result is a `CreateMultiResponse` (a Bundle of
+ resources). Mutually exclusive with `document`; must **not** be
+ accompanied by a `file`.
+
+ file : typing.Optional[core.File]
+ See core.File for more documentation
+
+ request_id : typing.Optional[str]
+ Optional idempotency token (max 256 bytes). Re-uploading under
+ the same token overwrites the same item instead of adding a
+ new one. The token is scoped to this job; the same token in
+ another job is independent and creates a separate item.
+
+ id : typing.Optional[str]
+ Optional caller-supplied correlation label (max 512 bytes),
+ echoed back on status and result listings so you can match the
+ server's item_id to your own record.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[UploadItemResponse]
+ Item accepted
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"lang2fhir/batch/{encode_path_param(job_id)}/items",
+ method="POST",
+ data={
+ "request_id": request_id,
+ "id": id,
+ },
+ files={
+ **(
+ {"document": (None, json.dumps(jsonable_encoder(document)), "application/json")}
+ if document is not OMIT
+ else {}
+ ),
+ **(
+ {"create": (None, json.dumps(jsonable_encoder(create)), "application/json")}
+ if create is not OMIT
+ else {}
+ ),
+ **({"file": file} if file is not None else {}),
+ },
+ request_options=request_options,
+ omit=OMIT,
+ force_multipart=True,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ UploadItemResponse,
+ parse_obj_as(
+ type_=UploadItemResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 409:
+ raise ConflictError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 413:
+ raise ContentTooLargeError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 499:
+ raise ClientClosedRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 504:
+ raise GatewayTimeoutError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ async def finalize(
+ self, job_id: str, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> AsyncHttpResponse[BatchJob]:
+ """
+ Seals the job's item set and starts processing. Takes no request body.
+ Finalize is idempotent: a retried finalize succeeds again.
+
+ If a previous upload did not complete, finalize returns a `409`; re-send
+ the missing upload (with the same `request_id`), then finalize.
+ Finalizing a job with no items is a `400`.
+
+ Parameters
+ ----------
+ job_id : str
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[BatchJob]
+ Job finalized and processing started
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"lang2fhir/batch/{encode_path_param(job_id)}/finalize",
+ method="POST",
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ BatchJob,
+ parse_obj_as(
+ type_=BatchJob, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 409:
+ raise ConflictError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 499:
+ raise ClientClosedRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 504:
+ raise GatewayTimeoutError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ async def get(
+ self,
+ job_id: str,
+ *,
+ cursor: typing.Optional[str] = None,
+ limit: typing.Optional[int] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AsyncHttpResponse[JobDetailResponse]:
+ """
+ Returns a job's record, its per-status item counts, and one page of
+ per-item statuses.
+
+ Items are listed in a stable order that is not upload order and is the
+ same across pages. Match each entry to your own records by its `id`
+ (your correlation label) or `item_id` (from the upload response),
+ never by position.
+
+ Parameters
+ ----------
+ job_id : str
+
+ cursor : typing.Optional[str]
+ Opaque pagination cursor from a previous page's next_cursor.
+
+ limit : typing.Optional[int]
+ Page size for the item-status page. Defaults to 20; values above 100 are clamped to 100.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[JobDetailResponse]
+ The job record, counts, and a page of item statuses
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"lang2fhir/batch/{encode_path_param(job_id)}",
+ method="GET",
+ params={
+ "cursor": cursor,
+ "limit": limit,
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ JobDetailResponse,
+ parse_obj_as(
+ type_=JobDetailResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 499:
+ raise ClientClosedRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 504:
+ raise GatewayTimeoutError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ async def get_results(
+ self,
+ job_id: str,
+ *,
+ cursor: typing.Optional[str] = None,
+ limit: typing.Optional[int] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AsyncHttpResponse[ResultsPageResponse]:
+ """
+ A lighter status page. Returns the same per-item status entries as
+ `GET /lang2fhir/batch/{job_id}`, but without the job record or counts,
+ and the entries carry `result_size` rather than any result content. Use
+ each entry's `item_id` to fetch that item's result from
+ `GET /lang2fhir/batch/{job_id}/results/{item_id}`.
+
+ Entries are listed in a stable order that is not upload order and is
+ the same across pages. Match each entry to your own records by its `id`
+ (your correlation label) or `item_id` (from the upload response),
+ never by position.
+
+ Parameters
+ ----------
+ job_id : str
+
+ cursor : typing.Optional[str]
+ Opaque pagination cursor from a previous page's next_cursor.
+
+ limit : typing.Optional[int]
+ Page size. Defaults to 20; values above 100 are clamped to 100.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[ResultsPageResponse]
+ A page of per-item result statuses
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"lang2fhir/batch/{encode_path_param(job_id)}/results",
+ method="GET",
+ params={
+ "cursor": cursor,
+ "limit": limit,
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ ResultsPageResponse,
+ parse_obj_as(
+ type_=ResultsPageResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 499:
+ raise ClientClosedRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 504:
+ raise GatewayTimeoutError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ async def get_result(
+ self, job_id: str, item_id: str, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]:
+ """
+ Streams one item's stored result bytes verbatim as `application/json`.
+ The body is the response the item's synchronous multi endpoint would have
+ returned — a `DocumentMultiResponse` for a document item or a
+ `CreateMultiResponse` for a create item.
+
+ Only a succeeded item has a result: an item that has not succeeded
+ (pending, processing, or failed) is a `409`, and a result that has
+ expired is a `404`.
+
+ Parameters
+ ----------
+ job_id : str
+
+ item_id : str
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[typing.Dict[str, typing.Any]]
+ The item's raw result JSON
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"lang2fhir/batch/{encode_path_param(job_id)}/results/{encode_path_param(item_id)}",
+ method="GET",
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ typing.Dict[str, typing.Any],
+ parse_obj_as(
+ type_=typing.Dict[str, typing.Any], # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 409:
+ raise ConflictError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 499:
+ raise ClientClosedRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 504:
+ raise GatewayTimeoutError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
diff --git a/src/phenoml/lang2fhir_batch/types/__init__.py b/src/phenoml/lang2fhir_batch/types/__init__.py
new file mode 100644
index 0000000..22e6244
--- /dev/null
+++ b/src/phenoml/lang2fhir_batch/types/__init__.py
@@ -0,0 +1,65 @@
+# This file was auto-generated by Fern from our API Definition.
+
+# isort: skip_file
+
+import typing
+from importlib import import_module
+
+if typing.TYPE_CHECKING:
+ from .batch_counts import BatchCounts
+ from .batch_error import BatchError
+ from .batch_item_status import BatchItemStatus
+ from .batch_item_status_status import BatchItemStatusStatus
+ from .batch_job import BatchJob
+ from .batch_job_status import BatchJobStatus
+ from .job_detail_response import JobDetailResponse
+ from .job_list_response import JobListResponse
+ from .results_page_response import ResultsPageResponse
+ from .upload_item_response import UploadItemResponse
+_dynamic_imports: typing.Dict[str, str] = {
+ "BatchCounts": ".batch_counts",
+ "BatchError": ".batch_error",
+ "BatchItemStatus": ".batch_item_status",
+ "BatchItemStatusStatus": ".batch_item_status_status",
+ "BatchJob": ".batch_job",
+ "BatchJobStatus": ".batch_job_status",
+ "JobDetailResponse": ".job_detail_response",
+ "JobListResponse": ".job_list_response",
+ "ResultsPageResponse": ".results_page_response",
+ "UploadItemResponse": ".upload_item_response",
+}
+
+
+def __getattr__(attr_name: str) -> typing.Any:
+ module_name = _dynamic_imports.get(attr_name)
+ if module_name is None:
+ raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}")
+ try:
+ module = import_module(module_name, __package__)
+ if module_name == f".{attr_name}":
+ return module
+ else:
+ return getattr(module, attr_name)
+ except ImportError as e:
+ raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e
+ except AttributeError as e:
+ raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e
+
+
+def __dir__():
+ lazy_attrs = list(_dynamic_imports.keys())
+ return sorted(lazy_attrs)
+
+
+__all__ = [
+ "BatchCounts",
+ "BatchError",
+ "BatchItemStatus",
+ "BatchItemStatusStatus",
+ "BatchJob",
+ "BatchJobStatus",
+ "JobDetailResponse",
+ "JobListResponse",
+ "ResultsPageResponse",
+ "UploadItemResponse",
+]
diff --git a/src/phenoml/lang2fhir_batch/types/batch_counts.py b/src/phenoml/lang2fhir_batch/types/batch_counts.py
new file mode 100644
index 0000000..867684d
--- /dev/null
+++ b/src/phenoml/lang2fhir_batch/types/batch_counts.py
@@ -0,0 +1,27 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class BatchCounts(UniversalBaseModel):
+ """
+ A job's per-status item tally.
+ """
+
+ total: int
+ pending: int
+ processing: int
+ succeeded: int
+ failed: int
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/phenoml/lang2fhir_batch/types/batch_error.py b/src/phenoml/lang2fhir_batch/types/batch_error.py
new file mode 100644
index 0000000..29efa45
--- /dev/null
+++ b/src/phenoml/lang2fhir_batch/types/batch_error.py
@@ -0,0 +1,40 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class BatchError(UniversalBaseModel):
+ """
+ A job- or item-level failure. Never contains document or patient content.
+ """
+
+ kind: str = pydantic.Field()
+ """
+ Short stable token to branch on. Item-level kinds: `invalid_input`
+ (the stored body was not a valid create/document request),
+ `processing_failed` (the conversion failed), `budget_exceeded` (the
+ item ran past its time budget — 600s for a document item, 450s for a
+ create item), `result_too_large` (the result exceeded the storage
+ cap), `input_unavailable` (the input could not be read), and
+ `retries_exhausted` / `attempts_exhausted` (buried after too many
+ failed attempts).
+ Job-level kinds: `timeout` (the job did not finish within 36 hours
+ of creation).
+ """
+
+ message: str = pydantic.Field()
+ """
+ Human-readable description of the failure.
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/phenoml/lang2fhir_batch/types/batch_item_status.py b/src/phenoml/lang2fhir_batch/types/batch_item_status.py
new file mode 100644
index 0000000..e2da4e9
--- /dev/null
+++ b/src/phenoml/lang2fhir_batch/types/batch_item_status.py
@@ -0,0 +1,64 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import datetime as dt
+import typing
+
+import pydantic
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from .batch_error import BatchError
+from .batch_item_status_status import BatchItemStatusStatus
+
+
+class BatchItemStatus(UniversalBaseModel):
+ """
+ One item's status line.
+ """
+
+ item_id: str = pydantic.Field()
+ """
+ Server-assigned opaque item ID, used in result URLs.
+ """
+
+ id: typing.Optional[str] = pydantic.Field(default=None)
+ """
+ The caller's correlation label, echoed back; omitted when none was supplied.
+ """
+
+ status: BatchItemStatusStatus = pydantic.Field()
+ """
+ Item status.
+ """
+
+ attempts: int = pydantic.Field()
+ """
+ Number of processing attempts so far. An item runs up to 3 attempts,
+ and only an interruption — a worker preemption or a recovered internal
+ error — is retried; a conversion error fails the item with no retry.
+ """
+
+ detect_retries: typing.Optional[int] = pydantic.Field(default=None)
+ """
+ How many times concept detection was re-run for the item because a
+ pass returned no concepts. Present only on a succeeded item whose
+ detection re-ran at least once; omitted otherwise.
+ """
+
+ result_size: typing.Optional[int] = pydantic.Field(default=None)
+ """
+ Size in bytes of the item's stored result. Present once the item has succeeded.
+ """
+
+ error: typing.Optional[BatchError] = None
+ completed_at: typing.Optional[dt.datetime] = pydantic.Field(default=None)
+ """
+ When the item finished. Absent until then.
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/phenoml/lang2fhir_batch/types/batch_item_status_status.py b/src/phenoml/lang2fhir_batch/types/batch_item_status_status.py
new file mode 100644
index 0000000..f18891d
--- /dev/null
+++ b/src/phenoml/lang2fhir_batch/types/batch_item_status_status.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+BatchItemStatusStatus = typing.Union[typing.Literal["pending", "processing", "succeeded", "failed"], typing.Any]
diff --git a/src/phenoml/lang2fhir_batch/types/batch_job.py b/src/phenoml/lang2fhir_batch/types/batch_job.py
new file mode 100644
index 0000000..a5382ab
--- /dev/null
+++ b/src/phenoml/lang2fhir_batch/types/batch_job.py
@@ -0,0 +1,73 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import datetime as dt
+import typing
+
+import pydantic
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from .batch_error import BatchError
+from .batch_job_status import BatchJobStatus
+
+
+class BatchJob(UniversalBaseModel):
+ """
+ One batch job record.
+ """
+
+ job_id: str = pydantic.Field()
+ """
+ Server-assigned job identifier.
+ """
+
+ request_id: typing.Optional[str] = pydantic.Field(default=None)
+ """
+ The idempotency token supplied at create, if any.
+ """
+
+ status: BatchJobStatus = pydantic.Field()
+ """
+ Job status. `completed` means every item has finished — some may have
+ failed, so check `counts` for the split. `failed` is a whole-job
+ failure (the job could not run at all), distinct from individual item
+ failures, which never fail the job.
+ """
+
+ finalized: bool = pydantic.Field()
+ """
+ Whether the job's item set has been sealed.
+ """
+
+ total_items: int = pydantic.Field()
+ """
+ The sealed item count. It is 0 until the job is finalized, so an
+ upload response always reports 0; poll the job after finalize for the
+ real count.
+ """
+
+ error: typing.Optional[BatchError] = pydantic.Field(default=None)
+ """
+ A whole-job failure. Present only on a `failed` job.
+ """
+
+ created_at: dt.datetime
+ updated_at: dt.datetime
+ completed_at: typing.Optional[dt.datetime] = pydantic.Field(default=None)
+ """
+ When the job finished. Absent until then.
+ """
+
+ expires_at: dt.datetime = pydantic.Field()
+ """
+ When the job and its stored inputs and results are deleted. Set 7
+ days out, with the clock restarting when the job reaches `completed`
+ or `failed`. At expiry the job's `request_id` is freed for reuse.
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/phenoml/lang2fhir_batch/types/batch_job_status.py b/src/phenoml/lang2fhir_batch/types/batch_job_status.py
new file mode 100644
index 0000000..3f6ffef
--- /dev/null
+++ b/src/phenoml/lang2fhir_batch/types/batch_job_status.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+BatchJobStatus = typing.Union[typing.Literal["pending", "processing", "completed", "failed"], typing.Any]
diff --git a/src/phenoml/lang2fhir_batch/types/job_detail_response.py b/src/phenoml/lang2fhir_batch/types/job_detail_response.py
new file mode 100644
index 0000000..c720eaf
--- /dev/null
+++ b/src/phenoml/lang2fhir_batch/types/job_detail_response.py
@@ -0,0 +1,36 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ...core.pydantic_utilities import IS_PYDANTIC_V2
+from .batch_counts import BatchCounts
+from .batch_item_status import BatchItemStatus
+from .batch_job import BatchJob
+
+
+class JobDetailResponse(BatchJob):
+ """
+ A job's record, its counts, and one page of item statuses.
+ """
+
+ counts: BatchCounts
+ items: typing.List[BatchItemStatus]
+ next_cursor: typing.Optional[str] = pydantic.Field(default=None)
+ """
+ Cursor for the next page, when has_more is true.
+ """
+
+ has_more: bool = pydantic.Field()
+ """
+ Whether more item statuses remain beyond this page.
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/phenoml/lang2fhir_batch/types/job_list_response.py b/src/phenoml/lang2fhir_batch/types/job_list_response.py
new file mode 100644
index 0000000..27042d0
--- /dev/null
+++ b/src/phenoml/lang2fhir_batch/types/job_list_response.py
@@ -0,0 +1,33 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from .batch_job import BatchJob
+
+
+class JobListResponse(UniversalBaseModel):
+ """
+ A page of job records, without per-job counts.
+ """
+
+ jobs: typing.List[BatchJob]
+ next_cursor: typing.Optional[str] = pydantic.Field(default=None)
+ """
+ Cursor for the next page, when has_more is true.
+ """
+
+ has_more: bool = pydantic.Field()
+ """
+ Whether more jobs remain beyond this page.
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/phenoml/lang2fhir_batch/types/results_page_response.py b/src/phenoml/lang2fhir_batch/types/results_page_response.py
new file mode 100644
index 0000000..af8aafd
--- /dev/null
+++ b/src/phenoml/lang2fhir_batch/types/results_page_response.py
@@ -0,0 +1,33 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from .batch_item_status import BatchItemStatus
+
+
+class ResultsPageResponse(UniversalBaseModel):
+ """
+ A page of per-item result statuses, without the job counts.
+ """
+
+ results: typing.List[BatchItemStatus]
+ next_cursor: typing.Optional[str] = pydantic.Field(default=None)
+ """
+ Cursor for the next page, when has_more is true.
+ """
+
+ has_more: bool = pydantic.Field()
+ """
+ Whether more results remain beyond this page.
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/phenoml/lang2fhir_batch/types/upload_item_response.py b/src/phenoml/lang2fhir_batch/types/upload_item_response.py
new file mode 100644
index 0000000..f2fdae8
--- /dev/null
+++ b/src/phenoml/lang2fhir_batch/types/upload_item_response.py
@@ -0,0 +1,40 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ...core.pydantic_utilities import IS_PYDANTIC_V2
+from .batch_job import BatchJob
+
+
+class UploadItemResponse(BatchJob):
+ """
+ The upload response: the job record plus the ID the server assigned the
+ uploaded item.
+ """
+
+ item_id: str = pydantic.Field()
+ """
+ The server-assigned ID of the uploaded item; use it to fetch the result.
+ """
+
+ id: typing.Optional[str] = pydantic.Field(default=None)
+ """
+ The caller's correlation label, echoed back; omitted when none was supplied.
+ """
+
+ deduplicated: bool = pydantic.Field()
+ """
+ True only when a repeated `request_id` resolved to an item whose
+ payload matched the one already stored. A same-token upload with a
+ changed payload overwrites in place and returns false.
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/phenoml/profiles/__init__.py b/src/phenoml/profiles/__init__.py
index d7560a6..6975ba2 100644
--- a/src/phenoml/profiles/__init__.py
+++ b/src/phenoml/profiles/__init__.py
@@ -13,11 +13,21 @@
ProfileSummary,
ProfileSummarySource,
ProfileUploadRequest,
+ ProfileVersionCreateRequest,
+ ProfileVersionListResponse,
)
- from .errors import BadRequestError, ForbiddenError, InternalServerError, NotFoundError, UnauthorizedError
- from . import profiles
+ from .errors import (
+ BadRequestError,
+ ConflictError,
+ ForbiddenError,
+ InternalServerError,
+ NotFoundError,
+ UnauthorizedError,
+ )
+ from . import profiles, versions
_dynamic_imports: typing.Dict[str, str] = {
"BadRequestError": ".errors",
+ "ConflictError": ".errors",
"FhirResource": ".types",
"ForbiddenError": ".errors",
"InternalServerError": ".errors",
@@ -27,8 +37,11 @@
"ProfileSummary": ".types",
"ProfileSummarySource": ".types",
"ProfileUploadRequest": ".types",
+ "ProfileVersionCreateRequest": ".types",
+ "ProfileVersionListResponse": ".types",
"UnauthorizedError": ".errors",
"profiles": ".profiles",
+ "versions": ".versions",
}
@@ -55,6 +68,7 @@ def __dir__():
__all__ = [
"BadRequestError",
+ "ConflictError",
"FhirResource",
"ForbiddenError",
"InternalServerError",
@@ -64,6 +78,9 @@ def __dir__():
"ProfileSummary",
"ProfileSummarySource",
"ProfileUploadRequest",
+ "ProfileVersionCreateRequest",
+ "ProfileVersionListResponse",
"UnauthorizedError",
"profiles",
+ "versions",
]
diff --git a/src/phenoml/profiles/client.py b/src/phenoml/profiles/client.py
index f78027a..f6c8211 100644
--- a/src/phenoml/profiles/client.py
+++ b/src/phenoml/profiles/client.py
@@ -10,6 +10,7 @@
if typing.TYPE_CHECKING:
from .profiles.client import AsyncProfilesClient as profiles_profiles_client_AsyncProfilesClient
from .profiles.client import ProfilesClient as profiles_profiles_client_ProfilesClient
+ from .versions.client import AsyncVersionsClient, VersionsClient
class ProfilesClient:
@@ -17,6 +18,7 @@ def __init__(self, *, client_wrapper: SyncClientWrapper):
self._raw_client = RawProfilesClient(client_wrapper=client_wrapper)
self._client_wrapper = client_wrapper
self._profiles: typing.Optional[profiles_profiles_client_ProfilesClient] = None
+ self._versions: typing.Optional[VersionsClient] = None
@property
def with_raw_response(self) -> RawProfilesClient:
@@ -37,12 +39,21 @@ def profiles(self):
self._profiles = profiles_profiles_client_ProfilesClient(client_wrapper=self._client_wrapper)
return self._profiles
+ @property
+ def versions(self):
+ if self._versions is None:
+ from .versions.client import VersionsClient # noqa: E402
+
+ self._versions = VersionsClient(client_wrapper=self._client_wrapper)
+ return self._versions
+
class AsyncProfilesClient:
def __init__(self, *, client_wrapper: AsyncClientWrapper):
self._raw_client = AsyncRawProfilesClient(client_wrapper=client_wrapper)
self._client_wrapper = client_wrapper
self._profiles: typing.Optional[profiles_profiles_client_AsyncProfilesClient] = None
+ self._versions: typing.Optional[AsyncVersionsClient] = None
@property
def with_raw_response(self) -> AsyncRawProfilesClient:
@@ -64,3 +75,11 @@ def profiles(self):
self._profiles = profiles_profiles_client_AsyncProfilesClient(client_wrapper=self._client_wrapper)
return self._profiles
+
+ @property
+ def versions(self):
+ if self._versions is None:
+ from .versions.client import AsyncVersionsClient # noqa: E402
+
+ self._versions = AsyncVersionsClient(client_wrapper=self._client_wrapper)
+ return self._versions
diff --git a/src/phenoml/profiles/errors/__init__.py b/src/phenoml/profiles/errors/__init__.py
index 306a114..29aa057 100644
--- a/src/phenoml/profiles/errors/__init__.py
+++ b/src/phenoml/profiles/errors/__init__.py
@@ -7,12 +7,14 @@
if typing.TYPE_CHECKING:
from .bad_request_error import BadRequestError
+ from .conflict_error import ConflictError
from .forbidden_error import ForbiddenError
from .internal_server_error import InternalServerError
from .not_found_error import NotFoundError
from .unauthorized_error import UnauthorizedError
_dynamic_imports: typing.Dict[str, str] = {
"BadRequestError": ".bad_request_error",
+ "ConflictError": ".conflict_error",
"ForbiddenError": ".forbidden_error",
"InternalServerError": ".internal_server_error",
"NotFoundError": ".not_found_error",
@@ -41,4 +43,11 @@ def __dir__():
return sorted(lazy_attrs)
-__all__ = ["BadRequestError", "ForbiddenError", "InternalServerError", "NotFoundError", "UnauthorizedError"]
+__all__ = [
+ "BadRequestError",
+ "ConflictError",
+ "ForbiddenError",
+ "InternalServerError",
+ "NotFoundError",
+ "UnauthorizedError",
+]
diff --git a/src/phenoml/profiles/errors/conflict_error.py b/src/phenoml/profiles/errors/conflict_error.py
new file mode 100644
index 0000000..abe1a9f
--- /dev/null
+++ b/src/phenoml/profiles/errors/conflict_error.py
@@ -0,0 +1,10 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+from ...core.api_error import ApiError
+
+
+class ConflictError(ApiError):
+ def __init__(self, body: typing.Any, headers: typing.Optional[typing.Dict[str, str]] = None):
+ super().__init__(status_code=409, headers=headers, body=body)
diff --git a/src/phenoml/profiles/profiles/client.py b/src/phenoml/profiles/profiles/client.py
index 5df8c82..c4c0368 100644
--- a/src/phenoml/profiles/profiles/client.py
+++ b/src/phenoml/profiles/profiles/client.py
@@ -40,12 +40,17 @@ def list(
The `url` query parameter filters by canonical URL. The canonical URL is the
stable key other platform features use to reference a profile (FHIR's
`meta.profile`, `baseDefinition`), since StructureDefinition ids are only
- unique within a package. A non-matching filter returns an empty list, not a 404.
+ unique within a package. An unpinned `url` filter returns metadata for
+ the profile's current StructureDefinition. Pinned `url|version` filters
+ resolve a retained version when present; otherwise they can fall back to
+ the profile's current StructureDefinition, whose content can change
+ through the profile update endpoint. A non-matching filter returns an
+ empty list, not a 404.
Parameters
----------
url : typing.Optional[str]
- Filter by canonical URL. Accepts the FHIR pinned form `url|version` (split on the last `|`); the bare form matches the current version.
+ Filter by canonical URL. Accepts the FHIR pinned form `url|version`; without a version pin, returns the profile's current StructureDefinition metadata.
request_options : typing.Optional[RequestOptions]
Request-specific configuration.
@@ -81,9 +86,8 @@ def create(
Creates a custom profile from a FHIR StructureDefinition supplied as a JSON
object. Metadata such as version, resource type, and url is read from the
StructureDefinition; the lowercase StructureDefinition id becomes the
- profile's lookup key. When id is omitted, a random UUID is assigned. Code
- system configuration is auto-extracted from the snapshot. Optionally group
- the profile under a named implementation guide.
+ profile's lookup key. When id is omitted, a random UUID is assigned.
+ Optionally group the profile under a named implementation guide.
Parameters
----------
@@ -110,7 +114,25 @@ def create(
client_secret="YOUR_CLIENT_SECRET",
)
client.profiles.profiles.create(
- structure_definition={"key": "value"},
+ structure_definition={
+ "resourceType": "StructureDefinition",
+ "id": "custom-patient",
+ "url": "http://phenoml.com/fhir/StructureDefinition/custom-patient",
+ "name": "CustomPatient",
+ "status": "active",
+ "fhirVersion": "4.0.1",
+ "kind": "resource",
+ "abstract": False,
+ "type": "Patient",
+ "baseDefinition": "http://hl7.org/fhir/StructureDefinition/Patient",
+ "derivation": "constraint",
+ "snapshot": {
+ "element": [
+ {"id": "Patient", "path": "Patient", "min": 0, "max": "*"}
+ ]
+ },
+ },
+ implementation_guide="acme-cardiology",
)
"""
_response = self._raw_client.create(
@@ -122,7 +144,8 @@ def create(
def get(self, id: str, *, request_options: typing.Optional[RequestOptions] = None) -> ProfileGetResponse:
"""
- Returns a single custom profile by id, including its full StructureDefinition JSON.
+ Returns a single custom profile by id, including its full StructureDefinition
+ JSON.
Parameters
----------
@@ -165,10 +188,12 @@ def update(
`id` path parameter is authoritative: if the StructureDefinition includes
an `id` it must match the path parameter, and if it omits one the path
parameter is used. The FHIR resource type of the profile cannot change.
- Code system configuration is
- re-derived from the new StructureDefinition. When `implementation_guide` is
- omitted, the profile keeps its existing implementation guide. The instance
- stores a single version per canonical URL, so this replaces it in place.
+ When `implementation_guide` is omitted, the profile keeps its existing
+ implementation guide. A retained version string is allowed only when
+ re-submitting the profile's current version with an unchanged
+ StructureDefinition; otherwise it returns a conflict. While the profile
+ has retained versions, its
+ canonical URL cannot be changed.
Parameters
----------
@@ -199,7 +224,25 @@ def update(
)
client.profiles.profiles.update(
id="custom-patient",
- structure_definition={"key": "value"},
+ structure_definition={
+ "resourceType": "StructureDefinition",
+ "id": "custom-patient",
+ "url": "http://phenoml.com/fhir/StructureDefinition/custom-patient",
+ "name": "CustomPatient",
+ "status": "active",
+ "fhirVersion": "4.0.1",
+ "kind": "resource",
+ "abstract": False,
+ "type": "Patient",
+ "baseDefinition": "http://hl7.org/fhir/StructureDefinition/Patient",
+ "derivation": "constraint",
+ "snapshot": {
+ "element": [
+ {"id": "Patient", "path": "Patient", "min": 0, "max": "*"}
+ ]
+ },
+ },
+ implementation_guide="acme-cardiology",
)
"""
_response = self._raw_client.update(
@@ -212,7 +255,9 @@ def update(
def delete(self, id: str, *, request_options: typing.Optional[RequestOptions] = None) -> None:
"""
- Permanently deletes a custom profile by id.
+ Permanently deletes a custom profile by id. This also deletes all retained
+ versions for that profile so the canonical URL can be reused by a later
+ upload.
Parameters
----------
@@ -268,12 +313,17 @@ async def list(
The `url` query parameter filters by canonical URL. The canonical URL is the
stable key other platform features use to reference a profile (FHIR's
`meta.profile`, `baseDefinition`), since StructureDefinition ids are only
- unique within a package. A non-matching filter returns an empty list, not a 404.
+ unique within a package. An unpinned `url` filter returns metadata for
+ the profile's current StructureDefinition. Pinned `url|version` filters
+ resolve a retained version when present; otherwise they can fall back to
+ the profile's current StructureDefinition, whose content can change
+ through the profile update endpoint. A non-matching filter returns an
+ empty list, not a 404.
Parameters
----------
url : typing.Optional[str]
- Filter by canonical URL. Accepts the FHIR pinned form `url|version` (split on the last `|`); the bare form matches the current version.
+ Filter by canonical URL. Accepts the FHIR pinned form `url|version`; without a version pin, returns the profile's current StructureDefinition metadata.
request_options : typing.Optional[RequestOptions]
Request-specific configuration.
@@ -317,9 +367,8 @@ async def create(
Creates a custom profile from a FHIR StructureDefinition supplied as a JSON
object. Metadata such as version, resource type, and url is read from the
StructureDefinition; the lowercase StructureDefinition id becomes the
- profile's lookup key. When id is omitted, a random UUID is assigned. Code
- system configuration is auto-extracted from the snapshot. Optionally group
- the profile under a named implementation guide.
+ profile's lookup key. When id is omitted, a random UUID is assigned.
+ Optionally group the profile under a named implementation guide.
Parameters
----------
@@ -351,7 +400,25 @@ async def create(
async def main() -> None:
await client.profiles.profiles.create(
- structure_definition={"key": "value"},
+ structure_definition={
+ "resourceType": "StructureDefinition",
+ "id": "custom-patient",
+ "url": "http://phenoml.com/fhir/StructureDefinition/custom-patient",
+ "name": "CustomPatient",
+ "status": "active",
+ "fhirVersion": "4.0.1",
+ "kind": "resource",
+ "abstract": False,
+ "type": "Patient",
+ "baseDefinition": "http://hl7.org/fhir/StructureDefinition/Patient",
+ "derivation": "constraint",
+ "snapshot": {
+ "element": [
+ {"id": "Patient", "path": "Patient", "min": 0, "max": "*"}
+ ]
+ },
+ },
+ implementation_guide="acme-cardiology",
)
@@ -366,7 +433,8 @@ async def main() -> None:
async def get(self, id: str, *, request_options: typing.Optional[RequestOptions] = None) -> ProfileGetResponse:
"""
- Returns a single custom profile by id, including its full StructureDefinition JSON.
+ Returns a single custom profile by id, including its full StructureDefinition
+ JSON.
Parameters
----------
@@ -417,10 +485,12 @@ async def update(
`id` path parameter is authoritative: if the StructureDefinition includes
an `id` it must match the path parameter, and if it omits one the path
parameter is used. The FHIR resource type of the profile cannot change.
- Code system configuration is
- re-derived from the new StructureDefinition. When `implementation_guide` is
- omitted, the profile keeps its existing implementation guide. The instance
- stores a single version per canonical URL, so this replaces it in place.
+ When `implementation_guide` is omitted, the profile keeps its existing
+ implementation guide. A retained version string is allowed only when
+ re-submitting the profile's current version with an unchanged
+ StructureDefinition; otherwise it returns a conflict. While the profile
+ has retained versions, its
+ canonical URL cannot be changed.
Parameters
----------
@@ -456,7 +526,25 @@ async def update(
async def main() -> None:
await client.profiles.profiles.update(
id="custom-patient",
- structure_definition={"key": "value"},
+ structure_definition={
+ "resourceType": "StructureDefinition",
+ "id": "custom-patient",
+ "url": "http://phenoml.com/fhir/StructureDefinition/custom-patient",
+ "name": "CustomPatient",
+ "status": "active",
+ "fhirVersion": "4.0.1",
+ "kind": "resource",
+ "abstract": False,
+ "type": "Patient",
+ "baseDefinition": "http://hl7.org/fhir/StructureDefinition/Patient",
+ "derivation": "constraint",
+ "snapshot": {
+ "element": [
+ {"id": "Patient", "path": "Patient", "min": 0, "max": "*"}
+ ]
+ },
+ },
+ implementation_guide="acme-cardiology",
)
@@ -472,7 +560,9 @@ async def main() -> None:
async def delete(self, id: str, *, request_options: typing.Optional[RequestOptions] = None) -> None:
"""
- Permanently deletes a custom profile by id.
+ Permanently deletes a custom profile by id. This also deletes all retained
+ versions for that profile so the canonical URL can be reused by a later
+ upload.
Parameters
----------
diff --git a/src/phenoml/profiles/profiles/raw_client.py b/src/phenoml/profiles/profiles/raw_client.py
index c142e36..907ac1d 100644
--- a/src/phenoml/profiles/profiles/raw_client.py
+++ b/src/phenoml/profiles/profiles/raw_client.py
@@ -11,6 +11,7 @@
from ...core.pydantic_utilities import parse_obj_as
from ...core.request_options import RequestOptions
from ..errors.bad_request_error import BadRequestError
+from ..errors.conflict_error import ConflictError
from ..errors.forbidden_error import ForbiddenError
from ..errors.internal_server_error import InternalServerError
from ..errors.not_found_error import NotFoundError
@@ -40,12 +41,17 @@ def list(
The `url` query parameter filters by canonical URL. The canonical URL is the
stable key other platform features use to reference a profile (FHIR's
`meta.profile`, `baseDefinition`), since StructureDefinition ids are only
- unique within a package. A non-matching filter returns an empty list, not a 404.
+ unique within a package. An unpinned `url` filter returns metadata for
+ the profile's current StructureDefinition. Pinned `url|version` filters
+ resolve a retained version when present; otherwise they can fall back to
+ the profile's current StructureDefinition, whose content can change
+ through the profile update endpoint. A non-matching filter returns an
+ empty list, not a 404.
Parameters
----------
url : typing.Optional[str]
- Filter by canonical URL. Accepts the FHIR pinned form `url|version` (split on the last `|`); the bare form matches the current version.
+ Filter by canonical URL. Accepts the FHIR pinned form `url|version`; without a version pin, returns the profile's current StructureDefinition metadata.
request_options : typing.Optional[RequestOptions]
Request-specific configuration.
@@ -126,9 +132,8 @@ def create(
Creates a custom profile from a FHIR StructureDefinition supplied as a JSON
object. Metadata such as version, resource type, and url is read from the
StructureDefinition; the lowercase StructureDefinition id becomes the
- profile's lookup key. When id is omitted, a random UUID is assigned. Code
- system configuration is auto-extracted from the snapshot. Optionally group
- the profile under a named implementation guide.
+ profile's lookup key. When id is omitted, a random UUID is assigned.
+ Optionally group the profile under a named implementation guide.
Parameters
----------
@@ -223,7 +228,8 @@ def get(
self, id: str, *, request_options: typing.Optional[RequestOptions] = None
) -> HttpResponse[ProfileGetResponse]:
"""
- Returns a single custom profile by id, including its full StructureDefinition JSON.
+ Returns a single custom profile by id, including its full StructureDefinition
+ JSON.
Parameters
----------
@@ -330,10 +336,12 @@ def update(
`id` path parameter is authoritative: if the StructureDefinition includes
an `id` it must match the path parameter, and if it omits one the path
parameter is used. The FHIR resource type of the profile cannot change.
- Code system configuration is
- re-derived from the new StructureDefinition. When `implementation_guide` is
- omitted, the profile keeps its existing implementation guide. The instance
- stores a single version per canonical URL, so this replaces it in place.
+ When `implementation_guide` is omitted, the profile keeps its existing
+ implementation guide. A retained version string is allowed only when
+ re-submitting the profile's current version with an unchanged
+ StructureDefinition; otherwise it returns a conflict. While the profile
+ has retained versions, its
+ canonical URL cannot be changed.
Parameters
----------
@@ -418,6 +426,17 @@ def update(
),
),
)
+ if _response.status_code == 409:
+ raise ConflictError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
if _response.status_code == 500:
raise InternalServerError(
headers=dict(_response.headers),
@@ -440,7 +459,9 @@ def update(
def delete(self, id: str, *, request_options: typing.Optional[RequestOptions] = None) -> HttpResponse[None]:
"""
- Permanently deletes a custom profile by id.
+ Permanently deletes a custom profile by id. This also deletes all retained
+ versions for that profile so the canonical URL can be reused by a later
+ upload.
Parameters
----------
@@ -542,12 +563,17 @@ async def list(
The `url` query parameter filters by canonical URL. The canonical URL is the
stable key other platform features use to reference a profile (FHIR's
`meta.profile`, `baseDefinition`), since StructureDefinition ids are only
- unique within a package. A non-matching filter returns an empty list, not a 404.
+ unique within a package. An unpinned `url` filter returns metadata for
+ the profile's current StructureDefinition. Pinned `url|version` filters
+ resolve a retained version when present; otherwise they can fall back to
+ the profile's current StructureDefinition, whose content can change
+ through the profile update endpoint. A non-matching filter returns an
+ empty list, not a 404.
Parameters
----------
url : typing.Optional[str]
- Filter by canonical URL. Accepts the FHIR pinned form `url|version` (split on the last `|`); the bare form matches the current version.
+ Filter by canonical URL. Accepts the FHIR pinned form `url|version`; without a version pin, returns the profile's current StructureDefinition metadata.
request_options : typing.Optional[RequestOptions]
Request-specific configuration.
@@ -628,9 +654,8 @@ async def create(
Creates a custom profile from a FHIR StructureDefinition supplied as a JSON
object. Metadata such as version, resource type, and url is read from the
StructureDefinition; the lowercase StructureDefinition id becomes the
- profile's lookup key. When id is omitted, a random UUID is assigned. Code
- system configuration is auto-extracted from the snapshot. Optionally group
- the profile under a named implementation guide.
+ profile's lookup key. When id is omitted, a random UUID is assigned.
+ Optionally group the profile under a named implementation guide.
Parameters
----------
@@ -725,7 +750,8 @@ async def get(
self, id: str, *, request_options: typing.Optional[RequestOptions] = None
) -> AsyncHttpResponse[ProfileGetResponse]:
"""
- Returns a single custom profile by id, including its full StructureDefinition JSON.
+ Returns a single custom profile by id, including its full StructureDefinition
+ JSON.
Parameters
----------
@@ -832,10 +858,12 @@ async def update(
`id` path parameter is authoritative: if the StructureDefinition includes
an `id` it must match the path parameter, and if it omits one the path
parameter is used. The FHIR resource type of the profile cannot change.
- Code system configuration is
- re-derived from the new StructureDefinition. When `implementation_guide` is
- omitted, the profile keeps its existing implementation guide. The instance
- stores a single version per canonical URL, so this replaces it in place.
+ When `implementation_guide` is omitted, the profile keeps its existing
+ implementation guide. A retained version string is allowed only when
+ re-submitting the profile's current version with an unchanged
+ StructureDefinition; otherwise it returns a conflict. While the profile
+ has retained versions, its
+ canonical URL cannot be changed.
Parameters
----------
@@ -920,6 +948,17 @@ async def update(
),
),
)
+ if _response.status_code == 409:
+ raise ConflictError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
if _response.status_code == 500:
raise InternalServerError(
headers=dict(_response.headers),
@@ -944,7 +983,9 @@ async def delete(
self, id: str, *, request_options: typing.Optional[RequestOptions] = None
) -> AsyncHttpResponse[None]:
"""
- Permanently deletes a custom profile by id.
+ Permanently deletes a custom profile by id. This also deletes all retained
+ versions for that profile so the canonical URL can be reused by a later
+ upload.
Parameters
----------
diff --git a/src/phenoml/profiles/types/__init__.py b/src/phenoml/profiles/types/__init__.py
index a7a1667..9e00e58 100644
--- a/src/phenoml/profiles/types/__init__.py
+++ b/src/phenoml/profiles/types/__init__.py
@@ -12,6 +12,8 @@
from .profile_summary import ProfileSummary
from .profile_summary_source import ProfileSummarySource
from .profile_upload_request import ProfileUploadRequest
+ from .profile_version_create_request import ProfileVersionCreateRequest
+ from .profile_version_list_response import ProfileVersionListResponse
_dynamic_imports: typing.Dict[str, str] = {
"FhirResource": ".fhir_resource",
"ProfileGetResponse": ".profile_get_response",
@@ -19,6 +21,8 @@
"ProfileSummary": ".profile_summary",
"ProfileSummarySource": ".profile_summary_source",
"ProfileUploadRequest": ".profile_upload_request",
+ "ProfileVersionCreateRequest": ".profile_version_create_request",
+ "ProfileVersionListResponse": ".profile_version_list_response",
}
@@ -50,4 +54,6 @@ def __dir__():
"ProfileSummary",
"ProfileSummarySource",
"ProfileUploadRequest",
+ "ProfileVersionCreateRequest",
+ "ProfileVersionListResponse",
]
diff --git a/src/phenoml/profiles/types/profile_get_response.py b/src/phenoml/profiles/types/profile_get_response.py
index b64f0b8..f2ea048 100644
--- a/src/phenoml/profiles/types/profile_get_response.py
+++ b/src/phenoml/profiles/types/profile_get_response.py
@@ -9,7 +9,11 @@
class ProfileGetResponse(ProfileSummary):
- structure_definition: typing.Optional[FhirResource] = pydantic.Field(default=None)
+ """
+ Metadata and full StructureDefinition JSON for either a custom profile's current StructureDefinition or one retained version.
+ """
+
+ structure_definition: FhirResource = pydantic.Field()
"""
The full FHIR StructureDefinition JSON.
"""
diff --git a/src/phenoml/profiles/types/profile_list_response.py b/src/phenoml/profiles/types/profile_list_response.py
index c344c44..dff12c2 100644
--- a/src/phenoml/profiles/types/profile_list_response.py
+++ b/src/phenoml/profiles/types/profile_list_response.py
@@ -8,7 +8,7 @@
class ProfileListResponse(UniversalBaseModel):
- profiles: typing.Optional[typing.List[ProfileSummary]] = None
+ profiles: typing.List[ProfileSummary]
if IS_PYDANTIC_V2:
model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
diff --git a/src/phenoml/profiles/types/profile_summary.py b/src/phenoml/profiles/types/profile_summary.py
index 4ddf537..b9687db 100644
--- a/src/phenoml/profiles/types/profile_summary.py
+++ b/src/phenoml/profiles/types/profile_summary.py
@@ -9,43 +9,65 @@
class ProfileSummary(UniversalBaseModel):
- id: typing.Optional[str] = pydantic.Field(default=None)
+ """
+ Metadata for either a custom profile's current StructureDefinition or one retained version. For retained versions, `source` is always `custom` and `updated_at` is equal to `created_at`.
+ """
+
+ id: str = pydantic.Field()
"""
The lowercase StructureDefinition id, used as the profile's lookup key.
"""
- source: typing.Optional[ProfileSummarySource] = pydantic.Field(default=None)
+ source: ProfileSummarySource = pydantic.Field()
"""
- The profile's origin. Listings currently return only custom (uploaded) profiles, so this is always "custom" today; built-in (US Core / R4 base) profiles would be surfaced via an opt-in parameter in a future release, not by changing the default behavior.
+ The profile's origin. Profile management responses currently return custom (uploaded) profiles, so this is always "custom" today.
"""
- resource_type: typing.Optional[str] = pydantic.Field(default=None)
+ resource_type: str = pydantic.Field()
"""
The FHIR resource type from the StructureDefinition.
"""
- url: typing.Optional[str] = pydantic.Field(default=None)
+ url: str = pydantic.Field()
"""
The canonical URL from the StructureDefinition.
"""
- version: typing.Optional[str] = pydantic.Field(default=None)
+ version: str = pydantic.Field()
"""
The version from the StructureDefinition.version field.
"""
- fhir_version: typing.Optional[str] = pydantic.Field(default=None)
+ status: typing.Optional[str] = pydantic.Field(default=None)
+ """
+ The publication status from StructureDefinition.status. Expected FHIR values include `draft`, `active`, `retired`, and `unknown`; the server preserves authored strings.
+ """
+
+ date: typing.Optional[str] = pydantic.Field(default=None)
+ """
+ The authored publication date from StructureDefinition.date, when present. This is a FHIR dateTime string and may be less precise than a full timestamp.
+ """
+
+ canonical: str = pydantic.Field()
+ """
+ The canonical profile reference, including the version pin when present.
+ """
+
+ fhir_version: str = pydantic.Field()
"""
The base FHIR version the StructureDefinition targets.
"""
- implementation_guide: typing.Optional[str] = pydantic.Field(default=None)
+ implementation_guide: str = pydantic.Field()
"""
The implementation guide the profile belongs to.
"""
- created_at: typing.Optional[dt.datetime] = None
- updated_at: typing.Optional[dt.datetime] = None
+ created_at: dt.datetime
+ updated_at: dt.datetime = pydantic.Field()
+ """
+ Last update timestamp for the profile's current StructureDefinition. For retained versions, this equals `created_at`.
+ """
if IS_PYDANTIC_V2:
model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
diff --git a/src/phenoml/profiles/types/profile_version_create_request.py b/src/phenoml/profiles/types/profile_version_create_request.py
new file mode 100644
index 0000000..2220af5
--- /dev/null
+++ b/src/phenoml/profiles/types/profile_version_create_request.py
@@ -0,0 +1,8 @@
+# This file was auto-generated by Fern from our API Definition.
+
+from .fhir_resource import FhirResource
+
+ProfileVersionCreateRequest = FhirResource
+"""
+A FHIR StructureDefinition as a JSON object. Must include resourceType: StructureDefinition, url, type, version, and a snapshot with elements. The canonical URL and resource type must match the profile when one already exists. If id is present, its lowercased value must match the path id; if omitted, the path id is used. The version may contain letters, numbers, and the punctuation characters `.`, `_`, `~`, `+`, and `-`; it cannot be exactly `.` or `..`.
+"""
diff --git a/src/phenoml/profiles/types/profile_version_list_response.py b/src/phenoml/profiles/types/profile_version_list_response.py
new file mode 100644
index 0000000..b68ba51
--- /dev/null
+++ b/src/phenoml/profiles/types/profile_version_list_response.py
@@ -0,0 +1,20 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from .profile_summary import ProfileSummary
+
+
+class ProfileVersionListResponse(UniversalBaseModel):
+ versions: typing.List[ProfileSummary]
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/phenoml/profiles/versions/__init__.py b/src/phenoml/profiles/versions/__init__.py
new file mode 100644
index 0000000..5cde020
--- /dev/null
+++ b/src/phenoml/profiles/versions/__init__.py
@@ -0,0 +1,4 @@
+# This file was auto-generated by Fern from our API Definition.
+
+# isort: skip_file
+
diff --git a/src/phenoml/profiles/versions/client.py b/src/phenoml/profiles/versions/client.py
new file mode 100644
index 0000000..a50fa24
--- /dev/null
+++ b/src/phenoml/profiles/versions/client.py
@@ -0,0 +1,392 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper
+from ...core.request_options import RequestOptions
+from ..types.profile_get_response import ProfileGetResponse
+from ..types.profile_summary import ProfileSummary
+from ..types.profile_version_create_request import ProfileVersionCreateRequest
+from ..types.profile_version_list_response import ProfileVersionListResponse
+from .raw_client import AsyncRawVersionsClient, RawVersionsClient
+
+# this is used as the default value for optional parameters
+OMIT = typing.cast(typing.Any, ...)
+
+
+class VersionsClient:
+ def __init__(self, *, client_wrapper: SyncClientWrapper):
+ self._raw_client = RawVersionsClient(client_wrapper=client_wrapper)
+
+ @property
+ def with_raw_response(self) -> RawVersionsClient:
+ """
+ Retrieves a raw implementation of this client that returns raw responses.
+
+ Returns
+ -------
+ RawVersionsClient
+ """
+ return self._raw_client
+
+ def list(self, id: str, *, request_options: typing.Optional[RequestOptions] = None) -> ProfileVersionListResponse:
+ """
+ Returns retained versions for a custom profile.
+
+ Parameters
+ ----------
+ id : str
+ The lowercase StructureDefinition id of the custom profile.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ ProfileVersionListResponse
+ List of profile versions
+
+ Examples
+ --------
+ from phenoml import PhenomlClient
+
+ client = PhenomlClient(
+ client_id="YOUR_CLIENT_ID",
+ client_secret="YOUR_CLIENT_SECRET",
+ )
+ client.profiles.versions.list(
+ id="custom-patient",
+ )
+ """
+ _response = self._raw_client.list(id, request_options=request_options)
+ return _response.data
+
+ def create(
+ self, id: str, *, request: ProfileVersionCreateRequest, request_options: typing.Optional[RequestOptions] = None
+ ) -> ProfileSummary:
+ """
+ Adds an immutable StructureDefinition version to a custom profile. If
+ the profile does not exist, it is created from the submitted version.
+ The StructureDefinition must include a non-empty `version`; its
+ canonical URL and resource type must match the profile when one already
+ exists. If it includes an `id`, that id must match the path parameter;
+ if it omits `id`, the path parameter is used. Profiles created through
+ this endpoint are grouped under `custom`. Posting the profile's current
+ StructureDefinition unchanged retains it as a version.
+ Version strings may contain letters, numbers, and the punctuation
+ characters `.`, `_`, `~`, `+`, and `-`; they cannot be exactly `.` or
+ `..`. Each profile can retain up to 250 versions; delete old
+ versions before adding more.
+
+ Parameters
+ ----------
+ id : str
+ The lowercase StructureDefinition id of the custom profile.
+
+ request : ProfileVersionCreateRequest
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ ProfileSummary
+ Profile version successfully created
+
+ Examples
+ --------
+ from phenoml import PhenomlClient
+
+ client = PhenomlClient(
+ client_id="YOUR_CLIENT_ID",
+ client_secret="YOUR_CLIENT_SECRET",
+ )
+ client.profiles.versions.create(
+ id="custom-patient",
+ request={"key": "value"},
+ )
+ """
+ _response = self._raw_client.create(id, request=request, request_options=request_options)
+ return _response.data
+
+ def get(
+ self, id: str, version: str, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> ProfileGetResponse:
+ """
+ Returns metadata and the full StructureDefinition for one retained
+ version. The returned StructureDefinition's id is the profile id. The
+ path version is the authored `StructureDefinition.version` value.
+
+ Parameters
+ ----------
+ id : str
+ The lowercase StructureDefinition id of the custom profile.
+
+ version : str
+ The authored StructureDefinition.version. It may contain letters, numbers, and the punctuation characters `.`, `_`, `~`, `+`, and `-`; it cannot be exactly `.` or `..`.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ ProfileGetResponse
+ The requested profile version
+
+ Examples
+ --------
+ from phenoml import PhenomlClient
+
+ client = PhenomlClient(
+ client_id="YOUR_CLIENT_ID",
+ client_secret="YOUR_CLIENT_SECRET",
+ )
+ client.profiles.versions.get(
+ id="custom-patient",
+ version="2.0.0",
+ )
+ """
+ _response = self._raw_client.get(id, version, request_options=request_options)
+ return _response.data
+
+ def delete(self, id: str, version: str, *, request_options: typing.Optional[RequestOptions] = None) -> None:
+ """
+ Deletes one retained version from a custom profile. The path
+ version is the authored `StructureDefinition.version` value.
+
+ Parameters
+ ----------
+ id : str
+ The lowercase StructureDefinition id of the custom profile.
+
+ version : str
+ The authored StructureDefinition.version. It may contain letters, numbers, and the punctuation characters `.`, `_`, `~`, `+`, and `-`; it cannot be exactly `.` or `..`.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ None
+
+ Examples
+ --------
+ from phenoml import PhenomlClient
+
+ client = PhenomlClient(
+ client_id="YOUR_CLIENT_ID",
+ client_secret="YOUR_CLIENT_SECRET",
+ )
+ client.profiles.versions.delete(
+ id="custom-patient",
+ version="2.0.0",
+ )
+ """
+ _response = self._raw_client.delete(id, version, request_options=request_options)
+ return _response.data
+
+
+class AsyncVersionsClient:
+ def __init__(self, *, client_wrapper: AsyncClientWrapper):
+ self._raw_client = AsyncRawVersionsClient(client_wrapper=client_wrapper)
+
+ @property
+ def with_raw_response(self) -> AsyncRawVersionsClient:
+ """
+ Retrieves a raw implementation of this client that returns raw responses.
+
+ Returns
+ -------
+ AsyncRawVersionsClient
+ """
+ return self._raw_client
+
+ async def list(
+ self, id: str, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> ProfileVersionListResponse:
+ """
+ Returns retained versions for a custom profile.
+
+ Parameters
+ ----------
+ id : str
+ The lowercase StructureDefinition id of the custom profile.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ ProfileVersionListResponse
+ List of profile versions
+
+ Examples
+ --------
+ import asyncio
+
+ from phenoml import AsyncPhenomlClient
+
+ client = AsyncPhenomlClient(
+ client_id="YOUR_CLIENT_ID",
+ client_secret="YOUR_CLIENT_SECRET",
+ )
+
+
+ async def main() -> None:
+ await client.profiles.versions.list(
+ id="custom-patient",
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.list(id, request_options=request_options)
+ return _response.data
+
+ async def create(
+ self, id: str, *, request: ProfileVersionCreateRequest, request_options: typing.Optional[RequestOptions] = None
+ ) -> ProfileSummary:
+ """
+ Adds an immutable StructureDefinition version to a custom profile. If
+ the profile does not exist, it is created from the submitted version.
+ The StructureDefinition must include a non-empty `version`; its
+ canonical URL and resource type must match the profile when one already
+ exists. If it includes an `id`, that id must match the path parameter;
+ if it omits `id`, the path parameter is used. Profiles created through
+ this endpoint are grouped under `custom`. Posting the profile's current
+ StructureDefinition unchanged retains it as a version.
+ Version strings may contain letters, numbers, and the punctuation
+ characters `.`, `_`, `~`, `+`, and `-`; they cannot be exactly `.` or
+ `..`. Each profile can retain up to 250 versions; delete old
+ versions before adding more.
+
+ Parameters
+ ----------
+ id : str
+ The lowercase StructureDefinition id of the custom profile.
+
+ request : ProfileVersionCreateRequest
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ ProfileSummary
+ Profile version successfully created
+
+ Examples
+ --------
+ import asyncio
+
+ from phenoml import AsyncPhenomlClient
+
+ client = AsyncPhenomlClient(
+ client_id="YOUR_CLIENT_ID",
+ client_secret="YOUR_CLIENT_SECRET",
+ )
+
+
+ async def main() -> None:
+ await client.profiles.versions.create(
+ id="custom-patient",
+ request={"key": "value"},
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.create(id, request=request, request_options=request_options)
+ return _response.data
+
+ async def get(
+ self, id: str, version: str, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> ProfileGetResponse:
+ """
+ Returns metadata and the full StructureDefinition for one retained
+ version. The returned StructureDefinition's id is the profile id. The
+ path version is the authored `StructureDefinition.version` value.
+
+ Parameters
+ ----------
+ id : str
+ The lowercase StructureDefinition id of the custom profile.
+
+ version : str
+ The authored StructureDefinition.version. It may contain letters, numbers, and the punctuation characters `.`, `_`, `~`, `+`, and `-`; it cannot be exactly `.` or `..`.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ ProfileGetResponse
+ The requested profile version
+
+ Examples
+ --------
+ import asyncio
+
+ from phenoml import AsyncPhenomlClient
+
+ client = AsyncPhenomlClient(
+ client_id="YOUR_CLIENT_ID",
+ client_secret="YOUR_CLIENT_SECRET",
+ )
+
+
+ async def main() -> None:
+ await client.profiles.versions.get(
+ id="custom-patient",
+ version="2.0.0",
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.get(id, version, request_options=request_options)
+ return _response.data
+
+ async def delete(self, id: str, version: str, *, request_options: typing.Optional[RequestOptions] = None) -> None:
+ """
+ Deletes one retained version from a custom profile. The path
+ version is the authored `StructureDefinition.version` value.
+
+ Parameters
+ ----------
+ id : str
+ The lowercase StructureDefinition id of the custom profile.
+
+ version : str
+ The authored StructureDefinition.version. It may contain letters, numbers, and the punctuation characters `.`, `_`, `~`, `+`, and `-`; it cannot be exactly `.` or `..`.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ None
+
+ Examples
+ --------
+ import asyncio
+
+ from phenoml import AsyncPhenomlClient
+
+ client = AsyncPhenomlClient(
+ client_id="YOUR_CLIENT_ID",
+ client_secret="YOUR_CLIENT_SECRET",
+ )
+
+
+ async def main() -> None:
+ await client.profiles.versions.delete(
+ id="custom-patient",
+ version="2.0.0",
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.delete(id, version, request_options=request_options)
+ return _response.data
diff --git a/src/phenoml/profiles/versions/raw_client.py b/src/phenoml/profiles/versions/raw_client.py
new file mode 100644
index 0000000..0a1e0c1
--- /dev/null
+++ b/src/phenoml/profiles/versions/raw_client.py
@@ -0,0 +1,874 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+from json.decoder import JSONDecodeError
+
+from ...core.api_error import ApiError
+from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper
+from ...core.http_response import AsyncHttpResponse, HttpResponse
+from ...core.jsonable_encoder import encode_path_param
+from ...core.parse_error import ParsingError
+from ...core.pydantic_utilities import parse_obj_as
+from ...core.request_options import RequestOptions
+from ..errors.bad_request_error import BadRequestError
+from ..errors.conflict_error import ConflictError
+from ..errors.forbidden_error import ForbiddenError
+from ..errors.internal_server_error import InternalServerError
+from ..errors.not_found_error import NotFoundError
+from ..errors.unauthorized_error import UnauthorizedError
+from ..types.profile_get_response import ProfileGetResponse
+from ..types.profile_summary import ProfileSummary
+from ..types.profile_version_create_request import ProfileVersionCreateRequest
+from ..types.profile_version_list_response import ProfileVersionListResponse
+from pydantic import ValidationError
+
+# this is used as the default value for optional parameters
+OMIT = typing.cast(typing.Any, ...)
+
+
+class RawVersionsClient:
+ def __init__(self, *, client_wrapper: SyncClientWrapper):
+ self._client_wrapper = client_wrapper
+
+ def list(
+ self, id: str, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> HttpResponse[ProfileVersionListResponse]:
+ """
+ Returns retained versions for a custom profile.
+
+ Parameters
+ ----------
+ id : str
+ The lowercase StructureDefinition id of the custom profile.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[ProfileVersionListResponse]
+ List of profile versions
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"fhir/profiles/{encode_path_param(id)}/versions",
+ method="GET",
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ ProfileVersionListResponse,
+ parse_obj_as(
+ type_=ProfileVersionListResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ def create(
+ self, id: str, *, request: ProfileVersionCreateRequest, request_options: typing.Optional[RequestOptions] = None
+ ) -> HttpResponse[ProfileSummary]:
+ """
+ Adds an immutable StructureDefinition version to a custom profile. If
+ the profile does not exist, it is created from the submitted version.
+ The StructureDefinition must include a non-empty `version`; its
+ canonical URL and resource type must match the profile when one already
+ exists. If it includes an `id`, that id must match the path parameter;
+ if it omits `id`, the path parameter is used. Profiles created through
+ this endpoint are grouped under `custom`. Posting the profile's current
+ StructureDefinition unchanged retains it as a version.
+ Version strings may contain letters, numbers, and the punctuation
+ characters `.`, `_`, `~`, `+`, and `-`; they cannot be exactly `.` or
+ `..`. Each profile can retain up to 250 versions; delete old
+ versions before adding more.
+
+ Parameters
+ ----------
+ id : str
+ The lowercase StructureDefinition id of the custom profile.
+
+ request : ProfileVersionCreateRequest
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[ProfileSummary]
+ Profile version successfully created
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"fhir/profiles/{encode_path_param(id)}/versions",
+ method="POST",
+ json=request,
+ request_options=request_options,
+ omit=OMIT,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ ProfileSummary,
+ parse_obj_as(
+ type_=ProfileSummary, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 409:
+ raise ConflictError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ def get(
+ self, id: str, version: str, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> HttpResponse[ProfileGetResponse]:
+ """
+ Returns metadata and the full StructureDefinition for one retained
+ version. The returned StructureDefinition's id is the profile id. The
+ path version is the authored `StructureDefinition.version` value.
+
+ Parameters
+ ----------
+ id : str
+ The lowercase StructureDefinition id of the custom profile.
+
+ version : str
+ The authored StructureDefinition.version. It may contain letters, numbers, and the punctuation characters `.`, `_`, `~`, `+`, and `-`; it cannot be exactly `.` or `..`.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[ProfileGetResponse]
+ The requested profile version
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"fhir/profiles/{encode_path_param(id)}/versions/{encode_path_param(version)}",
+ method="GET",
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ ProfileGetResponse,
+ parse_obj_as(
+ type_=ProfileGetResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ def delete(
+ self, id: str, version: str, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> HttpResponse[None]:
+ """
+ Deletes one retained version from a custom profile. The path
+ version is the authored `StructureDefinition.version` value.
+
+ Parameters
+ ----------
+ id : str
+ The lowercase StructureDefinition id of the custom profile.
+
+ version : str
+ The authored StructureDefinition.version. It may contain letters, numbers, and the punctuation characters `.`, `_`, `~`, `+`, and `-`; it cannot be exactly `.` or `..`.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[None]
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"fhir/profiles/{encode_path_param(id)}/versions/{encode_path_param(version)}",
+ method="DELETE",
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ return HttpResponse(response=_response, data=None)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+
+class AsyncRawVersionsClient:
+ def __init__(self, *, client_wrapper: AsyncClientWrapper):
+ self._client_wrapper = client_wrapper
+
+ async def list(
+ self, id: str, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> AsyncHttpResponse[ProfileVersionListResponse]:
+ """
+ Returns retained versions for a custom profile.
+
+ Parameters
+ ----------
+ id : str
+ The lowercase StructureDefinition id of the custom profile.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[ProfileVersionListResponse]
+ List of profile versions
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"fhir/profiles/{encode_path_param(id)}/versions",
+ method="GET",
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ ProfileVersionListResponse,
+ parse_obj_as(
+ type_=ProfileVersionListResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ async def create(
+ self, id: str, *, request: ProfileVersionCreateRequest, request_options: typing.Optional[RequestOptions] = None
+ ) -> AsyncHttpResponse[ProfileSummary]:
+ """
+ Adds an immutable StructureDefinition version to a custom profile. If
+ the profile does not exist, it is created from the submitted version.
+ The StructureDefinition must include a non-empty `version`; its
+ canonical URL and resource type must match the profile when one already
+ exists. If it includes an `id`, that id must match the path parameter;
+ if it omits `id`, the path parameter is used. Profiles created through
+ this endpoint are grouped under `custom`. Posting the profile's current
+ StructureDefinition unchanged retains it as a version.
+ Version strings may contain letters, numbers, and the punctuation
+ characters `.`, `_`, `~`, `+`, and `-`; they cannot be exactly `.` or
+ `..`. Each profile can retain up to 250 versions; delete old
+ versions before adding more.
+
+ Parameters
+ ----------
+ id : str
+ The lowercase StructureDefinition id of the custom profile.
+
+ request : ProfileVersionCreateRequest
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[ProfileSummary]
+ Profile version successfully created
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"fhir/profiles/{encode_path_param(id)}/versions",
+ method="POST",
+ json=request,
+ request_options=request_options,
+ omit=OMIT,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ ProfileSummary,
+ parse_obj_as(
+ type_=ProfileSummary, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 409:
+ raise ConflictError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ async def get(
+ self, id: str, version: str, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> AsyncHttpResponse[ProfileGetResponse]:
+ """
+ Returns metadata and the full StructureDefinition for one retained
+ version. The returned StructureDefinition's id is the profile id. The
+ path version is the authored `StructureDefinition.version` value.
+
+ Parameters
+ ----------
+ id : str
+ The lowercase StructureDefinition id of the custom profile.
+
+ version : str
+ The authored StructureDefinition.version. It may contain letters, numbers, and the punctuation characters `.`, `_`, `~`, `+`, and `-`; it cannot be exactly `.` or `..`.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[ProfileGetResponse]
+ The requested profile version
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"fhir/profiles/{encode_path_param(id)}/versions/{encode_path_param(version)}",
+ method="GET",
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ ProfileGetResponse,
+ parse_obj_as(
+ type_=ProfileGetResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ async def delete(
+ self, id: str, version: str, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> AsyncHttpResponse[None]:
+ """
+ Deletes one retained version from a custom profile. The path
+ version is the authored `StructureDefinition.version` value.
+
+ Parameters
+ ----------
+ id : str
+ The lowercase StructureDefinition id of the custom profile.
+
+ version : str
+ The authored StructureDefinition.version. It may contain letters, numbers, and the punctuation characters `.`, `_`, `~`, `+`, and `-`; it cannot be exactly `.` or `..`.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[None]
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"fhir/profiles/{encode_path_param(id)}/versions/{encode_path_param(version)}",
+ method="DELETE",
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ return AsyncHttpResponse(response=_response, data=None)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
diff --git a/tests/wire/test_lang2FhirBatch.py b/tests/wire/test_lang2FhirBatch.py
new file mode 100644
index 0000000..58179d3
--- /dev/null
+++ b/tests/wire/test_lang2FhirBatch.py
@@ -0,0 +1,78 @@
+from .conftest import get_client, verify_request_count
+
+
+def test_lang2FhirBatch_list_() -> None:
+ """Test list endpoint with WireMock"""
+ test_id = "lang2fhir_batch.list_.0"
+ client = get_client(test_id)
+ client.lang2fhir_batch.list(
+ cursor="cursor",
+ limit=1,
+ )
+ verify_request_count(test_id, "GET", "/lang2fhir/batch", {"cursor": "cursor", "limit": "1"}, 1)
+
+
+def test_lang2FhirBatch_create() -> None:
+ """Test create endpoint with WireMock"""
+ test_id = "lang2fhir_batch.create.0"
+ client = get_client(test_id)
+ client.lang2fhir_batch.create(
+ request_id="submit-2025-09-02-batch-001",
+ )
+ verify_request_count(test_id, "POST", "/lang2fhir/batch", None, 1)
+
+
+def test_lang2FhirBatch_upload_item() -> None:
+ """Test uploadItem endpoint with WireMock"""
+ test_id = "lang2fhir_batch.upload_item.0"
+ client = get_client(test_id)
+ client.lang2fhir_batch.upload_item(
+ job_id="job_id",
+ file="example_file",
+ )
+ verify_request_count(test_id, "POST", "/lang2fhir/batch/job_id/items", None, 1)
+
+
+def test_lang2FhirBatch_finalize() -> None:
+ """Test finalize endpoint with WireMock"""
+ test_id = "lang2fhir_batch.finalize.0"
+ client = get_client(test_id)
+ client.lang2fhir_batch.finalize(
+ job_id="job_id",
+ )
+ verify_request_count(test_id, "POST", "/lang2fhir/batch/job_id/finalize", None, 1)
+
+
+def test_lang2FhirBatch_get() -> None:
+ """Test get endpoint with WireMock"""
+ test_id = "lang2fhir_batch.get.0"
+ client = get_client(test_id)
+ client.lang2fhir_batch.get(
+ job_id="job_id",
+ cursor="cursor",
+ limit=1,
+ )
+ verify_request_count(test_id, "GET", "/lang2fhir/batch/job_id", {"cursor": "cursor", "limit": "1"}, 1)
+
+
+def test_lang2FhirBatch_get_results() -> None:
+ """Test getResults endpoint with WireMock"""
+ test_id = "lang2fhir_batch.get_results.0"
+ client = get_client(test_id)
+ client.lang2fhir_batch.get_results(
+ job_id="job_id",
+ cursor="cursor",
+ limit=1,
+ )
+ verify_request_count(test_id, "GET", "/lang2fhir/batch/job_id/results", {"cursor": "cursor", "limit": "1"}, 1)
+
+
+def test_lang2FhirBatch_get_result() -> None:
+ """Test getResult endpoint with WireMock"""
+ test_id = "lang2fhir_batch.get_result.0"
+ client = get_client(test_id)
+ client.lang2fhir_batch.get_result(
+ job_id="job_id",
+ item_id="item_id",
+ )
+ verify_request_count(test_id, "GET", "/lang2fhir/batch/job_id/results/item_id", None, 1)
diff --git a/tests/wire/test_profiles_profiles.py b/tests/wire/test_profiles_profiles.py
index 991135d..bf9f349 100644
--- a/tests/wire/test_profiles_profiles.py
+++ b/tests/wire/test_profiles_profiles.py
@@ -18,7 +18,21 @@ def test_profiles_profiles_create() -> None:
test_id = "profiles.profiles.create.0"
client = get_client(test_id)
client.profiles.profiles.create(
- structure_definition={"key": "value"},
+ structure_definition={
+ "resourceType": "StructureDefinition",
+ "id": "custom-patient",
+ "url": "http://phenoml.com/fhir/StructureDefinition/custom-patient",
+ "name": "CustomPatient",
+ "status": "active",
+ "fhirVersion": "4.0.1",
+ "kind": "resource",
+ "abstract": False,
+ "type": "Patient",
+ "baseDefinition": "http://hl7.org/fhir/StructureDefinition/Patient",
+ "derivation": "constraint",
+ "snapshot": {"element": [{"id": "Patient", "path": "Patient", "min": 0, "max": "*"}]},
+ },
+ implementation_guide="acme-cardiology",
)
verify_request_count(test_id, "POST", "/fhir/profiles", None, 1)
@@ -39,7 +53,21 @@ def test_profiles_profiles_update() -> None:
client = get_client(test_id)
client.profiles.profiles.update(
id="custom-patient",
- structure_definition={"key": "value"},
+ structure_definition={
+ "resourceType": "StructureDefinition",
+ "id": "custom-patient",
+ "url": "http://phenoml.com/fhir/StructureDefinition/custom-patient",
+ "name": "CustomPatient",
+ "status": "active",
+ "fhirVersion": "4.0.1",
+ "kind": "resource",
+ "abstract": False,
+ "type": "Patient",
+ "baseDefinition": "http://hl7.org/fhir/StructureDefinition/Patient",
+ "derivation": "constraint",
+ "snapshot": {"element": [{"id": "Patient", "path": "Patient", "min": 0, "max": "*"}]},
+ },
+ implementation_guide="acme-cardiology",
)
verify_request_count(test_id, "PUT", "/fhir/profiles/custom-patient", None, 1)
diff --git a/tests/wire/test_profiles_versions.py b/tests/wire/test_profiles_versions.py
new file mode 100644
index 0000000..e530f3e
--- /dev/null
+++ b/tests/wire/test_profiles_versions.py
@@ -0,0 +1,44 @@
+from .conftest import get_client, verify_request_count
+
+
+def test_profiles_versions_list_() -> None:
+ """Test list endpoint with WireMock"""
+ test_id = "profiles.versions.list_.0"
+ client = get_client(test_id)
+ client.profiles.versions.list(
+ id="custom-patient",
+ )
+ verify_request_count(test_id, "GET", "/fhir/profiles/custom-patient/versions", None, 1)
+
+
+def test_profiles_versions_create() -> None:
+ """Test create endpoint with WireMock"""
+ test_id = "profiles.versions.create.0"
+ client = get_client(test_id)
+ client.profiles.versions.create(
+ id="custom-patient",
+ request={"key": "value"},
+ )
+ verify_request_count(test_id, "POST", "/fhir/profiles/custom-patient/versions", None, 1)
+
+
+def test_profiles_versions_get() -> None:
+ """Test get endpoint with WireMock"""
+ test_id = "profiles.versions.get.0"
+ client = get_client(test_id)
+ client.profiles.versions.get(
+ id="custom-patient",
+ version="2.0.0",
+ )
+ verify_request_count(test_id, "GET", "/fhir/profiles/custom-patient/versions/2.0.0", None, 1)
+
+
+def test_profiles_versions_delete() -> None:
+ """Test delete endpoint with WireMock"""
+ test_id = "profiles.versions.delete.0"
+ client = get_client(test_id)
+ client.profiles.versions.delete(
+ id="custom-patient",
+ version="2.0.0",
+ )
+ verify_request_count(test_id, "DELETE", "/fhir/profiles/custom-patient/versions/2.0.0", None, 1)
diff --git a/wiremock/wiremock-mappings.json b/wiremock/wiremock-mappings.json
index 11ab612..042a584 100644
--- a/wiremock/wiremock-mappings.json
+++ b/wiremock/wiremock-mappings.json
@@ -1645,6 +1645,241 @@
}
}
},
+ {
+ "id": "5f010e1c-ed6c-4a4c-a052-a29ce2aadff7",
+ "name": "List batch jobs - default",
+ "request": {
+ "urlPathTemplate": "/lang2fhir/batch",
+ "method": "GET",
+ "queryParameters": {
+ "cursor": {
+ "equalTo": "cursor"
+ },
+ "limit": {
+ "equalTo": "1"
+ }
+ }
+ },
+ "response": {
+ "status": 200,
+ "body": "{\n \"jobs\": [\n {\n \"job_id\": \"l2f_batch_6f1d2c3a-8b4e-4f5a-9c7d-0e1f2a3b4c5d\",\n \"request_id\": \"submit-2025-09-02-batch-001\",\n \"status\": \"pending\",\n \"finalized\": false,\n \"total_items\": 12,\n \"error\": {\n \"kind\": \"processing_failed\",\n \"message\": \"the item could not be converted\"\n },\n \"created_at\": \"2024-01-15T09:30:00Z\",\n \"updated_at\": \"2024-01-15T09:30:00Z\",\n \"completed_at\": \"2024-01-15T09:30:00Z\",\n \"expires_at\": \"2024-01-15T09:30:00Z\"\n }\n ],\n \"next_cursor\": \"next_cursor\",\n \"has_more\": false\n}",
+ "headers": {
+ "Content-Type": "application/json"
+ }
+ },
+ "uuid": "5f010e1c-ed6c-4a4c-a052-a29ce2aadff7",
+ "persistent": true,
+ "priority": 3,
+ "metadata": {
+ "mocklab": {
+ "created": {
+ "at": "2020-01-01T00:00:00.000Z",
+ "via": "SYSTEM"
+ }
+ }
+ },
+ "postServeActions": []
+ },
+ {
+ "id": "bde73df9-796c-44ff-98c8-45bd5f3f393d",
+ "name": "Create a batch job - default",
+ "request": {
+ "urlPathTemplate": "/lang2fhir/batch",
+ "method": "POST"
+ },
+ "response": {
+ "status": 202,
+ "body": "{\n \"job_id\": \"l2f_batch_6f1d2c3a-8b4e-4f5a-9c7d-0e1f2a3b4c5d\",\n \"request_id\": \"submit-2025-09-02-batch-001\",\n \"status\": \"pending\",\n \"finalized\": false,\n \"total_items\": 12,\n \"error\": {\n \"kind\": \"processing_failed\",\n \"message\": \"the item could not be converted\"\n },\n \"created_at\": \"2024-01-15T09:30:00Z\",\n \"updated_at\": \"2024-01-15T09:30:00Z\",\n \"completed_at\": \"2024-01-15T09:30:00Z\",\n \"expires_at\": \"2024-01-15T09:30:00Z\"\n}",
+ "headers": {
+ "Content-Type": "application/json"
+ }
+ },
+ "uuid": "bde73df9-796c-44ff-98c8-45bd5f3f393d",
+ "persistent": true,
+ "priority": 3,
+ "metadata": {
+ "mocklab": {
+ "created": {
+ "at": "2020-01-01T00:00:00.000Z",
+ "via": "SYSTEM"
+ }
+ }
+ }
+ },
+ {
+ "id": "7e1803b1-313b-4543-b327-fdac0c385aba",
+ "name": "Upload one batch item - default",
+ "request": {
+ "urlPathTemplate": "/lang2fhir/batch/{job_id}/items",
+ "method": "POST",
+ "pathParameters": {
+ "job_id": {
+ "equalTo": "job_id"
+ }
+ }
+ },
+ "response": {
+ "status": 202,
+ "body": "{\n \"job_id\": \"l2f_batch_6f1d2c3a-8b4e-4f5a-9c7d-0e1f2a3b4c5d\",\n \"request_id\": \"submit-2025-09-02-batch-001\",\n \"status\": \"pending\",\n \"finalized\": false,\n \"total_items\": 12,\n \"error\": {\n \"kind\": \"processing_failed\",\n \"message\": \"the item could not be converted\"\n },\n \"created_at\": \"2024-01-15T09:30:00Z\",\n \"updated_at\": \"2024-01-15T09:30:00Z\",\n \"completed_at\": \"2024-01-15T09:30:00Z\",\n \"expires_at\": \"2024-01-15T09:30:00Z\",\n \"item_id\": \"9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08\",\n \"id\": \"chart-note-0042\",\n \"deduplicated\": false\n}",
+ "headers": {
+ "Content-Type": "application/json"
+ }
+ },
+ "uuid": "7e1803b1-313b-4543-b327-fdac0c385aba",
+ "persistent": true,
+ "priority": 3,
+ "metadata": {
+ "mocklab": {
+ "created": {
+ "at": "2020-01-01T00:00:00.000Z",
+ "via": "SYSTEM"
+ }
+ }
+ }
+ },
+ {
+ "id": "9a02971f-22a4-445b-84b8-736ae70fe586",
+ "name": "Finalize a batch job - default",
+ "request": {
+ "urlPathTemplate": "/lang2fhir/batch/{job_id}/finalize",
+ "method": "POST",
+ "pathParameters": {
+ "job_id": {
+ "equalTo": "job_id"
+ }
+ }
+ },
+ "response": {
+ "status": 202,
+ "body": "{\n \"job_id\": \"l2f_batch_6f1d2c3a-8b4e-4f5a-9c7d-0e1f2a3b4c5d\",\n \"request_id\": \"submit-2025-09-02-batch-001\",\n \"status\": \"pending\",\n \"finalized\": false,\n \"total_items\": 12,\n \"error\": {\n \"kind\": \"processing_failed\",\n \"message\": \"the item could not be converted\"\n },\n \"created_at\": \"2024-01-15T09:30:00Z\",\n \"updated_at\": \"2024-01-15T09:30:00Z\",\n \"completed_at\": \"2024-01-15T09:30:00Z\",\n \"expires_at\": \"2024-01-15T09:30:00Z\"\n}",
+ "headers": {
+ "Content-Type": "application/json"
+ }
+ },
+ "uuid": "9a02971f-22a4-445b-84b8-736ae70fe586",
+ "persistent": true,
+ "priority": 3,
+ "metadata": {
+ "mocklab": {
+ "created": {
+ "at": "2020-01-01T00:00:00.000Z",
+ "via": "SYSTEM"
+ }
+ }
+ }
+ },
+ {
+ "id": "4ae6e86d-67a4-4b81-afb6-652f0a1d0a26",
+ "name": "Get a batch job - default",
+ "request": {
+ "urlPathTemplate": "/lang2fhir/batch/{job_id}",
+ "method": "GET",
+ "pathParameters": {
+ "job_id": {
+ "equalTo": "job_id"
+ }
+ },
+ "queryParameters": {
+ "cursor": {
+ "equalTo": "cursor"
+ },
+ "limit": {
+ "equalTo": "1"
+ }
+ }
+ },
+ "response": {
+ "status": 200,
+ "body": "{\n \"job_id\": \"l2f_batch_6f1d2c3a-8b4e-4f5a-9c7d-0e1f2a3b4c5d\",\n \"request_id\": \"submit-2025-09-02-batch-001\",\n \"status\": \"pending\",\n \"finalized\": false,\n \"total_items\": 12,\n \"error\": {\n \"kind\": \"processing_failed\",\n \"message\": \"the item could not be converted\"\n },\n \"created_at\": \"2024-01-15T09:30:00Z\",\n \"updated_at\": \"2024-01-15T09:30:00Z\",\n \"completed_at\": \"2024-01-15T09:30:00Z\",\n \"expires_at\": \"2024-01-15T09:30:00Z\",\n \"counts\": {\n \"total\": 12,\n \"pending\": 3,\n \"processing\": 2,\n \"succeeded\": 6,\n \"failed\": 1\n },\n \"items\": [\n {\n \"item_id\": \"9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08\",\n \"id\": \"chart-note-0042\",\n \"status\": \"pending\",\n \"attempts\": 1,\n \"detect_retries\": 1,\n \"result_size\": 20482,\n \"error\": {\n \"kind\": \"processing_failed\",\n \"message\": \"the item could not be converted\"\n },\n \"completed_at\": \"2024-01-15T09:30:00Z\"\n }\n ],\n \"next_cursor\": \"next_cursor\",\n \"has_more\": false\n}",
+ "headers": {
+ "Content-Type": "application/json"
+ }
+ },
+ "uuid": "4ae6e86d-67a4-4b81-afb6-652f0a1d0a26",
+ "persistent": true,
+ "priority": 3,
+ "metadata": {
+ "mocklab": {
+ "created": {
+ "at": "2020-01-01T00:00:00.000Z",
+ "via": "SYSTEM"
+ }
+ }
+ }
+ },
+ {
+ "id": "f2086f8a-8680-4f4b-981e-69c8d9245f0f",
+ "name": "List a batch job's results - default",
+ "request": {
+ "urlPathTemplate": "/lang2fhir/batch/{job_id}/results",
+ "method": "GET",
+ "pathParameters": {
+ "job_id": {
+ "equalTo": "job_id"
+ }
+ },
+ "queryParameters": {
+ "cursor": {
+ "equalTo": "cursor"
+ },
+ "limit": {
+ "equalTo": "1"
+ }
+ }
+ },
+ "response": {
+ "status": 200,
+ "body": "{\n \"results\": [\n {\n \"item_id\": \"9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08\",\n \"id\": \"chart-note-0042\",\n \"status\": \"pending\",\n \"attempts\": 1,\n \"detect_retries\": 1,\n \"result_size\": 20482,\n \"error\": {\n \"kind\": \"processing_failed\",\n \"message\": \"the item could not be converted\"\n },\n \"completed_at\": \"2024-01-15T09:30:00Z\"\n }\n ],\n \"next_cursor\": \"next_cursor\",\n \"has_more\": false\n}",
+ "headers": {
+ "Content-Type": "application/json"
+ }
+ },
+ "uuid": "f2086f8a-8680-4f4b-981e-69c8d9245f0f",
+ "persistent": true,
+ "priority": 3,
+ "metadata": {
+ "mocklab": {
+ "created": {
+ "at": "2020-01-01T00:00:00.000Z",
+ "via": "SYSTEM"
+ }
+ }
+ }
+ },
+ {
+ "id": "29496381-fe05-4535-a70b-b830e4825e6b",
+ "name": "Get one batch item's result - default",
+ "request": {
+ "urlPathTemplate": "/lang2fhir/batch/{job_id}/results/{item_id}",
+ "method": "GET",
+ "pathParameters": {
+ "job_id": {
+ "equalTo": "job_id"
+ },
+ "item_id": {
+ "equalTo": "item_id"
+ }
+ }
+ },
+ "response": {
+ "status": 200,
+ "body": "{\n \"key\": \"value\"\n}",
+ "headers": {
+ "Content-Type": "application/json"
+ }
+ },
+ "uuid": "29496381-fe05-4535-a70b-b830e4825e6b",
+ "persistent": true,
+ "priority": 3,
+ "metadata": {
+ "mocklab": {
+ "created": {
+ "at": "2020-01-01T00:00:00.000Z",
+ "via": "SYSTEM"
+ }
+ }
+ }
+ },
{
"id": "9343ec90-6074-4728-ba0b-3e777524c972",
"name": "List custom FHIR profiles - default",
@@ -1659,7 +1894,7 @@
},
"response": {
"status": 200,
- "body": "{\n \"profiles\": [\n {\n \"id\": \"custom-patient\",\n \"source\": \"custom\",\n \"resource_type\": \"Patient\",\n \"url\": \"http://phenoml.com/fhir/StructureDefinition/custom-patient\",\n \"version\": \"1.0.0\",\n \"fhir_version\": \"4.0.1\",\n \"implementation_guide\": \"acme-cardiology\",\n \"created_at\": \"2024-01-15T09:30:00Z\",\n \"updated_at\": \"2024-01-15T09:30:00Z\"\n }\n ]\n}",
+ "body": "{\n \"profiles\": [\n {\n \"id\": \"custom-patient\",\n \"source\": \"custom\",\n \"resource_type\": \"Patient\",\n \"url\": \"http://phenoml.com/fhir/StructureDefinition/custom-patient\",\n \"version\": \"1.0.0\",\n \"status\": \"active\",\n \"date\": \"2026-08-24\",\n \"canonical\": \"http://phenoml.com/fhir/StructureDefinition/custom-patient|1.0.0\",\n \"fhir_version\": \"4.0.1\",\n \"implementation_guide\": \"acme-cardiology\",\n \"created_at\": \"2026-08-24T15:04:05Z\",\n \"updated_at\": \"2026-08-25T16:04:05Z\"\n }\n ]\n}",
"headers": {
"Content-Type": "application/json"
}
@@ -1686,7 +1921,7 @@
},
"response": {
"status": 201,
- "body": "{\n \"id\": \"custom-patient\",\n \"source\": \"custom\",\n \"resource_type\": \"Patient\",\n \"url\": \"http://phenoml.com/fhir/StructureDefinition/custom-patient\",\n \"version\": \"1.0.0\",\n \"fhir_version\": \"4.0.1\",\n \"implementation_guide\": \"acme-cardiology\",\n \"created_at\": \"2024-01-15T09:30:00Z\",\n \"updated_at\": \"2024-01-15T09:30:00Z\"\n}",
+ "body": "{\n \"id\": \"custom-patient\",\n \"source\": \"custom\",\n \"resource_type\": \"Patient\",\n \"url\": \"http://phenoml.com/fhir/StructureDefinition/custom-patient\",\n \"version\": \"1.0.0\",\n \"status\": \"active\",\n \"date\": \"2026-08-24\",\n \"canonical\": \"http://phenoml.com/fhir/StructureDefinition/custom-patient|1.0.0\",\n \"fhir_version\": \"4.0.1\",\n \"implementation_guide\": \"acme-cardiology\",\n \"created_at\": \"2026-08-24T15:04:05Z\",\n \"updated_at\": \"2026-08-25T16:04:05Z\"\n}",
"headers": {
"Content-Type": "application/json"
}
@@ -1717,7 +1952,7 @@
},
"response": {
"status": 200,
- "body": "{\n \"id\": \"custom-patient\",\n \"source\": \"custom\",\n \"resource_type\": \"Patient\",\n \"url\": \"http://phenoml.com/fhir/StructureDefinition/custom-patient\",\n \"version\": \"1.0.0\",\n \"fhir_version\": \"4.0.1\",\n \"implementation_guide\": \"acme-cardiology\",\n \"created_at\": \"2024-01-15T09:30:00Z\",\n \"updated_at\": \"2024-01-15T09:30:00Z\",\n \"structure_definition\": {\n \"key\": \"value\"\n }\n}",
+ "body": "{\n \"id\": \"custom-patient\",\n \"source\": \"custom\",\n \"resource_type\": \"Patient\",\n \"url\": \"http://phenoml.com/fhir/StructureDefinition/custom-patient\",\n \"version\": \"1.0.0\",\n \"status\": \"active\",\n \"date\": \"2026-08-24\",\n \"canonical\": \"http://phenoml.com/fhir/StructureDefinition/custom-patient|1.0.0\",\n \"fhir_version\": \"4.0.1\",\n \"implementation_guide\": \"acme-cardiology\",\n \"created_at\": \"2026-08-24T15:04:05Z\",\n \"updated_at\": \"2026-08-25T16:04:05Z\",\n \"structure_definition\": {\n \"resourceType\": \"StructureDefinition\",\n \"id\": \"custom-patient\",\n \"url\": \"http://phenoml.com/fhir/StructureDefinition/custom-patient\",\n \"version\": \"1.0.0\",\n \"name\": \"CustomPatient\",\n \"status\": \"active\",\n \"fhirVersion\": \"4.0.1\",\n \"kind\": \"resource\",\n \"abstract\": false,\n \"type\": \"Patient\",\n \"baseDefinition\": \"http://hl7.org/fhir/StructureDefinition/Patient\",\n \"derivation\": \"constraint\",\n \"snapshot\": {\n \"element\": [\n {\n \"id\": \"Patient\",\n \"path\": \"Patient\",\n \"min\": 0,\n \"max\": \"*\"\n }\n ]\n }\n }\n}",
"headers": {
"Content-Type": "application/json"
}
@@ -1748,7 +1983,7 @@
},
"response": {
"status": 200,
- "body": "{\n \"id\": \"custom-patient\",\n \"source\": \"custom\",\n \"resource_type\": \"Patient\",\n \"url\": \"http://phenoml.com/fhir/StructureDefinition/custom-patient\",\n \"version\": \"1.0.0\",\n \"fhir_version\": \"4.0.1\",\n \"implementation_guide\": \"acme-cardiology\",\n \"created_at\": \"2024-01-15T09:30:00Z\",\n \"updated_at\": \"2024-01-15T09:30:00Z\"\n}",
+ "body": "{\n \"id\": \"custom-patient\",\n \"source\": \"custom\",\n \"resource_type\": \"Patient\",\n \"url\": \"http://phenoml.com/fhir/StructureDefinition/custom-patient\",\n \"version\": \"1.0.0\",\n \"status\": \"active\",\n \"date\": \"2026-08-24\",\n \"canonical\": \"http://phenoml.com/fhir/StructureDefinition/custom-patient|1.0.0\",\n \"fhir_version\": \"4.0.1\",\n \"implementation_guide\": \"acme-cardiology\",\n \"created_at\": \"2026-08-24T15:04:05Z\",\n \"updated_at\": \"2026-08-25T16:04:05Z\"\n}",
"headers": {
"Content-Type": "application/json"
}
@@ -1796,6 +2031,136 @@
}
}
},
+ {
+ "id": "ece33377-1b58-43bf-9eb4-15018a2aa8a3",
+ "name": "List versions for a custom FHIR profile - default",
+ "request": {
+ "urlPathTemplate": "/fhir/profiles/{id}/versions",
+ "method": "GET",
+ "pathParameters": {
+ "id": {
+ "equalTo": "custom-patient"
+ }
+ }
+ },
+ "response": {
+ "status": 200,
+ "body": "{\n \"versions\": [\n {\n \"id\": \"custom-patient\",\n \"source\": \"custom\",\n \"resource_type\": \"Patient\",\n \"url\": \"http://phenoml.com/fhir/StructureDefinition/custom-patient\",\n \"version\": \"2.0.0\",\n \"status\": \"active\",\n \"date\": \"2026-08-26\",\n \"canonical\": \"http://phenoml.com/fhir/StructureDefinition/custom-patient|2.0.0\",\n \"fhir_version\": \"4.0.1\",\n \"implementation_guide\": \"acme-cardiology\",\n \"created_at\": \"2026-08-26T15:04:05Z\",\n \"updated_at\": \"2026-08-26T15:04:05Z\"\n },\n {\n \"id\": \"custom-patient\",\n \"source\": \"custom\",\n \"resource_type\": \"Patient\",\n \"url\": \"http://phenoml.com/fhir/StructureDefinition/custom-patient\",\n \"version\": \"1.0.0\",\n \"status\": \"active\",\n \"date\": \"2026-08-24\",\n \"canonical\": \"http://phenoml.com/fhir/StructureDefinition/custom-patient|1.0.0\",\n \"fhir_version\": \"4.0.1\",\n \"implementation_guide\": \"acme-cardiology\",\n \"created_at\": \"2026-08-24T15:04:05Z\",\n \"updated_at\": \"2026-08-24T15:04:05Z\"\n }\n ]\n}",
+ "headers": {
+ "Content-Type": "application/json"
+ }
+ },
+ "uuid": "ece33377-1b58-43bf-9eb4-15018a2aa8a3",
+ "persistent": true,
+ "priority": 3,
+ "metadata": {
+ "mocklab": {
+ "created": {
+ "at": "2020-01-01T00:00:00.000Z",
+ "via": "SYSTEM"
+ }
+ }
+ }
+ },
+ {
+ "id": "75dac82d-0cf5-471e-9221-a2bc87f07808",
+ "name": "Add a retained version to a custom FHIR profile - default",
+ "request": {
+ "urlPathTemplate": "/fhir/profiles/{id}/versions",
+ "method": "POST",
+ "pathParameters": {
+ "id": {
+ "equalTo": "custom-patient"
+ }
+ }
+ },
+ "response": {
+ "status": 201,
+ "body": "{\n \"id\": \"custom-patient\",\n \"source\": \"custom\",\n \"resource_type\": \"Patient\",\n \"url\": \"http://phenoml.com/fhir/StructureDefinition/custom-patient\",\n \"version\": \"1.0.0\",\n \"status\": \"active\",\n \"date\": \"2026-08-24\",\n \"canonical\": \"http://phenoml.com/fhir/StructureDefinition/custom-patient|1.0.0\",\n \"fhir_version\": \"4.0.1\",\n \"implementation_guide\": \"acme-cardiology\",\n \"created_at\": \"2026-08-24T15:04:05Z\",\n \"updated_at\": \"2026-08-25T16:04:05Z\"\n}",
+ "headers": {
+ "Content-Type": "application/json"
+ }
+ },
+ "uuid": "75dac82d-0cf5-471e-9221-a2bc87f07808",
+ "persistent": true,
+ "priority": 3,
+ "metadata": {
+ "mocklab": {
+ "created": {
+ "at": "2020-01-01T00:00:00.000Z",
+ "via": "SYSTEM"
+ }
+ }
+ }
+ },
+ {
+ "id": "f7707266-d3fb-4351-a61c-53398a9631a3",
+ "name": "Get a retained custom FHIR profile version - default",
+ "request": {
+ "urlPathTemplate": "/fhir/profiles/{id}/versions/{version}",
+ "method": "GET",
+ "pathParameters": {
+ "id": {
+ "equalTo": "custom-patient"
+ },
+ "version": {
+ "equalTo": "2.0.0"
+ }
+ }
+ },
+ "response": {
+ "status": 200,
+ "body": "{\n \"id\": \"custom-patient\",\n \"source\": \"custom\",\n \"resource_type\": \"Patient\",\n \"url\": \"http://phenoml.com/fhir/StructureDefinition/custom-patient\",\n \"version\": \"1.0.0\",\n \"status\": \"active\",\n \"date\": \"2026-08-24\",\n \"canonical\": \"http://phenoml.com/fhir/StructureDefinition/custom-patient|1.0.0\",\n \"fhir_version\": \"4.0.1\",\n \"implementation_guide\": \"acme-cardiology\",\n \"created_at\": \"2026-08-24T15:04:05Z\",\n \"updated_at\": \"2026-08-25T16:04:05Z\",\n \"structure_definition\": {\n \"resourceType\": \"StructureDefinition\",\n \"id\": \"custom-patient\",\n \"url\": \"http://phenoml.com/fhir/StructureDefinition/custom-patient\",\n \"version\": \"1.0.0\",\n \"name\": \"CustomPatient\",\n \"status\": \"active\",\n \"fhirVersion\": \"4.0.1\",\n \"kind\": \"resource\",\n \"abstract\": false,\n \"type\": \"Patient\",\n \"baseDefinition\": \"http://hl7.org/fhir/StructureDefinition/Patient\",\n \"derivation\": \"constraint\",\n \"snapshot\": {\n \"element\": [\n {\n \"id\": \"Patient\",\n \"path\": \"Patient\",\n \"min\": 0,\n \"max\": \"*\"\n }\n ]\n }\n }\n}",
+ "headers": {
+ "Content-Type": "application/json"
+ }
+ },
+ "uuid": "f7707266-d3fb-4351-a61c-53398a9631a3",
+ "persistent": true,
+ "priority": 3,
+ "metadata": {
+ "mocklab": {
+ "created": {
+ "at": "2020-01-01T00:00:00.000Z",
+ "via": "SYSTEM"
+ }
+ }
+ }
+ },
+ {
+ "id": "6a0a59a4-4cb6-4ff9-be29-71b9c876d086",
+ "name": "Delete a retained custom FHIR profile version - default",
+ "request": {
+ "urlPathTemplate": "/fhir/profiles/{id}/versions/{version}",
+ "method": "DELETE",
+ "pathParameters": {
+ "id": {
+ "equalTo": "custom-patient"
+ },
+ "version": {
+ "equalTo": "2.0.0"
+ }
+ }
+ },
+ "response": {
+ "status": 200,
+ "body": "\"\"",
+ "headers": {
+ "Content-Type": "application/json"
+ }
+ },
+ "uuid": "6a0a59a4-4cb6-4ff9-be29-71b9c876d086",
+ "persistent": true,
+ "priority": 3,
+ "metadata": {
+ "mocklab": {
+ "created": {
+ "at": "2020-01-01T00:00:00.000Z",
+ "via": "SYSTEM"
+ }
+ }
+ }
+ },
{
"id": "6630f5de-1d59-422e-8056-7adb27f0b508",
"name": "List all summary templates - default",
@@ -2479,6 +2844,6 @@
}
],
"meta": {
- "total": 82
+ "total": 93
}
}
\ No newline at end of file
From 0ea42d4b5c940a2d56d221ac73b2bd69fcaf9986 Mon Sep 17 00:00:00 2001
From: "fern-api[bot]" <115122769+fern-api[bot]@users.noreply.github.com>
Date: Fri, 4 Sep 2026 16:36:23 +0000
Subject: [PATCH 2/4] [fern-autoversion] feat!: promote required fields on
ProfileSummary and add profile versioning
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Several previously-optional fields on `ProfileSummary` and related types
are now required (non-Optional), a new `versions` sub-client and
`ConflictError` are introduced, and the top-level `lang2fhir_batch`
module is exported. The field-type promotions are breaking for callers
that construct these models directly or rely on `Optional` typing.
Key changes:
- `ProfileSummary` fields (`id`, `source`, `resource_type`, `url`, `version`, `fhir_version`, `implementation_guide`, `created_at`, `updated_at`) promoted from `Optional` to required; new fields `status`, `date`, and `canonical` added
- `ProfileGetResponse.structure_definition` promoted from `Optional[FhirResource]` to required `FhirResource`; `ProfileListResponse.profiles` promoted from `Optional[List[ProfileSummary]]` to required `List[ProfileSummary]`
- New `versions` sub-client (`ProfileVersionCreateRequest`, `ProfileVersionListResponse`) added under `phenoml.profiles` for managing retained profile versions
- New `ConflictError` (HTTP 409) raised by `profiles.profiles.update()` when a version conflict is detected
- New `lang2fhir_batch` module exported from the top-level `phenoml` package
🌿 Generated with Fern
---
.fern/metadata.json | 2 +-
.fern/replay.lock | 2 +-
changelog.md | 13 +++++++++++++
pyproject.toml | 2 +-
src/phenoml/core/client_wrapper.py | 2 +-
5 files changed, 17 insertions(+), 4 deletions(-)
diff --git a/.fern/metadata.json b/.fern/metadata.json
index 1241008..36e9833 100644
--- a/.fern/metadata.json
+++ b/.fern/metadata.json
@@ -13,5 +13,5 @@
"invokedBy": "ci",
"requestedVersion": "AUTO",
"ciProvider": "unknown",
- "sdkVersion": "0.0.0.dev0"
+ "sdkVersion": "17.0.0"
}
\ No newline at end of file
diff --git a/.fern/replay.lock b/.fern/replay.lock
index 3238037..92be38d 100644
--- a/.fern/replay.lock
+++ b/.fern/replay.lock
@@ -133,7 +133,7 @@ patches:
[tool.poetry]
name = "phenoml"
- -version = "0.0.0.dev0"
+ -version = "17.0.0"
+version = "16.11.0"
description = ""
readme = "README.md"
diff --git a/changelog.md b/changelog.md
index b68487d..26c9165 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,3 +1,16 @@
+## [17.0.0] - 2026-09-04
+### Breaking Changes
+- **`ProfileSummary`** — fields `id`, `source`, `resource_type`, `url`, `version`, `fhir_version`, `implementation_guide`, `created_at`, and `updated_at` are now required (non-`Optional`); update all construction sites to supply every field and remove any `None`-guard branches.
+- **`ProfileGetResponse.structure_definition`** — promoted from `Optional[FhirResource]` to a required `FhirResource`; remove any `None` guard around this field.
+- **`ProfileListResponse.profiles`** — promoted from `Optional[List[ProfileSummary]]` to a required `List[ProfileSummary]`; remove any `None` guard around this field.
+
+### Added
+- **`client.lang2fhir_batch`** — new sync and async sub-client (`Lang2FhirBatchClient` / `RawLang2FhirBatchClient`) for managing asynchronous bulk FHIR extraction jobs via `/lang2fhir/batch`; exposes `create`, `upload_item`, `finalize`, `list`, `get`, `get_results`, and `get_result` methods.
+- **`phenoml.lang2fhir_batch` models and errors** — new Pydantic response models (`BatchJob`, `JobListResponse`, `JobDetailResponse`, `ResultsPageResponse`, `UploadItemResponse`, and related status types) and eight typed `ApiError` subclasses (e.g., `ConflictError`, `ContentTooLargeError`, `GatewayTimeoutError`) exported from `phenoml.lang2fhir_batch`.
+- **`client.profiles.versions`** — new sync and async sub-client for managing immutable StructureDefinition versions on custom profiles; exposes `list`, `create`, `get`, and `delete` methods backed by `ProfileVersionListResponse` and `ProfileVersionCreateRequest`.
+- **`ProfileSummary.status`, `.date`, and `.canonical`** — new optional fields surfacing StructureDefinition publication status, authored date, and canonical version-pinned reference.
+- **`ConflictError` (profiles)** — new HTTP 409 `ApiError` subclass raised by `client.profiles.profiles.update()` and the profile versions endpoints on version conflicts; `lang2fhir_batch` module is now exported from the top-level `phenoml` package.
+
## [16.11.0] - 2026-08-26
### Added
- **`PatientReference`** — new Pydantic model with `system` and `value` fields representing a business identifier for an existing patient; exported from `phenoml.lang2fhir`.
diff --git a/pyproject.toml b/pyproject.toml
index cea8b20..1de13f3 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@ dynamic = ["version"]
[tool.poetry]
name = "phenoml"
-version = "0.0.0.dev0"
+version = "17.0.0"
description = ""
readme = "README.md"
authors = []
diff --git a/src/phenoml/core/client_wrapper.py b/src/phenoml/core/client_wrapper.py
index c03a243..2679d1b 100644
--- a/src/phenoml/core/client_wrapper.py
+++ b/src/phenoml/core/client_wrapper.py
@@ -38,7 +38,7 @@ def get_headers(self) -> typing.Dict[str, str]:
"X-Fern-Runtime": f"python/{platform.python_version()}",
"X-Fern-Platform": f"{platform.system().lower()}/{platform.release()}",
"X-Fern-SDK-Name": "phenoml",
- "X-Fern-SDK-Version": "0.0.0.dev0",
+ "X-Fern-SDK-Version": "17.0.0",
**(self.get_custom_headers() or {}),
}
token = self._get_token()
From 424a9e967c19f9b450754d06ba7b99f50231f798 Mon Sep 17 00:00:00 2001
From: "fern-api[bot]" <115122769+fern-api[bot]@users.noreply.github.com>
Date: Fri, 4 Sep 2026 16:36:24 +0000
Subject: [PATCH 3/4] [fern-replay] Applied customizations
Patches applied (1):
- patch-6516695e: Release 15.0.2: restore bundled openapi.json packaging (#169)
---
.fern/replay.lock | 20 +++++++++++++-------
pyproject.toml | 3 +++
2 files changed, 16 insertions(+), 7 deletions(-)
diff --git a/.fern/replay.lock b/.fern/replay.lock
index 92be38d..3f5e899 100644
--- a/.fern/replay.lock
+++ b/.fern/replay.lock
@@ -114,27 +114,33 @@ generations:
cli_version: unknown
generator_versions:
fernapi/fern-python-sdk: 5.18.1
-current_generation: d943cc568f9ebc7cdbd970a49c9584c685af4816
+ - commit_sha: e83d7ca09a7944b6c0248754f21a6ba60fa4629d
+ tree_hash: 8309b4a27dd63367987407eb1c8b980aa723393a
+ timestamp: 2026-09-04T16:35:29.404Z
+ cli_version: unknown
+ generator_versions:
+ fernapi/fern-python-sdk: 5.18.1
+current_generation: e83d7ca09a7944b6c0248754f21a6ba60fa4629d
patches:
- id: patch-6516695e
- content_hash: sha256:702e13b522b447abbe0ea94480170f9a906fdab2dbfacd3466b1e92277d4d881
+ content_hash: sha256:31f251d4e496ddda9f8277bd39de658824674539e8bba0081e495626fa0165e1
original_commit: 6516695ecaba47ae4bcc8119acca86a1113adeeb
original_message: "Release 15.0.2: restore bundled openapi.json packaging (#169)"
original_author: Gavin Sharp
- base_generation: d943cc568f9ebc7cdbd970a49c9584c685af4816
+ base_generation: e83d7ca09a7944b6c0248754f21a6ba60fa4629d
files:
- pyproject.toml
patch_content: |
diff --git a/pyproject.toml b/pyproject.toml
- index cea8b20..f599fde 100644
+ index cea8b20..17a6e91 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@ dynamic = ["version"]
[tool.poetry]
name = "phenoml"
- -version = "17.0.0"
- +version = "16.11.0"
+ -version = "0.0.0.dev0"
+ +version = "17.0.0"
description = ""
readme = "README.md"
authors = []
@@ -156,7 +162,7 @@ patches:
[tool.poetry]
name = "phenoml"
- version = "16.11.0"
+ version = "17.0.0"
description = ""
readme = "README.md"
authors = []
diff --git a/pyproject.toml b/pyproject.toml
index 1de13f3..17a6e91 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -31,6 +31,9 @@ classifiers = [
packages = [
{ include = "phenoml", from = "src"}
]
+include = [
+ { path = "src/phenoml/openapi/openapi.json", format = ["sdist", "wheel"] }
+]
[tool.poetry.urls]
Repository = 'https://github.com/phenoml/phenoml-python-sdk'
From 97209989dbcfe4d7ff99550794df9566a1d9d53d Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Fri, 4 Sep 2026 16:36:38 +0000
Subject: [PATCH 4/4] chore: sync OpenAPI spec + code-examples for
e2cfa711e7e5ad04621efc35d12dcfd7d060040d [skip ci]
---
code-examples.json | 291 +++++-
src/phenoml/openapi/openapi.json | 1447 +++++++++++++++++++++++++++---
2 files changed, 1625 insertions(+), 113 deletions(-)
diff --git a/code-examples.json b/code-examples.json
index 587ed6e..b21e89b 100644
--- a/code-examples.json
+++ b/code-examples.json
@@ -2,8 +2,8 @@
"metadata": {
"language": "python",
"packageName": "phenoml",
- "sdkVersion": "16.11.0",
- "specCommit": "4a08550f5db230949c7423d0ce5aa7055e8f0d65",
+ "sdkVersion": "17.0.0",
+ "specCommit": "e2cfa711e7e5ad04621efc35d12dcfd7d060040d",
"generatorName": "fernapi/fern-python-sdk"
},
"renderRules": {
@@ -2685,6 +2685,197 @@
}
}
},
+ "POST /lang2fhir/batch": {
+ "httpMethod": "POST",
+ "httpPath": "/lang2fhir/batch",
+ "request": {
+ "body": {
+ "request_id": "submit-2025-09-02-batch-001"
+ }
+ },
+ "response": {
+ "body": null
+ },
+ "render": {
+ "callTemplate": "client.lang2fhir_batch.create({{__body__}})",
+ "params": [],
+ "body": {
+ "fieldSeparator": ", ",
+ "fields": [
+ {
+ "jsonKey": "request_id",
+ "fieldTemplate": "request_id={{value}}",
+ "kind": "string",
+ "required": false
+ }
+ ]
+ }
+ }
+ },
+ "GET /lang2fhir/batch": {
+ "httpMethod": "GET",
+ "httpPath": "/lang2fhir/batch",
+ "request": {
+ "body": null
+ },
+ "response": {
+ "body": null
+ },
+ "render": {
+ "callTemplate": "client.lang2fhir_batch.list({{__body__}})",
+ "params": [],
+ "body": {
+ "fieldSeparator": ", ",
+ "fields": [
+ {
+ "jsonKey": "cursor",
+ "fieldTemplate": "cursor={{value}}",
+ "kind": "string",
+ "required": false
+ },
+ {
+ "jsonKey": "limit",
+ "fieldTemplate": "limit={{value}}",
+ "kind": "number",
+ "required": false
+ }
+ ]
+ }
+ }
+ },
+ "POST /lang2fhir/batch/{job_id}/items": {
+ "httpMethod": "POST",
+ "httpPath": "/lang2fhir/batch/{job_id}/items",
+ "request": {
+ "body": null
+ },
+ "response": {
+ "body": null
+ },
+ "render": {
+ "callTemplate": "client.lang2fhir_batch.upload_item(job_id={{job_id}})",
+ "params": [
+ {
+ "name": "job_id",
+ "kind": "string"
+ }
+ ]
+ }
+ },
+ "POST /lang2fhir/batch/{job_id}/finalize": {
+ "httpMethod": "POST",
+ "httpPath": "/lang2fhir/batch/{job_id}/finalize",
+ "request": {
+ "body": null
+ },
+ "response": {
+ "body": null
+ },
+ "render": {
+ "callTemplate": "client.lang2fhir_batch.finalize(job_id={{job_id}})",
+ "params": [
+ {
+ "name": "job_id",
+ "kind": "string"
+ }
+ ]
+ }
+ },
+ "GET /lang2fhir/batch/{job_id}": {
+ "httpMethod": "GET",
+ "httpPath": "/lang2fhir/batch/{job_id}",
+ "request": {
+ "body": null
+ },
+ "response": {
+ "body": null
+ },
+ "render": {
+ "callTemplate": "client.lang2fhir_batch.get(job_id={{job_id}}, {{__body__}})",
+ "params": [
+ {
+ "name": "job_id",
+ "kind": "string"
+ }
+ ],
+ "body": {
+ "fieldSeparator": ", ",
+ "fields": [
+ {
+ "jsonKey": "cursor",
+ "fieldTemplate": "cursor={{value}}",
+ "kind": "string",
+ "required": false
+ },
+ {
+ "jsonKey": "limit",
+ "fieldTemplate": "limit={{value}}",
+ "kind": "number",
+ "required": false
+ }
+ ]
+ }
+ }
+ },
+ "GET /lang2fhir/batch/{job_id}/results": {
+ "httpMethod": "GET",
+ "httpPath": "/lang2fhir/batch/{job_id}/results",
+ "request": {
+ "body": null
+ },
+ "response": {
+ "body": null
+ },
+ "render": {
+ "callTemplate": "client.lang2fhir_batch.get_results(job_id={{job_id}}, {{__body__}})",
+ "params": [
+ {
+ "name": "job_id",
+ "kind": "string"
+ }
+ ],
+ "body": {
+ "fieldSeparator": ", ",
+ "fields": [
+ {
+ "jsonKey": "cursor",
+ "fieldTemplate": "cursor={{value}}",
+ "kind": "string",
+ "required": false
+ },
+ {
+ "jsonKey": "limit",
+ "fieldTemplate": "limit={{value}}",
+ "kind": "number",
+ "required": false
+ }
+ ]
+ }
+ }
+ },
+ "GET /lang2fhir/batch/{job_id}/results/{item_id}": {
+ "httpMethod": "GET",
+ "httpPath": "/lang2fhir/batch/{job_id}/results/{item_id}",
+ "request": {
+ "body": null
+ },
+ "response": {
+ "body": null
+ },
+ "render": {
+ "callTemplate": "client.lang2fhir_batch.get_result(job_id={{job_id}}, item_id={{item_id}})",
+ "params": [
+ {
+ "name": "job_id",
+ "kind": "string"
+ },
+ {
+ "name": "item_id",
+ "kind": "string"
+ }
+ ]
+ }
+ },
"POST /cohort": {
"httpMethod": "POST",
"httpPath": "/cohort",
@@ -3877,6 +4068,102 @@
]
}
},
+ "GET /fhir/profiles/{id}/versions": {
+ "httpMethod": "GET",
+ "httpPath": "/fhir/profiles/{id}/versions",
+ "request": {
+ "body": null
+ },
+ "response": {
+ "body": null
+ },
+ "render": {
+ "callTemplate": "client.profiles.versions.list(id={{id}})",
+ "params": [
+ {
+ "name": "id",
+ "kind": "string"
+ }
+ ]
+ }
+ },
+ "POST /fhir/profiles/{id}/versions": {
+ "httpMethod": "POST",
+ "httpPath": "/fhir/profiles/{id}/versions",
+ "request": {
+ "body": null
+ },
+ "response": {
+ "body": null
+ },
+ "render": {
+ "callTemplate": "client.profiles.versions.create(id={{id}}, request={{__body__}})",
+ "params": [
+ {
+ "name": "id",
+ "kind": "string"
+ }
+ ],
+ "body": {
+ "fieldSeparator": ", ",
+ "fields": [
+ {
+ "jsonKey": "",
+ "fieldTemplate": "{{value}}",
+ "kind": "object",
+ "required": true,
+ "passthroughBody": true
+ }
+ ]
+ }
+ }
+ },
+ "GET /fhir/profiles/{id}/versions/{version}": {
+ "httpMethod": "GET",
+ "httpPath": "/fhir/profiles/{id}/versions/{version}",
+ "request": {
+ "body": null
+ },
+ "response": {
+ "body": null
+ },
+ "render": {
+ "callTemplate": "client.profiles.versions.get(id={{id}}, version={{version}})",
+ "params": [
+ {
+ "name": "id",
+ "kind": "string"
+ },
+ {
+ "name": "version",
+ "kind": "string"
+ }
+ ]
+ }
+ },
+ "DELETE /fhir/profiles/{id}/versions/{version}": {
+ "httpMethod": "DELETE",
+ "httpPath": "/fhir/profiles/{id}/versions/{version}",
+ "request": {
+ "body": null
+ },
+ "response": {
+ "body": null
+ },
+ "render": {
+ "callTemplate": "client.profiles.versions.delete(id={{id}}, version={{version}})",
+ "params": [
+ {
+ "name": "id",
+ "kind": "string"
+ },
+ {
+ "name": "version",
+ "kind": "string"
+ }
+ ]
+ }
+ },
"POST /tools/lang2fhir-and-create": {
"httpMethod": "POST",
"httpPath": "/tools/lang2fhir-and-create",
diff --git a/src/phenoml/openapi/openapi.json b/src/phenoml/openapi/openapi.json
index 5e7bdf7..a1d4afd 100644
--- a/src/phenoml/openapi/openapi.json
+++ b/src/phenoml/openapi/openapi.json
@@ -2,7 +2,7 @@
"openapi": "3.0.3",
"info": {
"title": "Phenoml API",
- "version": "4a08550f5db230949c7423d0ce5aa7055e8f0d65"
+ "version": "e2cfa711e7e5ad04621efc35d12dcfd7d060040d"
},
"x-services": [
{
@@ -4582,6 +4582,491 @@
"x-service": "fhir"
}
},
+ "/lang2fhir/batch": {
+ "post": {
+ "operationId": "batch_create",
+ "summary": "Create a batch job",
+ "description": "Opens an empty batch job. Items arrive on later upload calls and the set\nis sealed at finalize.\n\nSupplying `request_id` makes the create idempotent on that token: a\nretried submit whose response was lost returns the original job rather\nthan opening a second one. This dedupe is scoped to the calling\ncredential.\n\nAn instance may hold at most 4 active (pending or processing) jobs at\nonce; a create past that limit returns `409`. The limit is instance-wide\n\u2014 jobs are shared across the instance's credentials \u2014 so another\ncredential's jobs count against it.\n",
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/batch_CreateBatchRequest"
+ },
+ "example": {
+ "request_id": "submit-2025-09-02-batch-001"
+ }
+ }
+ }
+ },
+ "responses": {
+ "202": {
+ "description": "Batch job created (or an idempotent replay of an existing job)",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/batch_BatchJob"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Invalid request (e.g. request_id over its 256-byte limit)"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "409": {
+ "description": "The instance is at its 4-job active-batch limit; wait for one to finish"
+ },
+ "499": {
+ "description": "Client closed request before response was ready"
+ },
+ "500": {
+ "description": "Server error"
+ },
+ "504": {
+ "description": "Request timed out"
+ }
+ },
+ "security": [
+ {
+ "bearerAuth": []
+ }
+ ],
+ "x-service": "lang2fhir"
+ },
+ "get": {
+ "operationId": "batch_list",
+ "summary": "List batch jobs",
+ "description": "Returns a page of the instance's batch jobs, newest first, without\nper-job counts. Jobs are shared across the instance's credentials, so\nthis lists every batch job on the instance, not just the calling\ncredential's.\n",
+ "parameters": [
+ {
+ "name": "cursor",
+ "in": "query",
+ "required": false,
+ "description": "Opaque pagination cursor from a previous page's next_cursor.",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "limit",
+ "in": "query",
+ "required": false,
+ "description": "Page size. Defaults to 20; values above 100 are clamped to 100.",
+ "schema": {
+ "type": "integer",
+ "default": 20,
+ "maximum": 100
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "A page of batch jobs",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/batch_JobListResponse"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Invalid request (e.g. non-integer limit or malformed cursor)"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "499": {
+ "description": "Client closed request before response was ready"
+ },
+ "500": {
+ "description": "Server error"
+ },
+ "504": {
+ "description": "Request timed out"
+ }
+ },
+ "security": [
+ {
+ "bearerAuth": []
+ }
+ ],
+ "x-service": "lang2fhir"
+ }
+ },
+ "/lang2fhir/batch/{job_id}/items": {
+ "post": {
+ "operationId": "batch_uploadItem",
+ "summary": "Upload one batch item",
+ "description": "Stores one item of a job from a multipart upload. A batch's items arrive\none per request. The item carries **either** a `document` extraction\n(whose input file rides as raw bytes in the `file` part) **or** a\n`create` extraction (JSON only, no file).\n\nThe upload enforces these rules:\n- Set **exactly one** of `document` or `create`. Setting both, or\n neither, is a `400`.\n- When `document` is set, `file` is **required** \u2014 it supplies the\n document's binary content (PDF or image).\n- When `create` is set, `file` is **forbidden** \u2014 a create item carries\n no file.\n- `document` and `create` must each be a JSON **object**.\n\nOnly the item's structure is checked here: the fields inside `document`\nor `create` are not validated at upload. A body that is well-formed JSON\nbut not a valid request for its endpoint is still accepted with `202`\nand fails later during processing, recorded as an item `error`. A\nwrong-typed field the endpoint cannot decode fails as `invalid_input`; a\nbody that decodes but the pipeline rejects (for example, a missing\nrequired field) fails as `processing_failed`.\n\nSupplying `request_id` makes the upload idempotent on that token. A\nre-upload under the same token overwrites the same item rather than\nadding a second, so a client that lost an upload's response can safely\nre-send it. The response's `deduplicated` is `true` only when the\nre-uploaded payload matches the one already stored; a same-token upload\nwith a changed payload overwrites in place and returns `false`.\n\nSet a `request_id` on **every** upload: re-sending under the same token\nis the only way to repair a lost or incomplete upload, including the one\na finalize `409` reports. Without one, a re-send adds a new item instead\nof replacing the missing one, and the job cannot be finalized.\n\nUploads are rejected once the job has been finalized (`409`), once it\nholds its 500-item limit (`409`), or when the item is too large (`413` \u2014\nsee the raw-file limit in the API description).\n",
+ "parameters": [
+ {
+ "name": "job_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "multipart/form-data": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "document": {
+ "type": "object",
+ "description": "The JSON body of `POST /lang2fhir/document/multi`, **without**\nits base64 `content` field \u2014 the uploaded `file` supplies the\ncontent. Accepts that endpoint's fields (`version`, `provider`,\n`patient_reference`, `implementation_guide`, `detection_effort`,\n`validation_method`, `config`). This is the **multi**-resource\nbody: it has no single-`resource` field, and the item's result\nis a `DocumentMultiResponse` (a Bundle of resources). Mutually\nexclusive with `create`; requires `file`.\n"
+ },
+ "create": {
+ "type": "object",
+ "description": "The JSON body of `POST /lang2fhir/create/multi`. Accepts that\nendpoint's fields (`text`, `version`, `provider`,\n`patient_reference`, `implementation_guide`, `detection_effort`,\n`validation_method`, `resource_review`). This is the\n**multi**-resource body: it has no single-`resource` field, and\nthe item's result is a `CreateMultiResponse` (a Bundle of\nresources). Mutually exclusive with `document`; must **not** be\naccompanied by a `file`.\n"
+ },
+ "file": {
+ "type": "string",
+ "format": "binary",
+ "description": "The document's binary content (PDF, PNG, JPEG, or TIFF).\nRequired with `document`; forbidden with `create`.\n"
+ },
+ "request_id": {
+ "type": "string",
+ "maxLength": 256,
+ "description": "Optional idempotency token (max 256 bytes). Re-uploading under\nthe same token overwrites the same item instead of adding a\nnew one. The token is scoped to this job; the same token in\nanother job is independent and creates a separate item.\n"
+ },
+ "id": {
+ "type": "string",
+ "maxLength": 512,
+ "description": "Optional caller-supplied correlation label (max 512 bytes),\nechoed back on status and result listings so you can match the\nserver's item_id to your own record.\n"
+ }
+ }
+ },
+ "encoding": {
+ "document": {
+ "contentType": "application/json"
+ },
+ "create": {
+ "contentType": "application/json"
+ }
+ }
+ }
+ }
+ },
+ "responses": {
+ "202": {
+ "description": "Item accepted",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/batch_UploadItemResponse"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Invalid upload \u2014 e.g. both or neither of document/create set, a\nnon-object document/create, a document item missing its file, a\ncreate item carrying a file, or an over-length request_id/id.\n"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "404": {
+ "description": "Batch job not found"
+ },
+ "409": {
+ "description": "The job is finalized (no longer accepting items) or holds its 500-item limit"
+ },
+ "413": {
+ "description": "The upload is too large. Both size trips return 413: the request body\nexceeding the 32 MiB upload cap, and the item's stored payload\nexceeding the 28 MiB per-item limit (~21 MiB of raw file once\nbase64-encoded).\n"
+ },
+ "499": {
+ "description": "Client closed request before response was ready"
+ },
+ "500": {
+ "description": "Server error"
+ },
+ "504": {
+ "description": "Request timed out"
+ }
+ },
+ "security": [
+ {
+ "bearerAuth": []
+ }
+ ],
+ "x-service": "lang2fhir"
+ }
+ },
+ "/lang2fhir/batch/{job_id}/finalize": {
+ "post": {
+ "operationId": "batch_finalize",
+ "summary": "Finalize a batch job",
+ "description": "Seals the job's item set and starts processing. Takes no request body.\nFinalize is idempotent: a retried finalize succeeds again.\n\nIf a previous upload did not complete, finalize returns a `409`; re-send\nthe missing upload (with the same `request_id`), then finalize.\nFinalizing a job with no items is a `400`.\n",
+ "parameters": [
+ {
+ "name": "job_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "202": {
+ "description": "Job finalized and processing started",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/batch_BatchJob"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "The batch has no items to finalize"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "404": {
+ "description": "Batch job not found"
+ },
+ "409": {
+ "description": "A previous upload did not complete; re-send it with the same request_id, then finalize"
+ },
+ "499": {
+ "description": "Client closed request before response was ready"
+ },
+ "500": {
+ "description": "Server error"
+ },
+ "504": {
+ "description": "Request timed out"
+ }
+ },
+ "security": [
+ {
+ "bearerAuth": []
+ }
+ ],
+ "x-service": "lang2fhir"
+ }
+ },
+ "/lang2fhir/batch/{job_id}": {
+ "get": {
+ "operationId": "batch_get",
+ "summary": "Get a batch job",
+ "description": "Returns a job's record, its per-status item counts, and one page of\nper-item statuses.\n\nItems are listed in a stable order that is not upload order and is the\nsame across pages. Match each entry to your own records by its `id`\n(your correlation label) or `item_id` (from the upload response),\nnever by position.\n",
+ "parameters": [
+ {
+ "name": "job_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "cursor",
+ "in": "query",
+ "required": false,
+ "description": "Opaque pagination cursor from a previous page's next_cursor.",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "limit",
+ "in": "query",
+ "required": false,
+ "description": "Page size for the item-status page. Defaults to 20; values above 100 are clamped to 100.",
+ "schema": {
+ "type": "integer",
+ "default": 20,
+ "maximum": 100
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "The job record, counts, and a page of item statuses",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/batch_JobDetailResponse"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Invalid request (e.g. non-integer limit or malformed cursor)"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "404": {
+ "description": "Batch job not found"
+ },
+ "499": {
+ "description": "Client closed request before response was ready"
+ },
+ "500": {
+ "description": "Server error"
+ },
+ "504": {
+ "description": "Request timed out"
+ }
+ },
+ "security": [
+ {
+ "bearerAuth": []
+ }
+ ],
+ "x-service": "lang2fhir"
+ }
+ },
+ "/lang2fhir/batch/{job_id}/results": {
+ "get": {
+ "operationId": "batch_getResults",
+ "summary": "List a batch job's results",
+ "description": "A lighter status page. Returns the same per-item status entries as\n`GET /lang2fhir/batch/{job_id}`, but without the job record or counts,\nand the entries carry `result_size` rather than any result content. Use\neach entry's `item_id` to fetch that item's result from\n`GET /lang2fhir/batch/{job_id}/results/{item_id}`.\n\nEntries are listed in a stable order that is not upload order and is\nthe same across pages. Match each entry to your own records by its `id`\n(your correlation label) or `item_id` (from the upload response),\nnever by position.\n",
+ "parameters": [
+ {
+ "name": "job_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "cursor",
+ "in": "query",
+ "required": false,
+ "description": "Opaque pagination cursor from a previous page's next_cursor.",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "limit",
+ "in": "query",
+ "required": false,
+ "description": "Page size. Defaults to 20; values above 100 are clamped to 100.",
+ "schema": {
+ "type": "integer",
+ "default": 20,
+ "maximum": 100
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "A page of per-item result statuses",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/batch_ResultsPageResponse"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Invalid request (e.g. non-integer limit or malformed cursor)"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "404": {
+ "description": "Batch job not found"
+ },
+ "499": {
+ "description": "Client closed request before response was ready"
+ },
+ "500": {
+ "description": "Server error"
+ },
+ "504": {
+ "description": "Request timed out"
+ }
+ },
+ "security": [
+ {
+ "bearerAuth": []
+ }
+ ],
+ "x-service": "lang2fhir"
+ }
+ },
+ "/lang2fhir/batch/{job_id}/results/{item_id}": {
+ "get": {
+ "operationId": "batch_getResult",
+ "summary": "Get one batch item's result",
+ "description": "Streams one item's stored result bytes verbatim as `application/json`.\nThe body is the response the item's synchronous multi endpoint would have\nreturned \u2014 a `DocumentMultiResponse` for a document item or a\n`CreateMultiResponse` for a create item.\n\nOnly a succeeded item has a result: an item that has not succeeded\n(pending, processing, or failed) is a `409`, and a result that has\nexpired is a `404`.\n",
+ "parameters": [
+ {
+ "name": "job_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "item_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "The item's raw result JSON",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "description": "The item's extraction result \u2014 a DocumentMultiResponse (document\nitem) or CreateMultiResponse (create item).\n"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Invalid request (e.g. empty item_id)"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "404": {
+ "description": "Batch job or item not found, or the result has expired"
+ },
+ "409": {
+ "description": "The item has not succeeded, so it has no result to serve"
+ },
+ "499": {
+ "description": "Client closed request before response was ready"
+ },
+ "500": {
+ "description": "Server error"
+ },
+ "504": {
+ "description": "Request timed out"
+ }
+ },
+ "security": [
+ {
+ "bearerAuth": []
+ }
+ ],
+ "x-service": "lang2fhir"
+ }
+ },
"/cohort": {
"post": {
"operationId": "cohort_analyze",
@@ -4787,7 +5272,7 @@
"description": "Profile not found"
},
"422": {
- "description": "Generated FHIR resource failed validation"
+ "description": "Generated FHIR resource failed validation or terminology constraints"
},
"500": {
"description": "Server error"
@@ -4907,7 +5392,7 @@
"description": "Profile not found"
},
"422": {
- "description": "Generated FHIR resource failed validation"
+ "description": "Generated FHIR resource failed validation or terminology constraints"
},
"500": {
"description": "Server error"
@@ -6073,13 +6558,13 @@
"FHIR Artifacts / Profiles"
],
"summary": "List custom FHIR profiles",
- "description": "Returns metadata for every custom (uploaded) FHIR profile on this\ninstance, across all implementation guides. The full StructureDefinition\nJSON is omitted from each entry; fetch a single profile by id to retrieve it.\n\nThe `url` query parameter filters by canonical URL. The canonical URL is the\nstable key other platform features use to reference a profile (FHIR's\n`meta.profile`, `baseDefinition`), since StructureDefinition ids are only\nunique within a package. A non-matching filter returns an empty list, not a 404.\n",
+ "description": "Returns metadata for every custom (uploaded) FHIR profile on this\ninstance, across all implementation guides. The full StructureDefinition\nJSON is omitted from each entry; fetch a single profile by id to retrieve it.\n\nThe `url` query parameter filters by canonical URL. The canonical URL is the\nstable key other platform features use to reference a profile (FHIR's\n`meta.profile`, `baseDefinition`), since StructureDefinition ids are only\nunique within a package. An unpinned `url` filter returns metadata for\nthe profile's current StructureDefinition. Pinned `url|version` filters\nresolve a retained version when present; otherwise they can fall back to\nthe profile's current StructureDefinition, whose content can change\nthrough the profile update endpoint. A non-matching filter returns an\nempty list, not a 404.\n",
"parameters": [
{
"name": "url",
"in": "query",
"required": false,
- "description": "Filter by canonical URL. Accepts the FHIR pinned form `url|version` (split on the last `|`); the bare form matches the current version.\n",
+ "description": "Filter by canonical URL. Accepts the FHIR pinned form `url|version`; without a version pin, returns the profile's current StructureDefinition metadata.\n",
"schema": {
"type": "string"
},
@@ -6120,7 +6605,7 @@
"FHIR Artifacts / Profiles"
],
"summary": "Upload a custom FHIR profile",
- "description": "Creates a custom profile from a FHIR StructureDefinition supplied as a JSON\nobject. Metadata such as version, resource type, and url is read from the\nStructureDefinition; the lowercase StructureDefinition id becomes the\nprofile's lookup key. When id is omitted, a random UUID is assigned. Code\nsystem configuration is auto-extracted from the snapshot. Optionally group\nthe profile under a named implementation guide.\n",
+ "description": "Creates a custom profile from a FHIR StructureDefinition supplied as a JSON\nobject. Metadata such as version, resource type, and url is read from the\nStructureDefinition; the lowercase StructureDefinition id becomes the\nprofile's lookup key. When id is omitted, a random UUID is assigned.\nOptionally group the profile under a named implementation guide.\n",
"requestBody": {
"required": true,
"content": {
@@ -6143,7 +6628,147 @@
}
},
"400": {
- "description": "Invalid request or profile validation failed. Possible reasons:\n- Missing or invalid StructureDefinition JSON\n- The id collides with a built-in US Core or R4 base profile\n- A custom profile with the same id or url already exists\n"
+ "description": "Invalid request or profile validation failed. Possible reasons:\n- Missing or invalid StructureDefinition JSON\n- The id collides with a built-in US Core or R4 base profile\n- A custom profile with the same id or url already exists\n- The implementation_guide is reserved or invalid\n"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "403": {
+ "description": "Forbidden - custom profiles are only available on dedicated instances"
+ },
+ "500": {
+ "description": "Server error"
+ }
+ },
+ "security": [
+ {
+ "bearerAuth": []
+ }
+ ],
+ "x-service": "fhir_artifacts"
+ }
+ },
+ "/fhir/profiles/{id}": {
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "required": true,
+ "description": "The lowercase StructureDefinition id of the custom profile.",
+ "schema": {
+ "type": "string"
+ },
+ "example": "custom-patient"
+ }
+ ],
+ "get": {
+ "operationId": "profiles_get",
+ "tags": [
+ "FHIR Artifacts / Profiles"
+ ],
+ "summary": "Get a custom FHIR profile",
+ "description": "Returns a single custom profile by id, including its full StructureDefinition\nJSON.\n",
+ "responses": {
+ "200": {
+ "description": "The requested profile",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/profiles_ProfileGetResponse"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "The id is not a valid profile id"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "403": {
+ "description": "Forbidden - custom profiles are only available on dedicated instances"
+ },
+ "404": {
+ "description": "Profile not found"
+ },
+ "500": {
+ "description": "Server error"
+ }
+ },
+ "security": [
+ {
+ "bearerAuth": []
+ }
+ ],
+ "x-service": "fhir_artifacts"
+ },
+ "put": {
+ "operationId": "profiles_update",
+ "tags": [
+ "FHIR Artifacts / Profiles"
+ ],
+ "summary": "Update a custom FHIR profile",
+ "description": "Replaces an existing custom profile with a new StructureDefinition. The\n`id` path parameter is authoritative: if the StructureDefinition includes\nan `id` it must match the path parameter, and if it omits one the path\nparameter is used. The FHIR resource type of the profile cannot change.\nWhen `implementation_guide` is omitted, the profile keeps its existing\nimplementation guide. A retained version string is allowed only when\nre-submitting the profile's current version with an unchanged\nStructureDefinition; otherwise it returns a conflict. While the profile\nhas retained versions, its\ncanonical URL cannot be changed.\n",
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/profiles_ProfileUploadRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Profile successfully updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/profiles_ProfileSummary"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Invalid request or profile validation failed. Possible reasons:\n- Missing or invalid StructureDefinition JSON\n- The StructureDefinition id does not match the id path parameter\n- The resource type differs from the existing profile\n- The url collides with a built-in profile or another custom profile\n- The implementation_guide is reserved or invalid\n"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "403": {
+ "description": "Forbidden - custom profiles are only available on dedicated instances"
+ },
+ "404": {
+ "description": "Profile not found"
+ },
+ "409": {
+ "description": "Conflict. Possible reasons:\n- The requested StructureDefinition.version is already used by a retained version and this is not an unchanged re-submission of the profile's current version\n- The profile has retained versions and the request changes its canonical URL\n"
+ },
+ "500": {
+ "description": "Server error"
+ }
+ },
+ "security": [
+ {
+ "bearerAuth": []
+ }
+ ],
+ "x-service": "fhir_artifacts"
+ },
+ "delete": {
+ "operationId": "profiles_delete",
+ "tags": [
+ "FHIR Artifacts / Profiles"
+ ],
+ "summary": "Delete a custom FHIR profile",
+ "description": "Permanently deletes a custom profile by id. This also deletes all retained\nversions for that profile so the canonical URL can be reused by a later\nupload.\n",
+ "responses": {
+ "204": {
+ "description": "Profile successfully deleted"
+ },
+ "400": {
+ "description": "The id is not a valid profile id"
},
"401": {
"description": "Unauthorized"
@@ -6151,6 +6776,9 @@
"403": {
"description": "Forbidden - custom profiles are only available on dedicated instances"
},
+ "404": {
+ "description": "Profile not found"
+ },
"500": {
"description": "Server error"
}
@@ -6163,7 +6791,7 @@
"x-service": "fhir_artifacts"
}
},
- "/fhir/profiles/{id}": {
+ "/fhir/profiles/{id}/versions": {
"parameters": [
{
"name": "id",
@@ -6177,19 +6805,19 @@
}
],
"get": {
- "operationId": "profiles_get",
+ "operationId": "profiles_listVersions",
"tags": [
"FHIR Artifacts / Profiles"
],
- "summary": "Get a custom FHIR profile",
- "description": "Returns a single custom profile by id, including its full StructureDefinition JSON.\n",
+ "summary": "List versions for a custom FHIR profile",
+ "description": "Returns retained versions for a custom profile.\n",
"responses": {
"200": {
- "description": "The requested profile",
+ "description": "List of profile versions",
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/profiles_ProfileGetResponse"
+ "$ref": "#/components/schemas/profiles_ProfileVersionListResponse"
}
}
}
@@ -6217,26 +6845,35 @@
],
"x-service": "fhir_artifacts"
},
- "put": {
- "operationId": "profiles_update",
+ "post": {
+ "operationId": "profiles_createVersion",
"tags": [
"FHIR Artifacts / Profiles"
],
- "summary": "Update a custom FHIR profile",
- "description": "Replaces an existing custom profile with a new StructureDefinition. The\n`id` path parameter is authoritative: if the StructureDefinition includes\nan `id` it must match the path parameter, and if it omits one the path\nparameter is used. The FHIR resource type of the profile cannot change.\nCode system configuration is\nre-derived from the new StructureDefinition. When `implementation_guide` is\nomitted, the profile keeps its existing implementation guide. The instance\nstores a single version per canonical URL, so this replaces it in place.\n",
+ "summary": "Add a retained version to a custom FHIR profile",
+ "description": "Adds an immutable StructureDefinition version to a custom profile. If\nthe profile does not exist, it is created from the submitted version.\nThe StructureDefinition must include a non-empty `version`; its\ncanonical URL and resource type must match the profile when one already\nexists. If it includes an `id`, that id must match the path parameter;\nif it omits `id`, the path parameter is used. Profiles created through\nthis endpoint are grouped under `custom`. Posting the profile's current\nStructureDefinition unchanged retains it as a version.\nVersion strings may contain letters, numbers, and the punctuation\ncharacters `.`, `_`, `~`, `+`, and `-`; they cannot be exactly `.` or\n`..`. Each profile can retain up to 250 versions; delete old\nversions before adding more.\n",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/profiles_ProfileUploadRequest"
+ "$ref": "#/components/schemas/profiles_ProfileVersionCreateRequest"
}
}
}
},
"responses": {
- "200": {
- "description": "Profile successfully updated",
+ "201": {
+ "description": "Profile version successfully created",
+ "headers": {
+ "Location": {
+ "description": "Relative URL of the created profile version.",
+ "schema": {
+ "type": "string"
+ },
+ "example": "/fhir/profiles/custom-patient/versions/2.0.0"
+ }
+ },
"content": {
"application/json": {
"schema": {
@@ -6246,7 +6883,7 @@
}
},
"400": {
- "description": "Invalid request or profile validation failed. Possible reasons:\n- Missing or invalid StructureDefinition JSON\n- The StructureDefinition id does not match the id path parameter\n- The resource type differs from the existing profile\n- The url collides with a built-in profile or another custom profile\n"
+ "description": "Invalid request, version does not match the profile, or the canonical URL is already used by another profile.\n"
},
"401": {
"description": "Unauthorized"
@@ -6257,6 +6894,75 @@
"404": {
"description": "Profile not found"
},
+ "409": {
+ "description": "Conflict. Possible reasons:\n- The requested StructureDefinition.version is already in use by a different StructureDefinition: either the profile's current one or a retained version\n- The profile has reached the retained version limit\n"
+ },
+ "500": {
+ "description": "Server error"
+ }
+ },
+ "security": [
+ {
+ "bearerAuth": []
+ }
+ ],
+ "x-service": "fhir_artifacts"
+ }
+ },
+ "/fhir/profiles/{id}/versions/{version}": {
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "required": true,
+ "description": "The lowercase StructureDefinition id of the custom profile.",
+ "schema": {
+ "type": "string"
+ },
+ "example": "custom-patient"
+ },
+ {
+ "name": "version",
+ "in": "path",
+ "required": true,
+ "description": "The authored StructureDefinition.version. It may contain letters, numbers, and the punctuation characters `.`, `_`, `~`, `+`, and `-`; it cannot be exactly `.` or `..`.\n",
+ "schema": {
+ "type": "string",
+ "pattern": "^[A-Za-z0-9._~+-]+$"
+ },
+ "example": "2.0.0"
+ }
+ ],
+ "get": {
+ "operationId": "profiles_getVersion",
+ "tags": [
+ "FHIR Artifacts / Profiles"
+ ],
+ "summary": "Get a retained custom FHIR profile version",
+ "description": "Returns metadata and the full StructureDefinition for one retained\nversion. The returned StructureDefinition's id is the profile id. The\npath version is the authored `StructureDefinition.version` value.\n",
+ "responses": {
+ "200": {
+ "description": "The requested profile version",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/profiles_ProfileGetResponse"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "The id or version is invalid"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "403": {
+ "description": "Forbidden - custom profiles are only available on dedicated instances"
+ },
+ "404": {
+ "description": "Profile or version not found"
+ },
"500": {
"description": "Server error"
}
@@ -6269,18 +6975,18 @@
"x-service": "fhir_artifacts"
},
"delete": {
- "operationId": "profiles_delete",
+ "operationId": "profiles_deleteVersion",
"tags": [
"FHIR Artifacts / Profiles"
],
- "summary": "Delete a custom FHIR profile",
- "description": "Permanently deletes a custom profile by id.\n",
+ "summary": "Delete a retained custom FHIR profile version",
+ "description": "Deletes one retained version from a custom profile. The path\nversion is the authored `StructureDefinition.version` value.\n",
"responses": {
"204": {
- "description": "Profile successfully deleted"
+ "description": "Profile version successfully deleted"
},
"400": {
- "description": "The id is not a valid profile id"
+ "description": "The id or version is invalid"
},
"401": {
"description": "Unauthorized"
@@ -6289,7 +6995,7 @@
"description": "Forbidden - custom profiles are only available on dedicated instances"
},
"404": {
- "description": "Profile not found"
+ "description": "Profile or version not found"
},
"500": {
"description": "Server error"
@@ -10911,115 +11617,426 @@
}
}
},
- "fhir_provider_NoAuth": {
+ "fhir_provider_NoAuth": {
+ "type": "object",
+ "required": [
+ "auth_method"
+ ],
+ "properties": {
+ "auth_method": {
+ "type": "string",
+ "enum": [
+ "none"
+ ]
+ }
+ }
+ },
+ "fhir_provider_FhirProviderSetActiveAuthConfigRequest": {
+ "type": "object",
+ "required": [
+ "auth_config_id"
+ ],
+ "properties": {
+ "auth_config_id": {
+ "type": "string",
+ "description": "ID of the auth configuration to set as active",
+ "example": "auth-config-123"
+ }
+ }
+ },
+ "fhir_provider_FhirProviderRemoveAuthConfigRequest": {
+ "type": "object",
+ "required": [
+ "auth_config_id"
+ ],
+ "properties": {
+ "auth_config_id": {
+ "type": "string",
+ "description": "ID of the auth configuration to remove",
+ "example": "auth-config-123"
+ }
+ }
+ },
+ "fhir_provider_FhirQueryResponse": {
+ "type": "object",
+ "properties": {
+ "success": {
+ "type": "boolean",
+ "example": true
+ },
+ "status": {
+ "type": "integer",
+ "description": "HTTP status code from the FHIR server response",
+ "example": 200
+ },
+ "message": {
+ "type": "string",
+ "example": "Fhir provider searched successfully"
+ },
+ "data": {
+ "oneOf": [
+ {
+ "type": "object",
+ "description": "FHIR resource (Patient, Observation, Condition, etc.)",
+ "additionalProperties": true,
+ "example": {
+ "resourceType": "Patient",
+ "id": "example-patient",
+ "name": [
+ {
+ "family": "Doe",
+ "given": [
+ "John"
+ ]
+ }
+ ],
+ "gender": "male",
+ "birthDate": "1970-01-01"
+ }
+ },
+ {
+ "type": "object",
+ "description": "FHIR Bundle containing multiple resources",
+ "additionalProperties": true,
+ "example": {
+ "resourceType": "Bundle",
+ "type": "searchset",
+ "total": 1,
+ "entry": [
+ {
+ "resource": {
+ "resourceType": "Patient",
+ "id": "example-patient",
+ "name": [
+ {
+ "family": "Doe",
+ "given": [
+ "John"
+ ]
+ }
+ ]
+ }
+ }
+ ]
+ }
+ },
+ {
+ "type": "string",
+ "description": "Raw response text if not valid JSON",
+ "example": "Error: Invalid response format"
+ }
+ ]
+ }
+ }
+ },
+ "batch_CreateBatchRequest": {
+ "type": "object",
+ "description": "Body of a create-batch request. Opens an empty job.",
+ "properties": {
+ "request_id": {
+ "type": "string",
+ "description": "Optional client idempotency token. A retried create with the same\ntoken returns the original job instead of opening a second one.\n",
+ "example": "submit-2025-09-02-batch-001"
+ }
+ }
+ },
+ "batch_BatchError": {
+ "type": "object",
+ "description": "A job- or item-level failure. Never contains document or patient content.",
+ "required": [
+ "kind",
+ "message"
+ ],
+ "properties": {
+ "kind": {
+ "type": "string",
+ "description": "Short stable token to branch on. Item-level kinds: `invalid_input`\n(the stored body was not a valid create/document request),\n`processing_failed` (the conversion failed), `budget_exceeded` (the\nitem ran past its time budget \u2014 600s for a document item, 450s for a\ncreate item), `result_too_large` (the result exceeded the storage\ncap), `input_unavailable` (the input could not be read), and\n`retries_exhausted` / `attempts_exhausted` (buried after too many\nfailed attempts).\nJob-level kinds: `timeout` (the job did not finish within 36 hours\nof creation).\n",
+ "example": "processing_failed"
+ },
+ "message": {
+ "type": "string",
+ "description": "Human-readable description of the failure.",
+ "example": "the item could not be converted"
+ }
+ }
+ },
+ "batch_BatchJob": {
+ "type": "object",
+ "description": "One batch job record.",
+ "required": [
+ "job_id",
+ "status",
+ "finalized",
+ "total_items",
+ "created_at",
+ "updated_at",
+ "expires_at"
+ ],
+ "properties": {
+ "job_id": {
+ "type": "string",
+ "description": "Server-assigned job identifier.",
+ "example": "l2f_batch_6f1d2c3a-8b4e-4f5a-9c7d-0e1f2a3b4c5d"
+ },
+ "request_id": {
+ "type": "string",
+ "description": "The idempotency token supplied at create, if any.",
+ "example": "submit-2025-09-02-batch-001"
+ },
+ "status": {
+ "type": "string",
+ "description": "Job status. `completed` means every item has finished \u2014 some may have\nfailed, so check `counts` for the split. `failed` is a whole-job\nfailure (the job could not run at all), distinct from individual item\nfailures, which never fail the job.\n",
+ "enum": [
+ "pending",
+ "processing",
+ "completed",
+ "failed"
+ ],
+ "example": "pending"
+ },
+ "finalized": {
+ "type": "boolean",
+ "description": "Whether the job's item set has been sealed.",
+ "example": false
+ },
+ "total_items": {
+ "type": "integer",
+ "description": "The sealed item count. It is 0 until the job is finalized, so an\nupload response always reports 0; poll the job after finalize for the\nreal count.\n",
+ "example": 12
+ },
+ "error": {
+ "description": "A whole-job failure. Present only on a `failed` job.",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/batch_BatchError"
+ }
+ ]
+ },
+ "created_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "updated_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "completed_at": {
+ "type": "string",
+ "format": "date-time",
+ "description": "When the job finished. Absent until then."
+ },
+ "expires_at": {
+ "type": "string",
+ "format": "date-time",
+ "description": "When the job and its stored inputs and results are deleted. Set 7\ndays out, with the clock restarting when the job reaches `completed`\nor `failed`. At expiry the job's `request_id` is freed for reuse.\n"
+ }
+ }
+ },
+ "batch_BatchCounts": {
"type": "object",
+ "description": "A job's per-status item tally.",
"required": [
- "auth_method"
+ "total",
+ "pending",
+ "processing",
+ "succeeded",
+ "failed"
],
"properties": {
- "auth_method": {
- "type": "string",
- "enum": [
- "none"
- ]
+ "total": {
+ "type": "integer",
+ "example": 12
+ },
+ "pending": {
+ "type": "integer",
+ "example": 3
+ },
+ "processing": {
+ "type": "integer",
+ "example": 2
+ },
+ "succeeded": {
+ "type": "integer",
+ "example": 6
+ },
+ "failed": {
+ "type": "integer",
+ "example": 1
}
}
},
- "fhir_provider_FhirProviderSetActiveAuthConfigRequest": {
+ "batch_BatchItemStatus": {
"type": "object",
+ "description": "One item's status line.",
"required": [
- "auth_config_id"
+ "item_id",
+ "status",
+ "attempts"
],
"properties": {
- "auth_config_id": {
+ "item_id": {
"type": "string",
- "description": "ID of the auth configuration to set as active",
- "example": "auth-config-123"
+ "description": "Server-assigned opaque item ID, used in result URLs.",
+ "example": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
+ },
+ "id": {
+ "type": "string",
+ "description": "The caller's correlation label, echoed back; omitted when none was supplied.",
+ "example": "chart-note-0042"
+ },
+ "status": {
+ "type": "string",
+ "description": "Item status.",
+ "enum": [
+ "pending",
+ "processing",
+ "succeeded",
+ "failed"
+ ],
+ "example": "succeeded"
+ },
+ "attempts": {
+ "type": "integer",
+ "description": "Number of processing attempts so far. An item runs up to 3 attempts,\nand only an interruption \u2014 a worker preemption or a recovered internal\nerror \u2014 is retried; a conversion error fails the item with no retry.\n",
+ "example": 1
+ },
+ "detect_retries": {
+ "type": "integer",
+ "format": "int64",
+ "description": "How many times concept detection was re-run for the item because a\npass returned no concepts. Present only on a succeeded item whose\ndetection re-ran at least once; omitted otherwise.\n",
+ "example": 1
+ },
+ "result_size": {
+ "type": "integer",
+ "format": "int64",
+ "description": "Size in bytes of the item's stored result. Present once the item has succeeded.",
+ "example": 20482
+ },
+ "error": {
+ "$ref": "#/components/schemas/batch_BatchError"
+ },
+ "completed_at": {
+ "type": "string",
+ "format": "date-time",
+ "description": "When the item finished. Absent until then."
}
}
},
- "fhir_provider_FhirProviderRemoveAuthConfigRequest": {
+ "batch_UploadItemResponse": {
+ "description": "The upload response: the job record plus the ID the server assigned the\nuploaded item.\n",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/batch_BatchJob"
+ },
+ {
+ "type": "object",
+ "required": [
+ "item_id",
+ "deduplicated"
+ ],
+ "properties": {
+ "item_id": {
+ "type": "string",
+ "description": "The server-assigned ID of the uploaded item; use it to fetch the result.",
+ "example": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
+ },
+ "id": {
+ "type": "string",
+ "description": "The caller's correlation label, echoed back; omitted when none was supplied.",
+ "example": "chart-note-0042"
+ },
+ "deduplicated": {
+ "type": "boolean",
+ "description": "True only when a repeated `request_id` resolved to an item whose\npayload matched the one already stored. A same-token upload with a\nchanged payload overwrites in place and returns false.\n",
+ "example": false
+ }
+ }
+ }
+ ]
+ },
+ "batch_JobDetailResponse": {
+ "description": "A job's record, its counts, and one page of item statuses.\n",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/batch_BatchJob"
+ },
+ {
+ "type": "object",
+ "required": [
+ "counts",
+ "items",
+ "has_more"
+ ],
+ "properties": {
+ "counts": {
+ "$ref": "#/components/schemas/batch_BatchCounts"
+ },
+ "items": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/batch_BatchItemStatus"
+ }
+ },
+ "next_cursor": {
+ "type": "string",
+ "description": "Cursor for the next page, when has_more is true."
+ },
+ "has_more": {
+ "type": "boolean",
+ "description": "Whether more item statuses remain beyond this page.",
+ "example": false
+ }
+ }
+ }
+ ]
+ },
+ "batch_ResultsPageResponse": {
"type": "object",
+ "description": "A page of per-item result statuses, without the job counts.",
"required": [
- "auth_config_id"
+ "results",
+ "has_more"
],
"properties": {
- "auth_config_id": {
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/batch_BatchItemStatus"
+ }
+ },
+ "next_cursor": {
"type": "string",
- "description": "ID of the auth configuration to remove",
- "example": "auth-config-123"
+ "description": "Cursor for the next page, when has_more is true."
+ },
+ "has_more": {
+ "type": "boolean",
+ "description": "Whether more results remain beyond this page.",
+ "example": false
}
}
},
- "fhir_provider_FhirQueryResponse": {
+ "batch_JobListResponse": {
"type": "object",
+ "description": "A page of job records, without per-job counts.",
+ "required": [
+ "jobs",
+ "has_more"
+ ],
"properties": {
- "success": {
- "type": "boolean",
- "example": true
- },
- "status": {
- "type": "integer",
- "description": "HTTP status code from the FHIR server response",
- "example": 200
+ "jobs": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/batch_BatchJob"
+ }
},
- "message": {
+ "next_cursor": {
"type": "string",
- "example": "Fhir provider searched successfully"
+ "description": "Cursor for the next page, when has_more is true."
},
- "data": {
- "oneOf": [
- {
- "type": "object",
- "description": "FHIR resource (Patient, Observation, Condition, etc.)",
- "additionalProperties": true,
- "example": {
- "resourceType": "Patient",
- "id": "example-patient",
- "name": [
- {
- "family": "Doe",
- "given": [
- "John"
- ]
- }
- ],
- "gender": "male",
- "birthDate": "1970-01-01"
- }
- },
- {
- "type": "object",
- "description": "FHIR Bundle containing multiple resources",
- "additionalProperties": true,
- "example": {
- "resourceType": "Bundle",
- "type": "searchset",
- "total": 1,
- "entry": [
- {
- "resource": {
- "resourceType": "Patient",
- "id": "example-patient",
- "name": [
- {
- "family": "Doe",
- "given": [
- "John"
- ]
- }
- ]
- }
- }
- ]
- }
- },
- {
- "type": "string",
- "description": "Raw response text if not valid JSON",
- "example": "Error: Invalid response format"
- }
- ]
+ "has_more": {
+ "type": "boolean",
+ "description": "Whether more jobs remain beyond this page.",
+ "example": false
}
}
},
@@ -12064,6 +13081,32 @@
"required": [
"structure_definition"
],
+ "example": {
+ "implementation_guide": "acme-cardiology",
+ "structure_definition": {
+ "resourceType": "StructureDefinition",
+ "id": "custom-patient",
+ "url": "http://phenoml.com/fhir/StructureDefinition/custom-patient",
+ "name": "CustomPatient",
+ "status": "active",
+ "fhirVersion": "4.0.1",
+ "kind": "resource",
+ "abstract": false,
+ "type": "Patient",
+ "baseDefinition": "http://hl7.org/fhir/StructureDefinition/Patient",
+ "derivation": "constraint",
+ "snapshot": {
+ "element": [
+ {
+ "id": "Patient",
+ "path": "Patient",
+ "min": 0,
+ "max": "*"
+ }
+ ]
+ }
+ }
+ },
"properties": {
"structure_definition": {
"allOf": [
@@ -12111,6 +13154,33 @@
},
"profiles_ProfileSummary": {
"type": "object",
+ "description": "Metadata for either a custom profile's current StructureDefinition or one retained version. For retained versions, `source` is always `custom` and `updated_at` is equal to `created_at`.\n",
+ "required": [
+ "id",
+ "source",
+ "resource_type",
+ "url",
+ "version",
+ "canonical",
+ "fhir_version",
+ "implementation_guide",
+ "created_at",
+ "updated_at"
+ ],
+ "example": {
+ "id": "custom-patient",
+ "source": "custom",
+ "resource_type": "Patient",
+ "url": "http://phenoml.com/fhir/StructureDefinition/custom-patient",
+ "version": "1.0.0",
+ "status": "active",
+ "date": "2026-08-24",
+ "canonical": "http://phenoml.com/fhir/StructureDefinition/custom-patient|1.0.0",
+ "fhir_version": "4.0.1",
+ "implementation_guide": "acme-cardiology",
+ "created_at": "2026-08-24T15:04:05Z",
+ "updated_at": "2026-08-25T16:04:05Z"
+ },
"properties": {
"id": {
"type": "string",
@@ -12123,7 +13193,7 @@
"custom",
"builtin"
],
- "description": "The profile's origin. Listings currently return only custom (uploaded) profiles, so this is always \"custom\" today; built-in (US Core / R4 base) profiles would be surfaced via an opt-in parameter in a future release, not by changing the default behavior.\n",
+ "description": "The profile's origin. Profile management responses currently return custom (uploaded) profiles, so this is always \"custom\" today.\n",
"example": "custom"
},
"resource_type": {
@@ -12141,6 +13211,21 @@
"description": "The version from the StructureDefinition.version field.",
"example": "1.0.0"
},
+ "status": {
+ "type": "string",
+ "description": "The publication status from StructureDefinition.status. Expected FHIR values include `draft`, `active`, `retired`, and `unknown`; the server preserves authored strings.\n",
+ "example": "active"
+ },
+ "date": {
+ "type": "string",
+ "description": "The authored publication date from StructureDefinition.date, when present. This is a FHIR dateTime string and may be less precise than a full timestamp.\n",
+ "example": "2026-08-24"
+ },
+ "canonical": {
+ "type": "string",
+ "description": "The canonical profile reference, including the version pin when present.",
+ "example": "http://phenoml.com/fhir/StructureDefinition/custom-patient|1.0.0"
+ },
"fhir_version": {
"type": "string",
"description": "The base FHIR version the StructureDefinition targets.",
@@ -12157,12 +13242,34 @@
},
"updated_at": {
"type": "string",
- "format": "date-time"
+ "format": "date-time",
+ "description": "Last update timestamp for the profile's current StructureDefinition. For retained versions, this equals `created_at`.\n"
}
}
},
"profiles_ProfileListResponse": {
"type": "object",
+ "required": [
+ "profiles"
+ ],
+ "example": {
+ "profiles": [
+ {
+ "id": "custom-patient",
+ "source": "custom",
+ "resource_type": "Patient",
+ "url": "http://phenoml.com/fhir/StructureDefinition/custom-patient",
+ "version": "1.0.0",
+ "status": "active",
+ "date": "2026-08-24",
+ "canonical": "http://phenoml.com/fhir/StructureDefinition/custom-patient|1.0.0",
+ "fhir_version": "4.0.1",
+ "implementation_guide": "acme-cardiology",
+ "created_at": "2026-08-24T15:04:05Z",
+ "updated_at": "2026-08-25T16:04:05Z"
+ }
+ ]
+ },
"properties": {
"profiles": {
"type": "array",
@@ -12173,12 +13280,54 @@
}
},
"profiles_ProfileGetResponse": {
+ "description": "Metadata and full StructureDefinition JSON for either a custom profile's current StructureDefinition or one retained version.\n",
+ "example": {
+ "id": "custom-patient",
+ "source": "custom",
+ "resource_type": "Patient",
+ "url": "http://phenoml.com/fhir/StructureDefinition/custom-patient",
+ "version": "1.0.0",
+ "status": "active",
+ "date": "2026-08-24",
+ "canonical": "http://phenoml.com/fhir/StructureDefinition/custom-patient|1.0.0",
+ "fhir_version": "4.0.1",
+ "implementation_guide": "acme-cardiology",
+ "created_at": "2026-08-24T15:04:05Z",
+ "updated_at": "2026-08-25T16:04:05Z",
+ "structure_definition": {
+ "resourceType": "StructureDefinition",
+ "id": "custom-patient",
+ "url": "http://phenoml.com/fhir/StructureDefinition/custom-patient",
+ "version": "1.0.0",
+ "name": "CustomPatient",
+ "status": "active",
+ "fhirVersion": "4.0.1",
+ "kind": "resource",
+ "abstract": false,
+ "type": "Patient",
+ "baseDefinition": "http://hl7.org/fhir/StructureDefinition/Patient",
+ "derivation": "constraint",
+ "snapshot": {
+ "element": [
+ {
+ "id": "Patient",
+ "path": "Patient",
+ "min": 0,
+ "max": "*"
+ }
+ ]
+ }
+ }
+ },
"allOf": [
{
"$ref": "#/components/schemas/profiles_ProfileSummary"
},
{
"type": "object",
+ "required": [
+ "structure_definition"
+ ],
"properties": {
"structure_definition": {
"allOf": [
@@ -12192,6 +13341,82 @@
}
]
},
+ "profiles_ProfileVersionCreateRequest": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/profiles_FHIRResource"
+ }
+ ],
+ "description": "A FHIR StructureDefinition as a JSON object. Must include resourceType: StructureDefinition, url, type, version, and a snapshot with elements. The canonical URL and resource type must match the profile when one already exists. If id is present, its lowercased value must match the path id; if omitted, the path id is used. The version may contain letters, numbers, and the punctuation characters `.`, `_`, `~`, `+`, and `-`; it cannot be exactly `.` or `..`.\n",
+ "example": {
+ "resourceType": "StructureDefinition",
+ "id": "custom-patient",
+ "url": "http://phenoml.com/fhir/StructureDefinition/custom-patient",
+ "version": "2.0.0",
+ "name": "CustomPatient",
+ "status": "active",
+ "fhirVersion": "4.0.1",
+ "kind": "resource",
+ "abstract": false,
+ "type": "Patient",
+ "baseDefinition": "http://hl7.org/fhir/StructureDefinition/Patient",
+ "derivation": "constraint",
+ "snapshot": {
+ "element": [
+ {
+ "id": "Patient",
+ "path": "Patient"
+ }
+ ]
+ }
+ }
+ },
+ "profiles_ProfileVersionListResponse": {
+ "type": "object",
+ "required": [
+ "versions"
+ ],
+ "example": {
+ "versions": [
+ {
+ "id": "custom-patient",
+ "source": "custom",
+ "resource_type": "Patient",
+ "url": "http://phenoml.com/fhir/StructureDefinition/custom-patient",
+ "version": "2.0.0",
+ "status": "active",
+ "date": "2026-08-26",
+ "canonical": "http://phenoml.com/fhir/StructureDefinition/custom-patient|2.0.0",
+ "fhir_version": "4.0.1",
+ "implementation_guide": "acme-cardiology",
+ "created_at": "2026-08-26T15:04:05Z",
+ "updated_at": "2026-08-26T15:04:05Z"
+ },
+ {
+ "id": "custom-patient",
+ "source": "custom",
+ "resource_type": "Patient",
+ "url": "http://phenoml.com/fhir/StructureDefinition/custom-patient",
+ "version": "1.0.0",
+ "status": "active",
+ "date": "2026-08-24",
+ "canonical": "http://phenoml.com/fhir/StructureDefinition/custom-patient|1.0.0",
+ "fhir_version": "4.0.1",
+ "implementation_guide": "acme-cardiology",
+ "created_at": "2026-08-24T15:04:05Z",
+ "updated_at": "2026-08-24T15:04:05Z"
+ }
+ ]
+ },
+ "properties": {
+ "versions": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/profiles_ProfileSummary"
+ }
+ }
+ }
+ },
"profiles_FHIRResource": {
"type": "object",
"description": "A FHIR resource (schema depends on resource type)"
@@ -13482,7 +14707,7 @@
},
{
"name": "FHIR Artifacts / Profiles",
- "description": "Manage custom FHIR profiles (StructureDefinitions) as instance-level FHIR artifacts."
+ "description": "Manage custom FHIR profiles (StructureDefinitions)."
},
{
"name": "Tools / MCP Servers",