From 2e8cd57ecd0f437c33a0c3fb7a20730b3d1ea262 Mon Sep 17 00:00:00 2001 From: "fern-api[bot]" <115122769+fern-api[bot]@users.noreply.github.com> Date: Thu, 17 Sep 2026 18:57:23 +0000 Subject: [PATCH 1/4] [fern-generated] Update SDK Generated by Fern CLI Version: unknown Generators: - fernapi/fern-python-sdk: 5.30.0 --- .fern/metadata.json | 10 +- poetry.lock | 525 ++++++++------- pyproject.toml | 7 +- reference.md | 244 ++++++- src/phenoml/agent/chat/client.py | 2 +- src/phenoml/agent/chat/raw_client.py | 4 + src/phenoml/client.py | 30 +- src/phenoml/core/client_wrapper.py | 4 +- src/phenoml/core/http_client.py | 101 ++- src/phenoml/core/http_response.py | 4 + src/phenoml/core/jsonable_encoder.py | 13 + src/phenoml/core/oauth_token_provider.py | 8 +- src/phenoml/core/pydantic_utilities.py | 134 ++-- src/phenoml/implementation_guides/__init__.py | 23 +- .../implementation_guides/errors/__init__.py | 11 +- .../errors/conflict_error.py | 10 + .../implementation_guides/client.py | 226 ++++++- .../implementation_guides/raw_client.py | 354 +++++++++- .../implementation_guides/types/__init__.py | 12 +- .../types/fhir_implementation_guide.py | 38 ++ .../types/implementation_guide_summary.py | 12 +- .../implementation_guide_version_detail.py | 29 + src/phenoml/lang2fhir/__init__.py | 3 + src/phenoml/lang2fhir/client.py | 36 +- src/phenoml/lang2fhir/raw_client.py | 72 +- src/phenoml/lang2fhir/types/__init__.py | 3 + .../lang2fhir/types/resource_review.py | 2 +- .../types/resource_review_finding.py | 7 +- .../types/resource_review_flagged.py | 2 +- .../types/resource_review_remediated.py | 42 ++ .../lang2fhir/types/resource_review_result.py | 10 +- src/phenoml/lang2fhir_batch/client.py | 64 +- src/phenoml/lang2fhir_batch/raw_client.py | 86 +-- .../lang2fhir_batch/types/batch_error.py | 10 +- .../types/batch_item_status.py | 5 +- .../lang2fhir_batch/types/batch_job.py | 4 +- src/phenoml/profiles/profiles/raw_client.py | 22 + src/phenoml/profiles/versions/raw_client.py | 22 + tests/utils/test_http_client.py | 112 +++- ...plementationGuides_implementationGuides.py | 29 + tests/wire/test_lang2Fhir.py | 4 +- wiremock/wiremock-mappings.json | 624 ++++++++++++++++-- 42 files changed, 2381 insertions(+), 579 deletions(-) create mode 100644 src/phenoml/implementation_guides/errors/conflict_error.py create mode 100644 src/phenoml/implementation_guides/types/fhir_implementation_guide.py create mode 100644 src/phenoml/implementation_guides/types/implementation_guide_version_detail.py create mode 100644 src/phenoml/lang2fhir/types/resource_review_remediated.py diff --git a/.fern/metadata.json b/.fern/metadata.json index 508188d..b736600 100644 --- a/.fern/metadata.json +++ b/.fern/metadata.json @@ -1,17 +1,17 @@ { - "cliVersion": "5.118.0", + "cliVersion": "5.128.0", "generatorName": "fernapi/fern-python-sdk", - "generatorVersion": "5.18.1", + "generatorVersion": "5.30.0", "generatorConfig": { "client_class_name": "PhenomlClient", "wire_tests": { "enabled": true } }, - "originGitCommit": "26224ef37e22adb507f28c21e8991d4a27822bde", + "originGitCommit": "963927d77bf1a9feab3fa0eee0c37e3812b79507", "originGitCommitIsDirty": true, "invokedBy": "ci", "requestedVersion": "AUTO", "ciProvider": "unknown", - "sdkVersion": "17.0.0" -} + "sdkVersion": "0.0.0.dev0" +} \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index 3656b36..a1310da 100644 --- a/poetry.lock +++ b/poetry.lock @@ -688,15 +688,15 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "httpx-aiohttp" -version = "0.1.8" +version = "0.1.12" description = "Aiohttp transport for HTTPX" optional = true python-versions = ">=3.8" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "httpx_aiohttp-0.1.8-py3-none-any.whl", hash = "sha256:b7bd958d1331f3759a38a0ba22ad29832cb63ca69498c17735228055bf78fa7e"}, - {file = "httpx_aiohttp-0.1.8.tar.gz", hash = "sha256:756c5e74cdb568c3248ba63fe82bfe8bbe64b928728720f7eaac64b3cf46f308"}, + {file = "httpx_aiohttp-0.1.12-py3-none-any.whl", hash = "sha256:5b0eac39a7f360fa7867a60bcb46bb1024eada9c01cbfecdb54dc1edb3fb7141"}, + {file = "httpx_aiohttp-0.1.12.tar.gz", hash = "sha256:81feec51fd82c0ecfa0e9aaf1b1a6c2591260d5e2bcbeb7eb0277a78e610df2c"}, ] [package.dependencies] @@ -705,14 +705,14 @@ httpx = ">=0.27.0" [[package]] name = "idna" -version = "3.19" +version = "3.20" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.9" groups = ["main", "dev"] files = [ - {file = "idna-3.19-py3-none-any.whl", hash = "sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4"}, - {file = "idna-3.19.tar.gz", hash = "sha256:5e0811a4383b21dc5838069f801c4fb62113b7447663d2530d2bd6e77b49bf15"}, + {file = "idna-3.20-py3-none-any.whl", hash = "sha256:ab7ae7122974553370f0bdb919e1a960b2cd1bc1ef0276416d896db81c14582c"}, + {file = "idna-3.20.tar.gz", hash = "sha256:a7db850025b95ded1eae8a46181a1a6c56c92c96f0e2b005d9ff8dc0210cab44"}, ] [package.extras] @@ -1011,134 +1011,151 @@ testing = ["coverage", "pytest", "pytest-benchmark"] [[package]] name = "propcache" -version = "0.5.2" +version = "0.5.4" description = "Accelerated property cache" optional = true python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "propcache-0.5.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d5a81be28596d6559f6131ef33e10200de6e17643b3c74ce03f9eb103be6ae8b"}, - {file = "propcache-0.5.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29cbaac5ea0212663e6845e04b5e188d5a6ae6dd919810ac835bf1d3b42c3f4c"}, - {file = "propcache-0.5.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6bf3be92233808fcd338eba0fb4d0b59ec5772af4f4ecfcec450d1bfc0f8b5eb"}, - {file = "propcache-0.5.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2f8ea531c794b9d6274acd4e8d2c2ebcac590a4361d27482edd3010b79f1325e"}, - {file = "propcache-0.5.2-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:decfca4c79dd53ebab484b00cc4b6717d8c369f86e74aa4ca395a64ac651495e"}, - {file = "propcache-0.5.2-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4621064bbf28fa77ff64dd5d94367c04684c67d3a5bf1dff25f0cd0d98a38f3b"}, - {file = "propcache-0.5.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b96db7141a592cbc968daf1feea83a118e6ab378af4abbc72b248c895414c22d"}, - {file = "propcache-0.5.2-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1ca071adabaab6e9219924bbe00af821f1ee7de113a9eca1cdc292de3d120f4d"}, - {file = "propcache-0.5.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e4294d04a94dcab1b3bccd8b66d962dcad411a1d19414b2a41d1445f1de32ad0"}, - {file = "propcache-0.5.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:a0e399a2eccb91ed18721f86aa85757727400b6865c89e88934781deb9c8498b"}, - {file = "propcache-0.5.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:823581fd5cb08b12a48bfa11fe962a7916766b6170c17b028fbdf762b85eb9bf"}, - {file = "propcache-0.5.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:949c91d1a990cf3b2e8188dfcfb25005e0b834a06c63fa4ef9f360878ce21ecf"}, - {file = "propcache-0.5.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:cc1177027eda740fdb152706bd215a3f124e3eea15afc39f2cb9fe351b50619e"}, - {file = "propcache-0.5.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b05d643f944a8c3c4bd86d65ffd87bf3264b617f87791940302bc474d2ff5274"}, - {file = "propcache-0.5.2-cp310-cp310-win32.whl", hash = "sha256:8114f28879e0904748e831c3a7774261bd9e75f49be089f389a76f959dcd13fe"}, - {file = "propcache-0.5.2-cp310-cp310-win_amd64.whl", hash = "sha256:5fcb98e7598b1ee0addab320d90f65b530297a867dbfe9de52ea838077e16e3d"}, - {file = "propcache-0.5.2-cp310-cp310-win_arm64.whl", hash = "sha256:04dc2390d9edbbaef7461f33322555976ffddf0b650a038649d026358714e6c5"}, - {file = "propcache-0.5.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:74b70780220e2dd89175ca24b81b68b67c83db499ae611e7f2313cb329801c78"}, - {file = "propcache-0.5.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a4840ab0ae0216d952f4b53dc6d0b992bfc2bedbfe360bdd9b548bc184c08959"}, - {file = "propcache-0.5.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c6844ba6364fb12f403928a82cfd295ab103a2b315c77c747b2dbe4a41894ea7"}, - {file = "propcache-0.5.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2293949b855ce597f2826452d17c2d545fb5622379c4ea6fdf525e9b8e8a2511"}, - {file = "propcache-0.5.2-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0fd59b5af35f74da48d905dcbad55449ba13be91823cb05a9bd590bbf5b61660"}, - {file = "propcache-0.5.2-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29f9309a2e42b0d273be006fdb4be2d6c39a47f6f57d8fb1cf9f81481df81b66"}, - {file = "propcache-0.5.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5aaa2b923c1944ac8febd6609cb373540a5563e7cbcb0fd770f75dace2eb817b"}, - {file = "propcache-0.5.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:66ea454f095ddf5b6b14f56c064c0941c4788be11e18d2464cf643bf7203ff67"}, - {file = "propcache-0.5.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:95f1e3f4760d404b13c9976c0229b2b49a3c8e2c62a9ce92efdd2b11ada75e3f"}, - {file = "propcache-0.5.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:85341b12b9d55bad0bded24cac341bb34289469e03a11f3f583ea1cc1db0326c"}, - {file = "propcache-0.5.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:26a4dca084132874e639895c3135dfad5eb20bae209f62d1aeb31b03e601c3c0"}, - {file = "propcache-0.5.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:3b199b9b2b3d6a7edf3183ba8a9a137a22b97f7df525feb5ae1eccf026d2a9c6"}, - {file = "propcache-0.5.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e59bc9e66329185b93dab73f210f1a37f81cb40f321501db8017c9aea15dba27"}, - {file = "propcache-0.5.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:552ffadf6ad409844bc5919c42a0a83d88314cedddaea0e41e80a8b8fffe881f"}, - {file = "propcache-0.5.2-cp311-cp311-win32.whl", hash = "sha256:cd416c1de191973c52ff1a12a57446bfc7642797b282d7caf2162d7d1b8aa9a0"}, - {file = "propcache-0.5.2-cp311-cp311-win_amd64.whl", hash = "sha256:44e488ef40dbb452700b2b1f8188934121f6648f52c295055662d2191959ff82"}, - {file = "propcache-0.5.2-cp311-cp311-win_arm64.whl", hash = "sha256:54adaa85a22078d1e306304a40984dc5be99d599bf3dc0a24dc98f7daeab89ab"}, - {file = "propcache-0.5.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:806719138ecd720339a12410fb9614ac9b2b2d3a5fdf8235d56981c36f4039ba"}, - {file = "propcache-0.5.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:db2b80ea58eab4f86b2beec3cc8b39e8ff9276ac20e96b7cce43c8ae84cd6b5a"}, - {file = "propcache-0.5.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e5cbfac9f61484f7e9f3597775500cd3ebe8274e9b050c38f9525c77c97520bf"}, - {file = "propcache-0.5.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5dbc581d2814337da56222fab8dc5f161cd798a434e49bac27930aaef798e144"}, - {file = "propcache-0.5.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:857187f381f88c8e2fa2fe56ab94879d011b883d5a2ee5a1b60a8cd2a06846d9"}, - {file = "propcache-0.5.2-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:178b4a2cdaac1818e2bf1c5a99b94383fa73ea5382e032a48dec07dc5668dc42"}, - {file = "propcache-0.5.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f328175a2cde1f0ff2c4ed8ce968b9dcfb55f3a7153f39e2957ed994da13476"}, - {file = "propcache-0.5.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5671d09a36b06d0fd4a3da0fccbcae360e9b1570924171a15e9e0997f0249fba"}, - {file = "propcache-0.5.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:80168e2ebe4d3ec6599d10ad8f520304ae1cad9b6c5a95372aef1b66b7bfb53a"}, - {file = "propcache-0.5.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:45f11346f884bc47444f6e6647131055844134c3175b629f84952e2b5cd62b64"}, - {file = "propcache-0.5.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8e778ebd44ef4f66ed60a0416b06b489687db264a9c0b3620362f26489492913"}, - {file = "propcache-0.5.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:c0cb9ed24c8964e172768d455a38254c2dd8a552905729ce006cad3d3dda59b1"}, - {file = "propcache-0.5.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1d1ad32d9d4355e2be65574fd0bfd3677e7066b009cd5b9b2dee8aa6a6393b33"}, - {file = "propcache-0.5.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c80f4ba3e8f00189165999a742ee526ebeccedf6c3f7beb0c7df821e9772435a"}, - {file = "propcache-0.5.2-cp312-cp312-win32.whl", hash = "sha256:8c7972d8f193740d9175f0998ab38717e6cd322d5935c5b0fef8c0d323fd9031"}, - {file = "propcache-0.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:d9ee8826a7d47863a08ac44e1a5f611a462eefc3a194b492da242128bec75b42"}, - {file = "propcache-0.5.2-cp312-cp312-win_arm64.whl", hash = "sha256:2800a4a8ead6b28cccd1ec54b59346f0def7922ee1c7598e8499c733cfbb7c84"}, - {file = "propcache-0.5.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:099aaf4b4d1a02265b92a977edf00b5c4f63b3b17ac6de39b0d637c9cac0188a"}, - {file = "propcache-0.5.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:68ce1c44c7a813a7f71ea04315a8c7b330b63db99d059a797a4651bb6f69f117"}, - {file = "propcache-0.5.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fc299c129490f55f254cd90be0deca4764e36e9a7c08b4aa588479a3bbed3098"}, - {file = "propcache-0.5.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a6ae2198be502c10f09b2516e7b5d019816924bc3183a43ce792a7bd6625e6f4"}, - {file = "propcache-0.5.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6041d31504dc1779d700e1edcfb08eea334b357620b06681a4eabb57a74e574e"}, - {file = "propcache-0.5.2-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f7eabc04151c78a9f4d5bbb5f1faf571e4defeb4b585e0fe95b60ff2dbe4d3d7"}, - {file = "propcache-0.5.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4db0ba63d693afd40d249bd93f842b5f144f8fcbb83de05660373bcf30517b1d"}, - {file = "propcache-0.5.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1dbcf7675229b35d31abb6547d8ebc8c27a830ac3f9a794edff6254873ec7c0a"}, - {file = "propcache-0.5.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d310c013aad2c72f1c3f2f8dd3279d460a858c551f97aeb8c63e4693cca7b4d2"}, - {file = "propcache-0.5.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:06187263ddad280d05b4d8a8b3bb7d164cbebd469236544a42e6d9b28ac6a4fa"}, - {file = "propcache-0.5.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3115559b8effafd63b142ea5ed53d63a16ea6469cbc63dce4ee194b42db5d853"}, - {file = "propcache-0.5.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c60462af8e6dc30c35407c7237ea908d777b22862bbee27bc4699c0d8bcdc45a"}, - {file = "propcache-0.5.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:40314bca9ac559716fe374094fc81c11dcc34b64fd6c585360f5775690505704"}, - {file = "propcache-0.5.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:cfa21e036ce1e1db2be04ba3b85d2df1bb1702fa01932d984c5464c665228ff4"}, - {file = "propcache-0.5.2-cp313-cp313-win32.whl", hash = "sha256:f156a3529f38063b6dbaf356e15602a7f95f8055b1295a438433a6386f10463d"}, - {file = "propcache-0.5.2-cp313-cp313-win_amd64.whl", hash = "sha256:dfed59d0a5aeb01e242e66ff0300bc4a265a7c05f612d30016f0b60b1017d757"}, - {file = "propcache-0.5.2-cp313-cp313-win_arm64.whl", hash = "sha256:ba338430e87ceb9c8f0cf754de38a9860560261e56c00376debd628698a7364f"}, - {file = "propcache-0.5.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a592f5f3da71c8691c788c13cb6734b6d17663d2e1cb8caddf0673d01ef8847d"}, - {file = "propcache-0.5.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6a997d0489e9668a384fcfd5061b857aa5361de73191cac204d04b889cfbbafa"}, - {file = "propcache-0.5.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:10734b5484ea113152ee25a91dccedf81631791805d2c9ccb054958e51842c94"}, - {file = "propcache-0.5.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cafca7e56c12bb02ae16d283742bef25a61122e9dab2b5b3f2ccbe589ce32164"}, - {file = "propcache-0.5.2-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f064f8d2b59177878b7615df1735cd8fe3462ed6be8c7b217d17a276489c2b7f"}, - {file = "propcache-0.5.2-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f78abfa8dfc32376fd1aacf597b2f2fbbe0ea751419aee718af5d4f82537ef8c"}, - {file = "propcache-0.5.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f7467da8a9822bf1a55336f877340c5bcbd3c482afc43a99771169f74a26dedc"}, - {file = "propcache-0.5.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a6ddc6ac9e25de626c1f129c1b467d7ecd33ce2237d3fd0c4e429feef0a7ee1f"}, - {file = "propcache-0.5.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2f22cbbac9e26a8e864c0985ff1268d5d939d53d9d9411a9824279097e03a2cb"}, - {file = "propcache-0.5.2-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:fc76378c62a0f04d0cd82fbb1a2cd2d7e28fcb40d5873f28a6c44e388aaa2751"}, - {file = "propcache-0.5.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:acd2c8edba48e31e58a363b8cf4e5c7db3b04b3f9e371f601df30d9b0d244836"}, - {file = "propcache-0.5.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:452b5065457eb9991ec5eb38ff41d6cd4c991c9ac7c531c4d5849ae473a9a13f"}, - {file = "propcache-0.5.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:3430bb2bfe1331885c427745a751e774ee679fd4344f80b97bf879815fe8fa55"}, - {file = "propcache-0.5.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:cef6cea3922890dd6c9654971001fa797b526c16ab5e1e46c05fd6f877be7568"}, - {file = "propcache-0.5.2-cp313-cp313t-win32.whl", hash = "sha256:72d61e16dd78228b58c5d47be830ff3da7e5f139abdf0aef9d86cde1c5cf2191"}, - {file = "propcache-0.5.2-cp313-cp313t-win_amd64.whl", hash = "sha256:0958834041a0166d343b8d2cedcd8bcbaeb4fdbe0cf08320c5379f143c3be6e7"}, - {file = "propcache-0.5.2-cp313-cp313t-win_arm64.whl", hash = "sha256:6de8bd93ddde9b992cf2b2e0d796d501a19026b5b9fd87356d7d0779531a8d96"}, - {file = "propcache-0.5.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:46088abff4cba581dea21ae0467a480526cb25aa5f3c269e909f800328bc3999"}, - {file = "propcache-0.5.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fc88b26f08d634f7bc819a7852e5214f5802641ab8d9fd5326892292eee1993e"}, - {file = "propcache-0.5.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:97797ebb098e670a2f92dd66f32897e30d7615b14e7f59711de23e30a9072539"}, - {file = "propcache-0.5.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba57fffe4ac99c5d30076161b5866336d97600769bad35cc68f7774b15298a4e"}, - {file = "propcache-0.5.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:583c19759d9eec1e5b69e2fbef36a7d9c326041be9746cb822d335c8cedc2979"}, - {file = "propcache-0.5.2-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d0326e2e5e1f3163fa306c834e48e8d490e5fae607a097a40c0648109b47ba80"}, - {file = "propcache-0.5.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e00820e192c8dbebcafb383ebbf99030895f09905e7a0eb2e0340a0bcc2bc825"}, - {file = "propcache-0.5.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c66afea89b1e43725731d2004732a046fe6fe955d51f952c3e95a7314a284a39"}, - {file = "propcache-0.5.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d4dc37dec6c6cdad0b57881a5658fd14fbf53e333b1a86cf86559f190e1d9ec4"}, - {file = "propcache-0.5.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:5570dbcc97571c15f68068e529c92715a12f8d54030e272d264b377e22bd17a5"}, - {file = "propcache-0.5.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f814362777a9f841adddb200ecdf8f5cb1e5a3c4b7a86378edbd6ccb26edd702"}, - {file = "propcache-0.5.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:196913dea116aeb5a2ba95af4ddcb7ea85559ae07d8eee8751688310d09168c3"}, - {file = "propcache-0.5.2-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:6e7b8719005dd1175be4ab1cd25e9b98659a5e0347331506ec6760d2773a7fb5"}, - {file = "propcache-0.5.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:51f96d685ab16e88cab128cd37a52c5da540809c8b879fa047731bfcb4ad35a4"}, - {file = "propcache-0.5.2-cp314-cp314-win32.whl", hash = "sha256:cc6fc3cc62e8501d3ed62894425040d2728ecddb1ed072737a5c70bd537aa9f0"}, - {file = "propcache-0.5.2-cp314-cp314-win_amd64.whl", hash = "sha256:81e3a30b0bb60caa22033dd0f8a3618d1d67356212514f62c57db75cb0ef410c"}, - {file = "propcache-0.5.2-cp314-cp314-win_arm64.whl", hash = "sha256:0d2c9bf8528f135dbb805ce027567e09164f7efa51a2be07458a2c0420f292d0"}, - {file = "propcache-0.5.2-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:4bc8ff1feffc6a61c7002ffe84634c41b822e104990ae009f44a0834430070bb"}, - {file = "propcache-0.5.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:79aa3ff0a9b566633b642fa9caf7e21ed1c13d6feca718187873f199e1514078"}, - {file = "propcache-0.5.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1b31822f4474c4036bae62de9402710051d431a606d6a0f907fec79935a071aa"}, - {file = "propcache-0.5.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13fef48778b5a2a756523fdb781326b028ca75e32858b04f2cdd19f394564917"}, - {file = "propcache-0.5.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8b73ab70f1a3351fbc71f663b3e645af6dd0329100c353081cf69c37433fc6fe"}, - {file = "propcache-0.5.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5538d2c13d93e4698af7e092b57bc7298fd35d1d58e656ae18f23ee0d0378e03"}, - {file = "propcache-0.5.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd645f03898405cabe694fb8bc35241e3a9c332ec85627584fe3de201452b335"}, - {file = "propcache-0.5.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a473b3440261e0c60706e732b2ed2f517857344fc21bf48fdfe211e2d98eb285"}, - {file = "propcache-0.5.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7afa37062e6650640e932e4cc9297d81f9f42d9944029cc386b8247dea4da837"}, - {file = "propcache-0.5.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:8a90efd5777e996e42d568db9ac740b944d691e565cbfd31b2f7832f9184b2b8"}, - {file = "propcache-0.5.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:f19bb891234d72535764d703bfed1153cc34f4214d5bd7150aee1eec9e8f4366"}, - {file = "propcache-0.5.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:32775082acd2d807ee3db715c7770d38767b817870acfa08c29e057f3c4d5b56"}, - {file = "propcache-0.5.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9282fb1a3bccd038da9f768b927b24a0c753e466c086b7c4f3c6982851eefb2d"}, - {file = "propcache-0.5.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cc49723e2f60d6b32a0f0b08a3fd6d13203c07f1cd9566cfce0f12a917c967a2"}, - {file = "propcache-0.5.2-cp314-cp314t-win32.whl", hash = "sha256:2d7aa89ebca5acc98cba9d1472d976e394782f587bad6661003602a619fd1821"}, - {file = "propcache-0.5.2-cp314-cp314t-win_amd64.whl", hash = "sha256:d447bb0b3054be5818458fbb171208b1d9ff11eba14e18ca18b90cbb45767370"}, - {file = "propcache-0.5.2-cp314-cp314t-win_arm64.whl", hash = "sha256:fe67a3d11cd9b4efabfa45c3d00ffba2b26811442a73a581a94b67c2b5faccf6"}, - {file = "propcache-0.5.2-py3-none-any.whl", hash = "sha256:be1ddfcbb376e3de5d2e2db1d58d6d67463e6b4f9f040c000de8e300295465fe"}, - {file = "propcache-0.5.2.tar.gz", hash = "sha256:01c4fc7480cd0598bb4b57022df55b9ca296da7fc5a8760bd8451a7e63a7d427"}, + {file = "propcache-0.5.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b77c313314524ca9c38fbd70f73515d04597ac58c40c939bc0e71eeb4abff680"}, + {file = "propcache-0.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8f911c395cef73c510bac566da9507bb6a43e7763d0c79138dc60ee53f11207e"}, + {file = "propcache-0.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d83b12902eb8bce151259c86c03ba746600b2d994543de46e370cecf96c452f2"}, + {file = "propcache-0.5.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c9281e922c072158c91974d4589f1dbe0fee6d467f284c28e463f9f5a4d933f4"}, + {file = "propcache-0.5.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9f3551b8a35c1df3e7ea4d2d86edee15f0dde1bddd434a71744048683544d0ef"}, + {file = "propcache-0.5.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ec6a85f424afa8d23e0d9a094e5dbb6eda01da91c92b9183cd433768247ffc97"}, + {file = "propcache-0.5.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f574e460d1c8a08384a016fdb09ccf3543433263ed6b2f97104f979e64ea57c2"}, + {file = "propcache-0.5.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d8e017eeb7482bed34cdb0d61cf2bcfc88d104bbab296a17cd16a6af8aabc70e"}, + {file = "propcache-0.5.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f273dcf7149a50527c4fd1f55cfe9eac0f60753f5af544b4c9352578e20c0874"}, + {file = "propcache-0.5.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:fc2461ecc45f17893f8207e73b46ea8ba93e33630e51cf4af3fbc21d47462b1a"}, + {file = "propcache-0.5.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:279655a16973f1ee2bd2fe79973137681642fd9ae0d89215bba263726eb0dc3a"}, + {file = "propcache-0.5.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:e9f165403b81fea7e89c932d89046a1e3d9a3a60e8d7ef2f249dccdcb0982bf5"}, + {file = "propcache-0.5.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:1783582065a1f07f9d9ee1e992e13f15d7dc8fb1eb3a7476d43eb3f2e69d26bb"}, + {file = "propcache-0.5.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d605bb239b796e82a81c6709548b2bd460ab73b4590cb0c83de8a2dd9694d0f"}, + {file = "propcache-0.5.4-cp310-cp310-win32.whl", hash = "sha256:141fdbd73748db0cf7636035030aaac383d2efde8f34e7bc24594cc776d225b8"}, + {file = "propcache-0.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:146f48a9e4812611a7581003b1a39de56c34967046310c4171a68ef908c9a745"}, + {file = "propcache-0.5.4-cp310-cp310-win_arm64.whl", hash = "sha256:6c7599df2b57ebeea8de011b5f2f7b85de95e76037d43d34b95e328430275487"}, + {file = "propcache-0.5.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:897d1ddf6716e8f47200f7aad9a0efa6cc7586df66c6defa572f9eab379c078e"}, + {file = "propcache-0.5.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9cbfff4423eef4cc6cafc021469641a2b835f610b2647a6c5281903e21b8670d"}, + {file = "propcache-0.5.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3fc24f209c1b7f7f688b66b98293954f5504279760999b58920ee12dd8471c1d"}, + {file = "propcache-0.5.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:62530ca89187827e4a4fe733f971abe81a7542eeea48ff61995f19b64d7199c8"}, + {file = "propcache-0.5.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:56fc3f7599528db40b1efa0889a620116e2704144495273d66066e8164e45838"}, + {file = "propcache-0.5.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4f2d880ff60f45898f4acfa152aac8d04e3ee627d90ff4003491bf92239d5757"}, + {file = "propcache-0.5.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e9368e87a3efc285e559131092c5db643eb8e56de4ee42064d5baec22ef2bb5"}, + {file = "propcache-0.5.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:004e685b315646c410771836e72a44f143bbe624f29653a42687815069a303d5"}, + {file = "propcache-0.5.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:594eb4c6ec35e7179b058481f4e9f02521b56de16fa577c4b85c76fb1bf8a9f8"}, + {file = "propcache-0.5.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:2dba2f02d2d5c09ef8a0e6c1a42aeaa451f4be9898cb00b04fe98717da2eb23b"}, + {file = "propcache-0.5.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c3ef2818d63bc86071e9d2989ae75a1bc32b8f7059cfd9f5abbbee70c32e2ed6"}, + {file = "propcache-0.5.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:dd2ac8f5b643454c2cc6b6118b13da16e88f4a6434fc3ba61aca384029f04f36"}, + {file = "propcache-0.5.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:4054acf80d40456a0537f2913b349718649d8d6458a14ab7f48d0ce28c30869d"}, + {file = "propcache-0.5.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:40e94adb1e7d39ff28a8bd8d8b8fbd1df6b9f40976dbe379134f1ce058e532dd"}, + {file = "propcache-0.5.4-cp311-cp311-win32.whl", hash = "sha256:9f86f7259efe2c951f43e57d471c9b41daa5bfc7db9f67189059cf1ae6d77fd9"}, + {file = "propcache-0.5.4-cp311-cp311-win_amd64.whl", hash = "sha256:e904d4d01f36bd6e197590be1533c44e06058771e0746dd073a8ebb3ef880858"}, + {file = "propcache-0.5.4-cp311-cp311-win_arm64.whl", hash = "sha256:d42a9a856a4a6e2f6c10f1318c07e7daa498d6593abe745c71dae4521a26ca39"}, + {file = "propcache-0.5.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b28f41fa3b8c6900457f858ec5b03998f3a6d535fbc1bb2edec5961ea05ec429"}, + {file = "propcache-0.5.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:dcbf346a318a5e30063f547630b02bb787ce2f45b6368d5da143660b6a3835d8"}, + {file = "propcache-0.5.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:87a3caecf8095e48dc72f84bfa42e23a848cf410cc9cc13031fba4869b706a21"}, + {file = "propcache-0.5.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:60a64cbccaa11b7760ce705a14ada17ba459e7ca9f23ba587eb013821032d7ef"}, + {file = "propcache-0.5.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a74bfa37147cc08fb29df10bd9c16f40fa7f860cd3a6d2fff853323a94f6e17f"}, + {file = "propcache-0.5.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a4d7a54719b67338a305dca2ce6aafe366817df94ddfd4b5514374356f5ca546"}, + {file = "propcache-0.5.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2814ecd8e818f487bee4b0f921bc4d1c176cc5fc71ac0f072d0fa67eda4ac14b"}, + {file = "propcache-0.5.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6af4693716bfb03f1752ef1b30faa593db2c01d5272e9b8564a1549452a979ab"}, + {file = "propcache-0.5.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4fbc1a15dc8cd1689508758d626b372b1f09d28d9577667feaf9e6bfcd8efcbc"}, + {file = "propcache-0.5.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:cdee8205a44d0be91bbac4c41b95d86641b72dfc7aef1279400e4fda3f26a937"}, + {file = "propcache-0.5.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:9a2a8a50a93dee0268a860a07fa3b4bd968f8ce4dbd794957da772f395368526"}, + {file = "propcache-0.5.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7ffafcbfc7b549ab940047e505c831eabac5e67de53e1bc174adbc5285c55944"}, + {file = "propcache-0.5.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:d1f5a500bfcbb2c0ab85e98a0dcd70f5899d34efe365a0187700369a79603031"}, + {file = "propcache-0.5.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8a235f73d6e020855dc29dff012d920c02ee0feab8d73a24185a7569f4be1161"}, + {file = "propcache-0.5.4-cp312-cp312-win32.whl", hash = "sha256:b3083bfe87f95c756e610bd8025f26cbd1cd4aaa03a422f2d65efb7a97cd53d8"}, + {file = "propcache-0.5.4-cp312-cp312-win_amd64.whl", hash = "sha256:98914de2c4d7f0f9f4a8c6ea4bf05841f4175796941e3ef7d47eb718f22311fb"}, + {file = "propcache-0.5.4-cp312-cp312-win_arm64.whl", hash = "sha256:8876b39961e33d912afe3c1bee18ee564fdad0206f873cc15d522756b7f50737"}, + {file = "propcache-0.5.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:36c0d9db44b523ef93d03341b1c42d69ff01d673c053d1b1c6c3a363bcaa39ba"}, + {file = "propcache-0.5.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1d52a05dc417279f7e5c7618c5dfbbc29923aaf9bc0a5c1802ddcebf54c61a0"}, + {file = "propcache-0.5.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:44149f46500a0a41b95b4d99c2e586a77319539730607b9892974a092788b111"}, + {file = "propcache-0.5.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dbab5f5ff6897c81f355d079010cdae85b02e5a0b518b5251523b8ad8ae9ac3c"}, + {file = "propcache-0.5.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c3e98c55bde2bcf7db3c70d1aed7ae9aa8aebbf19a250c66645cde44cdb8b867"}, + {file = "propcache-0.5.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:db3ae52ccc150dbc84704e9d642743897f3e1c54742ff34cacb661e52e3818a9"}, + {file = "propcache-0.5.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f85915e00dcb1cd9f2f890ead064ed40a27df06f0db65be427b29482ae357572"}, + {file = "propcache-0.5.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c2ba30a89035b57b73e00475de948521602f543d79ce01db10b04b36c4c76fc8"}, + {file = "propcache-0.5.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ae58f361bd5dae942717c65d3413b478c70aea9c462599e7b9adad3731db3894"}, + {file = "propcache-0.5.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:96f7c5c15656040ddcbc51e56dc59b58aa25999d743c126abd425b9766ab43e9"}, + {file = "propcache-0.5.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7cc528e760a8af06f2b13e9b9f362cd90c7c718ea61228a96dbd31ba16ed7f47"}, + {file = "propcache-0.5.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:425f8cc86ab5018b4b8d4a23bc8e74d964bd3d757c3702e301aa79be76c53f6c"}, + {file = "propcache-0.5.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:a5793c7698a53f56f4a1889a4737c7eeb1b7ad0842fa6b1abca22913ff79c8c1"}, + {file = "propcache-0.5.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c02c0e570c5c7e077b0181a9f3cdb7d4c3617d1cda6b5c95bd5d34022923d82c"}, + {file = "propcache-0.5.4-cp313-cp313-win32.whl", hash = "sha256:3e413d7a4a9b4866b7a761d6060d434b64d23cd35122eda3b026a0bbe8196b25"}, + {file = "propcache-0.5.4-cp313-cp313-win_amd64.whl", hash = "sha256:0c889f6fa84957bc7e8b4eab71fd16a0455068d5045e3aa40c733071d2b2fd77"}, + {file = "propcache-0.5.4-cp313-cp313-win_arm64.whl", hash = "sha256:69fc35c0779522da366c563e5faf203ffc1f8ff0021d5b1337fa4efa5be73177"}, + {file = "propcache-0.5.4-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:e6720ba44ad7e72174314d0e1fb0172494cff5c73a3a8a2159c3d2402ff15565"}, + {file = "propcache-0.5.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:4cfe0a92ae30151869e67a4b5f5e105e4e03ad30b3f38e5211b5bf77d0881993"}, + {file = "propcache-0.5.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d759d05634f1b038fb625a66662a8c85e5a8fec912da381b5149ddac107482b"}, + {file = "propcache-0.5.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:251c63dd46a0659bb875cb254dc4c1e79ee91a847c737cd62373295afc2235dc"}, + {file = "propcache-0.5.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7a8d5ff04eb1f85698a78d20c62a14676e7b960dcafde09a388d60ad377d355d"}, + {file = "propcache-0.5.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7b9100a93b372418d8688f3f2a3e5b45c64d70ca4d6176e121aca1e3bfc1e32f"}, + {file = "propcache-0.5.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cc07876cfb079b6f6f36d21ce75784ad6c2c6b563eeac0ed26c2fa2669b85df9"}, + {file = "propcache-0.5.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0951315a6b3142ee2167404d707743f0157c110091342b1aa0accac5cf0e4acf"}, + {file = "propcache-0.5.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bee7d3aed13d56f54e681df38c3a23031bc9e3863f687d9d598825c9146acd7d"}, + {file = "propcache-0.5.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:4e985382be6d15da8d0c2710a6fa7b9070fc9ecdeefb7f580e88373984ec8be3"}, + {file = "propcache-0.5.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:9e9ab13760aa8b6d0881ae7cb04fd891d8d490cd2554ea8e79bb278399169bcc"}, + {file = "propcache-0.5.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:1b2f3bec4261a94019575481c726c29850f72e27907773c75b1de421e20e9f9d"}, + {file = "propcache-0.5.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:720cf832eb2d0b0dfee129cb3335a26f6ce3cc45ee1187e8f0731758caa16792"}, + {file = "propcache-0.5.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9fb0a5be8d9aa213150e8d8148a42aca4984b285bcad1e69587dc4298edd929b"}, + {file = "propcache-0.5.4-cp314-cp314-win32.whl", hash = "sha256:30cc1cebaf9aef49db06357a50398323ae04d70460c0491837d026ab7d6452ea"}, + {file = "propcache-0.5.4-cp314-cp314-win_amd64.whl", hash = "sha256:0a095db8e15a6020db149ecbed6461939fe74f6acaa3ae8b702a1fe8c38cd983"}, + {file = "propcache-0.5.4-cp314-cp314-win_arm64.whl", hash = "sha256:45488d1a5f9ab5bd90aaa1ca20f50fe1922b8ffad71a2009d2adf41355897aac"}, + {file = "propcache-0.5.4-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:53eaa697c4d0422ff4cb714d00231b43352064d97b944033b30c1d57cc506ec0"}, + {file = "propcache-0.5.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:886b59c4d28ca97dd23b025fdfc50a0356be934efbbbca89ad26230067f86fe5"}, + {file = "propcache-0.5.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3fa15757fea1dfcd5b7745cad9f4638929605531bd4018ab2adff7955f1a403d"}, + {file = "propcache-0.5.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6f0093ac3e9daada202c2082439d414a625c57184727a46e112a3fb2a81cb788"}, + {file = "propcache-0.5.4-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3cd3a7edb6b95b9b33998135ebfa18d709da82290fb8f27c858970b5a12c8b56"}, + {file = "propcache-0.5.4-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c174bfd1c48a1b51a3078e95586dde718374bac79719ab3541ec9e74aec40574"}, + {file = "propcache-0.5.4-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a219f0ac59817a9114dd2aa57c13180f993e819ba658c7ddab4b66ed1ee0d370"}, + {file = "propcache-0.5.4-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:17a7400cec0256f0a71ae71f9da398f9894c956ff6668a1c9d317b3367316320"}, + {file = "propcache-0.5.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:978f28401afbc76cdc3df9e1717b4229a06b626a1dcc75db4e1f2beb3884c3e9"}, + {file = "propcache-0.5.4-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:4a1f4f5ffa55dce6307631f3cb2948e117e665966ea512e0d502b16c24f567e7"}, + {file = "propcache-0.5.4-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:213bb68d9ced5cf2bf717b1071bf2b09b4b04c426256f9fe6d054c60318424c4"}, + {file = "propcache-0.5.4-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:286867fb156488c251a3721766e380ac4495e4fd6b51aaa1403d89ce7f4359d9"}, + {file = "propcache-0.5.4-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:445ee3bfb46e85838387fb3c536a73cc0b994dc192b004e40e170adc54aa2a7e"}, + {file = "propcache-0.5.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:48cb48c5346a97de792254af77715aa2529c2a1ebc5f586aa0aae44a02f1fe57"}, + {file = "propcache-0.5.4-cp314-cp314t-win32.whl", hash = "sha256:03b229037d25b801e7af53fd52b9fc49d9439b036fca1e087e02780631adfa97"}, + {file = "propcache-0.5.4-cp314-cp314t-win_amd64.whl", hash = "sha256:8a1fc236528c457cd739c88abe823da851b7ab645d72792f88658114cc340c12"}, + {file = "propcache-0.5.4-cp314-cp314t-win_arm64.whl", hash = "sha256:135036c5cfc93864affb0f9af9a27e5d7a71cb7bd745e7b6dbfc2d56cc30e827"}, + {file = "propcache-0.5.4-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:45bf2e730ab8905d0527fe05a86500f406e64305c34cc81ebe64b4617cab9760"}, + {file = "propcache-0.5.4-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:31eb43ba2edc704ab2ec27815315dd8a19def0fb16215be4cfe8d32fe78ffd51"}, + {file = "propcache-0.5.4-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:174507f82d3594622acb1dd2dafecf2d899d6d506335494e7107767bf05f3aae"}, + {file = "propcache-0.5.4-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:50e337653721d20ead710da33bf44487fbe8a0db8782714b60306481e9f95b51"}, + {file = "propcache-0.5.4-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0d21d0d2c82bbfeb1677a9711f38df968f9837576102bb4add1bd449d28d88f1"}, + {file = "propcache-0.5.4-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ccf4f7a79e26bb7efb06ecd50c177833b71df05cbc748701372325e6bcc17f6f"}, + {file = "propcache-0.5.4-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23278f808cd81d5ada7184a76606b925fb3389c60e1077b2cd7da7b1fcf0553c"}, + {file = "propcache-0.5.4-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e738ab81179510ce79b2eac9a6ecf47feffd9e76d1c72e403005dddb6e36c06c"}, + {file = "propcache-0.5.4-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:a419ee85e654927baabda3929c03c0cc1112bf472ff0dfd6142f4e3a81ca4162"}, + {file = "propcache-0.5.4-cp315-cp315-musllinux_1_2_armv7l.whl", hash = "sha256:b61805357d966680acf68b3b6d49772631ed9df44ebece10ff1460e117a7da8a"}, + {file = "propcache-0.5.4-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:58134228927cee6c047d626c08e60a81be604a20578a12ce752cc5c9a84d4826"}, + {file = "propcache-0.5.4-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:350b272b2279f4135a64fc0c304a5d08e28a137c9573442c606152446638a831"}, + {file = "propcache-0.5.4-cp315-cp315-musllinux_1_2_s390x.whl", hash = "sha256:45bebbe252550fec975ba3b62bc6f931643cfd3b5464ef47619cf3fef154e01c"}, + {file = "propcache-0.5.4-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:ada748108a43d29b7c328ba7db3755327cd94f028bcc1a7ee3f0addcfacd9c38"}, + {file = "propcache-0.5.4-cp315-cp315-win32.whl", hash = "sha256:ee19113bce2f3acd46432050688b70f61acd6857d75abb9ec96341b7e9ced123"}, + {file = "propcache-0.5.4-cp315-cp315-win_amd64.whl", hash = "sha256:ceb3e879afac028f93d272c957814695dc5569e4904262dbee92f6c41bd5e4a3"}, + {file = "propcache-0.5.4-cp315-cp315-win_arm64.whl", hash = "sha256:c83acbce9f2b5e3f5f5eda9e53d2001fed22fcdfef81274a9e02d8fd53b70a30"}, + {file = "propcache-0.5.4-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:a5e8ef588c109725dc713ba69aadcac00a1ef90c2ce9c0a8c7075128f569f47f"}, + {file = "propcache-0.5.4-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:4d86476a935c88963d9b8e1a9a0d38188790e9622169bfbafa173046846709d3"}, + {file = "propcache-0.5.4-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:f5470694918830da62fac9e69133b53d23b736d7070e587b27a4a2be37e08e68"}, + {file = "propcache-0.5.4-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10ef33a68a61ce317e095fd2e202a592ea92392b90944a78c993f0d9a73ab06c"}, + {file = "propcache-0.5.4-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5cacf3c9efd09df409dc33654dd077e1c245ba8fb747b0f0236ef41b7c49b589"}, + {file = "propcache-0.5.4-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:770e8209d018175fc0063936fa9583b6d27e88c5ad31543f3383d66080efdd62"}, + {file = "propcache-0.5.4-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03969626faf0783a592dfa17e28eac06018bd0b44dafae6943d53b92421a7f72"}, + {file = "propcache-0.5.4-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ef3b928d9c984322b5c44e6964d8dbc653da87d2d8ee1647fa6da43072e650a9"}, + {file = "propcache-0.5.4-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:7177c43eddf10a0893c4fec52ebb408fdcd7f7d63962caace9180d8f81b14ece"}, + {file = "propcache-0.5.4-cp315-cp315t-musllinux_1_2_armv7l.whl", hash = "sha256:420162a77f94eb1cf5ef7893f500016dabd548e73de956785a1dd899cc73006a"}, + {file = "propcache-0.5.4-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:3eb2e820e8e2101407da93f17c57cbb7d225461955fc60105daaba14cd421ee2"}, + {file = "propcache-0.5.4-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:13e52b6e0bde97dee98ab66552dbff2931649c96f1ac432eac299fe689ec373b"}, + {file = "propcache-0.5.4-cp315-cp315t-musllinux_1_2_s390x.whl", hash = "sha256:12682126712ddc19b70ff819debbd279e58adf1f0c8f8f8138c18ade2044b284"}, + {file = "propcache-0.5.4-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:3af0c8642b2da4815d86e631232ac8286e17644fad907c19508aa8e7cb4ba8ad"}, + {file = "propcache-0.5.4-cp315-cp315t-win32.whl", hash = "sha256:1df8d8561b21465c5dd56110a01caf897e026d065b4b84e98a488209094272ec"}, + {file = "propcache-0.5.4-cp315-cp315t-win_amd64.whl", hash = "sha256:02c0a34f16889cf800f10f0247a564d8ce6eeab6ffcd7c87198f769067eb8432"}, + {file = "propcache-0.5.4-cp315-cp315t-win_arm64.whl", hash = "sha256:dc4242ca653c9b30ab51c5f8193323e7bc0928f897ee9103201e59a43abcb72e"}, + {file = "propcache-0.5.4-py3-none-any.whl", hash = "sha256:62c60aec739ed00124573cce1178138fd690c7676352d67a37328c1cf51d7468"}, + {file = "propcache-0.5.4.tar.gz", hash = "sha256:ff6b113f50bc066a698db5d944d2c6dc7507168dd3341e255a8892fd0715a558"}, ] [[package]] @@ -1568,14 +1585,14 @@ typing-extensions = ">=4.15.0" [[package]] name = "urllib3" -version = "2.7.0" +version = "2.8.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897"}, - {file = "urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c"}, + {file = "urllib3-2.8.0-py3-none-any.whl", hash = "sha256:0cf3cae568d36aa9576b28dfb35f11328f1cb974ca7647d9475ebb86c75ac6e3"}, + {file = "urllib3-2.8.0.tar.gz", hash = "sha256:63bf2ead4c879426ebf22ef2a781eeb4aa3b4ae798a0435506f8687fd5bb9b63"}, ] [package.extras] @@ -1586,117 +1603,151 @@ zstd = ["backports-zstd (>=1.0.0) ; python_version < \"3.14\""] [[package]] name = "yarl" -version = "1.24.5" +version = "1.25.1" description = "Yet another URL library" optional = true python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "yarl-1.24.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88f50c94e21a0a7f14042c015b0eba1881af78562e7bf007e0033e624da59750"}, - {file = "yarl-1.24.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6efbccc3d7f75d5b03105172a8dc86d82ba4da86817952529dd93185f4a88be2"}, - {file = "yarl-1.24.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0ebfaffe1a16cb72141c8e09f18cc76856dbe58639f393a4f2b26e474b96b871"}, - {file = "yarl-1.24.5-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8ac73abdc7ab75610f95a8fd994c6457e87752b02a63987e188f937a1fc180f0"}, - {file = "yarl-1.24.5-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4d97a951a81039050e45f04e96689b58b8243fa5e62aa14fe67cb6075300885e"}, - {file = "yarl-1.24.5-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fe7b7bb170daccbba19ad33012d2b15f1e7942296fd4d45fc1b79013da8cc0f2"}, - {file = "yarl-1.24.5-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:89a1bbb58e0e3f7a283653d854b1e95d65e5cfd4af224dac5f02629ec1a3e621"}, - {file = "yarl-1.24.5-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7fa5e51397466ea7e98de493fa2ff1b8193cfef8a7b0f9b4842f92d342df0dba"}, - {file = "yarl-1.24.5-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4103b77b8a8225e413107d2349b65eb3c1c52627b5cc5c3c4c1c6a798b218950"}, - {file = "yarl-1.24.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f9f3e9c8a9ecffa57bef8fb4fa19e5fa4d2d8307cf6bac5b1fca5e5860f4ba00"}, - {file = "yarl-1.24.5-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c0ebc836c47a6477e182169c6a476fc691d12b518894bf7dd2572f0d59f1c7ed"}, - {file = "yarl-1.24.5-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:96d30286dd02679e32a39aa8f0b7498fc847fcda46cfc09df5513e82ce252440"}, - {file = "yarl-1.24.5-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:fd8c81f346b58f45818d09ea11db69a8d5fd34a224b79871f6d44f12cd7977b1"}, - {file = "yarl-1.24.5-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:5c55256dee8f4b27bfbf636c8363383c7c8db7890c7cba5217d7bd5f5f21dab6"}, - {file = "yarl-1.24.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9f4d8cf085a4c6a40fb97ea0f46938a8df43c85d31f9d45e2a8867ea9293790d"}, - {file = "yarl-1.24.5-cp310-cp310-win_amd64.whl", hash = "sha256:240cbec09667c1fed4c6cd0060b9ec57332427d7441289a2ed8875dc9fb2b224"}, - {file = "yarl-1.24.5-cp310-cp310-win_arm64.whl", hash = "sha256:8a6987eaad834cb32dd57d9d582225f0054a5d1af706ccfbbdba735af4927e13"}, - {file = "yarl-1.24.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2c1fe720934a16ea8e7146175cba2126f87f54912c8c5435e7f7c7a51ef808d3"}, - {file = "yarl-1.24.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c687ed078e145f5fd53a14854beff320e1d2ab76df03e2009c98f39a0f68f39a"}, - {file = "yarl-1.24.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:709f1efed56c4a145793c046cd4939f9959bcd818979a787b77d8e09c57a0840"}, - {file = "yarl-1.24.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:874019bd513008b009f58657134e5d0c5e030b3559bd0553976837adf52fe966"}, - {file = "yarl-1.24.5-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a4582acf7ef76482f6f511ebaf1946dae7f2e85ec4728b81a678c01df63bd723"}, - {file = "yarl-1.24.5-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2cabe6546e41dabe439999a23fcb5246e0c3b595b4315b96ef755252be90caeb"}, - {file = "yarl-1.24.5-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:17f57620f5475b3c69109376cc87e42a7af5db13c9398e4292772a706ff10780"}, - {file = "yarl-1.24.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:570fec8fbd22b032733625f03f10b7ff023bc399213db15e72a7acaef28c2f4e"}, - {file = "yarl-1.24.5-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5fede79c6f73ff2c3ef822864cb1ada23196e62756df53bc6231d351a49516a2"}, - {file = "yarl-1.24.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8ccf9aca873b767977c73df497a85dbedee4ee086ae9ae49dc461333b9b79f58"}, - {file = "yarl-1.24.5-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:ad5d8201d310b031e6cd839d9bac2d4e5a01533ce5d3d5b50b7de1ef3af1de61"}, - {file = "yarl-1.24.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:841f0852f48fefea3b12c9dfec00704dfa3aef5215d0e3ce564bb3d7cd8d57c6"}, - {file = "yarl-1.24.5-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:9baafc71b04f8f4bb0703b21d6fc9f0c30b346c636a532ff16ec8491a5ea4b1f"}, - {file = "yarl-1.24.5-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d897129df1a22b12aeed2c2c98df0785a2e8e6e0bde87b389491d0025c187077"}, - {file = "yarl-1.24.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dd625535328fd9882374356269227670189adfcc6a2d90284f323c05862eecbd"}, - {file = "yarl-1.24.5-cp311-cp311-win_amd64.whl", hash = "sha256:f4239bbec5a3577ddb49e4b50aeb32d8e5792098262ae2f63723f916a29b1a25"}, - {file = "yarl-1.24.5-cp311-cp311-win_arm64.whl", hash = "sha256:3ac6aff147deb9c09461b2d4bbdf6256831198f5d8a23f5d37138213090b6d8a"}, - {file = "yarl-1.24.5-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d693396e5aea78db03decd60aec9ece16c9b40ba00a587f089615ff4e718a81d"}, - {file = "yarl-1.24.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3363fcc96e665878946ad7a106b9a13eac0541766a690ef287c0232ac768b6ec"}, - {file = "yarl-1.24.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9d399bdcfb4a0f659b9b3788bbc89babe63d9a6a65aacdf4d4e7065ff2e6316c"}, - {file = "yarl-1.24.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90333fd89b43c0d08ac85f3f1447593fc2c66de18c3d6378d7125ea118dc7a54"}, - {file = "yarl-1.24.5-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:665b0a2c463cc9423dd647e0bfd9f4ccc9b50f768c55304d5e9f80b177c1de12"}, - {file = "yarl-1.24.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e006d3a974c4ee19512e5f058abedb6eef36a5e553c14812bdeba1758d812e6d"}, - {file = "yarl-1.24.5-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e7d42c531243450ef0d4d9c172e7ed6ef052640f195629065041b5add4e058d1"}, - {file = "yarl-1.24.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f08c7513ecef5aad65687bfdf6bc601ae9fccd04a42904501f8f7141abad9eb9"}, - {file = "yarl-1.24.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6c95b17fe34ed802f17e205112e6e10db92275c34fee290aa9bdc55a9c724027"}, - {file = "yarl-1.24.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:56b149b22de33b23b0c6077ab9518c6dcb538ad462e1830e68d06591ccf6e38b"}, - {file = "yarl-1.24.5-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:a8fe66b8f300da93798025a785a5b90b42f3810dc2b72283ff84a41aaaebc293"}, - {file = "yarl-1.24.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:377fe3732edbaf78ee74efdf2c9f49f6e99f20e7f9d2649fda3eb4badd77d76e"}, - {file = "yarl-1.24.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:e8ffa78582120024f476a611d7befc123cee59e47e8309d470cf667d806e613b"}, - {file = "yarl-1.24.5-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:daba5e594f06114e37db186efd2dd916609071e59daca901a0a2e71f02b142ce"}, - {file = "yarl-1.24.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:65be18ec59496c13908f02a2472751d9ef840b4f3fb5726f129306bf6a2a7bba"}, - {file = "yarl-1.24.5-cp312-cp312-win_amd64.whl", hash = "sha256:a929d878fec099030c292803b31e5d5540a7b6a31e6a3cc76cb4685fc2a2f51b"}, - {file = "yarl-1.24.5-cp312-cp312-win_arm64.whl", hash = "sha256:7ce27823052e2013b597e0c738b13e7e36b8ccb9400df8959417b052ab0fd92c"}, - {file = "yarl-1.24.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:79af890482fc94648e8cde4c68620378f7fef60932710fa17a66abc039244da2"}, - {file = "yarl-1.24.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:46c2f213e23a04b93a392942d782eb9e413e6ef6bf7c8c53884e599a5c174dcb"}, - {file = "yarl-1.24.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92ab3e11448f2ff7bf53c5a26eff0edc086898ec8b21fb154b85839ce1d88075"}, - {file = "yarl-1.24.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ebb0ec7f17803063d5aeb982f3b1bd2b2f4e4fae6751226cbd6ba1fcfe9e63ff"}, - {file = "yarl-1.24.5-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:82632daed195dcc8ea664e8556dc9bdbd671960fb3776bd92806ce05792c2448"}, - {file = "yarl-1.24.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:53e549287ef628fecba270045c9701b0c564563a9b0577d24a4ec75b8ab8040f"}, - {file = "yarl-1.24.5-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fcd3b77e2f17bbe4ca56ec7bcb07992647d19d0b9c05d84886dcd6f9eb810afd"}, - {file = "yarl-1.24.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d46b86567dd4e248c6c159fcbcdcce01e0a5c8a7cd2334a0fff759d0fa075b16"}, - {file = "yarl-1.24.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7f72c74aa99359e27a2ee8d6613fefa28b5f76a983c083074dfc2aaa4ab46213"}, - {file = "yarl-1.24.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3f45789ce415a7ec0820dc4f82925f9b5f7732070be1dec1f5f23ec381435a24"}, - {file = "yarl-1.24.5-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:6e73e7fe93f17a7b191f52ec9da9dd8c06a8fe735a1ecbd13b97d1c723bff385"}, - {file = "yarl-1.24.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4a36f9becdd4c5c52a20c3e9484128b070b1dcfc8944c006f3a528295a359a9c"}, - {file = "yarl-1.24.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:7bcbe0fcf850eae67b6b01749815a4f7161c560a844c769ad7b48fcd99f791c4"}, - {file = "yarl-1.24.5-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:24e861e9630e0daddcb9191fb187f60f034e17a4426f8101279f0c475cd74144"}, - {file = "yarl-1.24.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9335a099ad87287c37fe5d1a982ff392fa5efe5d14b40a730b1ec1d6a41382b4"}, - {file = "yarl-1.24.5-cp313-cp313-win_amd64.whl", hash = "sha256:2dbe06fc16bc91502bca713704022182e5729861ae00277c3a23354b40929740"}, - {file = "yarl-1.24.5-cp313-cp313-win_arm64.whl", hash = "sha256:6b8536851f9f65e7f00c7a1d49ba7f2be0ffe2c11555367fc9f50d9f842410a1"}, - {file = "yarl-1.24.5-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:2729fcfc4f6a596fb0c50f32090400aa9367774ac296a00387e65098c0befa76"}, - {file = "yarl-1.24.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ff330d3c30db4eb6b01d79e29d2d0b407a7ecad39cfd9ec993ece57396a2ec0d"}, - {file = "yarl-1.24.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e42d75862735da90e7fc5a7b23db0c976f737113a54b3c9777a9b665e9cbff75"}, - {file = "yarl-1.24.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a3732e66413163e72508da9eff9ce9d2846fde51fae45d3605393d3e6cd303e9"}, - {file = "yarl-1.24.5-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5b8ee53be440a0cffc991a27be3057e0530122548dbe7c0892df08822fce5ede"}, - {file = "yarl-1.24.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:af3aefa655adb5869491fa907e652290386800ae99cc50095cba71e2c6aefdca"}, - {file = "yarl-1.24.5-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2120b96872df4a117cde97d270bac96aea7cc52205d305cf4611df694a487027"}, - {file = "yarl-1.24.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:66410eb6345d467151934b49bfa70fb32f5b35a6140baa40ad97d6436abea2e9"}, - {file = "yarl-1.24.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4af7b7e1be0a69bee8210735fe6dcfc38879adfac6d62e789d53ba432d1ffa41"}, - {file = "yarl-1.24.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa139875ff98ab97da323cfadfaff08900d1ad42f1b5087b0b812a55c5a06373"}, - {file = "yarl-1.24.5-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:0055afc45e864b92729ac7600e2d102c17bef060647e74bca75fa84d66b9ff36"}, - {file = "yarl-1.24.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f0e466ed7511fe9d459a819edbc6c2585c0b6eabde9fa8a8947552468a7a6ef0"}, - {file = "yarl-1.24.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:f141474e85b7e54998ec5180530a7cda99ab29e282fa50e0756d89981a9b43c5"}, - {file = "yarl-1.24.5-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:e2935f8c39e3b03e83519292d78f075189978f3f4adc15a78144c7c8e2a1cba5"}, - {file = "yarl-1.24.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9d1216a7f6f77836617dba35687c5b78a4170afc3c3f18fc788f785ba26565c4"}, - {file = "yarl-1.24.5-cp314-cp314-win_amd64.whl", hash = "sha256:5ba4f78df2bcc19f764a4b26a8a4f5049c110090ad5825993aacb052bf8003ad"}, - {file = "yarl-1.24.5-cp314-cp314-win_arm64.whl", hash = "sha256:9e4e16c73d717c5cf27626c524d0a2e261ad20e46932b2670f64ad5dde23e26f"}, - {file = "yarl-1.24.5-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:e1ae548a9d901adca07899a4147a7c826bbcc06239d3ce9a59f57886a28a4c88"}, - {file = "yarl-1.24.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:ff405d91509d88e8d44129cd87b18d70acd1f0c1aeabd7bc3c46792b1fe2acba"}, - {file = "yarl-1.24.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:47e98aab9d8d82ff682e7b0b5dded33bf138a32b817fcf7fa3b27b2d7c412928"}, - {file = "yarl-1.24.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f0a658a6d3fafee5c6f63c58f3e785c8c43c93fbc02bf9f2b6663f8185e0971f"}, - {file = "yarl-1.24.5-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4377407001ca3c057773f44d8ddd6358fa5f691407c1ba92210bd3cf8d9e4c95"}, - {file = "yarl-1.24.5-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7c0494a31a1ac5461a226e7947a9c9b78c44e1dc7185164fa7e9651557a5d9bc"}, - {file = "yarl-1.24.5-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a7cff474ab7cd149765bb784cf6d78b32e18e20473fb7bda860bce98ab58e9da"}, - {file = "yarl-1.24.5-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cbb833ccacdb5519eff9b8b71ee618cc2801c878e77e288775d77c3a2ced858a"}, - {file = "yarl-1.24.5-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:82f75e05912e84b7a0fe57075d9c59de3cb352b928330f2eb69b2e1f54c3e1f0"}, - {file = "yarl-1.24.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:16a2f5010280020e90f5330257e6944bc33e73593b136cc5a241e6c1dc292498"}, - {file = "yarl-1.24.5-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:ffcd54362564dc1a30fb74d8b8a6e5a6b11ebd5e27266adc3b7427a21a6c9104"}, - {file = "yarl-1.24.5-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0465ec8cedc2349b97a6b595ace64084a50c6e839eca40aa0626f38b8350e331"}, - {file = "yarl-1.24.5-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:4db9aecb141cb7a5447171b57aa1ed3a8fee06af40b992ffc31206c0b0121550"}, - {file = "yarl-1.24.5-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:f540c013589084679a6c7fac07096b10159737918174f5dfc5e11bf5bca4dfe6"}, - {file = "yarl-1.24.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a61834fb15d81322d872eaafd333838ae7c9cea84067f232656f75965933d047"}, - {file = "yarl-1.24.5-cp314-cp314t-win_amd64.whl", hash = "sha256:5c88e5815a49d289e599f3513aa7fde0bc2092ff188f99c940f007f90f53d104"}, - {file = "yarl-1.24.5-cp314-cp314t-win_arm64.whl", hash = "sha256:cf139c02f5f23ef6532040a30ff662c00a318c952334f211046b8e60b7f17688"}, - {file = "yarl-1.24.5-py3-none-any.whl", hash = "sha256:a33700d13d9b7d84fd10947b09ff69fb9a792e519c8cb9764a3ca70baa6c23a7"}, - {file = "yarl-1.24.5.tar.gz", hash = "sha256:e81b83143bee16329c23db3c1b2d82b29892fcbcb849186d2f6e98a5abe9a57f"}, + {file = "yarl-1.25.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:142c06c4d6a35ee3ec5da08499805e879cb3ca7c1fbfbecb0140fe72403818d6"}, + {file = "yarl-1.25.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:24ce942011a61953e7d313438038f4d32ff21387b775f58a957f7a07dd55ef95"}, + {file = "yarl-1.25.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e23c82b63cd7652fc24d33ed6cc17099d607aa3b4fc4ddc75e95062f3d82df4"}, + {file = "yarl-1.25.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8ee202350cf57abf0e9502a41601841019c25d3db7ff52d980aaf31446254059"}, + {file = "yarl-1.25.1-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5df89f769cc8ff94c3d7e7603386fba309d25ce5240132d26c15baa8d0e96c4c"}, + {file = "yarl-1.25.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:83e9f4a25085bd4b7214701a0794ff1f50fc633ffb8bdfebf07abdd81c2db126"}, + {file = "yarl-1.25.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e636b64d24fd9c38053c5e389a1174c66361fa49dcfd220f4dd35b4abde7cb89"}, + {file = "yarl-1.25.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e5637ca8d0bd7fb72648a6c7934af4baaccb697657f7438c9d264fc2abb8b0b1"}, + {file = "yarl-1.25.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:683e362b8ba453080f7489c66f4ea794e751c35b72e7eab3575ef784c2fbc7fb"}, + {file = "yarl-1.25.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:df23df54b5114a17c2d0ef192433e2e5a9f0c5178c32375e90b7cfc965f349d0"}, + {file = "yarl-1.25.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:f53dcd26694f148f738edc052b5a69234833e739f10f4c3287bdfd8ec0f7b326"}, + {file = "yarl-1.25.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:b8075fe90bc08e40b8b8a1874fab42ee4c7b56af05c5886e9cc841397f916908"}, + {file = "yarl-1.25.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:a8c2b841478068440d8b733005d13a5ef535b9928cbc05f17182d410f32ba449"}, + {file = "yarl-1.25.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:ca32926d7d77bcc8838425c4c95e040a3ace1cb7dfdae599013458dcda2607ca"}, + {file = "yarl-1.25.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:192a866877a49993949ef1975864ad8728bea28ee810f6abe1a0729c2b500426"}, + {file = "yarl-1.25.1-cp310-cp310-win_amd64.whl", hash = "sha256:3f4d48a6112712973e676bd792121fee470e432d749177162d9949d5c9460a1b"}, + {file = "yarl-1.25.1-cp310-cp310-win_arm64.whl", hash = "sha256:48796ea00a303961507dc6c8437c4b325a6fc3f95f7c36c71b91ea9a8150963c"}, + {file = "yarl-1.25.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9d693bf4bf534e9ba3ae2780cfd577f5135629f7b5ac653490859d0b77864865"}, + {file = "yarl-1.25.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ab2054c5531af2a9ba7b69b8ec91e4f884420e83a8c5e579b013084cb57e5e5d"}, + {file = "yarl-1.25.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:564fdc7085d2245ab84f88882fdb1d6ac0723124bff6ded35bfb1c00f812630d"}, + {file = "yarl-1.25.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:acae6b45d1ace09b6ba3876da43b88366ef368f73b988c7f57e14231753d4420"}, + {file = "yarl-1.25.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:1fb2a01ba8cd9c5d2c5dc1ec35e0fc951d04b4f037541d4ac090c993ce58b3d7"}, + {file = "yarl-1.25.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e92b6bcc741b86d67606c40d3cb9c7cc8e6c737f81e31f4a94efc204456c92e3"}, + {file = "yarl-1.25.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:72c34ac7ad4314c19362d5ce27626dcc8429bd30bbf8c179f4234078851f9492"}, + {file = "yarl-1.25.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5add7b4ca7afeea91d52e4d4e4db3b1fe9885b71f07054560d8c4296b7441a2"}, + {file = "yarl-1.25.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:def538065f9e4d4cf1ae164bd59aba00dfa84f03923e0de4c3788f252d6bcd17"}, + {file = "yarl-1.25.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a191bfdb30a79b98e5d175d75285f9fcb78bf0e46ba5efda042e1c72071a0de"}, + {file = "yarl-1.25.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:71f42c5b9a948c113bbdebfa544598321431d064ff959d32e99b1feb61d68345"}, + {file = "yarl-1.25.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:72849d892954be4d09e569b8b831ac39ce58417fedc767d4308a0fe542018a40"}, + {file = "yarl-1.25.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:efb01a106f971cb3752856bca2318bbdf7f01bd8823779c461586cbe5ffd5258"}, + {file = "yarl-1.25.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:a1daf47cd95a7c3a63456336bc5aaa8c86dd3a47d07ed3d0e76132ae4666a5a1"}, + {file = "yarl-1.25.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9489e6abf47ba37f332075a91444c7cfedb03e6ce99fbb2f116bfe1ce810da3b"}, + {file = "yarl-1.25.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7306dee25b8a0e737363f347362b875094b4dc4e367311470656ae420fdbf8e"}, + {file = "yarl-1.25.1-cp311-cp311-win_arm64.whl", hash = "sha256:abb1384477f5901d436b5d2e5465954de46ea6098f59163d243660b5c4461d35"}, + {file = "yarl-1.25.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:94d7aa6debf92a1dd14cb5280b083a764169a13cfb23a452111160274ed989f4"}, + {file = "yarl-1.25.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:83d4a37e4b95da4d8bda930d6d35b75b4cdadbacbb4980cae290ea3100b5d51d"}, + {file = "yarl-1.25.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e029648f9c951db30e98a7d7ec90835db88ec4b32820efe2a9bdc2287e032eb6"}, + {file = "yarl-1.25.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4d781294bb815ecb5ea57ff6bbf8038e0a31a95fdf3e1788f66e0dc100d64b58"}, + {file = "yarl-1.25.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e12c538e00e7c1b286a07061046b90e8124e6a9793efae2c70db6a4aad07faad"}, + {file = "yarl-1.25.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7e4de3ac4adbad3d0bc7c6f4360a7dbff5de2f15e3b723be3198074e17fd9c40"}, + {file = "yarl-1.25.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:419f392a1da624877975709e3864dfe833af6cc7671b39318086d456e288380c"}, + {file = "yarl-1.25.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c6f117789d22dce188e5754e8bc65b7e6ebf8cb73963b9fa761f672a5883769d"}, + {file = "yarl-1.25.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:80e47012e730da131c9f059c80936783f9659aae22dc31c03c0595590d11ed54"}, + {file = "yarl-1.25.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e80f557716fd765439577131e526b8942ffc2c07bdbc5e39fa62f660ba1e963f"}, + {file = "yarl-1.25.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:f61964f235a43738bfac50da46fc4254943a7eea3051aeb0b6fc7c992c29fadc"}, + {file = "yarl-1.25.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e546fe1d4a93ebc2910f0d768baff19faa09843ab3f2036a67ed6e69fae4419d"}, + {file = "yarl-1.25.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:cce0727fd5ac04d372fa9bbfde9febc2bcf209aadfcf0468e45dec72719895d1"}, + {file = "yarl-1.25.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af4ea5b37403ef4e30f3927eaed540db942bde01d8d3ff083527c0704d1c9c68"}, + {file = "yarl-1.25.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:68782fdb4027b8d1eee25ec35e9a6db05e863b899eb0310b3a33b6c3fef55707"}, + {file = "yarl-1.25.1-cp312-cp312-win_amd64.whl", hash = "sha256:7d575b54cb3863ef9bc290ea4b009999d55dc237326131e4853cf33e888fee03"}, + {file = "yarl-1.25.1-cp312-cp312-win_arm64.whl", hash = "sha256:bc3ac7bf569f6b64dad04dd7808c7872dae8a97df657856eac05e9b7e3614a85"}, + {file = "yarl-1.25.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:25868beca8b6765f8f7d0e11fe6dd7c66dd4b0793b9500286d20cc92352126a5"}, + {file = "yarl-1.25.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:10b2fd95332f0d716d5eee3c9fb2ce8eada19082de7fee83d32e37992fd75c26"}, + {file = "yarl-1.25.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0f12afda4eea8c8994a76d4df1875c765194f5fbe8a9d197929ea303caee29ec"}, + {file = "yarl-1.25.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:14b79a30a93a3ce2e8832603fd0ab780ada281b0ba5110b519a634f2d7d7d1fc"}, + {file = "yarl-1.25.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4bd6340d20ae2c7ca719b87b426e808e90743b676d05d4c26c4fb5ca71f41184"}, + {file = "yarl-1.25.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:126a2533570c554719ca40a1288fdee1700b6bc82e7131aa69fa85252d92e651"}, + {file = "yarl-1.25.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a3faadac7d812ddac258feb57b9846b60c1b437c4f4b9ad42595c6f6fe4390df"}, + {file = "yarl-1.25.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:be80550d9bfe83d9b62398a37081a90434e6df2d978ec345c3d2820de6beddab"}, + {file = "yarl-1.25.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e07595c7d6f4db270ceede356a1bd1c07a34f1c26f958d1ed0cd7b48e0d2bba3"}, + {file = "yarl-1.25.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:eb96ed1ae6c7d072d60840c0434aef07a2df611812810807fbc54263a6053e9a"}, + {file = "yarl-1.25.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:3feb99222553a8cbedfa52c2f59dd84c3f50d5b582c728d522caf8d72769a54b"}, + {file = "yarl-1.25.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:a2ed0ba415ccdf08f14bf544cb78346d0f76086707ffee24921a2c84dbf1305a"}, + {file = "yarl-1.25.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2b49375d22299b0a834c2bca72f39aaecc270d96fb24c30424899676f487b22a"}, + {file = "yarl-1.25.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:ef74070ac553c59eb4f04258722066d6c6135b7baa03b2e9f2da65c096e96d98"}, + {file = "yarl-1.25.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0a66db89ea473abeac4b70523cafd94db3772380e565f9d28af7a179b7af71fa"}, + {file = "yarl-1.25.1-cp313-cp313-win_amd64.whl", hash = "sha256:1f51020b2eb8a003c84925638ec63c21a750a4bddd3a22ec8eac6a742dadf1b9"}, + {file = "yarl-1.25.1-cp313-cp313-win_arm64.whl", hash = "sha256:b10dd0557ba422715b5206b3743192135a6022acca8baec51aa127d0a75db8fe"}, + {file = "yarl-1.25.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:a9ca696eb02e5c02a8afd872ada510eba9b7fe6e68b9572c2e9a9b1941e31e2e"}, + {file = "yarl-1.25.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a5877f2255aab518ebe528289037699201d5dc5f045f2396cb30aa02db22f57f"}, + {file = "yarl-1.25.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7a5c3115595995779ee21f2567035793911c3802a43c74f3fbb0314929ec67ac"}, + {file = "yarl-1.25.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77e5099b99b37f3cf79c246998ca9f7313a78054cd1809ec46bc1afad47e1c4c"}, + {file = "yarl-1.25.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6efaf45df6a849cef613a03a94c845647456662f85438c886bb67a9c027c8c2c"}, + {file = "yarl-1.25.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d5f90e44653c4e0f78501ed9bb7d3fce835a8d62b7c6ed0cb16557534087e743"}, + {file = "yarl-1.25.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:632da579b2d879f6bad20f2cfa35ded1efe2f4f77f8abb26a6234a5b236acd2f"}, + {file = "yarl-1.25.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:30eec96e8a91bd588ce897c9543f6d5d8d34b28fbcba28a4dedf20ebeae9fe57"}, + {file = "yarl-1.25.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:12b6bc4906e11f5e1a1cdcb12296e7afbd366c783cc8073403cd2fb74334e453"}, + {file = "yarl-1.25.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9d6ed3d17bccce4c05343e1ca8da13bc5c02c812a4e7282ddd05e8769322d3fc"}, + {file = "yarl-1.25.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:f38a70074041d3b7e138e452799f5174198bae5bd5ab2000917badf403908c5f"}, + {file = "yarl-1.25.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:4ca89e4e21854ed27ec753297dde84b16c9f8e53b14a4866fb44457d643c19f8"}, + {file = "yarl-1.25.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:1ab7618921a93767387a4b83776f751588f5b5ae9bb5bc96620e2e2e00bca868"}, + {file = "yarl-1.25.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:0ae12ff2b805fa02c4dab838005caef735e39986322698c48588d3beacb65c62"}, + {file = "yarl-1.25.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:90c30ed53546da833c700115c0064c22120d1b1560f474699fd31f22dd668233"}, + {file = "yarl-1.25.1-cp314-cp314-win_amd64.whl", hash = "sha256:acfa7e22aa6c6e7a5996a41d275bfa01efa7ea56ab890590280e9063e2cf5c1b"}, + {file = "yarl-1.25.1-cp314-cp314-win_arm64.whl", hash = "sha256:8e7d98cdbb6d71e726f7d525952867096053d1f290dd4e3c50d7d313a136f414"}, + {file = "yarl-1.25.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:d21f0fa80a02d05299207eeaafef345d812ace96d5306e4ef265e1d419a615fa"}, + {file = "yarl-1.25.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:17c9877a89fb6e2bca6f9087eb24cd7fb434653946ef5075e470d23d49b52287"}, + {file = "yarl-1.25.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:29273edf1530e397bd07cb784db1fbe0d2590b77569f2e24679a9c0a2d763b94"}, + {file = "yarl-1.25.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b7abffdf37af1cec6a2ad69b827aa84320db5894791bc8ed932dc93fb274b7e9"}, + {file = "yarl-1.25.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2239a02249d9326655419e0168a28ca9008938eaab31dc29fc875c217927a6c0"}, + {file = "yarl-1.25.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:664ec6a520b74a1df2810666eb67695fcb77fa663e6ea0a25aaf2e529cb24dfa"}, + {file = "yarl-1.25.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4f1c91f5a5980a937ff8e238e98e6897e1ad74a4b1e2c0d68c73b5ffbb3f5c0b"}, + {file = "yarl-1.25.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7c88edaec8c349ad4c5ad4c486a3defcc4b80ceb2f074436ffa0a87caf5e76a6"}, + {file = "yarl-1.25.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:35dcbea443fafb3eece757ad4e514560ddeb6c34cfae1582c620d7b293d7feee"}, + {file = "yarl-1.25.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:882569ff613758cac762a457a5d72d6e211b28d4bcfea89d1d71ea942b02eac0"}, + {file = "yarl-1.25.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:d0f1489233a254bb3643d2f05de7d59019254d81daeca6b9162fe9edef57e0c7"}, + {file = "yarl-1.25.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:f41753a76f4f63927d03a0d8ba8f5ce0f2083bec29a8cfaccc55371b1564b96b"}, + {file = "yarl-1.25.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:8fb0eb4955adf0579001581f2f71a126e8781ba61bcd120f127b0401163c6c2d"}, + {file = "yarl-1.25.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:a1e32763e641a1566507d90a8d3b19bfc3cc04a9d4e5ae3e32189874ed4b58a3"}, + {file = "yarl-1.25.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:65b5b2066651b7432d389e9799d979c703bcc6ef44266bb8153ef54e91e4aab3"}, + {file = "yarl-1.25.1-cp314-cp314t-win_amd64.whl", hash = "sha256:734f6e5400352ac4254456003d462866c684703570929cff7a7bde015d0cb371"}, + {file = "yarl-1.25.1-cp314-cp314t-win_arm64.whl", hash = "sha256:287e99ff5aa4dc1c7630bfc683ded6f106d756c99dec432a2d7f197a784f51c6"}, + {file = "yarl-1.25.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:9b1bdaae98bc016825dd3c9d8ee1832f829b3341f9cc6ebd1a1b0a7fef7367cc"}, + {file = "yarl-1.25.1-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:e7011b8fb8c4054bf0c12e5edc6cd83778b0028e99ce59b18586ed036f92cfdc"}, + {file = "yarl-1.25.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:f074e8d4aa0a5798920ddb6de3d08b228c614ff3724c3e8bd7577f4bafea867b"}, + {file = "yarl-1.25.1-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d42e7e3ca399555578b4d617e3a6ecf13371b3743a115995fa010c7bf341459"}, + {file = "yarl-1.25.1-cp315-cp315-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:aa4ed3dd308548f9e707d9caaf005d2d7f8c1e7868f858dfeb47fe76e16b391d"}, + {file = "yarl-1.25.1-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:42a66563d8cc056ee32e6191e05097a7b2b3bc302e0bc3133daf8710eb18bd26"}, + {file = "yarl-1.25.1-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:98d370568f393215d605304cdb77b3d5539bd192c75b623c7304c42c8d6d8273"}, + {file = "yarl-1.25.1-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23bf5b403c879a54964e0feac7285688e04bb220074878d737d331522da0a5bf"}, + {file = "yarl-1.25.1-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d45673badd08456d0340e9364eddafe1c53a9d2896424294de4d7dd71ad3ee57"}, + {file = "yarl-1.25.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:0136d640dfa9b0523853e411430a99f8a91eca85774c6420285a33b755bc6de3"}, + {file = "yarl-1.25.1-cp315-cp315-musllinux_1_2_armv7l.whl", hash = "sha256:59ba3a6e1aa8cfe5adf4bd270fd965db21955401b7ca6f1696010c55ed4daec2"}, + {file = "yarl-1.25.1-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:87796fedc3ba97ec14fab55acb48584276e6c1e4c1e89c422bda62c838e754a9"}, + {file = "yarl-1.25.1-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:bd0912757081f89b107d6c00b2ff8a194401b0b87eadcf4481de2b865a8fd44f"}, + {file = "yarl-1.25.1-cp315-cp315-musllinux_1_2_s390x.whl", hash = "sha256:b51c159a9794633f5e0db7ecec7b2b6e3734eca1f5d17dc989ff3552a43ff78b"}, + {file = "yarl-1.25.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:319e070a01db9920fb63761843f96a104c8e2b9427266731810dc1e22595b17c"}, + {file = "yarl-1.25.1-cp315-cp315-win_amd64.whl", hash = "sha256:a2059a2d891bd156bc5184e7ab7a56e78a84dfcfdeac8c501b552533ad1c36ee"}, + {file = "yarl-1.25.1-cp315-cp315-win_arm64.whl", hash = "sha256:a78b50b4f7918a3de71105d5c0b93bbc57bb8339a4d03a9dfd449f9068e76f3d"}, + {file = "yarl-1.25.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:b5402a340723fa7da00b5cff987ddab61276be6d11251ea71ae02bcac54890d8"}, + {file = "yarl-1.25.1-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:eda19ea5ee88742f47a2340816e6f2d40b53bed3ab5b69794769f36af9f35bb4"}, + {file = "yarl-1.25.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:75baa6cf9b6d1c52f3e111a130e202fd8cf0a5b3a066c3f73d615e885092e4ec"}, + {file = "yarl-1.25.1-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dbcef5a9119ef653653132cccaf999b30a0af6f33bb0a4ba80bec30056868487"}, + {file = "yarl-1.25.1-cp315-cp315t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7efc9f082dfed77c316edffa9deb52888e1bc6789171887cc1f68e06d65465c8"}, + {file = "yarl-1.25.1-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fe01645169a2112aa1d4ebc3e4c5f029c5c8f97adfc32e5d37c993b39a994d75"}, + {file = "yarl-1.25.1-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1c557dfd5e3db046053a0bdc72261ade790ebe8e2c7a41b36b0ca1f14cb95f3"}, + {file = "yarl-1.25.1-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8ce4d6ccafb33d39bd78444612d14938ead674c25702ded2ee9c54a47735d225"}, + {file = "yarl-1.25.1-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:80a063f8297fc796296f00f100be520f209b23dc98f93ce8eba6ee7122598209"}, + {file = "yarl-1.25.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:7cb414a73e21a7ab58254926073f2930cb22f5b4314ea4260a687e2b3fd4dce3"}, + {file = "yarl-1.25.1-cp315-cp315t-musllinux_1_2_armv7l.whl", hash = "sha256:85a18376073f8a39aa07be34f9fc77e2869aa72c55c441efdd2cf79a0407504d"}, + {file = "yarl-1.25.1-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:77716e245c90f058466a05e6a465bb8600f767a8f4b18b4d40f3aff958e5f73c"}, + {file = "yarl-1.25.1-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:1e80dcf1446e1b080b1932b0d103c464a04112f5bc31f0f983ad418172063cde"}, + {file = "yarl-1.25.1-cp315-cp315t-musllinux_1_2_s390x.whl", hash = "sha256:bdc8d8b8c22e9e43ac68316b5e6cf083dec537f4ec213cb4aa967b583bc3fa64"}, + {file = "yarl-1.25.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:dfbf531053a0935f2e871bcd4753f90313688772ff8c017f5ea402e315a78c1f"}, + {file = "yarl-1.25.1-cp315-cp315t-win_amd64.whl", hash = "sha256:b13b88747769537f3d32e89e3a735da10c0a9e35d7322928c701b5f93d3afffd"}, + {file = "yarl-1.25.1-cp315-cp315t-win_arm64.whl", hash = "sha256:783dd1467083f4d3f7722ad6a313f24c173e7571372738fcb7a6e6d1ba48df25"}, + {file = "yarl-1.25.1-py3-none-any.whl", hash = "sha256:681c758b0490f9e96b78e5fa8e8dc6e648e9185bb6eaebe73183c33ea0c445f3"}, + {file = "yarl-1.25.1.tar.gz", hash = "sha256:03dd38de09bc213e9a8b29761eec33ee1d5318dac0e49d8af36e4d27830e23a7"}, ] [package.dependencies] @@ -1710,4 +1761,4 @@ aiohttp = ["aiohttp", "httpx-aiohttp"] [metadata] lock-version = "2.1" python-versions = "^3.10" -content-hash = "65ef513ef69a32344fa2133df094babe6493482ea7586d0e19edbcb2f4bbe7c8" +content-hash = "d6f20007544f9ff1b04d92f47b351e1eda2f66672b4c62350d35c919bc2ae9f3" diff --git a/pyproject.toml b/pyproject.toml index 17a6e91..1dc545d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ dynamic = ["version"] [tool.poetry] name = "phenoml" -version = "17.0.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' @@ -42,7 +39,7 @@ Repository = 'https://github.com/phenoml/phenoml-python-sdk' python = "^3.10" aiohttp = { version = ">=3.14.1,<4", optional = true, python = ">=3.10"} httpx = ">=0.21.2" -httpx-aiohttp = { version = "0.1.8", optional = true, python = ">=3.10"} +httpx-aiohttp = { version = "^0.1.8", optional = true, python = ">=3.10"} pydantic = ">= 1.9.2" pydantic-core = ">=2.18.2,<3.0.0" typing_extensions = ">= 4.0.0" diff --git a/reference.md b/reference.md index 99385a6..457012c 100644 --- a/reference.md +++ b/reference.md @@ -4689,11 +4689,9 @@ client.implementation_guides.implementation_guides.update(
-Deletes the stored metadata for an implementation guide — its -profile_context and timestamps. Member profiles keep their -implementation_guide assignment, so a guide still referenced by at least -one profile continues to appear in listings, just without context or -timestamps. +Deletes the stored name-level metadata and any exact canonical package +versions beneath the guide. Legacy member profile assignments are not +changed.
@@ -4751,6 +4749,184 @@ client.implementation_guides.implementation_guides.delete( + + + + +
client.implementation_guides.implementation_guides.create_version(...) -> ImplementationGuideVersionDetail +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Publishes an exact package beneath this guide family. PR 2 temporarily +permits one exact package version per guide family; publishing another +version returns `409 Conflict` until multi-version package support lands. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from phenoml import PhenomlClient +from phenoml.environment import PhenomlClientEnvironment +from phenoml.implementation_guides import FhirImplementationGuide + +client = PhenomlClient( + client_id="", + client_secret="", + environment=PhenomlClientEnvironment.DEFAULT, +) + +client.implementation_guides.implementation_guides.create_version( + name="name", + implementation_guide=FhirImplementationGuide( + resource_type="ImplementationGuide", + url="url", + version="version", + ), + profile_refs=[ + "profile_refs" + ], +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**name:** `str` + +
+
+ +
+
+ +**implementation_guide:** `FhirImplementationGuide` + +
+
+ +
+
+ +**profile_refs:** `typing.List[str]` — Exact canonical `url|version` references to builtin or custom profiles. A package can contain at most 250 references. + +
+
+ +
+
+ +**profile_context:** `typing.Optional[str]` — Natural-language profile-selection context for this package. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.implementation_guides.implementation_guides.get_version(...) -> ImplementationGuideVersionDetail +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from phenoml import PhenomlClient +from phenoml.environment import PhenomlClientEnvironment + +client = PhenomlClient( + client_id="", + client_secret="", + environment=PhenomlClientEnvironment.DEFAULT, +) + +client.implementation_guides.implementation_guides.get_version( + name="name", + version="1.0.0", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**name:** `str` + +
+
+ +
+
+ +**version:** `str` — The authored ImplementationGuide.version. It may contain letters, numbers, and the punctuation characters `.`, `_`, `~`, `+`, and `-`; it cannot be exactly `.` or `..`. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ +
@@ -5179,7 +5355,7 @@ client.lang2fhir.upload_profile(
-Extracts text from a document (PDF or image) and converts it into a structured FHIR resource. +Extracts text from a PDF, image, RTF, or XML/C-CDA document and converts it into a structured FHIR resource. **Patient identifier handling.** When generating a `patient` (or `patient-canvas`) resource, US Core requires `Patient.identifier` (a business identifier such as an MRN). When the source text contains an identifier, it is extracted with an appropriate URI system. When the source text does not contain a detectable identifier, a synthetic one is generated with `system: "urn:phenoml:lang2fhir-generated-id"` and a UUID `value` so the resource remains FHIR-valid and US Core conformant. Callers who need a tenant-specific namespace should rewrite the synthetic system after extraction.
@@ -5208,7 +5384,7 @@ client = PhenomlClient( client.lang2fhir.document( version="R4", resource="questionnaire", - content="JVBERi0xLjQKJeLjz9MK...(base64-encoded PDF or image bytes)", + content="JVBERi0xLjQKJeLjz9MK...(base64-encoded document bytes)", ) ``` @@ -5244,8 +5420,11 @@ client.lang2fhir.document( **content:** `str` Base64 encoded file content. -Supported file types: PDF (application/pdf), PNG (image/png), JPEG (image/jpeg), TIFF (image/tiff). +Supported file types: PDF (application/pdf), PNG (image/png), JPEG (image/jpeg), TIFF (image/tiff), RTF (application/rtf), XML/C-CDA (text/xml). +RTF and XML/C-CDA uploads are available on dedicated instances only. File type is auto-detected from content magic bytes. +The decoded file must not exceed 20 MiB. RTF and XML/C-CDA documents whose extracted text exceeds 1 MiB are rejected. +Generic XML must include an XML declaration; C-CDA documents rooted at `ClinicalDocument` may omit it.
@@ -5285,7 +5464,7 @@ File type is auto-detected from content magic bytes.
-Extracts text from a document (PDF or image) and converts it into multiple FHIR resources, +Extracts text from a PDF, image, RTF, or XML/C-CDA document and converts it into multiple FHIR resources, returned as a transaction Bundle. Combines document text extraction with multi-resource detection. Automatically detects Patient, Condition, MedicationRequest, Observation, and other resource types. Resources are linked with proper references (e.g., Conditions reference the Patient). @@ -5319,7 +5498,7 @@ client = PhenomlClient( client.lang2fhir.document_multi( version="R4", - content="JVBERi0xLjQKJeLjz9MK...(base64-encoded PDF or image bytes)", + content="JVBERi0xLjQKJeLjz9MK...(base64-encoded document bytes)", provider="medplum", config=DocumentConfig( split_classifications=[ @@ -5362,8 +5541,11 @@ client.lang2fhir.document_multi( **content:** `str` Base64 encoded file content. -Supported file types: PDF (application/pdf), PNG (image/png), JPEG (image/jpeg), TIFF (image/tiff). +Supported file types: PDF (application/pdf), PNG (image/png), JPEG (image/jpeg), TIFF (image/tiff), RTF (application/rtf), XML/C-CDA (text/xml). +RTF and XML/C-CDA uploads are available on dedicated instances only. File type is auto-detected from content magic bytes. +The decoded file must not exceed 20 MiB. RTF and XML/C-CDA documents whose extracted text exceeds 1 MiB are rejected. +Generic XML must include an XML declaration; C-CDA documents rooted at `ClinicalDocument` may omit it.
@@ -5540,10 +5722,8 @@ credential. A `request_id` whose job was canceled or failed before it finalized is released for a fresh replay; once a job is finalized, its `request_id` keeps resolving to it even after cancellation. -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. +There is no limit on how many jobs an instance may hold at once; how many +items run in parallel is a property of the instance, not of the job count. @@ -5587,8 +5767,9 @@ client.lang2fhir_batch.create( **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. +Optional client idempotency token (at most 256 UTF-8 bytes). A +retried create with the same token returns the original job instead +of opening a second one. @@ -5629,7 +5810,7 @@ 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). + document's file content (PDF, image, RTF, or XML/C-CDA). - When `create` is set, `file` is **forbidden** — a create item carries no file. - `document` and `create` must each be a JSON **object**. @@ -5743,7 +5924,13 @@ accompanied by a `file`. **file:** `typing.Optional[core.File]` -The document's binary content (PDF, PNG, JPEG, or TIFF). +The document's file content (PDF, PNG, JPEG, TIFF, RTF, or +XML/C-CDA). The document pipeline accepts files up to 20 MiB; +an upload that passes the storage cap but exceeds this limit +fails during processing. RTF and XML/C-CDA documents whose +extracted text exceeds 1 MiB also fail during processing. +Generic XML must include an XML declaration; C-CDA documents +rooted at `ClinicalDocument` may omit it. Required with `document`; forbidden with `create`. @@ -5754,10 +5941,11 @@ 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. +Optional idempotency token (at most 256 UTF-8 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. @@ -5767,9 +5955,9 @@ 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. +Optional caller-supplied correlation label (at most 512 UTF-8 +bytes), echoed back on status and result listings so you can +match the server's item_id to your own record. @@ -5880,8 +6068,8 @@ client.lang2fhir_batch.finalize(
-Drives a job to the terminal `canceled` state on request, freeing its -active-job slot immediately. Takes no request body. +Drives a job to the terminal `canceled` state on request. Takes no +request body. Cancel does not delete the job: the job record and any results already produced are preserved for the normal retention window, the same as a diff --git a/src/phenoml/agent/chat/client.py b/src/phenoml/agent/chat/client.py index 9939bf6..6cce68f 100644 --- a/src/phenoml/agent/chat/client.py +++ b/src/phenoml/agent/chat/client.py @@ -418,7 +418,7 @@ async def stream( async def main() -> None: - response = await client.agent.chat.stream( + response = client.agent.chat.stream( phenoml_on_behalf_of="Patient/550e8400-e29b-41d4-a716-446655440000", phenoml_fhir_provider="550e8400-e29b-41d4-a716-446655440000:eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c...", message="What is the patient's current condition?", diff --git a/src/phenoml/agent/chat/raw_client.py b/src/phenoml/agent/chat/raw_client.py index b163d97..221e657 100644 --- a/src/phenoml/agent/chat/raw_client.py +++ b/src/phenoml/agent/chat/raw_client.py @@ -277,6 +277,8 @@ def _iter(): for _sse in _event_source.iter_sse(): if _sse.data == None: return + if len(_sse.data) == 0: + continue try: yield typing.cast( AgentChatStreamEvent, @@ -766,6 +768,8 @@ async def _iter(): async for _sse in _event_source.aiter_sse(): if _sse.data == None: return + if len(_sse.data) == 0: + continue try: yield typing.cast( AgentChatStreamEvent, diff --git a/src/phenoml/client.py b/src/phenoml/client.py index 87cfa7e..8073193 100644 --- a/src/phenoml/client.py +++ b/src/phenoml/client.py @@ -63,8 +63,8 @@ class PhenomlClient: base_url : typing.Optional[str] The base url to use for requests from the client. - token : typing.Callable[[], str] - Authenticate by providing a callable that returns a pre-generated bearer token. In this mode, OAuth client credentials are not required. + token : typing.Union[str, typing.Callable[[], str]] + Authenticate by providing a pre-generated bearer token, or a callable that returns one. In this mode, OAuth client credentials are not required. timeout : typing.Optional[float] The timeout to be used, in seconds, for requests. By default the timeout is 60 seconds, unless a custom httpx client is used, in which case this default is not enforced. @@ -130,7 +130,7 @@ def __init__( follow_redirects: typing.Optional[bool] = True, httpx_client: typing.Optional[httpx.Client] = None, logging: typing.Optional[typing.Union[LogConfig, Logger]] = None, - token: typing.Callable[[], str], + token: typing.Union[str, typing.Callable[[], str]], ): ... def __init__( self, @@ -141,7 +141,7 @@ def __init__( headers: typing.Optional[typing.Dict[str, str]] = None, client_id: typing.Optional[str] = os.getenv("PHENOML_CLIENT_ID"), client_secret: typing.Optional[str] = os.getenv("PHENOML_CLIENT_SECRET"), - token: typing.Optional[typing.Callable[[], str]] = None, + token: typing.Optional[typing.Union[str, typing.Callable[[], str]]] = None, _token_getter_override: typing.Optional[typing.Callable[[], str]] = None, timeout: typing.Optional[float] = None, max_retries: typing.Optional[int] = None, @@ -155,7 +155,12 @@ def __init__( _defaulted_max_retries = max_retries if max_retries is not None else 2 if instance_url is not None: _instance_url = instance_url if instance_url is not None else "experiment.app.pheno.ml" - base_url = "https://{instanceUrl}".format(instanceUrl=_instance_url) + _environment_url_templates = { + PhenomlClientEnvironment.DEFAULT: "https://{instanceUrl}", + } + _url_template = _environment_url_templates.get(environment, "https://{instanceUrl}") + if base_url is None: + base_url = _url_template.format(instanceUrl=_instance_url) if token is not None: self._client_wrapper = SyncClientWrapper( base_url=_get_base_url(base_url=base_url, environment=environment), @@ -398,8 +403,8 @@ class AsyncPhenomlClient: base_url : typing.Optional[str] The base url to use for requests from the client. - token : typing.Callable[[], str] - Authenticate by providing a callable that returns a pre-generated bearer token. In this mode, OAuth client credentials are not required. + token : typing.Union[str, typing.Callable[[], str]] + Authenticate by providing a pre-generated bearer token, or a callable that returns one. In this mode, OAuth client credentials are not required. timeout : typing.Optional[float] The timeout to be used, in seconds, for requests. By default the timeout is 60 seconds, unless a custom httpx client is used, in which case this default is not enforced. @@ -465,7 +470,7 @@ def __init__( follow_redirects: typing.Optional[bool] = True, httpx_client: typing.Optional[httpx.AsyncClient] = None, logging: typing.Optional[typing.Union[LogConfig, Logger]] = None, - token: typing.Callable[[], str], + token: typing.Union[str, typing.Callable[[], str]], ): ... def __init__( self, @@ -476,7 +481,7 @@ def __init__( headers: typing.Optional[typing.Dict[str, str]] = None, client_id: typing.Optional[str] = os.getenv("PHENOML_CLIENT_ID"), client_secret: typing.Optional[str] = os.getenv("PHENOML_CLIENT_SECRET"), - token: typing.Optional[typing.Callable[[], str]] = None, + token: typing.Optional[typing.Union[str, typing.Callable[[], str]]] = None, _token_getter_override: typing.Optional[typing.Callable[[], str]] = None, timeout: typing.Optional[float] = None, max_retries: typing.Optional[int] = None, @@ -490,7 +495,12 @@ def __init__( _defaulted_max_retries = max_retries if max_retries is not None else 2 if instance_url is not None: _instance_url = instance_url if instance_url is not None else "experiment.app.pheno.ml" - base_url = "https://{instanceUrl}".format(instanceUrl=_instance_url) + _environment_url_templates = { + PhenomlClientEnvironment.DEFAULT: "https://{instanceUrl}", + } + _url_template = _environment_url_templates.get(environment, "https://{instanceUrl}") + if base_url is None: + base_url = _url_template.format(instanceUrl=_instance_url) if token is not None: self._client_wrapper = AsyncClientWrapper( base_url=_get_base_url(base_url=base_url, environment=environment), diff --git a/src/phenoml/core/client_wrapper.py b/src/phenoml/core/client_wrapper.py index dc25c0d..c03a243 100644 --- a/src/phenoml/core/client_wrapper.py +++ b/src/phenoml/core/client_wrapper.py @@ -33,12 +33,12 @@ def get_headers(self) -> typing.Dict[str, str]: import platform headers: typing.Dict[str, str] = { - "User-Agent": "phenoml/17.0.0", + "User-Agent": "phenoml/0.0.0-fern-placeholder", "X-Fern-Language": "Python", "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": "17.0.0", + "X-Fern-SDK-Version": "0.0.0.dev0", **(self.get_custom_headers() or {}), } token = self._get_token() diff --git a/src/phenoml/core/http_client.py b/src/phenoml/core/http_client.py index 124dce2..3e5a786 100644 --- a/src/phenoml/core/http_client.py +++ b/src/phenoml/core/http_client.py @@ -3,6 +3,7 @@ import asyncio import email.utils import re +import socket import time import typing from contextlib import asynccontextmanager, contextmanager @@ -23,6 +24,39 @@ JITTER_FACTOR = 0.2 # 20% random jitter +def get_keepalive_socket_options( + idle: int = 60, + intvl: int = 30, + cnt: int = 5, +) -> typing.List[typing.Tuple[int, int, int]]: + """ + Build TCP keepalive socket options for the current platform. + + Keepalive probes keep otherwise-idle connections alive so that long, + non-streaming requests survive idle-connection reaping by a firewall, + load balancer, or NAT. The available socket constants are OS-dependent, + so each option is guarded and only emitted when the platform defines it: + + - ``SO_KEEPALIVE`` is portable (Linux/macOS/Windows). + - The idle-before-first-probe knob is ``TCP_KEEPIDLE`` on Linux and modern + Windows, but ``TCP_KEEPALIVE`` on macOS. + - ``TCP_KEEPINTVL`` / ``TCP_KEEPCNT`` exist on Linux/macOS/modern Windows. + + Passing these tuples to ``httpx.HTTPTransport(socket_options=...)`` / + ``httpx.AsyncHTTPTransport(socket_options=...)`` applies them to every + connection the transport opens. + """ + opts: typing.List[typing.Tuple[int, int, int]] = [(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)] + idle_const = getattr(socket, "TCP_KEEPIDLE", None) or getattr(socket, "TCP_KEEPALIVE", None) + if idle_const: + opts.append((socket.IPPROTO_TCP, idle_const, idle)) + if hasattr(socket, "TCP_KEEPINTVL"): + opts.append((socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, intvl)) + if hasattr(socket, "TCP_KEEPCNT"): + opts.append((socket.IPPROTO_TCP, socket.TCP_KEEPCNT, cnt)) + return opts + + def _parse_retry_after(response_headers: httpx.Headers) -> typing.Optional[float]: """ This function parses the `Retry-After` header in a HTTP response and returns the number of seconds to wait. @@ -238,7 +272,16 @@ def get_request_body( data: typing.Optional[typing.Any], request_options: typing.Optional[RequestOptions], omit: typing.Optional[typing.Any], + optional_body: bool = False, ) -> typing.Tuple[typing.Optional[typing.Any], typing.Optional[typing.Any]]: + # A whole body left at the sentinel was never passed by the caller, so it is absent + # rather than empty: the request carries no content and no `Content-Type`. + if omit is not None: + if json is omit: + json = None + if data is omit: + data = None + json_body = None data_body = None if data is not None: @@ -254,14 +297,36 @@ def get_request_body( # Only collapse empty dict to None when the body was not explicitly provided # and there are no additional body parameters. This preserves explicit empty # bodies (e.g., when an endpoint has a request body type but all fields are optional). - if json_body == {} and json is None and not has_additional_body_parameters: + # `optional_body` marks an endpoint whose body the API does not require, where a body + # that ends up empty means the caller passed none of its properties, so the request is + # sent with no content and no `Content-Type`. + if json_body == {} and (json is None or optional_body) and not has_additional_body_parameters: json_body = None - if data_body == {} and data is None and not has_additional_body_parameters: + if data_body == {} and (data is None or optional_body) and not has_additional_body_parameters: data_body = None return json_body, data_body +def drop_content_type_without_body( + headers: typing.Dict[str, typing.Any], + *, + json_body: typing.Optional[typing.Any], + data_body: typing.Optional[typing.Any], + optional_body: bool, +) -> typing.Dict[str, typing.Any]: + """Strip ``Content-Type`` from a request that carries no body. + + ``get_request_body`` drops the body of an ``optional_body`` endpoint when the caller + supplied none of it, but the endpoint still passes the content type it would have used. + A request that sends nothing must not advertise a media type, so a server that branches + on the header sees a bodyless call for what it is. + """ + if not optional_body or json_body is not None or data_body is not None: + return headers + return {key: value for key, value in headers.items() if key.lower() != "content-type"} + + class HttpClient: def __init__( self, @@ -309,6 +374,7 @@ def request( request_options: typing.Optional[RequestOptions] = None, retries: int = 0, omit: typing.Optional[typing.Any] = None, + optional_body: bool = False, force_multipart: typing.Optional[bool] = None, ) -> httpx.Response: base_url = self.get_base_url(base_url) @@ -321,7 +387,9 @@ def request( ) timeout = _timeout if _timeout is not None else httpx.USE_CLIENT_DEFAULT - json_body, data_body = get_request_body(json=json, data=data, request_options=request_options, omit=omit) + json_body, data_body = get_request_body( + json=json, data=data, request_options=request_options, omit=omit, optional_body=optional_body + ) request_files: typing.Optional[RequestFiles] = ( convert_file_dict_to_httpx_tuples(remove_omit_from_dict(remove_none_from_dict(files), omit)) @@ -364,6 +432,9 @@ def request( } ) ) + _request_headers = drop_content_type_without_body( + _request_headers, json_body=json_body, data_body=data_body, optional_body=optional_body + ) if self.logger.is_debug(): self.logger.debug( @@ -472,6 +543,7 @@ def stream( request_options: typing.Optional[RequestOptions] = None, retries: int = 0, omit: typing.Optional[typing.Any] = None, + optional_body: bool = False, force_multipart: typing.Optional[bool] = None, ) -> typing.Iterator[httpx.Response]: base_url = self.get_base_url(base_url) @@ -493,7 +565,9 @@ def stream( if (request_files is None or len(request_files) == 0) and force_multipart: request_files = FORCE_MULTIPART - json_body, data_body = get_request_body(json=json, data=data, request_options=request_options, omit=omit) + json_body, data_body = get_request_body( + json=json, data=data, request_options=request_options, omit=omit, optional_body=optional_body + ) data_body = _maybe_filter_none_from_multipart_data(data_body, request_files, force_multipart) @@ -527,6 +601,9 @@ def stream( } ) ) + _request_headers = drop_content_type_without_body( + _request_headers, json_body=json_body, data_body=data_body, optional_body=optional_body + ) if self.logger.is_debug(): self.logger.debug( @@ -604,6 +681,7 @@ async def request( request_options: typing.Optional[RequestOptions] = None, retries: int = 0, omit: typing.Optional[typing.Any] = None, + optional_body: bool = False, force_multipart: typing.Optional[bool] = None, ) -> httpx.Response: base_url = self.get_base_url(base_url) @@ -625,7 +703,9 @@ async def request( if (request_files is None or len(request_files) == 0) and force_multipart: request_files = FORCE_MULTIPART - json_body, data_body = get_request_body(json=json, data=data, request_options=request_options, omit=omit) + json_body, data_body = get_request_body( + json=json, data=data, request_options=request_options, omit=omit, optional_body=optional_body + ) data_body = _maybe_filter_none_from_multipart_data(data_body, request_files, force_multipart) @@ -662,6 +742,9 @@ async def request( } ) ) + _request_headers = drop_content_type_without_body( + _request_headers, json_body=json_body, data_body=data_body, optional_body=optional_body + ) if self.logger.is_debug(): self.logger.debug( @@ -770,6 +853,7 @@ async def stream( request_options: typing.Optional[RequestOptions] = None, retries: int = 0, omit: typing.Optional[typing.Any] = None, + optional_body: bool = False, force_multipart: typing.Optional[bool] = None, ) -> typing.AsyncIterator[httpx.Response]: base_url = self.get_base_url(base_url) @@ -791,7 +875,9 @@ async def stream( if (request_files is None or len(request_files) == 0) and force_multipart: request_files = FORCE_MULTIPART - json_body, data_body = get_request_body(json=json, data=data, request_options=request_options, omit=omit) + json_body, data_body = get_request_body( + json=json, data=data, request_options=request_options, omit=omit, optional_body=optional_body + ) data_body = _maybe_filter_none_from_multipart_data(data_body, request_files, force_multipart) @@ -828,6 +914,9 @@ async def stream( } ) ) + _request_headers = drop_content_type_without_body( + _request_headers, json_body=json_body, data_body=data_body, optional_body=optional_body + ) if self.logger.is_debug(): self.logger.debug( diff --git a/src/phenoml/core/http_response.py b/src/phenoml/core/http_response.py index 00bb109..9aa1e18 100644 --- a/src/phenoml/core/http_response.py +++ b/src/phenoml/core/http_response.py @@ -24,6 +24,10 @@ def headers(self) -> Dict[str, str]: def status_code(self) -> int: return self._response.status_code + @property + def response(self) -> httpx.Response: + return self._response + class HttpResponse(Generic[T], BaseHttpResponse): """HTTP response wrapper that exposes response headers and data.""" diff --git a/src/phenoml/core/jsonable_encoder.py b/src/phenoml/core/jsonable_encoder.py index 5b0902e..f638cc9 100644 --- a/src/phenoml/core/jsonable_encoder.py +++ b/src/phenoml/core/jsonable_encoder.py @@ -15,6 +15,7 @@ from pathlib import PurePath from types import GeneratorType from typing import Any, Callable, Dict, List, Optional, Set, Union +from urllib.parse import quote import pydantic from .datetime_utils import serialize_datetime @@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str: if isinstance(obj, bool): return "true" if obj else "false" return str(jsonable_encoder(obj)) + + +def quote_path_param(obj: Any) -> str: + """Encode a value for use in a URL path segment, percent-encoding it. + + Same as encode_path_param, except the result is percent-encoded so + that a value containing "/" or ".." cannot change which endpoint + the request resolves to. + """ + if isinstance(obj, bool): + return "true" if obj else "false" + return quote(str(jsonable_encoder(obj)), safe="") diff --git a/src/phenoml/core/oauth_token_provider.py b/src/phenoml/core/oauth_token_provider.py index 0360d17..370bb86 100644 --- a/src/phenoml/core/oauth_token_provider.py +++ b/src/phenoml/core/oauth_token_provider.py @@ -31,7 +31,9 @@ def get_token(self) -> str: return self._refresh() def _refresh(self) -> str: - token_response = self._auth_client.get_token(client_id=self._client_id, client_secret=self._client_secret) + token_response = self._auth_client.get_token( + client_id=self._client_id, client_secret=self._client_secret, grant_type="client_credentials" + ) self._access_token = token_response.access_token self._expires_at = self._get_expires_at( expires_in_seconds=token_response.expires_in, buffer_in_minutes=self.BUFFER_IN_MINUTES @@ -62,7 +64,9 @@ async def get_token(self) -> str: return await self._refresh() async def _refresh(self) -> str: - token_response = await self._auth_client.get_token(client_id=self._client_id, client_secret=self._client_secret) + token_response = await self._auth_client.get_token( + client_id=self._client_id, client_secret=self._client_secret, grant_type="client_credentials" + ) self._access_token = token_response.access_token self._expires_at = self._get_expires_at( expires_in_seconds=token_response.expires_in, buffer_in_minutes=self.BUFFER_IN_MINUTES diff --git a/src/phenoml/core/pydantic_utilities.py b/src/phenoml/core/pydantic_utilities.py index 6587f5e..70816b9 100644 --- a/src/phenoml/core/pydantic_utilities.py +++ b/src/phenoml/core/pydantic_utilities.py @@ -5,6 +5,7 @@ import inspect import json import logging +import weakref from collections import defaultdict from dataclasses import asdict from typing import ( @@ -184,6 +185,58 @@ def _get_type_adapter(type_: Type[Any]) -> Any: return adapter +_field_alias_cache: "weakref.WeakKeyDictionary[type, Tuple[Dict[str, str], Tuple[str, ...]]]" = ( + weakref.WeakKeyDictionary() +) + + +def _get_field_aliases(model: type) -> Tuple[Dict[str, str], Tuple[str, ...]]: + """ + Map of field name to Pydantic alias for the fields whose alias differs from their name, together with the + keys that are ambiguous (an alias of one field and the name of another). Computed once per model class. + """ + cached = _field_alias_cache.get(model) + if cached is None: + fields: Mapping[str, Any] = ( + getattr(model, "model_fields", {}) if IS_PYDANTIC_V2 else getattr(model, "__fields__", {}) + ) + name_to_alias: Dict[str, str] = {} + for name, field in fields.items(): + alias = getattr(field, "alias", None) + if alias is not None and alias != name: + name_to_alias[name] = alias + cached = (name_to_alias, tuple(alias for alias in name_to_alias.values() if alias in fields)) + _field_alias_cache[model] = cached + return cached + + +def _coerce_keys_to_aliases(model: type, data: Any) -> Any: + """ + Accept Python field names in input by rewriting them to their Pydantic aliases, + while avoiding silent collisions when a key could refer to multiple fields. + """ + if not isinstance(data, Mapping): + return data + + name_to_alias, ambiguous_keys = _get_field_aliases(model) + for key in ambiguous_keys: + if key in data and name_to_alias.get(key, key) not in data: + raise ValueError( + f"Ambiguous input key '{key}': it is both a field name and an alias. " + "Provide the explicit alias key to disambiguate." + ) + + if not name_to_alias or not any(name in data for name in name_to_alias): + return data if isinstance(data, dict) else dict(data) + + rewritten: Dict[str, Any] = dict(data) + for name, alias in name_to_alias.items(): + if name in data and alias not in rewritten: + rewritten[alias] = rewritten.pop(name) + + return rewritten + + def parse_obj_as(type_: Type[T], object_: Any) -> T: # convert_and_respect_annotation_metadata is required for TypedDict aliasing. # @@ -193,20 +246,7 @@ def parse_obj_as(type_: Type[T], object_: Any) -> T: # - If the model encodes aliasing only via FieldMetadata annotations, then we MUST pre-dealias because Pydantic # will not recognize those aliases during validation. if inspect.isclass(type_) and issubclass(type_, pydantic.BaseModel): - has_pydantic_aliases = False - if IS_PYDANTIC_V2: - for field_name, field_info in getattr(type_, "model_fields", {}).items(): # type: ignore[attr-defined] - alias = getattr(field_info, "alias", None) - if alias is not None and alias != field_name: - has_pydantic_aliases = True - break - else: - for field in getattr(type_, "__fields__", {}).values(): - alias = getattr(field, "alias", None) - name = getattr(field, "name", None) - if alias is not None and name is not None and alias != name: - has_pydantic_aliases = True - break + has_pydantic_aliases = bool(_get_field_aliases(type_)[0]) dealiased_object = ( object_ @@ -239,39 +279,7 @@ class UniversalBaseModel(pydantic.BaseModel): @pydantic.model_validator(mode="before") # type: ignore[attr-defined] @classmethod def _coerce_field_names_to_aliases(cls, data: Any) -> Any: - """ - Accept Python field names in input by rewriting them to their Pydantic aliases, - while avoiding silent collisions when a key could refer to multiple fields. - """ - if not isinstance(data, Mapping): - return data - - fields = getattr(cls, "model_fields", {}) # type: ignore[attr-defined] - name_to_alias: Dict[str, str] = {} - alias_to_name: Dict[str, str] = {} - - for name, field_info in fields.items(): - alias = getattr(field_info, "alias", None) or name - name_to_alias[name] = alias - if alias != name: - alias_to_name[alias] = name - - # Detect ambiguous keys: a key that is an alias for one field and a name for another. - ambiguous_keys = set(alias_to_name.keys()).intersection(set(name_to_alias.keys())) - for key in ambiguous_keys: - if key in data and name_to_alias[key] not in data: - raise ValueError( - f"Ambiguous input key '{key}': it is both a field name and an alias. " - "Provide the explicit alias key to disambiguate." - ) - - original_keys = set(data.keys()) - rewritten: Dict[str, Any] = dict(data) - for name, alias in name_to_alias.items(): - if alias != name and name in original_keys and alias not in rewritten: - rewritten[alias] = rewritten.pop(name) - - return rewritten + return _coerce_keys_to_aliases(cls, data) @pydantic.model_serializer(mode="plain", when_used="json") # type: ignore[attr-defined] def serialize_model(self) -> Any: # type: ignore[name-defined] @@ -287,37 +295,7 @@ class Config: @pydantic.root_validator(pre=True) def _coerce_field_names_to_aliases(cls, values: Any) -> Any: - """ - Pydantic v1 equivalent of _coerce_field_names_to_aliases. - """ - if not isinstance(values, Mapping): - return values - - fields = getattr(cls, "__fields__", {}) - name_to_alias: Dict[str, str] = {} - alias_to_name: Dict[str, str] = {} - - for name, field in fields.items(): - alias = getattr(field, "alias", None) or name - name_to_alias[name] = alias - if alias != name: - alias_to_name[alias] = name - - ambiguous_keys = set(alias_to_name.keys()).intersection(set(name_to_alias.keys())) - for key in ambiguous_keys: - if key in values and name_to_alias[key] not in values: - raise ValueError( - f"Ambiguous input key '{key}': it is both a field name and an alias. " - "Provide the explicit alias key to disambiguate." - ) - - original_keys = set(values.keys()) - rewritten: Dict[str, Any] = dict(values) - for name, alias in name_to_alias.items(): - if alias != name and name in original_keys and alias not in rewritten: - rewritten[alias] = rewritten.pop(name) - - return rewritten + return _coerce_keys_to_aliases(cls, values) # type: ignore[arg-type] @classmethod def model_construct(cls: Type["Model"], _fields_set: Optional[Set[str]] = None, **values: Any) -> "Model": diff --git a/src/phenoml/implementation_guides/__init__.py b/src/phenoml/implementation_guides/__init__.py index 2d19295..8b08ecc 100644 --- a/src/phenoml/implementation_guides/__init__.py +++ b/src/phenoml/implementation_guides/__init__.py @@ -6,15 +6,31 @@ from importlib import import_module if typing.TYPE_CHECKING: - from .types import ImplementationGuideDetail, ImplementationGuideListResponse, ImplementationGuideSummary - from .errors import BadRequestError, ForbiddenError, InternalServerError, NotFoundError, UnauthorizedError + from .types import ( + FhirImplementationGuide, + ImplementationGuideDetail, + ImplementationGuideListResponse, + ImplementationGuideSummary, + ImplementationGuideVersionDetail, + ) + from .errors import ( + BadRequestError, + ConflictError, + ForbiddenError, + InternalServerError, + NotFoundError, + UnauthorizedError, + ) from . import implementation_guides _dynamic_imports: typing.Dict[str, str] = { "BadRequestError": ".errors", + "ConflictError": ".errors", + "FhirImplementationGuide": ".types", "ForbiddenError": ".errors", "ImplementationGuideDetail": ".types", "ImplementationGuideListResponse": ".types", "ImplementationGuideSummary": ".types", + "ImplementationGuideVersionDetail": ".types", "InternalServerError": ".errors", "NotFoundError": ".errors", "UnauthorizedError": ".errors", @@ -45,10 +61,13 @@ def __dir__(): __all__ = [ "BadRequestError", + "ConflictError", + "FhirImplementationGuide", "ForbiddenError", "ImplementationGuideDetail", "ImplementationGuideListResponse", "ImplementationGuideSummary", + "ImplementationGuideVersionDetail", "InternalServerError", "NotFoundError", "UnauthorizedError", diff --git a/src/phenoml/implementation_guides/errors/__init__.py b/src/phenoml/implementation_guides/errors/__init__.py index 306a114..29aa057 100644 --- a/src/phenoml/implementation_guides/errors/__init__.py +++ b/src/phenoml/implementation_guides/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/implementation_guides/errors/conflict_error.py b/src/phenoml/implementation_guides/errors/conflict_error.py new file mode 100644 index 0000000..abe1a9f --- /dev/null +++ b/src/phenoml/implementation_guides/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/implementation_guides/implementation_guides/client.py b/src/phenoml/implementation_guides/implementation_guides/client.py index 6cc3374..ba604eb 100644 --- a/src/phenoml/implementation_guides/implementation_guides/client.py +++ b/src/phenoml/implementation_guides/implementation_guides/client.py @@ -4,9 +4,11 @@ from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper from ...core.request_options import RequestOptions +from ..types.fhir_implementation_guide import FhirImplementationGuide from ..types.implementation_guide_detail import ImplementationGuideDetail from ..types.implementation_guide_list_response import ImplementationGuideListResponse from ..types.implementation_guide_summary import ImplementationGuideSummary +from ..types.implementation_guide_version_detail import ImplementationGuideVersionDetail from .raw_client import AsyncRawImplementationGuidesClient, RawImplementationGuidesClient # this is used as the default value for optional parameters @@ -136,11 +138,9 @@ def update( def delete(self, name: str, *, request_options: typing.Optional[RequestOptions] = None) -> None: """ - Deletes the stored metadata for an implementation guide — its - profile_context and timestamps. Member profiles keep their - implementation_guide assignment, so a guide still referenced by at least - one profile continues to appear in listings, just without context or - timestamps. + Deletes the stored name-level metadata and any exact canonical package + versions beneath the guide. Legacy member profile assignments are not + changed. Parameters ---------- @@ -169,6 +169,102 @@ def delete(self, name: str, *, request_options: typing.Optional[RequestOptions] _response = self._raw_client.delete(name, request_options=request_options) return _response.data + def create_version( + self, + name: str, + *, + implementation_guide: FhirImplementationGuide, + profile_refs: typing.Sequence[str], + profile_context: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> ImplementationGuideVersionDetail: + """ + Publishes an exact package beneath this guide family. PR 2 temporarily + permits one exact package version per guide family; publishing another + version returns `409 Conflict` until multi-version package support lands. + + Parameters + ---------- + name : str + + implementation_guide : FhirImplementationGuide + + profile_refs : typing.Sequence[str] + Exact canonical `url|version` references to builtin or custom profiles. A package can contain at most 250 references. + + profile_context : typing.Optional[str] + Natural-language profile-selection context for this package. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ImplementationGuideVersionDetail + Canonical package published + + Examples + -------- + from phenoml import PhenomlClient + from phenoml.implementation_guides import FhirImplementationGuide + + client = PhenomlClient( + client_id="YOUR_CLIENT_ID", + client_secret="YOUR_CLIENT_SECRET", + ) + client.implementation_guides.implementation_guides.create_version( + name="name", + implementation_guide=FhirImplementationGuide( + url="url", + version="version", + ), + profile_refs=["profile_refs"], + ) + """ + _response = self._raw_client.create_version( + name, + implementation_guide=implementation_guide, + profile_refs=profile_refs, + profile_context=profile_context, + request_options=request_options, + ) + return _response.data + + def get_version( + self, name: str, version: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> ImplementationGuideVersionDetail: + """ + Parameters + ---------- + name : str + + version : str + The authored ImplementationGuide.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 + ------- + ImplementationGuideVersionDetail + Exact canonical package + + Examples + -------- + from phenoml import PhenomlClient + + client = PhenomlClient( + client_id="YOUR_CLIENT_ID", + client_secret="YOUR_CLIENT_SECRET", + ) + client.implementation_guides.implementation_guides.get_version( + name="name", + version="1.0.0", + ) + """ + _response = self._raw_client.get_version(name, version, request_options=request_options) + return _response.data + class AsyncImplementationGuidesClient: def __init__(self, *, client_wrapper: AsyncClientWrapper): @@ -321,11 +417,9 @@ async def main() -> None: async def delete(self, name: str, *, request_options: typing.Optional[RequestOptions] = None) -> None: """ - Deletes the stored metadata for an implementation guide — its - profile_context and timestamps. Member profiles keep their - implementation_guide assignment, so a guide still referenced by at least - one profile continues to appear in listings, just without context or - timestamps. + Deletes the stored name-level metadata and any exact canonical package + versions beneath the guide. Legacy member profile assignments are not + changed. Parameters ---------- @@ -361,3 +455,115 @@ async def main() -> None: """ _response = await self._raw_client.delete(name, request_options=request_options) return _response.data + + async def create_version( + self, + name: str, + *, + implementation_guide: FhirImplementationGuide, + profile_refs: typing.Sequence[str], + profile_context: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> ImplementationGuideVersionDetail: + """ + Publishes an exact package beneath this guide family. PR 2 temporarily + permits one exact package version per guide family; publishing another + version returns `409 Conflict` until multi-version package support lands. + + Parameters + ---------- + name : str + + implementation_guide : FhirImplementationGuide + + profile_refs : typing.Sequence[str] + Exact canonical `url|version` references to builtin or custom profiles. A package can contain at most 250 references. + + profile_context : typing.Optional[str] + Natural-language profile-selection context for this package. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ImplementationGuideVersionDetail + Canonical package published + + Examples + -------- + import asyncio + + from phenoml import AsyncPhenomlClient + from phenoml.implementation_guides import FhirImplementationGuide + + client = AsyncPhenomlClient( + client_id="YOUR_CLIENT_ID", + client_secret="YOUR_CLIENT_SECRET", + ) + + + async def main() -> None: + await client.implementation_guides.implementation_guides.create_version( + name="name", + implementation_guide=FhirImplementationGuide( + url="url", + version="version", + ), + profile_refs=["profile_refs"], + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.create_version( + name, + implementation_guide=implementation_guide, + profile_refs=profile_refs, + profile_context=profile_context, + request_options=request_options, + ) + return _response.data + + async def get_version( + self, name: str, version: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> ImplementationGuideVersionDetail: + """ + Parameters + ---------- + name : str + + version : str + The authored ImplementationGuide.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 + ------- + ImplementationGuideVersionDetail + Exact canonical package + + 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.implementation_guides.implementation_guides.get_version( + name="name", + version="1.0.0", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.get_version(name, version, request_options=request_options) + return _response.data diff --git a/src/phenoml/implementation_guides/implementation_guides/raw_client.py b/src/phenoml/implementation_guides/implementation_guides/raw_client.py index 8700e84..1eb8291 100644 --- a/src/phenoml/implementation_guides/implementation_guides/raw_client.py +++ b/src/phenoml/implementation_guides/implementation_guides/raw_client.py @@ -10,14 +10,18 @@ from ...core.parse_error import ParsingError from ...core.pydantic_utilities import parse_obj_as from ...core.request_options import RequestOptions +from ...core.serialization import convert_and_respect_annotation_metadata 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.fhir_implementation_guide import FhirImplementationGuide from ..types.implementation_guide_detail import ImplementationGuideDetail from ..types.implementation_guide_list_response import ImplementationGuideListResponse from ..types.implementation_guide_summary import ImplementationGuideSummary +from ..types.implementation_guide_version_detail import ImplementationGuideVersionDetail from pydantic import ValidationError # this is used as the default value for optional parameters @@ -308,11 +312,9 @@ def update( def delete(self, name: str, *, request_options: typing.Optional[RequestOptions] = None) -> HttpResponse[None]: """ - Deletes the stored metadata for an implementation guide — its - profile_context and timestamps. Member profiles keep their - implementation_guide assignment, so a guide still referenced by at least - one profile continues to appear in listings, just without context or - timestamps. + Deletes the stored name-level metadata and any exact canonical package + versions beneath the guide. Legacy member profile assignments are not + changed. Parameters ---------- @@ -398,6 +400,173 @@ def delete(self, name: str, *, request_options: typing.Optional[RequestOptions] ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + def create_version( + self, + name: str, + *, + implementation_guide: FhirImplementationGuide, + profile_refs: typing.Sequence[str], + profile_context: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[ImplementationGuideVersionDetail]: + """ + Publishes an exact package beneath this guide family. PR 2 temporarily + permits one exact package version per guide family; publishing another + version returns `409 Conflict` until multi-version package support lands. + + Parameters + ---------- + name : str + + implementation_guide : FhirImplementationGuide + + profile_refs : typing.Sequence[str] + Exact canonical `url|version` references to builtin or custom profiles. A package can contain at most 250 references. + + profile_context : typing.Optional[str] + Natural-language profile-selection context for this package. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[ImplementationGuideVersionDetail] + Canonical package published + """ + _response = self._client_wrapper.httpx_client.request( + f"fhir/implementation-guides/{encode_path_param(name)}/versions", + method="POST", + json={ + "implementation_guide": convert_and_respect_annotation_metadata( + object_=implementation_guide, annotation=FhirImplementationGuide, direction="write" + ), + "profile_refs": profile_refs, + "profile_context": profile_context, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ImplementationGuideVersionDetail, + parse_obj_as( + type_=ImplementationGuideVersionDetail, # 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 == 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(), + ), + ), + ) + _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_version( + self, name: str, version: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[ImplementationGuideVersionDetail]: + """ + Parameters + ---------- + name : str + + version : str + The authored ImplementationGuide.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[ImplementationGuideVersionDetail] + Exact canonical package + """ + _response = self._client_wrapper.httpx_client.request( + f"fhir/implementation-guides/{encode_path_param(name)}/versions/{encode_path_param(version)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ImplementationGuideVersionDetail, + parse_obj_as( + type_=ImplementationGuideVersionDetail, # 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 == 404: + raise NotFoundError( + 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 AsyncRawImplementationGuidesClient: def __init__(self, *, client_wrapper: AsyncClientWrapper): @@ -685,11 +854,9 @@ async def delete( self, name: str, *, request_options: typing.Optional[RequestOptions] = None ) -> AsyncHttpResponse[None]: """ - Deletes the stored metadata for an implementation guide — its - profile_context and timestamps. Member profiles keep their - implementation_guide assignment, so a guide still referenced by at least - one profile continues to appear in listings, just without context or - timestamps. + Deletes the stored name-level metadata and any exact canonical package + versions beneath the guide. Legacy member profile assignments are not + changed. Parameters ---------- @@ -774,3 +941,170 @@ async def delete( 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_version( + self, + name: str, + *, + implementation_guide: FhirImplementationGuide, + profile_refs: typing.Sequence[str], + profile_context: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[ImplementationGuideVersionDetail]: + """ + Publishes an exact package beneath this guide family. PR 2 temporarily + permits one exact package version per guide family; publishing another + version returns `409 Conflict` until multi-version package support lands. + + Parameters + ---------- + name : str + + implementation_guide : FhirImplementationGuide + + profile_refs : typing.Sequence[str] + Exact canonical `url|version` references to builtin or custom profiles. A package can contain at most 250 references. + + profile_context : typing.Optional[str] + Natural-language profile-selection context for this package. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[ImplementationGuideVersionDetail] + Canonical package published + """ + _response = await self._client_wrapper.httpx_client.request( + f"fhir/implementation-guides/{encode_path_param(name)}/versions", + method="POST", + json={ + "implementation_guide": convert_and_respect_annotation_metadata( + object_=implementation_guide, annotation=FhirImplementationGuide, direction="write" + ), + "profile_refs": profile_refs, + "profile_context": profile_context, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ImplementationGuideVersionDetail, + parse_obj_as( + type_=ImplementationGuideVersionDetail, # 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 == 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(), + ), + ), + ) + _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_version( + self, name: str, version: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[ImplementationGuideVersionDetail]: + """ + Parameters + ---------- + name : str + + version : str + The authored ImplementationGuide.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[ImplementationGuideVersionDetail] + Exact canonical package + """ + _response = await self._client_wrapper.httpx_client.request( + f"fhir/implementation-guides/{encode_path_param(name)}/versions/{encode_path_param(version)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ImplementationGuideVersionDetail, + parse_obj_as( + type_=ImplementationGuideVersionDetail, # 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 == 404: + raise NotFoundError( + 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/implementation_guides/types/__init__.py b/src/phenoml/implementation_guides/types/__init__.py index 2b67df5..6c2a89f 100644 --- a/src/phenoml/implementation_guides/types/__init__.py +++ b/src/phenoml/implementation_guides/types/__init__.py @@ -6,13 +6,17 @@ from importlib import import_module if typing.TYPE_CHECKING: + from .fhir_implementation_guide import FhirImplementationGuide from .implementation_guide_detail import ImplementationGuideDetail from .implementation_guide_list_response import ImplementationGuideListResponse from .implementation_guide_summary import ImplementationGuideSummary + from .implementation_guide_version_detail import ImplementationGuideVersionDetail _dynamic_imports: typing.Dict[str, str] = { + "FhirImplementationGuide": ".fhir_implementation_guide", "ImplementationGuideDetail": ".implementation_guide_detail", "ImplementationGuideListResponse": ".implementation_guide_list_response", "ImplementationGuideSummary": ".implementation_guide_summary", + "ImplementationGuideVersionDetail": ".implementation_guide_version_detail", } @@ -37,4 +41,10 @@ def __dir__(): return sorted(lazy_attrs) -__all__ = ["ImplementationGuideDetail", "ImplementationGuideListResponse", "ImplementationGuideSummary"] +__all__ = [ + "FhirImplementationGuide", + "ImplementationGuideDetail", + "ImplementationGuideListResponse", + "ImplementationGuideSummary", + "ImplementationGuideVersionDetail", +] diff --git a/src/phenoml/implementation_guides/types/fhir_implementation_guide.py b/src/phenoml/implementation_guides/types/fhir_implementation_guide.py new file mode 100644 index 0000000..949b9df --- /dev/null +++ b/src/phenoml/implementation_guides/types/fhir_implementation_guide.py @@ -0,0 +1,38 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ...core.serialization import FieldMetadata + + +class FhirImplementationGuide(UniversalBaseModel): + """ + A complete authored FHIR ImplementationGuide JSON resource. + """ + + resource_type: typing_extensions.Annotated[ + typing.Literal["ImplementationGuide"], FieldMetadata(alias="resourceType"), pydantic.Field(alias="resourceType") + ] = "ImplementationGuide" + id: typing.Optional[str] = None + url: str + version: str + name: typing.Optional[str] = None + status: typing.Optional[str] = None + package_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="packageId"), pydantic.Field(alias="packageId") + ] = None + fhir_version: typing_extensions.Annotated[ + typing.Optional[typing.List[str]], FieldMetadata(alias="fhirVersion"), pydantic.Field(alias="fhirVersion") + ] = None + + 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/implementation_guides/types/implementation_guide_summary.py b/src/phenoml/implementation_guides/types/implementation_guide_summary.py index bd3364d..a6489ab 100644 --- a/src/phenoml/implementation_guides/types/implementation_guide_summary.py +++ b/src/phenoml/implementation_guides/types/implementation_guide_summary.py @@ -9,7 +9,7 @@ class ImplementationGuideSummary(UniversalBaseModel): """ - Metadata for an implementation guide. This is an instance-local grouping record, not a complete FHIR ImplementationGuide resource. + Metadata for an implementation guide. Canonical fields are present only for published canonical packages; metadata-only legacy records omit them. """ name: typing.Optional[str] = pydantic.Field(default=None) @@ -27,6 +27,16 @@ class ImplementationGuideSummary(UniversalBaseModel): The number of custom profiles in this implementation guide. """ + canonical_url: typing.Optional[str] = pydantic.Field(default=None) + """ + Canonical FHIR ImplementationGuide URL, when the family has an exact package. + """ + + version_count: typing.Optional[int] = pydantic.Field(default=None) + """ + Number of retained exact package versions. + """ + created_at: typing.Optional[dt.datetime] = pydantic.Field(default=None) """ Present only for guides that have stored metadata (a profile_context has been set). Omitted for guides that exist solely because a profile references them. diff --git a/src/phenoml/implementation_guides/types/implementation_guide_version_detail.py b/src/phenoml/implementation_guides/types/implementation_guide_version_detail.py new file mode 100644 index 0000000..a5c2e66 --- /dev/null +++ b/src/phenoml/implementation_guides/types/implementation_guide_version_detail.py @@ -0,0 +1,29 @@ +# 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 .fhir_implementation_guide import FhirImplementationGuide + + +class ImplementationGuideVersionDetail(UniversalBaseModel): + name: str + url: str + version: str + profile_context: str + profiles: typing.List[str] + profile_refs: typing.List[str] + implementation_guide: FhirImplementationGuide + created_at: dt.datetime + updated_at: dt.datetime + + 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/__init__.py b/src/phenoml/lang2fhir/__init__.py index 8b0af5d..78441a8 100644 --- a/src/phenoml/lang2fhir/__init__.py +++ b/src/phenoml/lang2fhir/__init__.py @@ -33,6 +33,7 @@ ResourceReviewFinding, ResourceReviewFindingFieldKind, ResourceReviewFlagged, + ResourceReviewRemediated, ResourceReviewResult, ResourceReviewTarget, ResourceReviewTargetFieldsItem, @@ -85,6 +86,7 @@ "ResourceReviewFinding": ".types", "ResourceReviewFindingFieldKind": ".types", "ResourceReviewFlagged": ".types", + "ResourceReviewRemediated": ".types", "ResourceReviewResult": ".types", "ResourceReviewTarget": ".types", "ResourceReviewTargetFieldsItem": ".types", @@ -152,6 +154,7 @@ def __dir__(): "ResourceReviewFinding", "ResourceReviewFindingFieldKind", "ResourceReviewFlagged", + "ResourceReviewRemediated", "ResourceReviewResult", "ResourceReviewTarget", "ResourceReviewTargetFieldsItem", diff --git a/src/phenoml/lang2fhir/client.py b/src/phenoml/lang2fhir/client.py index c4a0ad5..875324c 100644 --- a/src/phenoml/lang2fhir/client.py +++ b/src/phenoml/lang2fhir/client.py @@ -291,7 +291,7 @@ def document( request_options: typing.Optional[RequestOptions] = None, ) -> FhirResource: """ - Extracts text from a document (PDF or image) and converts it into a structured FHIR resource. + Extracts text from a PDF, image, RTF, or XML/C-CDA document and converts it into a structured FHIR resource. **Patient identifier handling.** When generating a `patient` (or `patient-canvas`) resource, US Core requires `Patient.identifier` (a business identifier such as an MRN). When the source text contains an identifier, it is extracted with an appropriate URI system. When the source text does not contain a detectable identifier, a synthetic one is generated with `system: "urn:phenoml:lang2fhir-generated-id"` and a UUID `value` so the resource remains FHIR-valid and US Core conformant. Callers who need a tenant-specific namespace should rewrite the synthetic system after extraction. @@ -305,8 +305,11 @@ def document( content : str Base64 encoded file content. - Supported file types: PDF (application/pdf), PNG (image/png), JPEG (image/jpeg), TIFF (image/tiff). + Supported file types: PDF (application/pdf), PNG (image/png), JPEG (image/jpeg), TIFF (image/tiff), RTF (application/rtf), XML/C-CDA (text/xml). + RTF and XML/C-CDA uploads are available on dedicated instances only. File type is auto-detected from content magic bytes. + The decoded file must not exceed 20 MiB. RTF and XML/C-CDA documents whose extracted text exceeds 1 MiB are rejected. + Generic XML must include an XML declaration; C-CDA documents rooted at `ClinicalDocument` may omit it. config : typing.Optional[DocumentConfig] @@ -329,7 +332,7 @@ def document( client.lang2fhir.document( version="R4", resource="questionnaire", - content="JVBERi0xLjQKJeLjz9MK...(base64-encoded PDF or image bytes)", + content="JVBERi0xLjQKJeLjz9MK...(base64-encoded document bytes)", ) """ _response = self._raw_client.document( @@ -351,7 +354,7 @@ def document_multi( request_options: typing.Optional[RequestOptions] = None, ) -> DocumentMultiResponse: """ - Extracts text from a document (PDF or image) and converts it into multiple FHIR resources, + Extracts text from a PDF, image, RTF, or XML/C-CDA document and converts it into multiple FHIR resources, returned as a transaction Bundle. Combines document text extraction with multi-resource detection. Automatically detects Patient, Condition, MedicationRequest, Observation, and other resource types. Resources are linked with proper references (e.g., Conditions reference the Patient). @@ -367,8 +370,11 @@ def document_multi( content : str Base64 encoded file content. - Supported file types: PDF (application/pdf), PNG (image/png), JPEG (image/jpeg), TIFF (image/tiff). + Supported file types: PDF (application/pdf), PNG (image/png), JPEG (image/jpeg), TIFF (image/tiff), RTF (application/rtf), XML/C-CDA (text/xml). + RTF and XML/C-CDA uploads are available on dedicated instances only. File type is auto-detected from content magic bytes. + The decoded file must not exceed 20 MiB. RTF and XML/C-CDA documents whose extracted text exceeds 1 MiB are rejected. + Generic XML must include an XML declaration; C-CDA documents rooted at `ClinicalDocument` may omit it. provider : typing.Optional[str] Optional FHIR provider name for provider-specific profiles @@ -405,7 +411,7 @@ def document_multi( ) client.lang2fhir.document_multi( version="R4", - content="JVBERi0xLjQKJeLjz9MK...(base64-encoded PDF or image bytes)", + content="JVBERi0xLjQKJeLjz9MK...(base64-encoded document bytes)", provider="medplum", config=DocumentConfig( split_classifications=[ @@ -737,7 +743,7 @@ async def document( request_options: typing.Optional[RequestOptions] = None, ) -> FhirResource: """ - Extracts text from a document (PDF or image) and converts it into a structured FHIR resource. + Extracts text from a PDF, image, RTF, or XML/C-CDA document and converts it into a structured FHIR resource. **Patient identifier handling.** When generating a `patient` (or `patient-canvas`) resource, US Core requires `Patient.identifier` (a business identifier such as an MRN). When the source text contains an identifier, it is extracted with an appropriate URI system. When the source text does not contain a detectable identifier, a synthetic one is generated with `system: "urn:phenoml:lang2fhir-generated-id"` and a UUID `value` so the resource remains FHIR-valid and US Core conformant. Callers who need a tenant-specific namespace should rewrite the synthetic system after extraction. @@ -751,8 +757,11 @@ async def document( content : str Base64 encoded file content. - Supported file types: PDF (application/pdf), PNG (image/png), JPEG (image/jpeg), TIFF (image/tiff). + Supported file types: PDF (application/pdf), PNG (image/png), JPEG (image/jpeg), TIFF (image/tiff), RTF (application/rtf), XML/C-CDA (text/xml). + RTF and XML/C-CDA uploads are available on dedicated instances only. File type is auto-detected from content magic bytes. + The decoded file must not exceed 20 MiB. RTF and XML/C-CDA documents whose extracted text exceeds 1 MiB are rejected. + Generic XML must include an XML declaration; C-CDA documents rooted at `ClinicalDocument` may omit it. config : typing.Optional[DocumentConfig] @@ -780,7 +789,7 @@ async def main() -> None: await client.lang2fhir.document( version="R4", resource="questionnaire", - content="JVBERi0xLjQKJeLjz9MK...(base64-encoded PDF or image bytes)", + content="JVBERi0xLjQKJeLjz9MK...(base64-encoded document bytes)", ) @@ -805,7 +814,7 @@ async def document_multi( request_options: typing.Optional[RequestOptions] = None, ) -> DocumentMultiResponse: """ - Extracts text from a document (PDF or image) and converts it into multiple FHIR resources, + Extracts text from a PDF, image, RTF, or XML/C-CDA document and converts it into multiple FHIR resources, returned as a transaction Bundle. Combines document text extraction with multi-resource detection. Automatically detects Patient, Condition, MedicationRequest, Observation, and other resource types. Resources are linked with proper references (e.g., Conditions reference the Patient). @@ -821,8 +830,11 @@ async def document_multi( content : str Base64 encoded file content. - Supported file types: PDF (application/pdf), PNG (image/png), JPEG (image/jpeg), TIFF (image/tiff). + Supported file types: PDF (application/pdf), PNG (image/png), JPEG (image/jpeg), TIFF (image/tiff), RTF (application/rtf), XML/C-CDA (text/xml). + RTF and XML/C-CDA uploads are available on dedicated instances only. File type is auto-detected from content magic bytes. + The decoded file must not exceed 20 MiB. RTF and XML/C-CDA documents whose extracted text exceeds 1 MiB are rejected. + Generic XML must include an XML declaration; C-CDA documents rooted at `ClinicalDocument` may omit it. provider : typing.Optional[str] Optional FHIR provider name for provider-specific profiles @@ -864,7 +876,7 @@ async def document_multi( async def main() -> None: await client.lang2fhir.document_multi( version="R4", - content="JVBERi0xLjQKJeLjz9MK...(base64-encoded PDF or image bytes)", + content="JVBERi0xLjQKJeLjz9MK...(base64-encoded document bytes)", provider="medplum", config=DocumentConfig( split_classifications=[ diff --git a/src/phenoml/lang2fhir/raw_client.py b/src/phenoml/lang2fhir/raw_client.py index 7848589..c2cdb41 100644 --- a/src/phenoml/lang2fhir/raw_client.py +++ b/src/phenoml/lang2fhir/raw_client.py @@ -537,7 +537,7 @@ def document( request_options: typing.Optional[RequestOptions] = None, ) -> HttpResponse[FhirResource]: """ - Extracts text from a document (PDF or image) and converts it into a structured FHIR resource. + Extracts text from a PDF, image, RTF, or XML/C-CDA document and converts it into a structured FHIR resource. **Patient identifier handling.** When generating a `patient` (or `patient-canvas`) resource, US Core requires `Patient.identifier` (a business identifier such as an MRN). When the source text contains an identifier, it is extracted with an appropriate URI system. When the source text does not contain a detectable identifier, a synthetic one is generated with `system: "urn:phenoml:lang2fhir-generated-id"` and a UUID `value` so the resource remains FHIR-valid and US Core conformant. Callers who need a tenant-specific namespace should rewrite the synthetic system after extraction. @@ -551,8 +551,11 @@ def document( content : str Base64 encoded file content. - Supported file types: PDF (application/pdf), PNG (image/png), JPEG (image/jpeg), TIFF (image/tiff). + Supported file types: PDF (application/pdf), PNG (image/png), JPEG (image/jpeg), TIFF (image/tiff), RTF (application/rtf), XML/C-CDA (text/xml). + RTF and XML/C-CDA uploads are available on dedicated instances only. File type is auto-detected from content magic bytes. + The decoded file must not exceed 20 MiB. RTF and XML/C-CDA documents whose extracted text exceeds 1 MiB are rejected. + Generic XML must include an XML declaration; C-CDA documents rooted at `ClinicalDocument` may omit it. config : typing.Optional[DocumentConfig] @@ -613,6 +616,17 @@ def document( ), ), ) + 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), @@ -691,7 +705,7 @@ def document_multi( request_options: typing.Optional[RequestOptions] = None, ) -> HttpResponse[DocumentMultiResponse]: """ - Extracts text from a document (PDF or image) and converts it into multiple FHIR resources, + Extracts text from a PDF, image, RTF, or XML/C-CDA document and converts it into multiple FHIR resources, returned as a transaction Bundle. Combines document text extraction with multi-resource detection. Automatically detects Patient, Condition, MedicationRequest, Observation, and other resource types. Resources are linked with proper references (e.g., Conditions reference the Patient). @@ -707,8 +721,11 @@ def document_multi( content : str Base64 encoded file content. - Supported file types: PDF (application/pdf), PNG (image/png), JPEG (image/jpeg), TIFF (image/tiff). + Supported file types: PDF (application/pdf), PNG (image/png), JPEG (image/jpeg), TIFF (image/tiff), RTF (application/rtf), XML/C-CDA (text/xml). + RTF and XML/C-CDA uploads are available on dedicated instances only. File type is auto-detected from content magic bytes. + The decoded file must not exceed 20 MiB. RTF and XML/C-CDA documents whose extracted text exceeds 1 MiB are rejected. + Generic XML must include an XML declaration; C-CDA documents rooted at `ClinicalDocument` may omit it. provider : typing.Optional[str] Optional FHIR provider name for provider-specific profiles @@ -789,6 +806,17 @@ def document_multi( ), ), ) + 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), @@ -1354,7 +1382,7 @@ async def document( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncHttpResponse[FhirResource]: """ - Extracts text from a document (PDF or image) and converts it into a structured FHIR resource. + Extracts text from a PDF, image, RTF, or XML/C-CDA document and converts it into a structured FHIR resource. **Patient identifier handling.** When generating a `patient` (or `patient-canvas`) resource, US Core requires `Patient.identifier` (a business identifier such as an MRN). When the source text contains an identifier, it is extracted with an appropriate URI system. When the source text does not contain a detectable identifier, a synthetic one is generated with `system: "urn:phenoml:lang2fhir-generated-id"` and a UUID `value` so the resource remains FHIR-valid and US Core conformant. Callers who need a tenant-specific namespace should rewrite the synthetic system after extraction. @@ -1368,8 +1396,11 @@ async def document( content : str Base64 encoded file content. - Supported file types: PDF (application/pdf), PNG (image/png), JPEG (image/jpeg), TIFF (image/tiff). + Supported file types: PDF (application/pdf), PNG (image/png), JPEG (image/jpeg), TIFF (image/tiff), RTF (application/rtf), XML/C-CDA (text/xml). + RTF and XML/C-CDA uploads are available on dedicated instances only. File type is auto-detected from content magic bytes. + The decoded file must not exceed 20 MiB. RTF and XML/C-CDA documents whose extracted text exceeds 1 MiB are rejected. + Generic XML must include an XML declaration; C-CDA documents rooted at `ClinicalDocument` may omit it. config : typing.Optional[DocumentConfig] @@ -1430,6 +1461,17 @@ async def document( ), ), ) + 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), @@ -1508,7 +1550,7 @@ async def document_multi( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncHttpResponse[DocumentMultiResponse]: """ - Extracts text from a document (PDF or image) and converts it into multiple FHIR resources, + Extracts text from a PDF, image, RTF, or XML/C-CDA document and converts it into multiple FHIR resources, returned as a transaction Bundle. Combines document text extraction with multi-resource detection. Automatically detects Patient, Condition, MedicationRequest, Observation, and other resource types. Resources are linked with proper references (e.g., Conditions reference the Patient). @@ -1524,8 +1566,11 @@ async def document_multi( content : str Base64 encoded file content. - Supported file types: PDF (application/pdf), PNG (image/png), JPEG (image/jpeg), TIFF (image/tiff). + Supported file types: PDF (application/pdf), PNG (image/png), JPEG (image/jpeg), TIFF (image/tiff), RTF (application/rtf), XML/C-CDA (text/xml). + RTF and XML/C-CDA uploads are available on dedicated instances only. File type is auto-detected from content magic bytes. + The decoded file must not exceed 20 MiB. RTF and XML/C-CDA documents whose extracted text exceeds 1 MiB are rejected. + Generic XML must include an XML declaration; C-CDA documents rooted at `ClinicalDocument` may omit it. provider : typing.Optional[str] Optional FHIR provider name for provider-specific profiles @@ -1606,6 +1651,17 @@ async def document_multi( ), ), ) + 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), diff --git a/src/phenoml/lang2fhir/types/__init__.py b/src/phenoml/lang2fhir/types/__init__.py index 1230315..faaded9 100644 --- a/src/phenoml/lang2fhir/types/__init__.py +++ b/src/phenoml/lang2fhir/types/__init__.py @@ -36,6 +36,7 @@ from .resource_review_finding import ResourceReviewFinding from .resource_review_finding_field_kind import ResourceReviewFindingFieldKind from .resource_review_flagged import ResourceReviewFlagged + from .resource_review_remediated import ResourceReviewRemediated from .resource_review_result import ResourceReviewResult from .resource_review_target import ResourceReviewTarget from .resource_review_target_fields_item import ResourceReviewTargetFieldsItem @@ -71,6 +72,7 @@ "ResourceReviewFinding": ".resource_review_finding", "ResourceReviewFindingFieldKind": ".resource_review_finding_field_kind", "ResourceReviewFlagged": ".resource_review_flagged", + "ResourceReviewRemediated": ".resource_review_remediated", "ResourceReviewResult": ".resource_review_result", "ResourceReviewTarget": ".resource_review_target", "ResourceReviewTargetFieldsItem": ".resource_review_target_fields_item", @@ -130,6 +132,7 @@ def __dir__(): "ResourceReviewFinding", "ResourceReviewFindingFieldKind", "ResourceReviewFlagged", + "ResourceReviewRemediated", "ResourceReviewResult", "ResourceReviewTarget", "ResourceReviewTargetFieldsItem", diff --git a/src/phenoml/lang2fhir/types/resource_review.py b/src/phenoml/lang2fhir/types/resource_review.py index 02f825d..6a62b33 100644 --- a/src/phenoml/lang2fhir/types/resource_review.py +++ b/src/phenoml/lang2fhir/types/resource_review.py @@ -9,7 +9,7 @@ class ResourceReview(UniversalBaseModel): """ - Opt-in, report-only faithfulness audit (honored by /lang2fhir/create/multi and /lang2fhir/document/multi). For each selected resource type an LLM checks whether selected dates and clinical code concepts are actually supported by the full source document. Resources with an unsupported field are pulled out of the returned bundle and reported under resource_review in the response. + Opt-in faithfulness audit (honored by /lang2fhir/create/multi and /lang2fhir/document/multi). For each selected resource type an LLM checks whether selected dates and clinical code concepts are actually supported by the full source document. An unsupported individual coding is removed when another coding remains in its concept. Resources with an unsupported structural field, a profile-required coding, or no coding remaining in an affected concept, are pulled out of the returned bundle and reported under resource_review in the response. """ targets: typing.List[ResourceReviewTarget] = pydantic.Field() diff --git a/src/phenoml/lang2fhir/types/resource_review_finding.py b/src/phenoml/lang2fhir/types/resource_review_finding.py index 5703149..c78e567 100644 --- a/src/phenoml/lang2fhir/types/resource_review_finding.py +++ b/src/phenoml/lang2fhir/types/resource_review_finding.py @@ -27,7 +27,12 @@ class ResourceReviewFinding(UniversalBaseModel): value: typing.Optional[str] = None supported: typing.Optional[bool] = pydantic.Field(default=None) """ - Always false for a flagged finding. + False when the reviewer found the field unsupported. Do not treat this field as a verdict when unaudited is true. + """ + + unaudited: typing.Optional[bool] = pydantic.Field(default=None) + """ + True when the reviewer did not return a verdict for this field; the resource was quarantined without treating the finding as evidence that the value is unsupported. """ rationale: typing.Optional[str] = pydantic.Field(default=None) diff --git a/src/phenoml/lang2fhir/types/resource_review_flagged.py b/src/phenoml/lang2fhir/types/resource_review_flagged.py index 01b845d..a3f08af 100644 --- a/src/phenoml/lang2fhir/types/resource_review_flagged.py +++ b/src/phenoml/lang2fhir/types/resource_review_flagged.py @@ -29,7 +29,7 @@ class ResourceReviewFlagged(UniversalBaseModel): findings: typing.Optional[typing.List[ResourceReviewFinding]] = pydantic.Field(default=None) """ - The unsupported fields that caused the resource to be flagged. + The findings that caused the resource to be quarantined. """ if IS_PYDANTIC_V2: diff --git a/src/phenoml/lang2fhir/types/resource_review_remediated.py b/src/phenoml/lang2fhir/types/resource_review_remediated.py new file mode 100644 index 0000000..0db4999 --- /dev/null +++ b/src/phenoml/lang2fhir/types/resource_review_remediated.py @@ -0,0 +1,42 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ...core.serialization import FieldMetadata +from .resource_review_finding import ResourceReviewFinding + + +class ResourceReviewRemediated(UniversalBaseModel): + temp_id: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="tempId"), + pydantic.Field(alias="tempId", description="The urn:uuid of the remediated resource (its bundle fullUrl)."), + ] = None + """ + The urn:uuid of the remediated resource (its bundle fullUrl). + """ + + resource_type: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="resourceType"), pydantic.Field(alias="resourceType") + ] = None + action: typing.Optional[typing.Literal["removed_codings"]] = pydantic.Field(default=None) + """ + The safe change applied to the resource in the returned bundle. + """ + + findings: typing.Optional[typing.List[ResourceReviewFinding]] = pydantic.Field(default=None) + """ + Findings for fields in the pre-remediation resource that caused this action. + """ + + 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/types/resource_review_result.py b/src/phenoml/lang2fhir/types/resource_review_result.py index fa91ed5..16ae116 100644 --- a/src/phenoml/lang2fhir/types/resource_review_result.py +++ b/src/phenoml/lang2fhir/types/resource_review_result.py @@ -5,16 +5,22 @@ import pydantic from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel from .resource_review_flagged import ResourceReviewFlagged +from .resource_review_remediated import ResourceReviewRemediated class ResourceReviewResult(UniversalBaseModel): """ - Present when resource_review was requested and at least one resource was flagged. + Present when resource_review was requested and at least one resource was quarantined or safely remediated. The returned bundle is authoritative and contains the post-review representation of every retained resource. """ flagged: typing.Optional[typing.List[ResourceReviewFlagged]] = pydantic.Field(default=None) """ - Resources pulled from the bundle because a reviewed field was not supported by the source. + Resources pulled from the bundle because an unsupported finding could not be safely repaired. + """ + + remediated: typing.Optional[typing.List[ResourceReviewRemediated]] = pydantic.Field(default=None) + """ + Resources retained in the bundle after unsupported codings were safely removed. """ if IS_PYDANTIC_V2: diff --git a/src/phenoml/lang2fhir_batch/client.py b/src/phenoml/lang2fhir_batch/client.py index d641755..08698ab 100644 --- a/src/phenoml/lang2fhir_batch/client.py +++ b/src/phenoml/lang2fhir_batch/client.py @@ -90,16 +90,15 @@ def create( finalized is released for a fresh replay; once a job is finalized, its `request_id` keeps resolving to it even after cancellation. - 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. + There is no limit on how many jobs an instance may hold at once; how many + items run in parallel is a property of the instance, not of the job count. 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. + Optional client idempotency token (at most 256 UTF-8 bytes). 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. @@ -145,7 +144,7 @@ def upload_item( - 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). + document's file content (PDF, image, RTF, or XML/C-CDA). - When `create` is set, `file` is **forbidden** — a create item carries no file. - `document` and `create` must each be a JSON **object**. @@ -202,15 +201,16 @@ def upload_item( 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. + Optional idempotency token (at most 256 UTF-8 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. + Optional caller-supplied correlation label (at most 512 UTF-8 + 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. @@ -281,8 +281,8 @@ def finalize(self, job_id: str, *, request_options: typing.Optional[RequestOptio def cancel(self, job_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> BatchJob: """ - Drives a job to the terminal `canceled` state on request, freeing its - active-job slot immediately. Takes no request body. + Drives a job to the terminal `canceled` state on request. Takes no + request body. Cancel does not delete the job: the job record and any results already produced are preserved for the normal retention window, the same as a @@ -554,16 +554,15 @@ async def create( finalized is released for a fresh replay; once a job is finalized, its `request_id` keeps resolving to it even after cancellation. - 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. + There is no limit on how many jobs an instance may hold at once; how many + items run in parallel is a property of the instance, not of the job count. 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. + Optional client idempotency token (at most 256 UTF-8 bytes). 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. @@ -617,7 +616,7 @@ async def upload_item( - 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). + document's file content (PDF, image, RTF, or XML/C-CDA). - When `create` is set, `file` is **forbidden** — a create item carries no file. - `document` and `create` must each be a JSON **object**. @@ -674,15 +673,16 @@ async def upload_item( 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. + Optional idempotency token (at most 256 UTF-8 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. + Optional caller-supplied correlation label (at most 512 UTF-8 + 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. @@ -769,8 +769,8 @@ async def main() -> None: async def cancel(self, job_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> BatchJob: """ - Drives a job to the terminal `canceled` state on request, freeing its - active-job slot immediately. Takes no request body. + Drives a job to the terminal `canceled` state on request. Takes no + request body. Cancel does not delete the job: the job record and any results already produced are preserved for the normal retention window, the same as a diff --git a/src/phenoml/lang2fhir_batch/raw_client.py b/src/phenoml/lang2fhir_batch/raw_client.py index 8873fac..d72d92e 100644 --- a/src/phenoml/lang2fhir_batch/raw_client.py +++ b/src/phenoml/lang2fhir_batch/raw_client.py @@ -161,16 +161,15 @@ def create( finalized is released for a fresh replay; once a job is finalized, its `request_id` keeps resolving to it even after cancellation. - 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. + There is no limit on how many jobs an instance may hold at once; how many + items run in parallel is a property of the instance, not of the job count. 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. + Optional client idempotency token (at most 256 UTF-8 bytes). 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. @@ -224,17 +223,6 @@ def create( ), ), ) - 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), @@ -298,7 +286,7 @@ def upload_item( - 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). + document's file content (PDF, image, RTF, or XML/C-CDA). - When `create` is set, `file` is **forbidden** — a create item carries no file. - `document` and `create` must each be a JSON **object**. @@ -355,15 +343,16 @@ def upload_item( 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. + Optional idempotency token (at most 256 UTF-8 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. + Optional caller-supplied correlation label (at most 512 UTF-8 + 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. @@ -630,8 +619,8 @@ def finalize( def cancel(self, job_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> HttpResponse[BatchJob]: """ - Drives a job to the terminal `canceled` state on request, freeing its - active-job slot immediately. Takes no request body. + Drives a job to the terminal `canceled` state on request. Takes no + request body. Cancel does not delete the job: the job record and any results already produced are preserved for the normal retention window, the same as a @@ -1265,16 +1254,15 @@ async def create( finalized is released for a fresh replay; once a job is finalized, its `request_id` keeps resolving to it even after cancellation. - 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. + There is no limit on how many jobs an instance may hold at once; how many + items run in parallel is a property of the instance, not of the job count. 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. + Optional client idempotency token (at most 256 UTF-8 bytes). 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. @@ -1328,17 +1316,6 @@ async def create( ), ), ) - 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), @@ -1402,7 +1379,7 @@ async def upload_item( - 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). + document's file content (PDF, image, RTF, or XML/C-CDA). - When `create` is set, `file` is **forbidden** — a create item carries no file. - `document` and `create` must each be a JSON **object**. @@ -1459,15 +1436,16 @@ async def upload_item( 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. + Optional idempotency token (at most 256 UTF-8 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. + Optional caller-supplied correlation label (at most 512 UTF-8 + 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. @@ -1736,8 +1714,8 @@ async def cancel( self, job_id: str, *, request_options: typing.Optional[RequestOptions] = None ) -> AsyncHttpResponse[BatchJob]: """ - Drives a job to the terminal `canceled` state on request, freeing its - active-job slot immediately. Takes no request body. + Drives a job to the terminal `canceled` state on request. Takes no + request body. Cancel does not delete the job: the job record and any results already produced are preserved for the normal retention window, the same as a diff --git a/src/phenoml/lang2fhir_batch/types/batch_error.py b/src/phenoml/lang2fhir_batch/types/batch_error.py index 29efa45..396e45a 100644 --- a/src/phenoml/lang2fhir_batch/types/batch_error.py +++ b/src/phenoml/lang2fhir_batch/types/batch_error.py @@ -15,12 +15,10 @@ class BatchError(UniversalBaseModel): """ 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). + `processing_failed` (the conversion failed), `result_too_large` (the + result exceeded the storage cap), `input_unavailable` (the input + could not be read), and `retries_exhausted` / `attempts_exhausted` + (the item could not complete after repeated interruptions). Job-level kinds: `timeout` (the job did not finish within 36 hours of creation). """ diff --git a/src/phenoml/lang2fhir_batch/types/batch_item_status.py b/src/phenoml/lang2fhir_batch/types/batch_item_status.py index e2da4e9..84e5f98 100644 --- a/src/phenoml/lang2fhir_batch/types/batch_item_status.py +++ b/src/phenoml/lang2fhir_batch/types/batch_item_status.py @@ -31,9 +31,8 @@ class BatchItemStatus(UniversalBaseModel): 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. + Number of processing attempts started so far. The service may retry + interrupted work; a conversion error fails the item without retry. """ detect_retries: typing.Optional[int] = pydantic.Field(default=None) diff --git a/src/phenoml/lang2fhir_batch/types/batch_job.py b/src/phenoml/lang2fhir_batch/types/batch_job.py index a574933..91bd46b 100644 --- a/src/phenoml/lang2fhir_batch/types/batch_job.py +++ b/src/phenoml/lang2fhir_batch/types/batch_job.py @@ -31,8 +31,8 @@ class BatchJob(UniversalBaseModel): failure (the job could not run at all), distinct from individual item failures, which never fail the job. `canceled` is a caller-requested cancellation via `POST /lang2fhir/batch/{job_id}/cancel`; like the - other terminal states it frees the job's active-job slot and keeps any - results already produced readable for the retention window. + other terminal states it keeps any results already produced readable + for the retention window. """ finalized: bool = pydantic.Field() diff --git a/src/phenoml/profiles/profiles/raw_client.py b/src/phenoml/profiles/profiles/raw_client.py index 907ac1d..54ef7fc 100644 --- a/src/phenoml/profiles/profiles/raw_client.py +++ b/src/phenoml/profiles/profiles/raw_client.py @@ -527,6 +527,17 @@ def delete(self, id: str, *, request_options: typing.Optional[RequestOptions] = ), ), ) + 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), @@ -1051,6 +1062,17 @@ async def delete( ), ), ) + 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), diff --git a/src/phenoml/profiles/versions/raw_client.py b/src/phenoml/profiles/versions/raw_client.py index 0a1e0c1..7f77e6e 100644 --- a/src/phenoml/profiles/versions/raw_client.py +++ b/src/phenoml/profiles/versions/raw_client.py @@ -429,6 +429,17 @@ def delete( ), ), ) + 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), @@ -853,6 +864,17 @@ async def delete( ), ), ) + 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), diff --git a/tests/utils/test_http_client.py b/tests/utils/test_http_client.py index 732c2f9..4cf2992 100644 --- a/tests/utils/test_http_client.py +++ b/tests/utils/test_http_client.py @@ -11,6 +11,7 @@ HttpClient, _build_url, _should_retry, + drop_content_type_without_body, get_request_body, remove_none_from_dict, ) @@ -121,6 +122,67 @@ def test_explicit_empty_json_body_is_preserved() -> None: assert data_body2 == {} +def test_omitted_body_sends_no_content() -> None: + """A body left at the sentinel was never passed, so nothing is sent. + + This is how an endpoint whose body may be omitted stays bodyless, as opposed to + sending an empty ``{}``. An explicit ``None`` still reaches the wire as ``null``. + """ + omit = cast(Any, ...) + unrelated_request_options: RequestOptions = {"max_retries": 3} + + json_body, data_body = get_request_body(json=omit, data=None, request_options=unrelated_request_options, omit=omit) + assert json_body is None + assert data_body is None + + json_body2, data_body2 = get_request_body( + json=None, data=omit, request_options=unrelated_request_options, omit=omit + ) + assert json_body2 is None + assert data_body2 is None + + # an explicitly passed body is untouched + json_body3, _ = get_request_body(json={"hello": "world"}, data=None, request_options=None, omit=omit) + assert json_body3 == {"hello": "world"} + + +def test_optional_body_sends_no_content_when_every_property_is_omitted() -> None: + """An endpoint that inlines an omittable body sends nothing once every property is omitted. + + The body reaches the client as a dict of sentinels rather than as a single argument, so + ``optional_body`` is what tells the client that an empty result means "no body at all". + """ + omit = cast(Any, ...) + + json_body, data_body = get_request_body( + json={"amount": omit, "source": omit}, data=None, request_options=None, omit=omit, optional_body=True + ) + assert json_body is None + assert data_body is None + + # a body the API requires still goes out as `{}` + required_json_body, _ = get_request_body( + json={"amount": omit, "source": omit}, data=None, request_options=None, omit=omit + ) + assert required_json_body == {} + + # a property the caller did pass keeps the body + populated_json_body, _ = get_request_body( + json={"amount": 1, "source": omit}, data=None, request_options=None, omit=omit, optional_body=True + ) + assert populated_json_body == {"amount": 1} + + # additional body parameters keep the body, since the caller asked for them + with_additional_body_parameters, _ = get_request_body( + json={"amount": omit}, + data=None, + request_options={"additional_body_parameters": {"custom": "value"}}, + omit=omit, + optional_body=True, + ) + assert with_additional_body_parameters == {"custom": "value"} + + def test_json_body_preserves_none_values() -> None: """Test that JSON bodies preserve None values (they become JSON null).""" json_body, data_body = get_request_body( @@ -672,28 +734,20 @@ def _make_response(status_code: int) -> httpx.Response: return httpx.Response(status_code=status_code, content=b"") -@pytest.mark.parametrize( - "status_code", - [408, 409, 429, 500, 501, 502, 503, 504, 599], -) +RETRYABLE_STATUS_CODES = [408, 409, 429, 500, 501, 502, 503, 504, 599] +NON_RETRYABLE_STATUS_CODES = [200, 201, 301, 400, 401, 403, 404] + + +@pytest.mark.parametrize("status_code", RETRYABLE_STATUS_CODES) def test_should_retry_retryable_status_codes(status_code: int) -> None: - """Legacy mode: retries on 408, 409, 429, and all >= 500.""" assert _should_retry(_make_response(status_code)) is True -@pytest.mark.parametrize( - "status_code", - [200, 201, 301, 400, 401, 403, 404], -) +@pytest.mark.parametrize("status_code", NON_RETRYABLE_STATUS_CODES) def test_should_not_retry_non_retryable_status_codes(status_code: int) -> None: assert _should_retry(_make_response(status_code)) is False -def test_should_retry_599_upper_boundary() -> None: - """Legacy mode retries on >= 500, which includes 599.""" - assert _should_retry(_make_response(599)) is True - - # --------------------------------------------------------------------------- # RequestOptions timeout resolution tests (timeout / deprecated timeout_in_seconds) # --------------------------------------------------------------------------- @@ -759,3 +813,33 @@ async def test_async_request_options_timeout_takes_precedence() -> None: ) await http_client.request(path="/test", method="GET", request_options={"timeout": 30, "timeout_in_seconds": 45}) assert dummy_client.last_request_kwargs["timeout"] == 30 + + +def test_drop_content_type_without_body_omits_header_for_bodyless_optional_call() -> None: + """An optional-body endpoint the caller left empty must not advertise a media type. + + `get_request_body` drops the body, but the endpoint still hands over the content type it + would have used, so a server that branches on the header would see a JSON request carrying + nothing at all. + """ + headers = {"content-type": "application/json", "authorization": "Bearer x"} + + assert drop_content_type_without_body(headers, json_body=None, data_body=None, optional_body=True) == { + "authorization": "Bearer x" + } + + +def test_drop_content_type_without_body_keeps_header_when_a_body_is_sent() -> None: + headers = {"content-type": "application/json"} + + assert ( + drop_content_type_without_body(headers, json_body={"amount": 60}, data_body=None, optional_body=True) == headers + ) + assert drop_content_type_without_body(headers, json_body=None, data_body="raw", optional_body=True) == headers + + +def test_drop_content_type_without_body_leaves_required_body_endpoints_alone() -> None: + """Without the opt-in, an endpoint keeps the header it has always sent.""" + headers = {"Content-Type": "application/json"} + + assert drop_content_type_without_body(headers, json_body=None, data_body=None, optional_body=False) == headers diff --git a/tests/wire/test_implementationGuides_implementationGuides.py b/tests/wire/test_implementationGuides_implementationGuides.py index f65a229..f0cef92 100644 --- a/tests/wire/test_implementationGuides_implementationGuides.py +++ b/tests/wire/test_implementationGuides_implementationGuides.py @@ -1,5 +1,7 @@ from .conftest import get_client, verify_request_count +from phenoml.implementation_guides import FhirImplementationGuide + def test_implementationGuides_implementationGuides_list_() -> None: """Test list endpoint with WireMock""" @@ -37,3 +39,30 @@ def test_implementationGuides_implementationGuides_delete() -> None: name="acme-cardiology", ) verify_request_count(test_id, "DELETE", "/fhir/implementation-guides/acme-cardiology", None, 1) + + +def test_implementationGuides_implementationGuides_create_version() -> None: + """Test createVersion endpoint with WireMock""" + test_id = "implementation_guides.implementation_guides.create_version.0" + client = get_client(test_id) + client.implementation_guides.implementation_guides.create_version( + name="name", + implementation_guide=FhirImplementationGuide( + resource_type="ImplementationGuide", + url="url", + version="version", + ), + profile_refs=["profile_refs"], + ) + verify_request_count(test_id, "POST", "/fhir/implementation-guides/name/versions", None, 1) + + +def test_implementationGuides_implementationGuides_get_version() -> None: + """Test getVersion endpoint with WireMock""" + test_id = "implementation_guides.implementation_guides.get_version.0" + client = get_client(test_id) + client.implementation_guides.implementation_guides.get_version( + name="name", + version="1.0.0", + ) + verify_request_count(test_id, "GET", "/fhir/implementation-guides/name/versions/1.0.0", None, 1) diff --git a/tests/wire/test_lang2Fhir.py b/tests/wire/test_lang2Fhir.py index 8095b87..5e23cb8 100644 --- a/tests/wire/test_lang2Fhir.py +++ b/tests/wire/test_lang2Fhir.py @@ -55,7 +55,7 @@ def test_lang2Fhir_document() -> None: client.lang2fhir.document( version="R4", resource="questionnaire", - content="JVBERi0xLjQKJeLjz9MK...(base64-encoded PDF or image bytes)", + content="JVBERi0xLjQKJeLjz9MK...(base64-encoded document bytes)", ) verify_request_count(test_id, "POST", "/lang2fhir/document", None, 1) @@ -66,7 +66,7 @@ def test_lang2Fhir_document_multi() -> None: client = get_client(test_id) client.lang2fhir.document_multi( version="R4", - content="JVBERi0xLjQKJeLjz9MK...(base64-encoded PDF or image bytes)", + content="JVBERi0xLjQKJeLjz9MK...(base64-encoded document bytes)", provider="medplum", config=DocumentConfig( split_classifications=[ diff --git a/wiremock/wiremock-mappings.json b/wiremock/wiremock-mappings.json index 9d3fc77..1f0e821 100644 --- a/wiremock/wiremock-mappings.json +++ b/wiremock/wiremock-mappings.json @@ -5,7 +5,12 @@ "name": "Create a new agent - default", "request": { "urlPathTemplate": "/agent/create", - "method": "POST" + "method": "POST", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + } }, "response": { "status": 201, @@ -32,6 +37,11 @@ "request": { "urlPathTemplate": "/agent/list", "method": "GET", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + }, "queryParameters": { "tags": { "equalTo": "tags" @@ -64,6 +74,11 @@ "request": { "urlPathTemplate": "/agent/{id}", "method": "GET", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + }, "pathParameters": { "id": { "equalTo": "id" @@ -95,6 +110,11 @@ "request": { "urlPathTemplate": "/agent/{id}", "method": "PUT", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + }, "pathParameters": { "id": { "equalTo": "id" @@ -126,6 +146,11 @@ "request": { "urlPathTemplate": "/agent/{id}", "method": "DELETE", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + }, "pathParameters": { "id": { "equalTo": "id" @@ -157,6 +182,11 @@ "request": { "urlPathTemplate": "/agent/{id}", "method": "PATCH", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + }, "pathParameters": { "id": { "equalTo": "id" @@ -187,7 +217,12 @@ "name": "Chat with agent - Query Patient Condition", "request": { "urlPathTemplate": "/agent/chat", - "method": "POST" + "method": "POST", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + } }, "response": { "status": 200, @@ -213,7 +248,12 @@ "name": "Chat with agent (streaming) - default", "request": { "urlPathTemplate": "/agent/stream-chat", - "method": "POST" + "method": "POST", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + } }, "response": { "status": 200, @@ -240,6 +280,11 @@ "request": { "urlPathTemplate": "/agent/chat/messages", "method": "GET", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + }, "queryParameters": { "chat_session_id": { "equalTo": "chat_session_id" @@ -280,7 +325,12 @@ "name": "Create agent prompt - default", "request": { "urlPathTemplate": "/agent/prompts", - "method": "POST" + "method": "POST", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + } }, "response": { "status": 201, @@ -306,7 +356,12 @@ "name": "List agent prompts - default", "request": { "urlPathTemplate": "/agent/prompts/list", - "method": "GET" + "method": "GET", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + } }, "response": { "status": 200, @@ -334,6 +389,11 @@ "request": { "urlPathTemplate": "/agent/prompts/{id}", "method": "GET", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + }, "pathParameters": { "id": { "equalTo": "id" @@ -365,6 +425,11 @@ "request": { "urlPathTemplate": "/agent/prompts/{id}", "method": "PUT", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + }, "pathParameters": { "id": { "equalTo": "id" @@ -396,6 +461,11 @@ "request": { "urlPathTemplate": "/agent/prompts/{id}", "method": "DELETE", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + }, "pathParameters": { "id": { "equalTo": "id" @@ -427,6 +497,11 @@ "request": { "urlPathTemplate": "/agent/prompts/{id}", "method": "PATCH", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + }, "pathParameters": { "id": { "equalTo": "id" @@ -483,7 +558,12 @@ "name": "Analyze text for patient cohort criteria - default", "request": { "urlPathTemplate": "/cohort", - "method": "POST" + "method": "POST", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + } }, "response": { "status": 200, @@ -509,7 +589,12 @@ "name": "Upload custom code system - JSON codes (preferred)", "request": { "urlPathTemplate": "/construe/upload", - "method": "POST" + "method": "POST", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + } }, "response": { "status": 202, @@ -535,7 +620,12 @@ "name": "List available code systems - default", "request": { "urlPathTemplate": "/construe/codes/systems", - "method": "GET" + "method": "GET", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + } }, "response": { "status": 200, @@ -563,6 +653,11 @@ "request": { "urlPathTemplate": "/construe/codes/systems/{codesystem}", "method": "GET", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + }, "pathParameters": { "codesystem": { "equalTo": "ICD-10-CM" @@ -599,6 +694,11 @@ "request": { "urlPathTemplate": "/construe/codes/systems/{codesystem}", "method": "DELETE", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + }, "pathParameters": { "codesystem": { "equalTo": "CUSTOM_CODES" @@ -635,6 +735,11 @@ "request": { "urlPathTemplate": "/construe/codes/systems/{codesystem}/export", "method": "GET", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + }, "pathParameters": { "codesystem": { "equalTo": "CUSTOM_CODES" @@ -670,7 +775,12 @@ "name": "Submit feedback on extraction results - default", "request": { "urlPathTemplate": "/construe/feedback", - "method": "POST" + "method": "POST", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + } }, "response": { "status": 201, @@ -696,7 +806,12 @@ "name": "Extract medical codes from text - Basic Extraction", "request": { "urlPathTemplate": "/construe/extract", - "method": "POST" + "method": "POST", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + } }, "response": { "status": 200, @@ -722,7 +837,12 @@ "name": "[Alpha] Extract medical codes with phenocr - HPO Phenotype Extraction", "request": { "urlPathTemplate": "/construe/phenocr", - "method": "POST" + "method": "POST", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + } }, "response": { "status": 200, @@ -748,7 +868,12 @@ "name": "Crosswalk a code to target code systems - ICD-10-CM to HPO", "request": { "urlPathTemplate": "/construe/codes/crosswalk", - "method": "POST" + "method": "POST", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + } }, "response": { "status": 200, @@ -775,6 +900,11 @@ "request": { "urlPathTemplate": "/construe/codes/{codesystem}", "method": "GET", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + }, "pathParameters": { "codesystem": { "equalTo": "ICD-10-CM" @@ -817,6 +947,11 @@ "request": { "urlPathTemplate": "/construe/codes/{codesystem}/{codeID}", "method": "GET", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + }, "pathParameters": { "codesystem": { "equalTo": "ICD-10-CM" @@ -856,6 +991,11 @@ "request": { "urlPathTemplate": "/construe/codes/{codesystem}/search/semantic", "method": "GET", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + }, "pathParameters": { "codesystem": { "equalTo": "ICD-10-CM" @@ -898,6 +1038,11 @@ "request": { "urlPathTemplate": "/construe/codes/{codesystem}/search/text", "method": "GET", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + }, "pathParameters": { "codesystem": { "equalTo": "ICD-10-CM" @@ -940,6 +1085,11 @@ "request": { "urlPathTemplate": "/fhir-provider/{fhir_provider_id}/fhir/{fhir_path}", "method": "GET", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + }, "pathParameters": { "fhir_provider_id": { "equalTo": "550e8400-e29b-41d4-a716-446655440000" @@ -974,6 +1124,11 @@ "request": { "urlPathTemplate": "/fhir-provider/{fhir_provider_id}/fhir/{fhir_path}", "method": "POST", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + }, "pathParameters": { "fhir_provider_id": { "equalTo": "550e8400-e29b-41d4-a716-446655440000" @@ -1008,6 +1163,11 @@ "request": { "urlPathTemplate": "/fhir-provider/{fhir_provider_id}/fhir/{fhir_path}", "method": "PUT", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + }, "pathParameters": { "fhir_provider_id": { "equalTo": "550e8400-e29b-41d4-a716-446655440000" @@ -1042,6 +1202,11 @@ "request": { "urlPathTemplate": "/fhir-provider/{fhir_provider_id}/fhir/{fhir_path}", "method": "DELETE", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + }, "pathParameters": { "fhir_provider_id": { "equalTo": "550e8400-e29b-41d4-a716-446655440000" @@ -1076,6 +1241,11 @@ "request": { "urlPathTemplate": "/fhir-provider/{fhir_provider_id}/fhir/{fhir_path}", "method": "PATCH", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + }, "pathParameters": { "fhir_provider_id": { "equalTo": "550e8400-e29b-41d4-a716-446655440000" @@ -1110,6 +1280,11 @@ "request": { "urlPathTemplate": "/fhir-provider/{fhir_provider_id}/fhir", "method": "POST", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + }, "pathParameters": { "fhir_provider_id": { "equalTo": "550e8400-e29b-41d4-a716-446655440000" @@ -1140,7 +1315,12 @@ "name": "Map FHIR resources to OMOP CDM v5.4 - Mapping result", "request": { "urlPathTemplate": "/fhir2omop/create", - "method": "POST" + "method": "POST", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + } }, "response": { "status": 200, @@ -1166,7 +1346,12 @@ "name": "Create FHIR provider - default", "request": { "urlPathTemplate": "/fhir-provider", - "method": "POST" + "method": "POST", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + } }, "response": { "status": 201, @@ -1192,7 +1377,12 @@ "name": "List FHIR providers - default", "request": { "urlPathTemplate": "/fhir-provider/list", - "method": "GET" + "method": "GET", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + } }, "response": { "status": 200, @@ -1220,6 +1410,11 @@ "request": { "urlPathTemplate": "/fhir-provider/{fhir_provider_id}", "method": "GET", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + }, "pathParameters": { "fhir_provider_id": { "equalTo": "fhir_provider_id" @@ -1251,6 +1446,11 @@ "request": { "urlPathTemplate": "/fhir-provider/{fhir_provider_id}", "method": "DELETE", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + }, "pathParameters": { "fhir_provider_id": { "equalTo": "fhir_provider_id" @@ -1282,6 +1482,11 @@ "request": { "urlPathTemplate": "/fhir-provider/{fhir_provider_id}/add-auth-config", "method": "PATCH", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + }, "pathParameters": { "fhir_provider_id": { "equalTo": "1716d214-de93-43a4-aa6b-a878d864e2ad" @@ -1313,6 +1518,11 @@ "request": { "urlPathTemplate": "/fhir-provider/{fhir_provider_id}/set-active-auth-config", "method": "PATCH", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + }, "pathParameters": { "fhir_provider_id": { "equalTo": "1716d214-de93-43a4-aa6b-a878d864e2ad" @@ -1344,6 +1554,11 @@ "request": { "urlPathTemplate": "/fhir-provider/{fhir_provider_id}/remove-auth-config", "method": "PATCH", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + }, "pathParameters": { "fhir_provider_id": { "equalTo": "1716d214-de93-43a4-aa6b-a878d864e2ad" @@ -1374,11 +1589,16 @@ "name": "List implementation guides - default", "request": { "urlPathTemplate": "/fhir/implementation-guides", - "method": "GET" + "method": "GET", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + } }, "response": { "status": 200, - "body": "{\n \"implementation_guides\": [\n {\n \"name\": \"acme-cardiology\",\n \"profile_context\": \"When the text mentions phenotypic features, prefer the hpo-observation profile over Condition.\",\n \"profile_count\": 3,\n \"created_at\": \"2024-01-15T09:30:00Z\",\n \"updated_at\": \"2024-01-15T09:30:00Z\"\n }\n ]\n}", + "body": "{\n \"implementation_guides\": [\n {\n \"name\": \"acme-cardiology\",\n \"profile_context\": \"When the text mentions phenotypic features, prefer the hpo-observation profile over Condition.\",\n \"profile_count\": 3,\n \"canonical_url\": \"canonical_url\",\n \"version_count\": 1,\n \"created_at\": \"2024-01-15T09:30:00Z\",\n \"updated_at\": \"2024-01-15T09:30:00Z\"\n }\n ]\n}", "headers": { "Content-Type": "application/json" } @@ -1402,6 +1622,11 @@ "request": { "urlPathTemplate": "/fhir/implementation-guides/{name}", "method": "GET", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + }, "pathParameters": { "name": { "equalTo": "acme-cardiology" @@ -1410,7 +1635,7 @@ }, "response": { "status": 200, - "body": "{\n \"name\": \"acme-cardiology\",\n \"profile_context\": \"When the text mentions phenotypic features, prefer the hpo-observation profile over Condition.\",\n \"profile_count\": 3,\n \"created_at\": \"2024-01-15T09:30:00Z\",\n \"updated_at\": \"2024-01-15T09:30:00Z\",\n \"profiles\": [\n \"custom-patient\",\n \"acme-vital-signs\"\n ]\n}", + "body": "{\n \"name\": \"acme-cardiology\",\n \"profile_context\": \"When the text mentions phenotypic features, prefer the hpo-observation profile over Condition.\",\n \"profile_count\": 3,\n \"canonical_url\": \"canonical_url\",\n \"version_count\": 1,\n \"created_at\": \"2024-01-15T09:30:00Z\",\n \"updated_at\": \"2024-01-15T09:30:00Z\",\n \"profiles\": [\n \"custom-patient\",\n \"acme-vital-signs\"\n ]\n}", "headers": { "Content-Type": "application/json" } @@ -1433,6 +1658,11 @@ "request": { "urlPathTemplate": "/fhir/implementation-guides/{name}", "method": "PUT", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + }, "pathParameters": { "name": { "equalTo": "acme-cardiology" @@ -1441,7 +1671,7 @@ }, "response": { "status": 200, - "body": "{\n \"name\": \"acme-cardiology\",\n \"profile_context\": \"When the text mentions phenotypic features, prefer the hpo-observation profile over Condition.\",\n \"profile_count\": 3,\n \"created_at\": \"2024-01-15T09:30:00Z\",\n \"updated_at\": \"2024-01-15T09:30:00Z\"\n}", + "body": "{\n \"name\": \"acme-cardiology\",\n \"profile_context\": \"When the text mentions phenotypic features, prefer the hpo-observation profile over Condition.\",\n \"profile_count\": 3,\n \"canonical_url\": \"canonical_url\",\n \"version_count\": 1,\n \"created_at\": \"2024-01-15T09:30:00Z\",\n \"updated_at\": \"2024-01-15T09:30:00Z\"\n}", "headers": { "Content-Type": "application/json" } @@ -1459,11 +1689,16 @@ } }, { - "id": "44652922-4793-4275-840e-4dc1b1bde786", - "name": "Delete an implementation guide's metadata - default", + "id": "2678a6e7-10b0-4621-ad5c-67980574a8f8", + "name": "Delete an implementation guide family - default", "request": { "urlPathTemplate": "/fhir/implementation-guides/{name}", "method": "DELETE", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + }, "pathParameters": { "name": { "equalTo": "acme-cardiology" @@ -1477,7 +1712,82 @@ "Content-Type": "application/json" } }, - "uuid": "44652922-4793-4275-840e-4dc1b1bde786", + "uuid": "2678a6e7-10b0-4621-ad5c-67980574a8f8", + "persistent": true, + "priority": 3, + "metadata": { + "mocklab": { + "created": { + "at": "2020-01-01T00:00:00.000Z", + "via": "SYSTEM" + } + } + } + }, + { + "id": "a7a30315-de87-4485-a1e4-15eeea48b530", + "name": "Publish an exact custom-profile package - default", + "request": { + "urlPathTemplate": "/fhir/implementation-guides/{name}/versions", + "method": "POST", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + }, + "pathParameters": { + "name": { + "equalTo": "name" + } + } + }, + "response": { + "status": 201, + "body": "{\n \"name\": \"name\",\n \"url\": \"url\",\n \"version\": \"version\",\n \"profile_context\": \"profile_context\",\n \"profiles\": [\n \"profiles\"\n ],\n \"profile_refs\": [\n \"profile_refs\"\n ],\n \"implementation_guide\": {\n \"resourceType\": \"ImplementationGuide\",\n \"id\": \"id\",\n \"url\": \"url\",\n \"version\": \"version\",\n \"name\": \"name\",\n \"status\": \"status\",\n \"packageId\": \"packageId\",\n \"fhirVersion\": [\n \"fhirVersion\"\n ]\n },\n \"created_at\": \"2024-01-15T09:30:00Z\",\n \"updated_at\": \"2024-01-15T09:30:00Z\"\n}", + "headers": { + "Content-Type": "application/json" + } + }, + "uuid": "a7a30315-de87-4485-a1e4-15eeea48b530", + "persistent": true, + "priority": 3, + "metadata": { + "mocklab": { + "created": { + "at": "2020-01-01T00:00:00.000Z", + "via": "SYSTEM" + } + } + } + }, + { + "id": "bfd69c60-e799-438e-a780-2c4c1fd1a9a8", + "name": "Get an exact custom-profile package - default", + "request": { + "urlPathTemplate": "/fhir/implementation-guides/{name}/versions/{version}", + "method": "GET", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + }, + "pathParameters": { + "name": { + "equalTo": "name" + }, + "version": { + "equalTo": "1.0.0" + } + } + }, + "response": { + "status": 200, + "body": "{\n \"name\": \"name\",\n \"url\": \"url\",\n \"version\": \"version\",\n \"profile_context\": \"profile_context\",\n \"profiles\": [\n \"profiles\"\n ],\n \"profile_refs\": [\n \"profile_refs\"\n ],\n \"implementation_guide\": {\n \"resourceType\": \"ImplementationGuide\",\n \"id\": \"id\",\n \"url\": \"url\",\n \"version\": \"version\",\n \"name\": \"name\",\n \"status\": \"status\",\n \"packageId\": \"packageId\",\n \"fhirVersion\": [\n \"fhirVersion\"\n ]\n },\n \"created_at\": \"2024-01-15T09:30:00Z\",\n \"updated_at\": \"2024-01-15T09:30:00Z\"\n}", + "headers": { + "Content-Type": "application/json" + } + }, + "uuid": "bfd69c60-e799-438e-a780-2c4c1fd1a9a8", "persistent": true, "priority": 3, "metadata": { @@ -1494,7 +1804,12 @@ "name": "Create FHIR resource from text - Condition Resource", "request": { "urlPathTemplate": "/lang2fhir/create", - "method": "POST" + "method": "POST", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + } }, "response": { "status": 200, @@ -1520,11 +1835,16 @@ "name": "Extract multiple FHIR resources from text - Full Patient Record", "request": { "urlPathTemplate": "/lang2fhir/create/multi", - "method": "POST" + "method": "POST", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + } }, "response": { "status": 200, - "body": "{\n \"success\": true,\n \"message\": \"Successfully extracted 3 resources\",\n \"bundle\": {\n \"resourceType\": \"Bundle\",\n \"type\": \"transaction\",\n \"entry\": [\n {\n \"fullUrl\": \"urn:uuid:patient-001\",\n \"resource\": {\n \"resourceType\": \"Patient\",\n \"name\": [\n {\n \"given\": [\n \"John\"\n ],\n \"family\": \"Smith\"\n }\n ],\n \"gender\": \"male\"\n },\n \"request\": {\n \"method\": \"POST\",\n \"url\": \"Patient\"\n }\n },\n {\n \"fullUrl\": \"urn:uuid:condition-001\",\n \"resource\": {\n \"resourceType\": \"Condition\",\n \"code\": {\n \"text\": \"Type 2 Diabetes\"\n }\n },\n \"request\": {\n \"method\": \"POST\",\n \"url\": \"Condition\"\n }\n },\n {\n \"fullUrl\": \"urn:uuid:medication-001\",\n \"resource\": {\n \"resourceType\": \"MedicationRequest\",\n \"medicationCodeableConcept\": {\n \"text\": \"Metformin 500mg\"\n }\n },\n \"request\": {\n \"method\": \"POST\",\n \"url\": \"MedicationRequest\"\n }\n }\n ]\n },\n \"resources\": [\n {\n \"tempId\": \"urn:uuid:a842c4bc-f6cb-4555-9741-ac3aec4ef0b8\",\n \"resourceType\": \"Patient\",\n \"description\": \"John Smith (DOB 1980-05-12) was diagnosed with Type 2 Diabetes during office visit on 2025-03-01 with Dr. Chen\",\n \"originalText\": \"diagnosed with Type 2 Diabetes\",\n \"group\": \"clinical\",\n \"sourcePages\": [\n 2\n ]\n }\n ],\n \"validation\": {\n \"passes\": [\n {}\n ],\n \"fixed\": true,\n \"attempts\": 1,\n \"summary\": \"summary\"\n },\n \"resource_review\": {\n \"flagged\": [\n {\n \"tempId\": \"urn:uuid:a842c4bc-f6cb-4555-9741-ac3aec4ef0b8\",\n \"resourceType\": \"Condition\",\n \"findings\": [\n {\n \"fieldPath\": \"onsetDateTime\",\n \"value\": \"2024-01-15\",\n \"supported\": false,\n \"rationale\": \"Date is the visit date, not when the condition began.\"\n }\n ]\n }\n ]\n }\n}", + "body": "{\n \"success\": true,\n \"message\": \"Successfully extracted 3 resources\",\n \"bundle\": {\n \"resourceType\": \"Bundle\",\n \"type\": \"transaction\",\n \"entry\": [\n {\n \"fullUrl\": \"urn:uuid:patient-001\",\n \"resource\": {\n \"resourceType\": \"Patient\",\n \"name\": [\n {\n \"given\": [\n \"John\"\n ],\n \"family\": \"Smith\"\n }\n ],\n \"gender\": \"male\"\n },\n \"request\": {\n \"method\": \"POST\",\n \"url\": \"Patient\"\n }\n },\n {\n \"fullUrl\": \"urn:uuid:condition-001\",\n \"resource\": {\n \"resourceType\": \"Condition\",\n \"code\": {\n \"text\": \"Type 2 Diabetes\"\n }\n },\n \"request\": {\n \"method\": \"POST\",\n \"url\": \"Condition\"\n }\n },\n {\n \"fullUrl\": \"urn:uuid:medication-001\",\n \"resource\": {\n \"resourceType\": \"MedicationRequest\",\n \"medicationCodeableConcept\": {\n \"text\": \"Metformin 500mg\"\n }\n },\n \"request\": {\n \"method\": \"POST\",\n \"url\": \"MedicationRequest\"\n }\n }\n ]\n },\n \"resources\": [\n {\n \"tempId\": \"urn:uuid:a842c4bc-f6cb-4555-9741-ac3aec4ef0b8\",\n \"resourceType\": \"Patient\",\n \"description\": \"John Smith (DOB 1980-05-12) was diagnosed with Type 2 Diabetes during office visit on 2025-03-01 with Dr. Chen\",\n \"originalText\": \"diagnosed with Type 2 Diabetes\",\n \"group\": \"clinical\",\n \"sourcePages\": [\n 2\n ]\n }\n ],\n \"validation\": {\n \"passes\": [\n {}\n ],\n \"fixed\": true,\n \"attempts\": 1,\n \"summary\": \"summary\"\n },\n \"resource_review\": {\n \"flagged\": [\n {\n \"tempId\": \"urn:uuid:a842c4bc-f6cb-4555-9741-ac3aec4ef0b8\",\n \"resourceType\": \"Condition\",\n \"findings\": [\n {\n \"fieldPath\": \"onsetDateTime\",\n \"value\": \"2024-01-15\",\n \"supported\": false,\n \"unaudited\": true,\n \"rationale\": \"Date is the visit date, not when the condition began.\"\n }\n ]\n }\n ],\n \"remediated\": [\n {\n \"tempId\": \"urn:uuid:a842c4bc-f6cb-4555-9741-ac3aec4ef0b8\",\n \"resourceType\": \"Condition\",\n \"findings\": [\n {\n \"fieldPath\": \"onsetDateTime\",\n \"value\": \"2024-01-15\",\n \"supported\": false,\n \"unaudited\": true,\n \"rationale\": \"Date is the visit date, not when the condition began.\"\n }\n ]\n }\n ]\n }\n}", "headers": { "Content-Type": "application/json" } @@ -1546,7 +1866,12 @@ "name": "Generate FHIR search parameters from text - Search Appointments", "request": { "urlPathTemplate": "/lang2fhir/search", - "method": "POST" + "method": "POST", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + } }, "response": { "status": 200, @@ -1572,7 +1897,12 @@ "name": "Upload custom FHIR profile (deprecated) - default", "request": { "urlPathTemplate": "/lang2fhir/profile/upload", - "method": "POST" + "method": "POST", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + } }, "response": { "status": 201, @@ -1598,7 +1928,12 @@ "name": "Convert document to FHIR resource - default", "request": { "urlPathTemplate": "/lang2fhir/document", - "method": "POST" + "method": "POST", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + } }, "response": { "status": 200, @@ -1624,11 +1959,16 @@ "name": "Extract multiple FHIR resources from a document - default", "request": { "urlPathTemplate": "/lang2fhir/document/multi", - "method": "POST" + "method": "POST", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + } }, "response": { "status": 200, - "body": "{\n \"success\": true,\n \"message\": \"Successfully extracted 3 resources\",\n \"bundle\": {\n \"resourceType\": \"Bundle\",\n \"type\": \"transaction\",\n \"entry\": [\n {\n \"fullUrl\": \"urn:uuid:patient-001\",\n \"resource\": {\n \"resourceType\": \"Patient\",\n \"meta\": {\n \"tag\": [\n {\n \"system\": \"https://phenoml.com/fhir/split-classification\",\n \"code\": \"clinical\"\n }\n ]\n },\n \"name\": [\n {\n \"given\": [\n \"John\"\n ],\n \"family\": \"Doe\"\n }\n ],\n \"gender\": \"male\",\n \"birthDate\": \"1979-03-15\"\n },\n \"request\": {\n \"method\": \"POST\",\n \"url\": \"Patient\"\n }\n },\n {\n \"fullUrl\": \"urn:uuid:condition-001\",\n \"resource\": {\n \"resourceType\": \"Condition\",\n \"meta\": {\n \"tag\": [\n {\n \"system\": \"https://phenoml.com/fhir/split-classification\",\n \"code\": \"clinical\"\n }\n ]\n },\n \"code\": {\n \"text\": \"Type 2 Diabetes Mellitus\"\n },\n \"subject\": {\n \"reference\": \"urn:uuid:patient-001\"\n }\n },\n \"request\": {\n \"method\": \"POST\",\n \"url\": \"Condition\"\n }\n },\n {\n \"fullUrl\": \"urn:uuid:medication-001\",\n \"resource\": {\n \"resourceType\": \"MedicationRequest\",\n \"meta\": {\n \"tag\": [\n {\n \"system\": \"https://phenoml.com/fhir/split-classification\",\n \"code\": \"clinical\"\n }\n ]\n },\n \"medicationCodeableConcept\": {\n \"text\": \"Metformin 500mg\"\n },\n \"subject\": {\n \"reference\": \"urn:uuid:patient-001\"\n }\n },\n \"request\": {\n \"method\": \"POST\",\n \"url\": \"MedicationRequest\"\n }\n }\n ]\n },\n \"resources\": [\n {\n \"tempId\": \"urn:uuid:patient-001\",\n \"resourceType\": \"Patient\",\n \"description\": \"John Doe, born 1979-03-15\",\n \"originalText\": \"John Doe, DOB 1979-03-15\",\n \"group\": \"clinical\",\n \"sourcePages\": [\n 1\n ]\n },\n {\n \"tempId\": \"urn:uuid:condition-001\",\n \"resourceType\": \"Condition\",\n \"description\": \"Type 2 Diabetes Mellitus diagnosis\",\n \"originalText\": \"diagnosed with Type 2 Diabetes\",\n \"group\": \"clinical\",\n \"sourcePages\": [\n 1\n ]\n },\n {\n \"tempId\": \"urn:uuid:medication-001\",\n \"resourceType\": \"MedicationRequest\",\n \"description\": \"Metformin 500mg prescription\",\n \"originalText\": \"Prescribed Metformin 500mg\",\n \"group\": \"clinical\",\n \"sourcePages\": [\n 2\n ]\n }\n ],\n \"validation\": {\n \"passes\": [\n {}\n ],\n \"fixed\": true,\n \"attempts\": 1,\n \"summary\": \"summary\"\n },\n \"resource_review\": {\n \"flagged\": [\n {\n \"tempId\": \"urn:uuid:a842c4bc-f6cb-4555-9741-ac3aec4ef0b8\",\n \"resourceType\": \"Condition\",\n \"findings\": [\n {\n \"fieldPath\": \"onsetDateTime\",\n \"value\": \"2024-01-15\",\n \"supported\": false,\n \"rationale\": \"Date is the visit date, not when the condition began.\"\n }\n ]\n }\n ]\n },\n \"page_classifications\": [\n {\n \"page_number\": 1,\n \"include\": true,\n \"classification_id\": \"clinical\",\n \"reason\": \"clinical demographics and diagnosis\"\n },\n {\n \"page_number\": 2,\n \"include\": true,\n \"classification_id\": \"clinical\",\n \"reason\": \"clinical medication details\"\n },\n {\n \"page_number\": 3,\n \"include\": false,\n \"classification_id\": \"admin\",\n \"reason\": \"administrative cover sheet\"\n }\n ]\n}", + "body": "{\n \"success\": true,\n \"message\": \"Successfully extracted 3 resources\",\n \"bundle\": {\n \"resourceType\": \"Bundle\",\n \"type\": \"transaction\",\n \"entry\": [\n {\n \"fullUrl\": \"urn:uuid:patient-001\",\n \"resource\": {\n \"resourceType\": \"Patient\",\n \"meta\": {\n \"tag\": [\n {\n \"system\": \"https://phenoml.com/fhir/split-classification\",\n \"code\": \"clinical\"\n }\n ]\n },\n \"name\": [\n {\n \"given\": [\n \"John\"\n ],\n \"family\": \"Doe\"\n }\n ],\n \"gender\": \"male\",\n \"birthDate\": \"1979-03-15\"\n },\n \"request\": {\n \"method\": \"POST\",\n \"url\": \"Patient\"\n }\n },\n {\n \"fullUrl\": \"urn:uuid:condition-001\",\n \"resource\": {\n \"resourceType\": \"Condition\",\n \"meta\": {\n \"tag\": [\n {\n \"system\": \"https://phenoml.com/fhir/split-classification\",\n \"code\": \"clinical\"\n }\n ]\n },\n \"code\": {\n \"text\": \"Type 2 Diabetes Mellitus\"\n },\n \"subject\": {\n \"reference\": \"urn:uuid:patient-001\"\n }\n },\n \"request\": {\n \"method\": \"POST\",\n \"url\": \"Condition\"\n }\n },\n {\n \"fullUrl\": \"urn:uuid:medication-001\",\n \"resource\": {\n \"resourceType\": \"MedicationRequest\",\n \"meta\": {\n \"tag\": [\n {\n \"system\": \"https://phenoml.com/fhir/split-classification\",\n \"code\": \"clinical\"\n }\n ]\n },\n \"medicationCodeableConcept\": {\n \"text\": \"Metformin 500mg\"\n },\n \"subject\": {\n \"reference\": \"urn:uuid:patient-001\"\n }\n },\n \"request\": {\n \"method\": \"POST\",\n \"url\": \"MedicationRequest\"\n }\n }\n ]\n },\n \"resources\": [\n {\n \"tempId\": \"urn:uuid:patient-001\",\n \"resourceType\": \"Patient\",\n \"description\": \"John Doe, born 1979-03-15\",\n \"originalText\": \"John Doe, DOB 1979-03-15\",\n \"group\": \"clinical\",\n \"sourcePages\": [\n 1\n ]\n },\n {\n \"tempId\": \"urn:uuid:condition-001\",\n \"resourceType\": \"Condition\",\n \"description\": \"Type 2 Diabetes Mellitus diagnosis\",\n \"originalText\": \"diagnosed with Type 2 Diabetes\",\n \"group\": \"clinical\",\n \"sourcePages\": [\n 1\n ]\n },\n {\n \"tempId\": \"urn:uuid:medication-001\",\n \"resourceType\": \"MedicationRequest\",\n \"description\": \"Metformin 500mg prescription\",\n \"originalText\": \"Prescribed Metformin 500mg\",\n \"group\": \"clinical\",\n \"sourcePages\": [\n 2\n ]\n }\n ],\n \"validation\": {\n \"passes\": [\n {}\n ],\n \"fixed\": true,\n \"attempts\": 1,\n \"summary\": \"summary\"\n },\n \"resource_review\": {\n \"flagged\": [\n {\n \"tempId\": \"urn:uuid:a842c4bc-f6cb-4555-9741-ac3aec4ef0b8\",\n \"resourceType\": \"Condition\",\n \"findings\": [\n {\n \"fieldPath\": \"onsetDateTime\",\n \"value\": \"2024-01-15\",\n \"supported\": false,\n \"unaudited\": true,\n \"rationale\": \"Date is the visit date, not when the condition began.\"\n }\n ]\n }\n ],\n \"remediated\": [\n {\n \"tempId\": \"urn:uuid:a842c4bc-f6cb-4555-9741-ac3aec4ef0b8\",\n \"resourceType\": \"Condition\",\n \"findings\": [\n {\n \"fieldPath\": \"onsetDateTime\",\n \"value\": \"2024-01-15\",\n \"supported\": false,\n \"unaudited\": true,\n \"rationale\": \"Date is the visit date, not when the condition began.\"\n }\n ]\n }\n ]\n },\n \"page_classifications\": [\n {\n \"page_number\": 1,\n \"include\": true,\n \"classification_id\": \"clinical\",\n \"reason\": \"clinical demographics and diagnosis\"\n },\n {\n \"page_number\": 2,\n \"include\": true,\n \"classification_id\": \"clinical\",\n \"reason\": \"clinical medication details\"\n },\n {\n \"page_number\": 3,\n \"include\": false,\n \"classification_id\": \"admin\",\n \"reason\": \"administrative cover sheet\"\n }\n ]\n}", "headers": { "Content-Type": "application/json" } @@ -1651,6 +1991,11 @@ "request": { "urlPathTemplate": "/lang2fhir/batch", "method": "GET", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + }, "queryParameters": { "cursor": { "equalTo": "cursor" @@ -1685,7 +2030,12 @@ "name": "Create a batch job - default", "request": { "urlPathTemplate": "/lang2fhir/batch", - "method": "POST" + "method": "POST", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + } }, "response": { "status": 202, @@ -1712,6 +2062,11 @@ "request": { "urlPathTemplate": "/lang2fhir/batch/{job_id}/items", "method": "POST", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + }, "pathParameters": { "job_id": { "equalTo": "job_id" @@ -1743,6 +2098,11 @@ "request": { "urlPathTemplate": "/lang2fhir/batch/{job_id}/finalize", "method": "POST", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + }, "pathParameters": { "job_id": { "equalTo": "job_id" @@ -1774,6 +2134,11 @@ "request": { "urlPathTemplate": "/lang2fhir/batch/{job_id}/cancel", "method": "POST", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + }, "pathParameters": { "job_id": { "equalTo": "job_id" @@ -1805,6 +2170,11 @@ "request": { "urlPathTemplate": "/lang2fhir/batch/{job_id}", "method": "GET", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + }, "pathParameters": { "job_id": { "equalTo": "job_id" @@ -1844,6 +2214,11 @@ "request": { "urlPathTemplate": "/lang2fhir/batch/{job_id}/results", "method": "GET", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + }, "pathParameters": { "job_id": { "equalTo": "job_id" @@ -1883,6 +2258,11 @@ "request": { "urlPathTemplate": "/lang2fhir/batch/{job_id}/results/{item_id}", "method": "GET", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + }, "pathParameters": { "job_id": { "equalTo": "job_id" @@ -1917,6 +2297,11 @@ "request": { "urlPathTemplate": "/fhir/profiles", "method": "GET", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + }, "queryParameters": { "url": { "equalTo": "http://phenoml.com/fhir/StructureDefinition/custom-patient|1.0.0" @@ -1948,7 +2333,12 @@ "name": "Upload a custom FHIR profile - default", "request": { "urlPathTemplate": "/fhir/profiles", - "method": "POST" + "method": "POST", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + } }, "response": { "status": 201, @@ -1975,6 +2365,11 @@ "request": { "urlPathTemplate": "/fhir/profiles/{id}", "method": "GET", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + }, "pathParameters": { "id": { "equalTo": "custom-patient" @@ -2006,6 +2401,11 @@ "request": { "urlPathTemplate": "/fhir/profiles/{id}", "method": "PUT", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + }, "pathParameters": { "id": { "equalTo": "custom-patient" @@ -2037,6 +2437,11 @@ "request": { "urlPathTemplate": "/fhir/profiles/{id}", "method": "DELETE", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + }, "pathParameters": { "id": { "equalTo": "custom-patient" @@ -2068,6 +2473,11 @@ "request": { "urlPathTemplate": "/fhir/profiles/{id}/versions", "method": "GET", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + }, "pathParameters": { "id": { "equalTo": "custom-patient" @@ -2099,6 +2509,11 @@ "request": { "urlPathTemplate": "/fhir/profiles/{id}/versions", "method": "POST", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + }, "pathParameters": { "id": { "equalTo": "custom-patient" @@ -2130,6 +2545,11 @@ "request": { "urlPathTemplate": "/fhir/profiles/{id}/versions/{version}", "method": "GET", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + }, "pathParameters": { "id": { "equalTo": "custom-patient" @@ -2164,6 +2584,11 @@ "request": { "urlPathTemplate": "/fhir/profiles/{id}/versions/{version}", "method": "DELETE", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + }, "pathParameters": { "id": { "equalTo": "custom-patient" @@ -2197,7 +2622,12 @@ "name": "List all summary templates - default", "request": { "urlPathTemplate": "/fhir2summary/templates", - "method": "GET" + "method": "GET", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + } }, "response": { "status": 200, @@ -2224,7 +2654,12 @@ "name": "Create a new summary template - Discharge Summary Template", "request": { "urlPathTemplate": "/fhir2summary/template", - "method": "POST" + "method": "POST", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + } }, "response": { "status": 200, @@ -2251,6 +2686,11 @@ "request": { "urlPathTemplate": "/fhir2summary/template/{id}", "method": "GET", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + }, "pathParameters": { "id": { "equalTo": "id" @@ -2282,6 +2722,11 @@ "request": { "urlPathTemplate": "/fhir2summary/template/{id}", "method": "PUT", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + }, "pathParameters": { "id": { "equalTo": "id" @@ -2313,6 +2758,11 @@ "request": { "urlPathTemplate": "/fhir2summary/template/{id}", "method": "DELETE", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + }, "pathParameters": { "id": { "equalTo": "id" @@ -2343,7 +2793,12 @@ "name": "Generate a summary from FHIR resources - Narrative Summary", "request": { "urlPathTemplate": "/fhir2summary/create", - "method": "POST" + "method": "POST", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + } }, "response": { "status": 200, @@ -2369,7 +2824,12 @@ "name": "Create FHIR resource from text and store it - default", "request": { "urlPathTemplate": "/tools/lang2fhir-and-create", - "method": "POST" + "method": "POST", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + } }, "response": { "status": 200, @@ -2395,7 +2855,12 @@ "name": "Extract and store multiple FHIR resources - default", "request": { "urlPathTemplate": "/tools/lang2fhir-and-create-multi", - "method": "POST" + "method": "POST", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + } }, "response": { "status": 200, @@ -2421,7 +2886,12 @@ "name": "Search FHIR resources from natural language - default", "request": { "urlPathTemplate": "/tools/lang2fhir-and-search", - "method": "POST" + "method": "POST", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + } }, "response": { "status": 200, @@ -2447,7 +2917,12 @@ "name": "Analyze patient cohorts - default", "request": { "urlPathTemplate": "/tools/cohort", - "method": "POST" + "method": "POST", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + } }, "response": { "status": 200, @@ -2473,7 +2948,12 @@ "name": "Create MCP server - default", "request": { "urlPathTemplate": "/tools/mcp-server/create", - "method": "POST" + "method": "POST", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + } }, "response": { "status": 200, @@ -2499,7 +2979,12 @@ "name": "List MCP servers - default", "request": { "urlPathTemplate": "/tools/mcp-server/list", - "method": "GET" + "method": "GET", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + } }, "response": { "status": 200, @@ -2527,6 +3012,11 @@ "request": { "urlPathTemplate": "/tools/mcp-server/{mcp_server_id}", "method": "GET", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + }, "pathParameters": { "mcp_server_id": { "equalTo": "mcp_server_id" @@ -2558,6 +3048,11 @@ "request": { "urlPathTemplate": "/tools/mcp-server/{mcp_server_id}", "method": "DELETE", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + }, "pathParameters": { "mcp_server_id": { "equalTo": "mcp_server_id" @@ -2589,6 +3084,11 @@ "request": { "urlPathTemplate": "/tools/mcp-server/{mcp_server_id}/list", "method": "GET", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + }, "pathParameters": { "mcp_server_id": { "equalTo": "mcp_server_id" @@ -2620,6 +3120,11 @@ "request": { "urlPathTemplate": "/tools/mcp-server/tool/{mcp_server_tool_id}", "method": "GET", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + }, "pathParameters": { "mcp_server_tool_id": { "equalTo": "mcp_server_tool_id" @@ -2651,6 +3156,11 @@ "request": { "urlPathTemplate": "/tools/mcp-server/tool/{mcp_server_tool_id}", "method": "DELETE", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + }, "pathParameters": { "mcp_server_tool_id": { "equalTo": "mcp_server_tool_id" @@ -2682,6 +3192,11 @@ "request": { "urlPathTemplate": "/workflows", "method": "GET", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + }, "queryParameters": { "verbose": { "equalTo": "true" @@ -2714,6 +3229,11 @@ "request": { "urlPathTemplate": "/workflows", "method": "POST", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + }, "queryParameters": { "verbose": { "equalTo": "true" @@ -2745,6 +3265,11 @@ "request": { "urlPathTemplate": "/workflows/{id}", "method": "GET", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + }, "pathParameters": { "id": { "equalTo": "id" @@ -2781,6 +3306,11 @@ "request": { "urlPathTemplate": "/workflows/{id}", "method": "PUT", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + }, "pathParameters": { "id": { "equalTo": "id" @@ -2817,6 +3347,11 @@ "request": { "urlPathTemplate": "/workflows/{id}", "method": "DELETE", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + }, "pathParameters": { "id": { "equalTo": "id" @@ -2848,6 +3383,11 @@ "request": { "urlPathTemplate": "/workflows/{id}/execute", "method": "POST", + "headers": { + "Authorization": { + "matches": "Bearer .+" + } + }, "pathParameters": { "id": { "equalTo": "7a8b9c0d-1234-5678-abcd-ef9876543210" @@ -2875,6 +3415,6 @@ } ], "meta": { - "total": 94 + "total": 96 } } \ No newline at end of file From 65130a0225c2416e10cd48e340bbed4615158976 Mon Sep 17 00:00:00 2001 From: "fern-api[bot]" <115122769+fern-api[bot]@users.noreply.github.com> Date: Thu, 17 Sep 2026 18:57:23 +0000 Subject: [PATCH 2/4] [fern-autoversion] feat: add implementation guide versioning and TCP keepalive support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduce `create_version` and `get_version` methods on the `ImplementationGuidesClient` (sync and async) for publishing and retrieving exact canonical FHIR package versions beneath a guide family. Also adds TCP keepalive socket options to the HTTP transport layer and refactors internal request-body handling to correctly suppress `Content-Type` headers on bodyless optional-body endpoints. Key changes: - Add `create_version(name, *, implementation_guide, profile_refs, profile_context?)` and `get_version(name, version)` to sync and async `ImplementationGuidesClient` - Add new `ImplementationGuideVersionDetail` and `FhirImplementationGuide` types supporting the versioned package API - Add `get_keepalive_socket_options()` helper and wire TCP keepalive into HTTP transports to keep idle connections alive through firewalls and NAT - Add `optional_body` flag and `drop_content_type_without_body()` to suppress `Content-Type` on requests that carry no body - Refactor `_coerce_keys_to_aliases` / `_get_field_aliases` into shared cached helpers in `pydantic_utilities.py` 🌿 Generated with Fern --- .fern/metadata.json | 2 +- changelog.md | 18 ++++++++++++++++++ pyproject.toml | 2 +- src/phenoml/core/client_wrapper.py | 2 +- 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/.fern/metadata.json b/.fern/metadata.json index b736600..8a8729a 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.1.0" } \ No newline at end of file diff --git a/changelog.md b/changelog.md index 2a9ecbb..6340eb0 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,21 @@ +## [17.1.0] - 2026-09-17 +### Added +- **`client.implementation_guides.create_version(...)` and `get_version(...)`** — new sync and async methods for publishing and retrieving immutable canonical FHIR package versions beneath a named implementation guide family. +- **`client.lang2fhir_batch`** — new sync and async client covering the full batch FHIR extraction job lifecycle (`create`, `upload_item`, `finalize`, `cancel`, `get`, `get_results`, `get_result`). +- **`client.profiles.versions`** — new sync and async client for managing immutable retained StructureDefinition versions (`list`, `create`, `get`, `delete`). +- **`ResourceReviewResult.remediated` and `ResourceReviewFinding.unaudited`** — new optional fields on review results; `remediated` lists resources auto-corrected without quarantine, `unaudited` flags inconclusive findings. +- **New Pydantic models** — `ImplementationGuideVersionDetail`, `FhirImplementationGuide`, `ResourceReviewRemediated`, `BatchJob`, `BatchError`, `BatchCounts`, `BatchItemStatus`, and related response types added across `phenoml.implementation_guides`, `phenoml.lang2fhir`, and `phenoml.lang2fhir_batch`. + +### Changed +- **`token` parameter on `PhenomlClient` and `AsyncPhenomlClient`** — now accepts `Union[str, Callable[[], str]]`, allowing a plain bearer token string to be passed directly. +- **`client.lang2fhir.document` and `client.lang2fhir.document_multi`** — now accept RTF and XML/C-CDA content types (dedicated instances only) and raise `ForbiddenError` on HTTP 403. +- **`implementation_guides.delete(...)`** — now also removes any exact canonical package versions beneath the guide in addition to name-level metadata. +- **TCP keepalive and `Content-Type` suppression** — the HTTP transport now enables TCP keepalive probes on all connections and omits the `Content-Type` header on bodyless requests. +- **`ImplementationGuideSummary` and `ProfileSummary`** — gain new optional fields (`canonical_url`, `version_count`, `status`, `date`, `canonical`) exposing publication metadata. + +### Fixed +- **SSE stream handlers in `agent.chat`** — empty SSE data frames are now skipped instead of being passed to the JSON parser, preventing spurious parse errors during streaming. + ## [17.0.0] - 2026-09-09 ### Breaking Changes - **`ProfileSummary`** — `id`, `source`, `resource_type`, `url`, `version`, `fhir_version`, `implementation_guide`, `created_at`, and `updated_at` are now required; remove `None` guards for these fields. diff --git a/pyproject.toml b/pyproject.toml index 1dc545d..ccf246c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ dynamic = ["version"] [tool.poetry] name = "phenoml" -version = "0.0.0.dev0" +version = "17.1.0" description = "" readme = "README.md" authors = [] diff --git a/src/phenoml/core/client_wrapper.py b/src/phenoml/core/client_wrapper.py index c03a243..9e27324 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.1.0", **(self.get_custom_headers() or {}), } token = self._get_token() From ccf9bcf52d4f87a3e0828ddc3e78f7e1a60232b8 Mon Sep 17 00:00:00 2001 From: "fern-api[bot]" <115122769+fern-api[bot]@users.noreply.github.com> Date: Thu, 17 Sep 2026 18:57: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 | 27 +++++++++++++++++++++------ pyproject.toml | 3 +++ 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/.fern/replay.lock b/.fern/replay.lock index 25dc6cb..38cf9dc 100644 --- a/.fern/replay.lock +++ b/.fern/replay.lock @@ -120,21 +120,36 @@ generations: cli_version: unknown generator_versions: fernapi/fern-python-sdk: 5.18.1 -current_generation: 3ac2feef8a9595d7dea2a99b024322ae61959133 + - commit_sha: a35bdf79058a1c050092e7941fb5517f943be49e + tree_hash: 0864e2f4bf70cd7e812c472caf95a035af02c2fc + timestamp: 2026-09-17T18:56:20.049Z + cli_version: unknown + generator_versions: + fernapi/fern-python-sdk: 5.30.0 +current_generation: a35bdf79058a1c050092e7941fb5517f943be49e patches: - id: patch-6516695e - content_hash: sha256:5caeff601ccd2db4bda36068ff6c1ac7dba46f1c049cccc367a7db553613acad + content_hash: sha256:4b07085503a73b0d7551a0483d04ec6c821df4b63f77762193d83acc0307f6db original_commit: 6516695ecaba47ae4bcc8119acca86a1113adeeb original_message: "Release 15.0.2: restore bundled openapi.json packaging (#169)" original_author: Gavin Sharp - base_generation: 3ac2feef8a9595d7dea2a99b024322ae61959133 + base_generation: a35bdf79058a1c050092e7941fb5517f943be49e files: - pyproject.toml patch_content: | diff --git a/pyproject.toml b/pyproject.toml - index cea8b20..f3f1f73 100644 + index 1dc545d..4b6ff5e 100644 --- a/pyproject.toml +++ b/pyproject.toml + @@ -4,7 +4,7 @@ dynamic = ["version"] + + [tool.poetry] + name = "phenoml" + -version = "0.0.0.dev0" + +version = "17.1.0" + description = "" + readme = "README.md" + authors = [] @@ -31,6 +31,9 @@ classifiers = [ packages = [ { include = "phenoml", from = "src"} @@ -153,7 +168,7 @@ patches: [tool.poetry] name = "phenoml" - version = "17.0.0" + version = "17.1.0" description = "" readme = "README.md" authors = [] @@ -191,7 +206,7 @@ patches: python = "^3.10" aiohttp = { version = ">=3.14.1,<4", optional = true, python = ">=3.10"} httpx = ">=0.21.2" - httpx-aiohttp = { version = "0.1.8", optional = true, python = ">=3.10"} + httpx-aiohttp = { version = "^0.1.8", optional = true, python = ">=3.10"} pydantic = ">= 1.9.2" pydantic-core = ">=2.18.2,<3.0.0" typing_extensions = ">= 4.0.0" diff --git a/pyproject.toml b/pyproject.toml index ccf246c..4b6ff5e 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 30a7ce660e175aa012af4d6b0e120697ea587976 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 17 Sep 2026 18:57:41 +0000 Subject: [PATCH 4/4] chore: sync OpenAPI spec + code-examples for 963927d77bf1a9feab3fa0eee0c37e3812b79507 [skip ci] --- code-examples.json | 79 ++++++- src/phenoml/openapi/openapi.json | 349 ++++++++++++++++++++++++++++--- 2 files changed, 392 insertions(+), 36 deletions(-) diff --git a/code-examples.json b/code-examples.json index 79472cc..5ee6f98 100644 --- a/code-examples.json +++ b/code-examples.json @@ -2,8 +2,8 @@ "metadata": { "language": "python", "packageName": "phenoml", - "sdkVersion": "17.0.0", - "specCommit": "26224ef37e22adb507f28c21e8991d4a27822bde", + "sdkVersion": "17.1.0", + "specCommit": "963927d77bf1a9feab3fa0eee0c37e3812b79507", "generatorName": "fernapi/fern-python-sdk" }, "renderRules": { @@ -3250,7 +3250,7 @@ "body": { "version": "R4", "resource": "questionnaire", - "content": "JVBERi0xLjQKJeLjz9MK...(base64-encoded PDF or image bytes)" + "content": "JVBERi0xLjQKJeLjz9MK...(base64-encoded document bytes)" } }, "response": { @@ -3312,7 +3312,7 @@ "request": { "body": { "version": "R4", - "content": "JVBERi0xLjQKJeLjz9MK...(base64-encoded PDF or image bytes)", + "content": "JVBERi0xLjQKJeLjz9MK...(base64-encoded document bytes)", "provider": "medplum", "config": { "split_classifications": [ @@ -3957,6 +3957,77 @@ ] } }, + "POST /fhir/implementation-guides/{name}/versions": { + "httpMethod": "POST", + "httpPath": "/fhir/implementation-guides/{name}/versions", + "request": { + "body": null + }, + "response": { + "body": null + }, + "render": { + "callTemplate": "client.implementation_guides.implementation_guides.create_version(name={{name}}, {{__body__}})", + "params": [ + { + "name": "name", + "kind": "string" + } + ], + "body": { + "fieldSeparator": ", ", + "fields": [ + { + "jsonKey": "implementation_guide", + "fieldTemplate": "implementation_guide={{value}}", + "kind": "object", + "required": true + }, + { + "jsonKey": "profile_refs", + "fieldTemplate": "profile_refs={{value}}", + "kind": "list", + "required": true, + "items": { + "jsonKey": "", + "fieldTemplate": "{{value}}", + "kind": "string", + "required": true + } + }, + { + "jsonKey": "profile_context", + "fieldTemplate": "profile_context={{value}}", + "kind": "string", + "required": false + } + ] + } + } + }, + "GET /fhir/implementation-guides/{name}/versions/{version}": { + "httpMethod": "GET", + "httpPath": "/fhir/implementation-guides/{name}/versions/{version}", + "request": { + "body": null + }, + "response": { + "body": null + }, + "render": { + "callTemplate": "client.implementation_guides.implementation_guides.get_version(name={{name}}, version={{version}})", + "params": [ + { + "name": "name", + "kind": "string" + }, + { + "name": "version", + "kind": "string" + } + ] + } + }, "GET /fhir/profiles": { "httpMethod": "GET", "httpPath": "/fhir/profiles", diff --git a/src/phenoml/openapi/openapi.json b/src/phenoml/openapi/openapi.json index 0a3b0ed..3b5d23a 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": "26224ef37e22adb507f28c21e8991d4a27822bde" + "version": "963927d77bf1a9feab3fa0eee0c37e3812b79507" }, "x-services": [ { @@ -4586,7 +4586,7 @@ "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. A `request_id` whose job was canceled or failed before it\nfinalized is released for a fresh replay; once a job is finalized, its\n`request_id` keeps resolving to it even after cancellation.\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", + "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. A `request_id` whose job was canceled or failed before it\nfinalized is released for a fresh replay; once a job is finalized, its\n`request_id` keeps resolving to it even after cancellation.\n\nThere is no limit on how many jobs an instance may hold at once; how many\nitems run in parallel is a property of the instance, not of the job count.\n", "requestBody": { "required": true, "content": { @@ -4617,9 +4617,6 @@ "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" }, @@ -4702,7 +4699,7 @@ "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", + "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 file content (PDF, image, RTF, or XML/C-CDA).\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", @@ -4731,17 +4728,15 @@ "file": { "type": "string", "format": "binary", - "description": "The document's binary content (PDF, PNG, JPEG, or TIFF).\nRequired with `document`; forbidden with `create`.\n" + "description": "The document's file content (PDF, PNG, JPEG, TIFF, RTF, or\nXML/C-CDA). The document pipeline accepts files up to 20 MiB;\nan upload that passes the storage cap but exceeds this limit\nfails during processing. RTF and XML/C-CDA documents whose\nextracted text exceeds 1 MiB also fail during processing.\nGeneric XML must include an XML declaration; C-CDA documents\nrooted at `ClinicalDocument` may omit it.\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" + "description": "Optional idempotency token (at most 256 UTF-8 bytes).\nRe-uploading under the same token overwrites the same item\ninstead of adding a new one. The token is scoped to this job;\nthe same token in another job is independent and creates a\nseparate 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" + "description": "Optional caller-supplied correlation label (at most 512 UTF-8\nbytes), echoed back on status and result listings so you can\nmatch the server's item_id to your own record.\n" } } }, @@ -4860,7 +4855,7 @@ "post": { "operationId": "batch_cancel", "summary": "Cancel a batch job", - "description": "Drives a job to the terminal `canceled` state on request, freeing its\nactive-job slot immediately. Takes no request body.\n\nCancel does not delete the job: the job record and any results already\nproduced are preserved for the normal retention window, the same as a\n`completed` or `failed` job. Items stop being processed and keep the state\nthey held at cancellation, so a canceled job's `counts` may show\nunfinished items that never resolve.\n\nCancel is idempotent: canceling an already-`canceled` job returns `200`\nwith the job. Canceling a job that has already `completed` or `failed` is\na `409`.\n", + "description": "Drives a job to the terminal `canceled` state on request. Takes no\nrequest body.\n\nCancel does not delete the job: the job record and any results already\nproduced are preserved for the normal retention window, the same as a\n`completed` or `failed` job. Items stop being processed and keep the state\nthey held at cancellation, so a canceled job's `counts` may show\nunfinished items that never resolve.\n\nCancel is idempotent: canceling an already-`canceled` job returns `200`\nwith the job. Canceling a job that has already `completed` or `failed` is\na `409`.\n", "parameters": [ { "name": "job_id", @@ -5605,7 +5600,7 @@ "post": { "operationId": "lang2fhir_document", "summary": "Convert document to FHIR resource", - "description": "Extracts text from a document (PDF or image) and converts it into a structured FHIR resource.\n\n**Patient identifier handling.** When generating a `patient` (or `patient-canvas`) resource, US Core requires `Patient.identifier` (a business identifier such as an MRN). When the source text contains an identifier, it is extracted with an appropriate URI system. When the source text does not contain a detectable identifier, a synthetic one is generated with `system: \"urn:phenoml:lang2fhir-generated-id\"` and a UUID `value` so the resource remains FHIR-valid and US Core conformant. Callers who need a tenant-specific namespace should rewrite the synthetic system after extraction.\n", + "description": "Extracts text from a PDF, image, RTF, or XML/C-CDA document and converts it into a structured FHIR resource.\n\n**Patient identifier handling.** When generating a `patient` (or `patient-canvas`) resource, US Core requires `Patient.identifier` (a business identifier such as an MRN). When the source text contains an identifier, it is extracted with an appropriate URI system. When the source text does not contain a detectable identifier, a synthetic one is generated with `system: \"urn:phenoml:lang2fhir-generated-id\"` and a UUID `value` so the resource remains FHIR-valid and US Core conformant. Callers who need a tenant-specific namespace should rewrite the synthetic system after extraction.\n", "requestBody": { "required": true, "content": { @@ -5616,7 +5611,7 @@ "example": { "version": "R4", "resource": "questionnaire", - "content": "JVBERi0xLjQKJeLjz9MK...(base64-encoded PDF or image bytes)" + "content": "JVBERi0xLjQKJeLjz9MK...(base64-encoded document bytes)" } } } @@ -5655,6 +5650,9 @@ "401": { "description": "Unauthorized" }, + "403": { + "description": "Forbidden - RTF and XML/C-CDA uploads are only available on dedicated instances" + }, "404": { "description": "Profile not found" }, @@ -5683,7 +5681,7 @@ "post": { "operationId": "lang2fhir_documentMulti", "summary": "Extract multiple FHIR resources from a document", - "description": "Extracts text from a document (PDF or image) and converts it into multiple FHIR resources,\nreturned as a transaction Bundle. Combines document text extraction with multi-resource detection.\nAutomatically detects Patient, Condition, MedicationRequest, Observation, and other resource types.\nResources are linked with proper references (e.g., Conditions reference the Patient).\n\n**Patient identifier handling.** US Core requires `Patient.identifier` (a business identifier such as an MRN). When the source text contains an identifier, it is extracted with an appropriate URI system. When the source text does not contain a detectable identifier, a synthetic one is generated with `system: \"urn:phenoml:lang2fhir-generated-id\"` and a UUID `value` so the bundle remains FHIR-valid and US Core conformant. Callers who need a tenant-specific namespace should rewrite the synthetic system after extraction.\n\n**Split classifications (optional).** `config.split_classifications` is a caller-defined list, not a fixed taxonomy. Choose each classification `id` and write a natural-language `description` for the per-page classifier. For each page, the classifier assigns the best-matching classification or leaves the page ungrouped. Classifications with `operation: \"group\"` keep matching pages and label resources extracted from those pages; classifications with `operation: \"drop\"` remove matching pages before extraction. The `clinical` and `admin` ids in the example are illustrative, not a fixed set.\n", + "description": "Extracts text from a PDF, image, RTF, or XML/C-CDA document and converts it into multiple FHIR resources,\nreturned as a transaction Bundle. Combines document text extraction with multi-resource detection.\nAutomatically detects Patient, Condition, MedicationRequest, Observation, and other resource types.\nResources are linked with proper references (e.g., Conditions reference the Patient).\n\n**Patient identifier handling.** US Core requires `Patient.identifier` (a business identifier such as an MRN). When the source text contains an identifier, it is extracted with an appropriate URI system. When the source text does not contain a detectable identifier, a synthetic one is generated with `system: \"urn:phenoml:lang2fhir-generated-id\"` and a UUID `value` so the bundle remains FHIR-valid and US Core conformant. Callers who need a tenant-specific namespace should rewrite the synthetic system after extraction.\n\n**Split classifications (optional).** `config.split_classifications` is a caller-defined list, not a fixed taxonomy. Choose each classification `id` and write a natural-language `description` for the per-page classifier. For each page, the classifier assigns the best-matching classification or leaves the page ungrouped. Classifications with `operation: \"group\"` keep matching pages and label resources extracted from those pages; classifications with `operation: \"drop\"` remove matching pages before extraction. The `clinical` and `admin` ids in the example are illustrative, not a fixed set.\n", "requestBody": { "required": true, "content": { @@ -5693,7 +5691,7 @@ }, "example": { "version": "R4", - "content": "JVBERi0xLjQKJeLjz9MK...(base64-encoded PDF or image bytes)", + "content": "JVBERi0xLjQKJeLjz9MK...(base64-encoded document bytes)", "provider": "medplum", "config": { "split_classifications": [ @@ -5868,6 +5866,9 @@ "401": { "description": "Unauthorized" }, + "403": { + "description": "Forbidden - RTF and XML/C-CDA uploads are only available on dedicated instances" + }, "404": { "description": "Profile not found" }, @@ -6574,11 +6575,11 @@ "tags": [ "FHIR Artifacts / Implementation Guides" ], - "summary": "Delete an implementation guide's metadata", - "description": "Deletes the stored metadata for an implementation guide \u2014 its\nprofile_context and timestamps. Member profiles keep their\nimplementation_guide assignment, so a guide still referenced by at least\none profile continues to appear in listings, just without context or\ntimestamps.\n", + "summary": "Delete an implementation guide family", + "description": "Deletes the stored name-level metadata and any exact canonical package\nversions beneath the guide. Legacy member profile assignments are not\nchanged.\n", "responses": { "204": { - "description": "Implementation guide metadata successfully deleted" + "description": "Implementation guide family successfully deleted" }, "400": { "description": "The name is reserved or malformed" @@ -6590,7 +6591,7 @@ "description": "Forbidden - custom profiles are only available on dedicated instances" }, "404": { - "description": "No implementation guide metadata exists for this name" + "description": "No implementation guide family exists for this name" }, "500": { "description": "Server error" @@ -6604,6 +6605,117 @@ "x-service": "fhir_artifacts" } }, + "/fhir/implementation-guides/{name}/versions": { + "parameters": [ + { + "name": "name", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "post": { + "operationId": "implementation-guides_createVersion", + "tags": [ + "FHIR Artifacts / Implementation Guides" + ], + "summary": "Publish an exact custom-profile package", + "description": "Publishes an exact package beneath this guide family. PR 2 temporarily\npermits one exact package version per guide family; publishing another\nversion returns `409 Conflict` until multi-version package support lands.\n", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/implementation-guides_CreateCanonicalImplementationGuideRequest" + } + } + } + }, + "responses": { + "201": { + "description": "Canonical package published", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/implementation-guides_ImplementationGuideVersionDetail" + } + } + } + }, + "400": { + "description": "Invalid ImplementationGuide, profile reference, version, or context" + }, + "404": { + "description": "An exact profile reference was not found" + }, + "409": { + "description": "A package family URL or exact version already exists" + } + }, + "security": [ + { + "bearerAuth": [] + } + ], + "x-service": "fhir_artifacts" + } + }, + "/fhir/implementation-guides/{name}/versions/{version}": { + "parameters": [ + { + "name": "name", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "version", + "in": "path", + "required": true, + "description": "The authored ImplementationGuide.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": "1.0.0" + } + ], + "get": { + "operationId": "implementation-guides_getVersion", + "tags": [ + "FHIR Artifacts / Implementation Guides" + ], + "summary": "Get an exact custom-profile package", + "responses": { + "200": { + "description": "Exact canonical package", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/implementation-guides_ImplementationGuideVersionDetail" + } + } + } + }, + "400": { + "description": "Invalid guide name or version" + }, + "404": { + "description": "Package version not found" + } + }, + "security": [ + { + "bearerAuth": [] + } + ], + "x-service": "fhir_artifacts" + } + }, "/fhir/profiles": { "get": { "operationId": "profiles_list", @@ -6832,6 +6944,9 @@ "404": { "description": "Profile not found" }, + "409": { + "description": "Profile is pinned by an exact implementation guide package" + }, "500": { "description": "Server error" } @@ -7050,6 +7165,9 @@ "404": { "description": "Profile or version not found" }, + "409": { + "description": "Profile version is pinned by an exact implementation guide package" + }, "500": { "description": "Server error" } @@ -11788,7 +11906,7 @@ "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", + "description": "Optional client idempotency token (at most 256 UTF-8 bytes). A\nretried create with the same token returns the original job instead\nof opening a second one.\n", "example": "submit-2025-09-02-batch-001" } } @@ -11803,7 +11921,7 @@ "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", + "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), `result_too_large` (the\nresult exceeded the storage cap), `input_unavailable` (the input\ncould not be read), and `retries_exhausted` / `attempts_exhausted`\n(the item could not complete after repeated interruptions).\nJob-level kinds: `timeout` (the job did not finish within 36 hours\nof creation).\n", "example": "processing_failed" }, "message": { @@ -11838,7 +11956,7 @@ }, "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. `canceled` is a caller-requested\ncancellation via `POST /lang2fhir/batch/{job_id}/cancel`; like the\nother terminal states it frees the job's active-job slot and keeps any\nresults already produced readable for the retention window.\n", + "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. `canceled` is a caller-requested\ncancellation via `POST /lang2fhir/batch/{job_id}/cancel`; like the\nother terminal states it keeps any results already produced readable\nfor the retention window.\n", "enum": [ "pending", "processing", @@ -11951,7 +12069,7 @@ }, "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", + "description": "Number of processing attempts started so far. The service may retry\ninterrupted work; a conversion error fails the item without retry.\n", "example": 1 }, "detect_retries": { @@ -12568,7 +12686,7 @@ }, "content": { "type": "string", - "description": "Base64 encoded file content.\nSupported file types: PDF (application/pdf), PNG (image/png), JPEG (image/jpeg), TIFF (image/tiff).\nFile type is auto-detected from content magic bytes.\n" + "description": "Base64 encoded file content.\nSupported file types: PDF (application/pdf), PNG (image/png), JPEG (image/jpeg), TIFF (image/tiff), RTF (application/rtf), XML/C-CDA (text/xml).\nRTF and XML/C-CDA uploads are available on dedicated instances only.\nFile type is auto-detected from content magic bytes.\nThe decoded file must not exceed 20 MiB. RTF and XML/C-CDA documents whose extracted text exceeds 1 MiB are rejected.\nGeneric XML must include an XML declaration; C-CDA documents rooted at `ClinicalDocument` may omit it.\n" }, "provider": { "type": "string", @@ -12708,7 +12826,7 @@ }, "lang2fhir_ResourceReview": { "type": "object", - "description": "Opt-in, report-only faithfulness audit (honored by /lang2fhir/create/multi and /lang2fhir/document/multi). For each selected resource type an LLM checks whether selected dates and clinical code concepts are actually supported by the full source document. Resources with an unsupported field are pulled out of the returned bundle and reported under resource_review in the response.\n", + "description": "Opt-in faithfulness audit (honored by /lang2fhir/create/multi and /lang2fhir/document/multi). For each selected resource type an LLM checks whether selected dates and clinical code concepts are actually supported by the full source document. An unsupported individual coding is removed when another coding remains in its concept. Resources with an unsupported structural field, a profile-required coding, or no coding remaining in an affected concept, are pulled out of the returned bundle and reported under resource_review in the response.\n", "required": [ "targets" ], @@ -12754,14 +12872,21 @@ }, "lang2fhir_ResourceReviewResult": { "type": "object", - "description": "Present when resource_review was requested and at least one resource was flagged.\n", + "description": "Present when resource_review was requested and at least one resource was quarantined or safely remediated. The returned bundle is authoritative and contains the post-review representation of every retained resource.\n", "properties": { "flagged": { "type": "array", - "description": "Resources pulled from the bundle because a reviewed field was not supported by the source.", + "description": "Resources pulled from the bundle because an unsupported finding could not be safely repaired.", "items": { "$ref": "#/components/schemas/lang2fhir_ResourceReviewFlagged" } + }, + "remediated": { + "type": "array", + "description": "Resources retained in the bundle after unsupported codings were safely removed.", + "items": { + "$ref": "#/components/schemas/lang2fhir_ResourceReviewRemediated" + } } } }, @@ -12783,7 +12908,35 @@ }, "findings": { "type": "array", - "description": "The unsupported fields that caused the resource to be flagged.", + "description": "The findings that caused the resource to be quarantined.", + "items": { + "$ref": "#/components/schemas/lang2fhir_ResourceReviewFinding" + } + } + } + }, + "lang2fhir_ResourceReviewRemediated": { + "type": "object", + "properties": { + "tempId": { + "type": "string", + "description": "The urn:uuid of the remediated resource (its bundle fullUrl).", + "example": "urn:uuid:a842c4bc-f6cb-4555-9741-ac3aec4ef0b8" + }, + "resourceType": { + "type": "string", + "example": "Condition" + }, + "action": { + "type": "string", + "enum": [ + "removed_codings" + ], + "description": "The safe change applied to the resource in the returned bundle." + }, + "findings": { + "type": "array", + "description": "Findings for fields in the pre-remediation resource that caused this action.", "items": { "$ref": "#/components/schemas/lang2fhir_ResourceReviewFinding" } @@ -12812,9 +12965,14 @@ }, "supported": { "type": "boolean", - "description": "Always false for a flagged finding.", + "description": "False when the reviewer found the field unsupported. Do not treat this field as a verdict when unaudited is true.", "example": false }, + "unaudited": { + "type": "boolean", + "description": "True when the reviewer did not return a verdict for this field; the resource was quarantined without treating the finding as evidence that the value is unsupported.", + "example": true + }, "rationale": { "type": "string", "description": "Short explanation of why the value is not supported by the source.", @@ -12861,7 +13019,7 @@ }, "content": { "type": "string", - "description": "Base64 encoded file content.\nSupported file types: PDF (application/pdf), PNG (image/png), JPEG (image/jpeg), TIFF (image/tiff).\nFile type is auto-detected from content magic bytes.\n" + "description": "Base64 encoded file content.\nSupported file types: PDF (application/pdf), PNG (image/png), JPEG (image/jpeg), TIFF (image/tiff), RTF (application/rtf), XML/C-CDA (text/xml).\nRTF and XML/C-CDA uploads are available on dedicated instances only.\nFile type is auto-detected from content magic bytes.\nThe decoded file must not exceed 20 MiB. RTF and XML/C-CDA documents whose extracted text exceeds 1 MiB are rejected.\nGeneric XML must include an XML declaration; C-CDA documents rooted at `ClinicalDocument` may omit it.\n" }, "config": { "$ref": "#/components/schemas/lang2fhir_DocumentConfig" @@ -13056,7 +13214,7 @@ }, "implementation-guides_ImplementationGuideSummary": { "type": "object", - "description": "Metadata for an implementation guide. This is an instance-local grouping record, not a complete FHIR ImplementationGuide resource.\n", + "description": "Metadata for an implementation guide. Canonical fields are present only for published canonical packages; metadata-only legacy records omit them.\n", "properties": { "name": { "type": "string", @@ -13073,6 +13231,14 @@ "description": "The number of custom profiles in this implementation guide.", "example": 3 }, + "canonical_url": { + "type": "string", + "description": "Canonical FHIR ImplementationGuide URL, when the family has an exact package." + }, + "version_count": { + "type": "integer", + "description": "Number of retained exact package versions." + }, "created_at": { "type": "string", "format": "date-time", @@ -13119,6 +13285,125 @@ } ] }, + "implementation-guides_CreateCanonicalImplementationGuideRequest": { + "type": "object", + "required": [ + "implementation_guide", + "profile_refs" + ], + "properties": { + "implementation_guide": { + "$ref": "#/components/schemas/implementation-guides_FHIRImplementationGuide" + }, + "profile_refs": { + "type": "array", + "minItems": 1, + "maxItems": 250, + "description": "Exact canonical `url|version` references to builtin or custom profiles. A package can contain at most 250 references.\n", + "items": { + "type": "string" + } + }, + "profile_context": { + "type": "string", + "maxLength": 2000, + "description": "Natural-language profile-selection context for this package." + } + } + }, + "implementation-guides_ImplementationGuideVersionDetail": { + "type": "object", + "required": [ + "name", + "url", + "version", + "profile_context", + "profiles", + "profile_refs", + "implementation_guide", + "created_at", + "updated_at" + ], + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "version": { + "type": "string" + }, + "profile_context": { + "type": "string" + }, + "profiles": { + "type": "array", + "items": { + "type": "string" + } + }, + "profile_refs": { + "type": "array", + "items": { + "type": "string" + } + }, + "implementation_guide": { + "$ref": "#/components/schemas/implementation-guides_FHIRImplementationGuide" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } + }, + "implementation-guides_FHIRImplementationGuide": { + "type": "object", + "description": "A complete authored FHIR ImplementationGuide JSON resource.", + "required": [ + "resourceType", + "url", + "version" + ], + "additionalProperties": true, + "properties": { + "resourceType": { + "type": "string", + "enum": [ + "ImplementationGuide" + ] + }, + "id": { + "type": "string" + }, + "url": { + "type": "string" + }, + "version": { + "type": "string" + }, + "name": { + "type": "string" + }, + "status": { + "type": "string" + }, + "packageId": { + "type": "string" + }, + "fhirVersion": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, "implementation-guides_UpdateImplementationGuideRequest": { "type": "object", "properties": {