diff --git a/generators/python-v2/dynamic-snippets/src/context/DynamicSnippetsGeneratorContext.ts b/generators/python-v2/dynamic-snippets/src/context/DynamicSnippetsGeneratorContext.ts
index 673555a889d6..f039adcc4ff6 100644
--- a/generators/python-v2/dynamic-snippets/src/context/DynamicSnippetsGeneratorContext.ts
+++ b/generators/python-v2/dynamic-snippets/src/context/DynamicSnippetsGeneratorContext.ts
@@ -101,12 +101,16 @@ export class DynamicSnippetsGeneratorContext extends AbstractDynamicSnippetsGene
discriminantValue: FernIr.dynamic.NameAndWireValue;
}): python.Reference {
const unionClassName = this.getClassName(unionDeclaration.name);
- const variantSuffix = discriminantValue.name.pascalCase.safeName;
+ const variantName = discriminantValue.name.pascalCase.safeName;
const modulePath = [
...this.getRootModulePath(),
...unionDeclaration.fernFilepath.allParts.map((part) => part.snakeCase.safeName)
];
- return python.reference({ name: `${unionClassName}_${variantSuffix}`, modulePath });
+ const name =
+ this.customConfig.pydantic_config?.union_naming === "v1"
+ ? `${variantName}${unionClassName}`
+ : `${unionClassName}_${variantName}`;
+ return python.reference({ name, modulePath });
}
public useTypedDictRequests(): boolean {
diff --git a/generators/python-v2/sdk/src/wire-tests/WireTestGenerator.ts b/generators/python-v2/sdk/src/wire-tests/WireTestGenerator.ts
index 75646c1b4be5..1e25da87a55e 100644
--- a/generators/python-v2/sdk/src/wire-tests/WireTestGenerator.ts
+++ b/generators/python-v2/sdk/src/wire-tests/WireTestGenerator.ts
@@ -48,7 +48,10 @@ export class WireTestGenerator {
config: {
organization: context.config.organization,
workspaceName: context.config.workspaceName,
- customConfig: context.customConfig
+ // Pass the raw customConfig (not the parsed SdkCustomConfigSchema) so that
+ // fields consumed by the dynamic snippets generator (e.g. pydantic_config)
+ // are preserved. SdkCustomConfigSchema.parse() strips unknown keys.
+ customConfig: context.config.customConfig
} as FernGeneratorExec.GeneratorConfig
});
}
diff --git a/generators/python/sdk/changes/5.5.2/fix-wire-test-union-naming-v1.yml b/generators/python/sdk/changes/5.5.2/fix-wire-test-union-naming-v1.yml
new file mode 100644
index 000000000000..b0bcf15b26e6
--- /dev/null
+++ b/generators/python/sdk/changes/5.5.2/fix-wire-test-union-naming-v1.yml
@@ -0,0 +1,8 @@
+# yaml-language-server: $schema=../../../../../fern-changes-yml.schema.json
+
+- summary: |
+ Fix generated wire tests and dynamic snippets to honor `pydantic_config.union_naming: v1`.
+ Previously, variant class names in wire tests and README/reference snippets were always
+ emitted with the v0 suffix style (e.g. `UnionName_Variant`), causing import errors when
+ the SDK types were generated with v1 prefix-style names (e.g. `VariantUnionName`).
+ type: fix
diff --git a/generators/python/sdk/versions.yml b/generators/python/sdk/versions.yml
index 979f86b181b4..44770e92ac2f 100644
--- a/generators/python/sdk/versions.yml
+++ b/generators/python/sdk/versions.yml
@@ -1,4 +1,14 @@
# yaml-language-server: $schema=../../../fern-versions-yml.schema.json
+- version: 5.5.2
+ changelogEntry:
+ - summary: |
+ Fix generated wire tests and dynamic snippets to honor `pydantic_config.union_naming: v1`.
+ Previously, variant class names in wire tests and README/reference snippets were always
+ emitted with the v0 suffix style (e.g. `UnionName_Variant`), causing import errors when
+ the SDK types were generated with v1 prefix-style names (e.g. `VariantUnionName`).
+ type: fix
+ createdAt: "2026-04-18"
+ irVersion: 66
- version: 5.5.1
changelogEntry:
- summary: |
diff --git a/seed/python-sdk/seed.yml b/seed/python-sdk/seed.yml
index 345039d73ad6..78b8933488e4 100644
--- a/seed/python-sdk/seed.yml
+++ b/seed/python-sdk/seed.yml
@@ -386,6 +386,12 @@ fixtures:
union_naming: v1
use_inheritance_for_extended_models: false
outputFolder: union-naming-v1
+ - customConfig:
+ enable_wire_tests: true
+ pydantic_config:
+ union_naming: v1
+ use_inheritance_for_extended_models: false
+ outputFolder: union-naming-v1-wire-tests
websocket:
- outputFolder: websocket-base
- customConfig:
@@ -475,3 +481,9 @@ allowedFailures:
- server-sent-event-examples:with-wire-tests # TODO: update wiretests geranation then remove it
- trace
- multi-url-environment-reference
+ # The unions API has two services (`union` and `bigunion`) that both expose
+ # `GET /{id}` on the root base-path, which causes a WireMock stub collision at
+ # test runtime. Code generation and imports are verified; one wire test
+ # assertion fails due to the colliding mocks. This is pre-existing and unrelated
+ # to the union_naming v1 fix this fixture regression-tests.
+ - unions:union-naming-v1-wire-tests
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/.fern/metadata.json b/seed/python-sdk/unions/union-naming-v1-wire-tests/.fern/metadata.json
new file mode 100644
index 000000000000..26e96ed65452
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/.fern/metadata.json
@@ -0,0 +1,18 @@
+{
+ "cliVersion": "DUMMY",
+ "generatorName": "fernapi/fern-python-sdk",
+ "generatorVersion": "local",
+ "generatorConfig": {
+ "enable_wire_tests": true,
+ "pydantic_config": {
+ "union_naming": "v1"
+ },
+ "use_inheritance_for_extended_models": false
+ },
+ "originGitCommit": "DUMMY",
+ "originGitCommitIsDirty": null,
+ "invokedBy": "ci",
+ "requestedVersion": "0.0.1",
+ "ciProvider": "github",
+ "sdkVersion": "0.0.1"
+}
\ No newline at end of file
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/.github/workflows/ci.yml b/seed/python-sdk/unions/union-naming-v1-wire-tests/.github/workflows/ci.yml
new file mode 100644
index 000000000000..fd1df043d08d
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/.github/workflows/ci.yml
@@ -0,0 +1,71 @@
+name: ci
+on: [push]
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: false
+
+jobs:
+ compile:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout repo
+ uses: actions/checkout@v4
+ - name: Set up python
+ uses: actions/setup-python@v4
+ with:
+ python-version: "3.10"
+ - name: Bootstrap poetry
+ run: |
+ curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1
+ - name: Install dependencies
+ run: poetry install
+ - name: Compile
+ run: poetry run mypy .
+ test:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout repo
+ uses: actions/checkout@v4
+ - name: Set up python
+ uses: actions/setup-python@v4
+ with:
+ python-version: "3.10"
+ - name: Bootstrap poetry
+ run: |
+ curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1
+ - name: Install dependencies
+ run: poetry install
+
+ - name: Test
+ run: poetry run pytest -rP -n auto .
+
+ - name: Install aiohttp extra
+ run: poetry install --extras aiohttp
+
+ - name: Test (aiohttp)
+ run: poetry run pytest -rP -n auto -m aiohttp .
+
+ publish:
+ needs: [compile, test]
+ if: github.event_name == 'push' && contains(github.ref, 'refs/tags/')
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout repo
+ uses: actions/checkout@v4
+ - name: Set up python
+ uses: actions/setup-python@v4
+ with:
+ python-version: "3.10"
+ - name: Bootstrap poetry
+ run: |
+ curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1
+ - name: Install dependencies
+ run: poetry install
+ - name: Publish to pypi
+ run: |
+ poetry config repositories.remote
+ poetry --no-interaction -v publish --build --repository remote --username "$PYPI_USERNAME" --password "$PYPI_PASSWORD"
+ env:
+ PYPI_USERNAME: ${{ secrets.PYPI_USERNAME }}
+ PYPI_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/.gitignore b/seed/python-sdk/unions/union-naming-v1-wire-tests/.gitignore
new file mode 100644
index 000000000000..d2e4ca808d21
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/.gitignore
@@ -0,0 +1,5 @@
+.mypy_cache/
+.ruff_cache/
+__pycache__/
+dist/
+poetry.toml
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/README.md b/seed/python-sdk/unions/union-naming-v1-wire-tests/README.md
new file mode 100644
index 000000000000..b7566fa13631
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/README.md
@@ -0,0 +1,164 @@
+# Seed Python Library
+
+[](https://buildwithfern.com?utm_source=github&utm_medium=github&utm_campaign=readme&utm_source=Seed%2FPython)
+[](https://pypi.python.org/pypi/fern_unions)
+
+The Seed Python library provides convenient access to the Seed APIs from Python.
+
+## Table of Contents
+
+- [Installation](#installation)
+- [Reference](#reference)
+- [Usage](#usage)
+- [Async Client](#async-client)
+- [Exception Handling](#exception-handling)
+- [Advanced](#advanced)
+ - [Access Raw Response Data](#access-raw-response-data)
+ - [Retries](#retries)
+ - [Timeouts](#timeouts)
+ - [Custom Client](#custom-client)
+- [Contributing](#contributing)
+
+## Installation
+
+```sh
+pip install fern_unions
+```
+
+## Reference
+
+A full reference for this library is available [here](./reference.md).
+
+## Usage
+
+Instantiate and use the client with the following:
+
+```python
+from seed import SeedUnions
+
+client = SeedUnions(
+ base_url="https://yourhost.com/path/to/api",
+)
+
+client.bigunion.get(
+ id="id",
+)
+```
+
+## Async Client
+
+The SDK also exports an `async` client so that you can make non-blocking calls to our API. Note that if you are constructing an Async httpx client class to pass into this client, use `httpx.AsyncClient()` instead of `httpx.Client()` (e.g. for the `httpx_client` parameter of this client).
+
+```python
+import asyncio
+
+from seed import AsyncSeedUnions
+
+client = AsyncSeedUnions(
+ base_url="https://yourhost.com/path/to/api",
+)
+
+
+async def main() -> None:
+ await client.bigunion.get(
+ id="id",
+ )
+
+
+asyncio.run(main())
+```
+
+## Exception Handling
+
+When the API returns a non-success status code (4xx or 5xx response), a subclass of the following error
+will be thrown.
+
+```python
+from seed.core.api_error import ApiError
+
+try:
+ client.bigunion.get(...)
+except ApiError as e:
+ print(e.status_code)
+ print(e.body)
+```
+
+## Advanced
+
+### Access Raw Response Data
+
+The SDK provides access to raw response data, including headers, through the `.with_raw_response` property.
+The `.with_raw_response` property returns a "raw" client that can be used to access the `.headers` and `.data` attributes.
+
+```python
+from seed import SeedUnions
+
+client = SeedUnions(...)
+response = client.bigunion.with_raw_response.get(...)
+print(response.headers) # access the response headers
+print(response.status_code) # access the response status code
+print(response.data) # access the underlying object
+```
+
+### Retries
+
+The SDK is instrumented with automatic retries with exponential backoff. A request will be retried as long
+as the request is deemed retryable and the number of retry attempts has not grown larger than the configured
+retry limit (default: 2).
+
+A request is deemed retryable when any of the following HTTP status codes is returned:
+
+- [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) (Timeout)
+- [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) (Too Many Requests)
+- [5XX](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) (Internal Server Errors)
+
+Use the `max_retries` request option to configure this behavior.
+
+```python
+client.bigunion.get(..., request_options={
+ "max_retries": 1
+})
+```
+
+### Timeouts
+
+The SDK defaults to a 60 second timeout. You can configure this with a timeout option at the client or request level.
+
+```python
+from seed import SeedUnions
+
+client = SeedUnions(..., timeout=20.0)
+
+# Override timeout for a specific method
+client.bigunion.get(..., request_options={
+ "timeout_in_seconds": 1
+})
+```
+
+### Custom Client
+
+You can override the `httpx` client to customize it for your use-case. Some common use-cases include support for proxies
+and transports.
+
+```python
+import httpx
+from seed import SeedUnions
+
+client = SeedUnions(
+ ...,
+ httpx_client=httpx.Client(
+ proxy="http://my.test.proxy.example.com",
+ transport=httpx.HTTPTransport(local_address="0.0.0.0"),
+ ),
+)
+```
+
+## Contributing
+
+While we value open-source contributions to this SDK, this library is generated programmatically.
+Additions made directly to this library would have to be moved over to our generation code,
+otherwise they would be overwritten upon the next generated release. Feel free to open a PR as
+a proof of concept, but know that we will not be able to merge it as-is. We suggest opening
+an issue first to discuss with us!
+
+On the other hand, contributions to the README are always very welcome!
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/poetry.lock b/seed/python-sdk/unions/union-naming-v1-wire-tests/poetry.lock
new file mode 100644
index 000000000000..55e3ba0367f5
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/poetry.lock
@@ -0,0 +1,1614 @@
+# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand.
+
+[[package]]
+name = "aiohappyeyeballs"
+version = "2.6.1"
+description = "Happy Eyeballs for asyncio"
+optional = true
+python-versions = ">=3.9"
+files = [
+ {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"},
+ {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"},
+]
+
+[[package]]
+name = "aiohttp"
+version = "3.13.5"
+description = "Async http client/server framework (asyncio)"
+optional = true
+python-versions = ">=3.9"
+files = [
+ {file = "aiohttp-3.13.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:02222e7e233295f40e011c1b00e3b0bd451f22cf853a0304c3595633ee47da4b"},
+ {file = "aiohttp-3.13.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bace460460ed20614fa6bc8cb09966c0b8517b8c58ad8046828c6078d25333b5"},
+ {file = "aiohttp-3.13.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f546a4dc1e6a5edbb9fd1fd6ad18134550e096a5a43f4ad74acfbd834fc6670"},
+ {file = "aiohttp-3.13.5-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c86969d012e51b8e415a8c6ce96f7857d6a87d6207303ab02d5d11ef0cad2274"},
+ {file = "aiohttp-3.13.5-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b6f6cd1560c5fa427e3b6074bb24d2c64e225afbb7165008903bd42e4e33e28a"},
+ {file = "aiohttp-3.13.5-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:636bc362f0c5bbc7372bc3ae49737f9e3030dbce469f0f422c8f38079780363d"},
+ {file = "aiohttp-3.13.5-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6a7cbeb06d1070f1d14895eeeed4dac5913b22d7b456f2eb969f11f4b3993796"},
+ {file = "aiohttp-3.13.5-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bca9ef7517fd7874a1a08970ae88f497bf5c984610caa0bf40bd7e8450852b95"},
+ {file = "aiohttp-3.13.5-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:019a67772e034a0e6b9b17c13d0a8fe56ad9fb150fc724b7f3ffd3724288d9e5"},
+ {file = "aiohttp-3.13.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f34ecee82858e41dd217734f0c41a532bd066bcaab636ad830f03a30b2a96f2a"},
+ {file = "aiohttp-3.13.5-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:4eac02d9af4813ee289cd63a361576da36dba57f5a1ab36377bc2600db0cbb73"},
+ {file = "aiohttp-3.13.5-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4beac52e9fe46d6abf98b0176a88154b742e878fdf209d2248e99fcdf73cd297"},
+ {file = "aiohttp-3.13.5-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:c180f480207a9b2475f2b8d8bd7204e47aec952d084b2a2be58a782ffcf96074"},
+ {file = "aiohttp-3.13.5-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2837fb92951564d6339cedae4a7231692aa9f73cbc4fb2e04263b96844e03b4e"},
+ {file = "aiohttp-3.13.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d9010032a0b9710f58012a1e9c222528763d860ba2ee1422c03473eab47703e7"},
+ {file = "aiohttp-3.13.5-cp310-cp310-win32.whl", hash = "sha256:7c4b6668b2b2b9027f209ddf647f2a4407784b5d88b8be4efcc72036f365baf9"},
+ {file = "aiohttp-3.13.5-cp310-cp310-win_amd64.whl", hash = "sha256:cd3db5927bf9167d5a6157ddb2f036f6b6b0ad001ac82355d43e97a4bde76d76"},
+ {file = "aiohttp-3.13.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7ab7229b6f9b5c1ba4910d6c41a9eb11f543eadb3f384df1b4c293f4e73d44d6"},
+ {file = "aiohttp-3.13.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8f14c50708bb156b3a3ca7230b3d820199d56a48e3af76fa21c2d6087190fe3d"},
+ {file = "aiohttp-3.13.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e7d2f8616f0ff60bd332022279011776c3ac0faa0f1b463f7bb12326fbc97a1c"},
+ {file = "aiohttp-3.13.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2567b72e1ffc3ab25510db43f355b29eeada56c0a622e58dcdb19530eb0a3cb"},
+ {file = "aiohttp-3.13.5-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:fb0540c854ac9c0c5ad495908fdfd3e332d553ec731698c0e29b1877ba0d2ec6"},
+ {file = "aiohttp-3.13.5-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c9883051c6972f58bfc4ebb2116345ee2aa151178e99c3f2b2bbe2af712abd13"},
+ {file = "aiohttp-3.13.5-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2294172ce08a82fb7c7273485895de1fa1186cc8294cfeb6aef4af42ad261174"},
+ {file = "aiohttp-3.13.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3a807cabd5115fb55af198b98178997a5e0e57dead43eb74a93d9c07d6d4a7dc"},
+ {file = "aiohttp-3.13.5-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:aa6d0d932e0f39c02b80744273cd5c388a2d9bc07760a03164f229c8e02662f6"},
+ {file = "aiohttp-3.13.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:60869c7ac4aaabe7110f26499f3e6e5696eae98144735b12a9c3d9eae2b51a49"},
+ {file = "aiohttp-3.13.5-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:26d2f8546f1dfa75efa50c3488215a903c0168d253b75fba4210f57ab77a0fb8"},
+ {file = "aiohttp-3.13.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1162a1492032c82f14271e831c8f4b49f2b6078f4f5fc74de2c912fa225d51d"},
+ {file = "aiohttp-3.13.5-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:8b14eb3262fad0dc2f89c1a43b13727e709504972186ff6a99a3ecaa77102b6c"},
+ {file = "aiohttp-3.13.5-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:ca9ac61ac6db4eb6c2a0cd1d0f7e1357647b638ccc92f7e9d8d133e71ed3c6ac"},
+ {file = "aiohttp-3.13.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7996023b2ed59489ae4762256c8516df9820f751cf2c5da8ed2fb20ee50abab3"},
+ {file = "aiohttp-3.13.5-cp311-cp311-win32.whl", hash = "sha256:77dfa48c9f8013271011e51c00f8ada19851f013cde2c48fca1ba5e0caf5bb06"},
+ {file = "aiohttp-3.13.5-cp311-cp311-win_amd64.whl", hash = "sha256:d3a4834f221061624b8887090637db9ad4f61752001eae37d56c52fddade2dc8"},
+ {file = "aiohttp-3.13.5-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:023ecba036ddd840b0b19bf195bfae970083fd7024ce1ac22e9bba90464620e9"},
+ {file = "aiohttp-3.13.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:15c933ad7920b7d9a20de151efcd05a6e38302cbf0e10c9b2acb9a42210a2416"},
+ {file = "aiohttp-3.13.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ab2899f9fa2f9f741896ebb6fa07c4c883bfa5c7f2ddd8cf2aafa86fa981b2d2"},
+ {file = "aiohttp-3.13.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a60eaa2d440cd4707696b52e40ed3e2b0f73f65be07fd0ef23b6b539c9c0b0b4"},
+ {file = "aiohttp-3.13.5-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:55b3bdd3292283295774ab585160c4004f4f2f203946997f49aac032c84649e9"},
+ {file = "aiohttp-3.13.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c2b2355dc094e5f7d45a7bb262fe7207aa0460b37a0d87027dcf21b5d890e7d5"},
+ {file = "aiohttp-3.13.5-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b38765950832f7d728297689ad78f5f2cf79ff82487131c4d26fe6ceecdc5f8e"},
+ {file = "aiohttp-3.13.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b18f31b80d5a33661e08c89e202edabf1986e9b49c42b4504371daeaa11b47c1"},
+ {file = "aiohttp-3.13.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:33add2463dde55c4f2d9635c6ab33ce154e5ecf322bd26d09af95c5f81cfa286"},
+ {file = "aiohttp-3.13.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:327cc432fdf1356fb4fbc6fe833ad4e9f6aacb71a8acaa5f1855e4b25910e4a9"},
+ {file = "aiohttp-3.13.5-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:7c35b0bf0b48a70b4cb4fc5d7bed9b932532728e124874355de1a0af8ec4bc88"},
+ {file = "aiohttp-3.13.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:df23d57718f24badef8656c49743e11a89fd6f5358fa8a7b96e728fda2abf7d3"},
+ {file = "aiohttp-3.13.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:02e048037a6501a5ec1f6fc9736135aec6eb8a004ce48838cb951c515f32c80b"},
+ {file = "aiohttp-3.13.5-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:31cebae8b26f8a615d2b546fee45d5ffb76852ae6450e2a03f42c9102260d6fe"},
+ {file = "aiohttp-3.13.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:888e78eb5ca55a615d285c3c09a7a91b42e9dd6fc699b166ebd5dee87c9ccf14"},
+ {file = "aiohttp-3.13.5-cp312-cp312-win32.whl", hash = "sha256:8bd3ec6376e68a41f9f95f5ed170e2fcf22d4eb27a1f8cb361d0508f6e0557f3"},
+ {file = "aiohttp-3.13.5-cp312-cp312-win_amd64.whl", hash = "sha256:110e448e02c729bcebb18c60b9214a87ba33bac4a9fa5e9a5f139938b56c6cb1"},
+ {file = "aiohttp-3.13.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a5029cc80718bbd545123cd8fe5d15025eccaaaace5d0eeec6bd556ad6163d61"},
+ {file = "aiohttp-3.13.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4bb6bf5811620003614076bdc807ef3b5e38244f9d25ca5fe888eaccea2a9832"},
+ {file = "aiohttp-3.13.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a84792f8631bf5a94e52d9cc881c0b824ab42717165a5579c760b830d9392ac9"},
+ {file = "aiohttp-3.13.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:57653eac22c6a4c13eb22ecf4d673d64a12f266e72785ab1c8b8e5940d0e8090"},
+ {file = "aiohttp-3.13.5-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5e5f7debc7a57af53fdf5c5009f9391d9f4c12867049d509bf7bb164a6e295b"},
+ {file = "aiohttp-3.13.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c719f65bebcdf6716f10e9eff80d27567f7892d8988c06de12bbbd39307c6e3a"},
+ {file = "aiohttp-3.13.5-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d97f93fdae594d886c5a866636397e2bcab146fd7a132fd6bb9ce182224452f8"},
+ {file = "aiohttp-3.13.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3df334e39d4c2f899a914f1dba283c1aadc311790733f705182998c6f7cae665"},
+ {file = "aiohttp-3.13.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fe6970addfea9e5e081401bcbadf865d2b6da045472f58af08427e108d618540"},
+ {file = "aiohttp-3.13.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7becdf835feff2f4f335d7477f121af787e3504b48b449ff737afb35869ba7bb"},
+ {file = "aiohttp-3.13.5-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:676e5651705ad5d8a70aeb8eb6936c436d8ebbd56e63436cb7dd9bb36d2a9a46"},
+ {file = "aiohttp-3.13.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:9b16c653d38eb1a611cc898c41e76859ca27f119d25b53c12875fd0474ae31a8"},
+ {file = "aiohttp-3.13.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:999802d5fa0389f58decd24b537c54aa63c01c3219ce17d1214cbda3c2b22d2d"},
+ {file = "aiohttp-3.13.5-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:ec707059ee75732b1ba130ed5f9580fe10ff75180c812bc267ded039db5128c6"},
+ {file = "aiohttp-3.13.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2d6d44a5b48132053c2f6cd5c8cb14bc67e99a63594e336b0f2af81e94d5530c"},
+ {file = "aiohttp-3.13.5-cp313-cp313-win32.whl", hash = "sha256:329f292ed14d38a6c4c435e465f48bebb47479fd676a0411936cc371643225cc"},
+ {file = "aiohttp-3.13.5-cp313-cp313-win_amd64.whl", hash = "sha256:69f571de7500e0557801c0b51f4780482c0ec5fe2ac851af5a92cfce1af1cb83"},
+ {file = "aiohttp-3.13.5-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:eb4639f32fd4a9904ab8fb45bf3383ba71137f3d9d4ba25b3b3f3109977c5b8c"},
+ {file = "aiohttp-3.13.5-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:7e5dc4311bd5ac493886c63cbf76ab579dbe4641268e7c74e48e774c74b6f2be"},
+ {file = "aiohttp-3.13.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:756c3c304d394977519824449600adaf2be0ccee76d206ee339c5e76b70ded25"},
+ {file = "aiohttp-3.13.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecc26751323224cf8186efcf7fbcbc30f4e1d8c7970659daf25ad995e4032a56"},
+ {file = "aiohttp-3.13.5-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:10a75acfcf794edf9d8db50e5a7ec5fc818b2a8d3f591ce93bc7b1210df016d2"},
+ {file = "aiohttp-3.13.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0f7a18f258d124cd678c5fe072fe4432a4d5232b0657fca7c1847f599233c83a"},
+ {file = "aiohttp-3.13.5-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:df6104c009713d3a89621096f3e3e88cc323fd269dbd7c20afe18535094320be"},
+ {file = "aiohttp-3.13.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:241a94f7de7c0c3b616627aaad530fe2cb620084a8b144d3be7b6ecfe95bae3b"},
+ {file = "aiohttp-3.13.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c974fb66180e58709b6fc402846f13791240d180b74de81d23913abe48e96d94"},
+ {file = "aiohttp-3.13.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:6e27ea05d184afac78aabbac667450c75e54e35f62238d44463131bd3f96753d"},
+ {file = "aiohttp-3.13.5-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a79a6d399cef33a11b6f004c67bb07741d91f2be01b8d712d52c75711b1e07c7"},
+ {file = "aiohttp-3.13.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c632ce9c0b534fbe25b52c974515ed674937c5b99f549a92127c85f771a78772"},
+ {file = "aiohttp-3.13.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:fceedde51fbd67ee2bcc8c0b33d0126cc8b51ef3bbde2f86662bd6d5a6f10ec5"},
+ {file = "aiohttp-3.13.5-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f92995dfec9420bb69ae629abf422e516923ba79ba4403bc750d94fb4a6c68c1"},
+ {file = "aiohttp-3.13.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:20ae0ff08b1f2c8788d6fb85afcb798654ae6ba0b747575f8562de738078457b"},
+ {file = "aiohttp-3.13.5-cp314-cp314-win32.whl", hash = "sha256:b20df693de16f42b2472a9c485e1c948ee55524786a0a34345511afdd22246f3"},
+ {file = "aiohttp-3.13.5-cp314-cp314-win_amd64.whl", hash = "sha256:f85c6f327bf0b8c29da7d93b1cabb6363fb5e4e160a32fa241ed2dce21b73162"},
+ {file = "aiohttp-3.13.5-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:1efb06900858bb618ff5cee184ae2de5828896c448403d51fb633f09e109be0a"},
+ {file = "aiohttp-3.13.5-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:fee86b7c4bd29bdaf0d53d14739b08a106fdda809ca5fe032a15f52fae5fe254"},
+ {file = "aiohttp-3.13.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:20058e23909b9e65f9da62b396b77dfa95965cbe840f8def6e572538b1d32e36"},
+ {file = "aiohttp-3.13.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cf20a8d6868cb15a73cab329ffc07291ba8c22b1b88176026106ae39aa6df0f"},
+ {file = "aiohttp-3.13.5-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:330f5da04c987f1d5bdb8ae189137c77139f36bd1cb23779ca1a354a4b027800"},
+ {file = "aiohttp-3.13.5-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6f1cbf0c7926d315c3c26c2da41fd2b5d2fe01ac0e157b78caefc51a782196cf"},
+ {file = "aiohttp-3.13.5-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:53fc049ed6390d05423ba33103ded7281fe897cf97878f369a527070bd95795b"},
+ {file = "aiohttp-3.13.5-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:898703aa2667e3c5ca4c54ca36cd73f58b7a38ef87a5606414799ebce4d3fd3a"},
+ {file = "aiohttp-3.13.5-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0494a01ca9584eea1e5fbd6d748e61ecff218c51b576ee1999c23db7066417d8"},
+ {file = "aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6cf81fe010b8c17b09495cbd15c1d35afbc8fb405c0c9cf4738e5ae3af1d65be"},
+ {file = "aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:c564dd5f09ddc9d8f2c2d0a301cd30a79a2cc1b46dd1a73bef8f0038863d016b"},
+ {file = "aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:2994be9f6e51046c4f864598fd9abeb4fba6e88f0b2152422c9666dcd4aea9c6"},
+ {file = "aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:157826e2fa245d2ef46c83ea8a5faf77ca19355d278d425c29fda0beb3318037"},
+ {file = "aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:a8aca50daa9493e9e13c0f566201a9006f080e7c50e5e90d0b06f53146a54500"},
+ {file = "aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3b13560160d07e047a93f23aaa30718606493036253d5430887514715b67c9d9"},
+ {file = "aiohttp-3.13.5-cp314-cp314t-win32.whl", hash = "sha256:9a0f4474b6ea6818b41f82172d799e4b3d29e22c2c520ce4357856fced9af2f8"},
+ {file = "aiohttp-3.13.5-cp314-cp314t-win_amd64.whl", hash = "sha256:18a2f6c1182c51baa1d28d68fea51513cb2a76612f038853c0ad3c145423d3d9"},
+ {file = "aiohttp-3.13.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:347542f0ea3f95b2a955ee6656461fa1c776e401ac50ebce055a6c38454a0adf"},
+ {file = "aiohttp-3.13.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:178c7b5e62b454c2bc790786e6058c3cc968613b4419251b478c153a4aec32b1"},
+ {file = "aiohttp-3.13.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:af545c2cffdb0967a96b6249e6f5f7b0d92cdfd267f9d5238d5b9ca63e8edb10"},
+ {file = "aiohttp-3.13.5-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:206b7b3ef96e4ce211754f0cd003feb28b7d81f0ad26b8d077a5d5161436067f"},
+ {file = "aiohttp-3.13.5-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:ee5e86776273de1795947d17bddd6bb19e0365fd2af4289c0d2c5454b6b1d36b"},
+ {file = "aiohttp-3.13.5-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:95d14ca7abefde230f7639ec136ade282655431fd5db03c343b19dda72dd1643"},
+ {file = "aiohttp-3.13.5-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:912d4b6af530ddb1338a66229dac3a25ff11d4448be3ec3d6340583995f56031"},
+ {file = "aiohttp-3.13.5-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e999f0c88a458c836d5fb521814e92ed2172c649200336a6df514987c1488258"},
+ {file = "aiohttp-3.13.5-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:39380e12bd1f2fdab4285b6e055ad48efbaed5c836433b142ed4f5b9be71036a"},
+ {file = "aiohttp-3.13.5-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9efcc0f11d850cefcafdd9275b9576ad3bfb539bed96807663b32ad99c4d4b88"},
+ {file = "aiohttp-3.13.5-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:147b4f501d0292077f29d5268c16bb7c864a1f054d7001c4c1812c0421ea1ed0"},
+ {file = "aiohttp-3.13.5-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:d147004fede1b12f6013a6dbb2a26a986a671a03c6ea740ddc76500e5f1c399f"},
+ {file = "aiohttp-3.13.5-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:9277145d36a01653863899c665243871434694bcc3431922c3b35c978061bdb8"},
+ {file = "aiohttp-3.13.5-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:4e704c52438f66fdd89588346183d898bb42167cf88f8b7ff1c0f9fc957c348f"},
+ {file = "aiohttp-3.13.5-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a8a4d3427e8de1312ddf309cc482186466c79895b3a139fed3259fc01dfa9a5b"},
+ {file = "aiohttp-3.13.5-cp39-cp39-win32.whl", hash = "sha256:6f497a6876aa4b1a102b04996ce4c1170c7040d83faa9387dd921c16e30d5c83"},
+ {file = "aiohttp-3.13.5-cp39-cp39-win_amd64.whl", hash = "sha256:cb979826071c0986a5f08333a36104153478ce6018c58cba7f9caddaf63d5d67"},
+ {file = "aiohttp-3.13.5.tar.gz", hash = "sha256:9d98cc980ecc96be6eb4c1994ce35d28d8b1f5e5208a23b421187d1209dbb7d1"},
+]
+
+[package.dependencies]
+aiohappyeyeballs = ">=2.5.0"
+aiosignal = ">=1.4.0"
+async-timeout = {version = ">=4.0,<6.0", markers = "python_version < \"3.11\""}
+attrs = ">=17.3.0"
+frozenlist = ">=1.1.1"
+multidict = ">=4.5,<7.0"
+propcache = ">=0.2.0"
+yarl = ">=1.17.0,<2.0"
+
+[package.extras]
+speedups = ["Brotli (>=1.2)", "aiodns (>=3.3.0)", "backports.zstd", "brotlicffi (>=1.2)"]
+
+[[package]]
+name = "aiosignal"
+version = "1.4.0"
+description = "aiosignal: a list of registered asynchronous callbacks"
+optional = true
+python-versions = ">=3.9"
+files = [
+ {file = "aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e"},
+ {file = "aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7"},
+]
+
+[package.dependencies]
+frozenlist = ">=1.1.0"
+typing-extensions = {version = ">=4.2", markers = "python_version < \"3.13\""}
+
+[[package]]
+name = "annotated-types"
+version = "0.7.0"
+description = "Reusable constraint types to use with typing.Annotated"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"},
+ {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"},
+]
+
+[[package]]
+name = "anyio"
+version = "4.13.0"
+description = "High-level concurrency and networking framework on top of asyncio or Trio"
+optional = false
+python-versions = ">=3.10"
+files = [
+ {file = "anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708"},
+ {file = "anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc"},
+]
+
+[package.dependencies]
+exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""}
+idna = ">=2.8"
+typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""}
+
+[package.extras]
+trio = ["trio (>=0.32.0)"]
+
+[[package]]
+name = "async-timeout"
+version = "5.0.1"
+description = "Timeout context manager for asyncio programs"
+optional = true
+python-versions = ">=3.8"
+files = [
+ {file = "async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c"},
+ {file = "async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3"},
+]
+
+[[package]]
+name = "attrs"
+version = "26.1.0"
+description = "Classes Without Boilerplate"
+optional = true
+python-versions = ">=3.9"
+files = [
+ {file = "attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309"},
+ {file = "attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32"},
+]
+
+[[package]]
+name = "backports-asyncio-runner"
+version = "1.2.0"
+description = "Backport of asyncio.Runner, a context manager that controls event loop life cycle."
+optional = false
+python-versions = "<3.11,>=3.8"
+files = [
+ {file = "backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5"},
+ {file = "backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162"},
+]
+
+[[package]]
+name = "certifi"
+version = "2026.2.25"
+description = "Python package for providing Mozilla's CA Bundle."
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa"},
+ {file = "certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7"},
+]
+
+[[package]]
+name = "charset-normalizer"
+version = "3.4.7"
+description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "charset_normalizer-3.4.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cdd68a1fb318e290a2077696b7eb7a21a49163c455979c639bf5a5dcdc46617d"},
+ {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e17b8d5d6a8c47c85e68ca8379def1303fd360c3e22093a807cd34a71cd082b8"},
+ {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:511ef87c8aec0783e08ac18565a16d435372bc1ac25a91e6ac7f5ef2b0bff790"},
+ {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:007d05ec7321d12a40227aae9e2bc6dca73f3cb21058999a1df9e193555a9dcc"},
+ {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf29836da5119f3c8a8a70667b0ef5fdca3bb12f80fd06487cfa575b3909b393"},
+ {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:12d8baf840cc7889b37c7c770f478adea7adce3dcb3944d02ec87508e2dcf153"},
+ {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d560742f3c0d62afaccf9f41fe485ed69bd7661a241f86a3ef0f0fb8b1a397af"},
+ {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b14b2d9dac08e28bb8046a1a0434b1750eb221c8f5b87a68f4fa11a6f97b5e34"},
+ {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:bc17a677b21b3502a21f66a8cc64f5bfad4df8a0b8434d661666f8ce90ac3af1"},
+ {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:750e02e074872a3fad7f233b47734166440af3cdea0add3e95163110816d6752"},
+ {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:4e5163c14bffd570ef2affbfdd77bba66383890797df43dc8b4cc7d6f500bf53"},
+ {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6ed74185b2db44f41ef35fd1617c5888e59792da9bbc9190d6c7300617182616"},
+ {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:94e1885b270625a9a828c9793b4d52a64445299baa1fea5a173bf1d3dd9a1a5a"},
+ {file = "charset_normalizer-3.4.7-cp310-cp310-win32.whl", hash = "sha256:6785f414ae0f3c733c437e0f3929197934f526d19dfaa75e18fdb4f94c6fb374"},
+ {file = "charset_normalizer-3.4.7-cp310-cp310-win_amd64.whl", hash = "sha256:6696b7688f54f5af4462118f0bfa7c1621eeb87154f77fa04b9295ce7a8f2943"},
+ {file = "charset_normalizer-3.4.7-cp310-cp310-win_arm64.whl", hash = "sha256:66671f93accb62ed07da56613636f3641f1a12c13046ce91ffc923721f23c008"},
+ {file = "charset_normalizer-3.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7641bb8895e77f921102f72833904dcd9901df5d6d72a2ab8f31d04b7e51e4e7"},
+ {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:202389074300232baeb53ae2569a60901f7efadd4245cf3a3bf0617d60b439d7"},
+ {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:30b8d1d8c52a48c2c5690e152c169b673487a2a58de1ec7393196753063fcd5e"},
+ {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:532bc9bf33a68613fd7d65e4b1c71a6a38d7d42604ecf239c77392e9b4e8998c"},
+ {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fe249cb4651fd12605b7288b24751d8bfd46d35f12a20b1ba33dea122e690df"},
+ {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:65bcd23054beab4d166035cabbc868a09c1a49d1efe458fe8e4361215df40265"},
+ {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:08e721811161356f97b4059a9ba7bafb23ea5ee2255402c42881c214e173c6b4"},
+ {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e060d01aec0a910bdccb8be71faf34e7799ce36950f8294c8bf612cba65a2c9e"},
+ {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:38c0109396c4cfc574d502df99742a45c72c08eff0a36158b6f04000043dbf38"},
+ {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1c2a768fdd44ee4a9339a9b0b130049139b8ce3c01d2ce09f67f5a68048d477c"},
+ {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:1a87ca9d5df6fe460483d9a5bbf2b18f620cbed41b432e2bddb686228282d10b"},
+ {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d635aab80466bc95771bb78d5370e74d36d1fe31467b6b29b8b57b2a3cd7d22c"},
+ {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ae196f021b5e7c78e918242d217db021ed2a6ace2bc6ae94c0fc596221c7f58d"},
+ {file = "charset_normalizer-3.4.7-cp311-cp311-win32.whl", hash = "sha256:adb2597b428735679446b46c8badf467b4ca5f5056aae4d51a19f9570301b1ad"},
+ {file = "charset_normalizer-3.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:8e385e4267ab76874ae30db04c627faaaf0b509e1ccc11a95b3fc3e83f855c00"},
+ {file = "charset_normalizer-3.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:d4a48e5b3c2a489fae013b7589308a40146ee081f6f509e047e0e096084ceca1"},
+ {file = "charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46"},
+ {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2"},
+ {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b"},
+ {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a"},
+ {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116"},
+ {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb"},
+ {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1"},
+ {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15"},
+ {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5"},
+ {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d"},
+ {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7"},
+ {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464"},
+ {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49"},
+ {file = "charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c"},
+ {file = "charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6"},
+ {file = "charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d"},
+ {file = "charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063"},
+ {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c"},
+ {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66"},
+ {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18"},
+ {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd"},
+ {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215"},
+ {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859"},
+ {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8"},
+ {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5"},
+ {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832"},
+ {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6"},
+ {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48"},
+ {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a"},
+ {file = "charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e"},
+ {file = "charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110"},
+ {file = "charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b"},
+ {file = "charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0"},
+ {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a"},
+ {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b"},
+ {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41"},
+ {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e"},
+ {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae"},
+ {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18"},
+ {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b"},
+ {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356"},
+ {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab"},
+ {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46"},
+ {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44"},
+ {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72"},
+ {file = "charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10"},
+ {file = "charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f"},
+ {file = "charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246"},
+ {file = "charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24"},
+ {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79"},
+ {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960"},
+ {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4"},
+ {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e"},
+ {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1"},
+ {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44"},
+ {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e"},
+ {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3"},
+ {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0"},
+ {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e"},
+ {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb"},
+ {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe"},
+ {file = "charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0"},
+ {file = "charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c"},
+ {file = "charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d"},
+ {file = "charset_normalizer-3.4.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e5f4d355f0a2b1a31bc3edec6795b46324349c9cb25eed068049e4f472fb4259"},
+ {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:16d971e29578a5e97d7117866d15889a4a07befe0e87e703ed63cd90cb348c01"},
+ {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dca4bbc466a95ba9c0234ef56d7dd9509f63da22274589ebd4ed7f1f4d4c54e3"},
+ {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e80c8378d8f3d83cd3164da1ad2df9e37a666cdde7b1cb2298ed0b558064be30"},
+ {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:36836d6ff945a00b88ba1e4572d721e60b5b8c98c155d465f56ad19d68f23734"},
+ {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux_2_31_armv7l.whl", hash = "sha256:bd9b23791fe793e4968dba0c447e12f78e425c59fc0e3b97f6450f4781f3ee60"},
+ {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:aef65cd602a6d0e0ff6f9930fcb1c8fec60dd2cfcb6facaf4bdb0e5873042db0"},
+ {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:82b271f5137d07749f7bf32f70b17ab6eaabedd297e75dce75081a24f76eb545"},
+ {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:1efde3cae86c8c273f1eb3b287be7d8499420cf2fe7585c41d370d3e790054a5"},
+ {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:c593052c465475e64bbfe5dbd81680f64a67fdc752c56d7a0ae205dc8aeefe0f"},
+ {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_riscv64.whl", hash = "sha256:af21eb4409a119e365397b2adbaca4c9ccab56543a65d5dbd9f920d6ac29f686"},
+ {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:84c018e49c3bf790f9c2771c45e9313a08c2c2a6342b162cd650258b57817706"},
+ {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dd915403e231e6b1809fe9b6d9fc55cf8fb5e02765ac625d9cd623342a7905d7"},
+ {file = "charset_normalizer-3.4.7-cp38-cp38-win32.whl", hash = "sha256:320ade88cfb846b8cd6b4ddf5ee9e80ee0c1f52401f2456b84ae1ae6a1a5f207"},
+ {file = "charset_normalizer-3.4.7-cp38-cp38-win_amd64.whl", hash = "sha256:1dc8b0ea451d6e69735094606991f32867807881400f808a106ee1d963c46a83"},
+ {file = "charset_normalizer-3.4.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:177a0ba5f0211d488e295aaf82707237e331c24788d8d76c96c5a41594723217"},
+ {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e0d51f618228538a3e8f46bd246f87a6cd030565e015803691603f55e12afb5"},
+ {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:14265bfe1f09498b9d8ec91e9ec9fa52775edf90fcbde092b25f4a33d444fea9"},
+ {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:87fad7d9ba98c86bcb41b2dc8dbb326619be2562af1f8ff50776a39e55721c5a"},
+ {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f22dec1690b584cea26fade98b2435c132c1b5f68e39f5a0b7627cd7ae31f1dc"},
+ {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux_2_31_armv7l.whl", hash = "sha256:d61f00a0869d77422d9b2aba989e2d24afa6ffd552af442e0e58de4f35ea6d00"},
+ {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6370e8686f662e6a3941ee48ed4742317cafbe5707e36406e9df792cdb535776"},
+ {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a6c5863edfbe888d9eff9c8b8087354e27618d9da76425c119293f11712a6319"},
+ {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:ed065083d0898c9d5b4bbec7b026fd755ff7454e6e8b73a67f8c744b13986e24"},
+ {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:2cd4a60d0e2fb04537162c62bbbb4182f53541fe0ede35cdf270a1c1e723cc42"},
+ {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:813c0e0132266c08eb87469a642cb30aaff57c5f426255419572aaeceeaa7bf4"},
+ {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:07d9e39b01743c3717745f4c530a6349eadbfa043c7577eef86c502c15df2c67"},
+ {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c0f081d69a6e58272819b70288d3221a6ee64b98df852631c80f293514d3b274"},
+ {file = "charset_normalizer-3.4.7-cp39-cp39-win32.whl", hash = "sha256:8751d2787c9131302398b11e6c8068053dcb55d5a8964e114b6e196cf16cb366"},
+ {file = "charset_normalizer-3.4.7-cp39-cp39-win_amd64.whl", hash = "sha256:12a6fff75f6bc66711b73a2f0addfc4c8c15a20e805146a02d147a318962c444"},
+ {file = "charset_normalizer-3.4.7-cp39-cp39-win_arm64.whl", hash = "sha256:bb8cc7534f51d9a017b93e3e85b260924f909601c3df002bcdb58ddb4dc41a5c"},
+ {file = "charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d"},
+ {file = "charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5"},
+]
+
+[[package]]
+name = "colorama"
+version = "0.4.6"
+description = "Cross-platform colored terminal text."
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
+files = [
+ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
+ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
+]
+
+[[package]]
+name = "exceptiongroup"
+version = "1.3.1"
+description = "Backport of PEP 654 (exception groups)"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598"},
+ {file = "exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219"},
+]
+
+[package.dependencies]
+typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.13\""}
+
+[package.extras]
+test = ["pytest (>=6)"]
+
+[[package]]
+name = "execnet"
+version = "2.1.2"
+description = "execnet: rapid multi-Python deployment"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "execnet-2.1.2-py3-none-any.whl", hash = "sha256:67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec"},
+ {file = "execnet-2.1.2.tar.gz", hash = "sha256:63d83bfdd9a23e35b9c6a3261412324f964c2ec8dcd8d3c6916ee9373e0befcd"},
+]
+
+[package.extras]
+testing = ["hatch", "pre-commit", "pytest", "tox"]
+
+[[package]]
+name = "frozenlist"
+version = "1.8.0"
+description = "A list-like structure which implements collections.abc.MutableSequence"
+optional = true
+python-versions = ">=3.9"
+files = [
+ {file = "frozenlist-1.8.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b37f6d31b3dcea7deb5e9696e529a6aa4a898adc33db82da12e4c60a7c4d2011"},
+ {file = "frozenlist-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef2b7b394f208233e471abc541cc6991f907ffd47dc72584acee3147899d6565"},
+ {file = "frozenlist-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a88f062f072d1589b7b46e951698950e7da00442fc1cacbe17e19e025dc327ad"},
+ {file = "frozenlist-1.8.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f57fb59d9f385710aa7060e89410aeb5058b99e62f4d16b08b91986b9a2140c2"},
+ {file = "frozenlist-1.8.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:799345ab092bee59f01a915620b5d014698547afd011e691a208637312db9186"},
+ {file = "frozenlist-1.8.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c23c3ff005322a6e16f71bf8692fcf4d5a304aaafe1e262c98c6d4adc7be863e"},
+ {file = "frozenlist-1.8.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8a76ea0f0b9dfa06f254ee06053d93a600865b3274358ca48a352ce4f0798450"},
+ {file = "frozenlist-1.8.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c7366fe1418a6133d5aa824ee53d406550110984de7637d65a178010f759c6ef"},
+ {file = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:13d23a45c4cebade99340c4165bd90eeb4a56c6d8a9d8aa49568cac19a6d0dc4"},
+ {file = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:e4a3408834f65da56c83528fb52ce7911484f0d1eaf7b761fc66001db1646eff"},
+ {file = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:42145cd2748ca39f32801dad54aeea10039da6f86e303659db90db1c4b614c8c"},
+ {file = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e2de870d16a7a53901e41b64ffdf26f2fbb8917b3e6ebf398098d72c5b20bd7f"},
+ {file = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:20e63c9493d33ee48536600d1a5c95eefc870cd71e7ab037763d1fbb89cc51e7"},
+ {file = "frozenlist-1.8.0-cp310-cp310-win32.whl", hash = "sha256:adbeebaebae3526afc3c96fad434367cafbfd1b25d72369a9e5858453b1bb71a"},
+ {file = "frozenlist-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:667c3777ca571e5dbeb76f331562ff98b957431df140b54c85fd4d52eea8d8f6"},
+ {file = "frozenlist-1.8.0-cp310-cp310-win_arm64.whl", hash = "sha256:80f85f0a7cc86e7a54c46d99c9e1318ff01f4687c172ede30fd52d19d1da1c8e"},
+ {file = "frozenlist-1.8.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:09474e9831bc2b2199fad6da3c14c7b0fbdd377cce9d3d77131be28906cb7d84"},
+ {file = "frozenlist-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:17c883ab0ab67200b5f964d2b9ed6b00971917d5d8a92df149dc2c9779208ee9"},
+ {file = "frozenlist-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fa47e444b8ba08fffd1c18e8cdb9a75db1b6a27f17507522834ad13ed5922b93"},
+ {file = "frozenlist-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2552f44204b744fba866e573be4c1f9048d6a324dfe14475103fd51613eb1d1f"},
+ {file = "frozenlist-1.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:957e7c38f250991e48a9a73e6423db1bb9dd14e722a10f6b8bb8e16a0f55f695"},
+ {file = "frozenlist-1.8.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8585e3bb2cdea02fc88ffa245069c36555557ad3609e83be0ec71f54fd4abb52"},
+ {file = "frozenlist-1.8.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:edee74874ce20a373d62dc28b0b18b93f645633c2943fd90ee9d898550770581"},
+ {file = "frozenlist-1.8.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c9a63152fe95756b85f31186bddf42e4c02c6321207fd6601a1c89ebac4fe567"},
+ {file = "frozenlist-1.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b6db2185db9be0a04fecf2f241c70b63b1a242e2805be291855078f2b404dd6b"},
+ {file = "frozenlist-1.8.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f4be2e3d8bc8aabd566f8d5b8ba7ecc09249d74ba3c9ed52e54dc23a293f0b92"},
+ {file = "frozenlist-1.8.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c8d1634419f39ea6f5c427ea2f90ca85126b54b50837f31497f3bf38266e853d"},
+ {file = "frozenlist-1.8.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:1a7fa382a4a223773ed64242dbe1c9c326ec09457e6b8428efb4118c685c3dfd"},
+ {file = "frozenlist-1.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:11847b53d722050808926e785df837353bd4d75f1d494377e59b23594d834967"},
+ {file = "frozenlist-1.8.0-cp311-cp311-win32.whl", hash = "sha256:27c6e8077956cf73eadd514be8fb04d77fc946a7fe9f7fe167648b0b9085cc25"},
+ {file = "frozenlist-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:ac913f8403b36a2c8610bbfd25b8013488533e71e62b4b4adce9c86c8cea905b"},
+ {file = "frozenlist-1.8.0-cp311-cp311-win_arm64.whl", hash = "sha256:d4d3214a0f8394edfa3e303136d0575eece0745ff2b47bd2cb2e66dd92d4351a"},
+ {file = "frozenlist-1.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78f7b9e5d6f2fdb88cdde9440dc147259b62b9d3b019924def9f6478be254ac1"},
+ {file = "frozenlist-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:229bf37d2e4acdaf808fd3f06e854a4a7a3661e871b10dc1f8f1896a3b05f18b"},
+ {file = "frozenlist-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f833670942247a14eafbb675458b4e61c82e002a148f49e68257b79296e865c4"},
+ {file = "frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:494a5952b1c597ba44e0e78113a7266e656b9794eec897b19ead706bd7074383"},
+ {file = "frozenlist-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f423a119f4777a4a056b66ce11527366a8bb92f54e541ade21f2374433f6d4"},
+ {file = "frozenlist-1.8.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3462dd9475af2025c31cc61be6652dfa25cbfb56cbbf52f4ccfe029f38decaf8"},
+ {file = "frozenlist-1.8.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4c800524c9cd9bac5166cd6f55285957fcfc907db323e193f2afcd4d9abd69b"},
+ {file = "frozenlist-1.8.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d6a5df73acd3399d893dafc71663ad22534b5aa4f94e8a2fabfe856c3c1b6a52"},
+ {file = "frozenlist-1.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:405e8fe955c2280ce66428b3ca55e12b3c4e9c336fb2103a4937e891c69a4a29"},
+ {file = "frozenlist-1.8.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:908bd3f6439f2fef9e85031b59fd4f1297af54415fb60e4254a95f75b3cab3f3"},
+ {file = "frozenlist-1.8.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:294e487f9ec720bd8ffcebc99d575f7eff3568a08a253d1ee1a0378754b74143"},
+ {file = "frozenlist-1.8.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:74c51543498289c0c43656701be6b077f4b265868fa7f8a8859c197006efb608"},
+ {file = "frozenlist-1.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:776f352e8329135506a1d6bf16ac3f87bc25b28e765949282dcc627af36123aa"},
+ {file = "frozenlist-1.8.0-cp312-cp312-win32.whl", hash = "sha256:433403ae80709741ce34038da08511d4a77062aa924baf411ef73d1146e74faf"},
+ {file = "frozenlist-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:34187385b08f866104f0c0617404c8eb08165ab1272e884abc89c112e9c00746"},
+ {file = "frozenlist-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:fe3c58d2f5db5fbd18c2987cba06d51b0529f52bc3a6cdc33d3f4eab725104bd"},
+ {file = "frozenlist-1.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8d92f1a84bb12d9e56f818b3a746f3efba93c1b63c8387a73dde655e1e42282a"},
+ {file = "frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96153e77a591c8adc2ee805756c61f59fef4cf4073a9275ee86fe8cba41241f7"},
+ {file = "frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f21f00a91358803399890ab167098c131ec2ddd5f8f5fd5fe9c9f2c6fcd91e40"},
+ {file = "frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fb30f9626572a76dfe4293c7194a09fb1fe93ba94c7d4f720dfae3b646b45027"},
+ {file = "frozenlist-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaa352d7047a31d87dafcacbabe89df0aa506abb5b1b85a2fb91bc3faa02d822"},
+ {file = "frozenlist-1.8.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:03ae967b4e297f58f8c774c7eabcce57fe3c2434817d4385c50661845a058121"},
+ {file = "frozenlist-1.8.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6292f1de555ffcc675941d65fffffb0a5bcd992905015f85d0592201793e0e5"},
+ {file = "frozenlist-1.8.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29548f9b5b5e3460ce7378144c3010363d8035cea44bc0bf02d57f5a685e084e"},
+ {file = "frozenlist-1.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ec3cc8c5d4084591b4237c0a272cc4f50a5b03396a47d9caaf76f5d7b38a4f11"},
+ {file = "frozenlist-1.8.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:517279f58009d0b1f2e7c1b130b377a349405da3f7621ed6bfae50b10adf20c1"},
+ {file = "frozenlist-1.8.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:db1e72ede2d0d7ccb213f218df6a078a9c09a7de257c2fe8fcef16d5925230b1"},
+ {file = "frozenlist-1.8.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b4dec9482a65c54a5044486847b8a66bf10c9cb4926d42927ec4e8fd5db7fed8"},
+ {file = "frozenlist-1.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:21900c48ae04d13d416f0e1e0c4d81f7931f73a9dfa0b7a8746fb2fe7dd970ed"},
+ {file = "frozenlist-1.8.0-cp313-cp313-win32.whl", hash = "sha256:8b7b94a067d1c504ee0b16def57ad5738701e4ba10cec90529f13fa03c833496"},
+ {file = "frozenlist-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:878be833caa6a3821caf85eb39c5ba92d28e85df26d57afb06b35b2efd937231"},
+ {file = "frozenlist-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:44389d135b3ff43ba8cc89ff7f51f5a0bb6b63d829c8300f79a2fe4fe61bcc62"},
+ {file = "frozenlist-1.8.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e25ac20a2ef37e91c1b39938b591457666a0fa835c7783c3a8f33ea42870db94"},
+ {file = "frozenlist-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07cdca25a91a4386d2e76ad992916a85038a9b97561bf7a3fd12d5d9ce31870c"},
+ {file = "frozenlist-1.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e0c11f2cc6717e0a741f84a527c52616140741cd812a50422f83dc31749fb52"},
+ {file = "frozenlist-1.8.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3210649ee28062ea6099cfda39e147fa1bc039583c8ee4481cb7811e2448c51"},
+ {file = "frozenlist-1.8.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581ef5194c48035a7de2aefc72ac6539823bb71508189e5de01d60c9dcd5fa65"},
+ {file = "frozenlist-1.8.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3ef2d026f16a2b1866e1d86fc4e1291e1ed8a387b2c333809419a2f8b3a77b82"},
+ {file = "frozenlist-1.8.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5500ef82073f599ac84d888e3a8c1f77ac831183244bfd7f11eaa0289fb30714"},
+ {file = "frozenlist-1.8.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50066c3997d0091c411a66e710f4e11752251e6d2d73d70d8d5d4c76442a199d"},
+ {file = "frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5c1c8e78426e59b3f8005e9b19f6ff46e5845895adbde20ece9218319eca6506"},
+ {file = "frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:eefdba20de0d938cec6a89bd4d70f346a03108a19b9df4248d3cf0d88f1b0f51"},
+ {file = "frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cf253e0e1c3ceb4aaff6df637ce033ff6535fb8c70a764a8f46aafd3d6ab798e"},
+ {file = "frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:032efa2674356903cd0261c4317a561a6850f3ac864a63fc1583147fb05a79b0"},
+ {file = "frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6da155091429aeba16851ecb10a9104a108bcd32f6c1642867eadaee401c1c41"},
+ {file = "frozenlist-1.8.0-cp313-cp313t-win32.whl", hash = "sha256:0f96534f8bfebc1a394209427d0f8a63d343c9779cda6fc25e8e121b5fd8555b"},
+ {file = "frozenlist-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5d63a068f978fc69421fb0e6eb91a9603187527c86b7cd3f534a5b77a592b888"},
+ {file = "frozenlist-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf0a7e10b077bf5fb9380ad3ae8ce20ef919a6ad93b4552896419ac7e1d8e042"},
+ {file = "frozenlist-1.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cee686f1f4cadeb2136007ddedd0aaf928ab95216e7691c63e50a8ec066336d0"},
+ {file = "frozenlist-1.8.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:119fb2a1bd47307e899c2fac7f28e85b9a543864df47aa7ec9d3c1b4545f096f"},
+ {file = "frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4970ece02dbc8c3a92fcc5228e36a3e933a01a999f7094ff7c23fbd2beeaa67c"},
+ {file = "frozenlist-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cba69cb73723c3f329622e34bdbf5ce1f80c21c290ff04256cff1cd3c2036ed2"},
+ {file = "frozenlist-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:778a11b15673f6f1df23d9586f83c4846c471a8af693a22e066508b77d201ec8"},
+ {file = "frozenlist-1.8.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0325024fe97f94c41c08872db482cf8ac4800d80e79222c6b0b7b162d5b13686"},
+ {file = "frozenlist-1.8.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:97260ff46b207a82a7567b581ab4190bd4dfa09f4db8a8b49d1a958f6aa4940e"},
+ {file = "frozenlist-1.8.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:54b2077180eb7f83dd52c40b2750d0a9f175e06a42e3213ce047219de902717a"},
+ {file = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2f05983daecab868a31e1da44462873306d3cbfd76d1f0b5b69c473d21dbb128"},
+ {file = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:33f48f51a446114bc5d251fb2954ab0164d5be02ad3382abcbfe07e2531d650f"},
+ {file = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:154e55ec0655291b5dd1b8731c637ecdb50975a2ae70c606d100750a540082f7"},
+ {file = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:4314debad13beb564b708b4a496020e5306c7333fa9a3ab90374169a20ffab30"},
+ {file = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:073f8bf8becba60aa931eb3bc420b217bb7d5b8f4750e6f8b3be7f3da85d38b7"},
+ {file = "frozenlist-1.8.0-cp314-cp314-win32.whl", hash = "sha256:bac9c42ba2ac65ddc115d930c78d24ab8d4f465fd3fc473cdedfccadb9429806"},
+ {file = "frozenlist-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:3e0761f4d1a44f1d1a47996511752cf3dcec5bbdd9cc2b4fe595caf97754b7a0"},
+ {file = "frozenlist-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:d1eaff1d00c7751b7c6662e9c5ba6eb2c17a2306ba5e2a37f24ddf3cc953402b"},
+ {file = "frozenlist-1.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d3bb933317c52d7ea5004a1c442eef86f426886fba134ef8cf4226ea6ee1821d"},
+ {file = "frozenlist-1.8.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8009897cdef112072f93a0efdce29cd819e717fd2f649ee3016efd3cd885a7ed"},
+ {file = "frozenlist-1.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2c5dcbbc55383e5883246d11fd179782a9d07a986c40f49abe89ddf865913930"},
+ {file = "frozenlist-1.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:39ecbc32f1390387d2aa4f5a995e465e9e2f79ba3adcac92d68e3e0afae6657c"},
+ {file = "frozenlist-1.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92db2bf818d5cc8d9c1f1fc56b897662e24ea5adb36ad1f1d82875bd64e03c24"},
+ {file = "frozenlist-1.8.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dc43a022e555de94c3b68a4ef0b11c4f747d12c024a520c7101709a2144fb37"},
+ {file = "frozenlist-1.8.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb89a7f2de3602cfed448095bab3f178399646ab7c61454315089787df07733a"},
+ {file = "frozenlist-1.8.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:33139dc858c580ea50e7e60a1b0ea003efa1fd42e6ec7fdbad78fff65fad2fd2"},
+ {file = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:168c0969a329b416119507ba30b9ea13688fafffac1b7822802537569a1cb0ef"},
+ {file = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:28bd570e8e189d7f7b001966435f9dac6718324b5be2990ac496cf1ea9ddb7fe"},
+ {file = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b2a095d45c5d46e5e79ba1e5b9cb787f541a8dee0433836cea4b96a2c439dcd8"},
+ {file = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:eab8145831a0d56ec9c4139b6c3e594c7a83c2c8be25d5bcf2d86136a532287a"},
+ {file = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:974b28cf63cc99dfb2188d8d222bc6843656188164848c4f679e63dae4b0708e"},
+ {file = "frozenlist-1.8.0-cp314-cp314t-win32.whl", hash = "sha256:342c97bf697ac5480c0a7ec73cd700ecfa5a8a40ac923bd035484616efecc2df"},
+ {file = "frozenlist-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:06be8f67f39c8b1dc671f5d83aaefd3358ae5cdcf8314552c57e7ed3e6475bdd"},
+ {file = "frozenlist-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:102e6314ca4da683dca92e3b1355490fed5f313b768500084fbe6371fddfdb79"},
+ {file = "frozenlist-1.8.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d8b7138e5cd0647e4523d6685b0eac5d4be9a184ae9634492f25c6eb38c12a47"},
+ {file = "frozenlist-1.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a6483e309ca809f1efd154b4d37dc6d9f61037d6c6a81c2dc7a15cb22c8c5dca"},
+ {file = "frozenlist-1.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1b9290cf81e95e93fdf90548ce9d3c1211cf574b8e3f4b3b7cb0537cf2227068"},
+ {file = "frozenlist-1.8.0-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:59a6a5876ca59d1b63af8cd5e7ffffb024c3dc1e9cf9301b21a2e76286505c95"},
+ {file = "frozenlist-1.8.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6dc4126390929823e2d2d9dc79ab4046ed74680360fc5f38b585c12c66cdf459"},
+ {file = "frozenlist-1.8.0-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:332db6b2563333c5671fecacd085141b5800cb866be16d5e3eb15a2086476675"},
+ {file = "frozenlist-1.8.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9ff15928d62a0b80bb875655c39bf517938c7d589554cbd2669be42d97c2cb61"},
+ {file = "frozenlist-1.8.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7bf6cdf8e07c8151fba6fe85735441240ec7f619f935a5205953d58009aef8c6"},
+ {file = "frozenlist-1.8.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:48e6d3f4ec5c7273dfe83ff27c91083c6c9065af655dc2684d2c200c94308bb5"},
+ {file = "frozenlist-1.8.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:1a7607e17ad33361677adcd1443edf6f5da0ce5e5377b798fba20fae194825f3"},
+ {file = "frozenlist-1.8.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:5a3a935c3a4e89c733303a2d5a7c257ea44af3a56c8202df486b7f5de40f37e1"},
+ {file = "frozenlist-1.8.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:940d4a017dbfed9daf46a3b086e1d2167e7012ee297fef9e1c545c4d022f5178"},
+ {file = "frozenlist-1.8.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b9be22a69a014bc47e78072d0ecae716f5eb56c15238acca0f43d6eb8e4a5bda"},
+ {file = "frozenlist-1.8.0-cp39-cp39-win32.whl", hash = "sha256:1aa77cb5697069af47472e39612976ed05343ff2e84a3dcf15437b232cbfd087"},
+ {file = "frozenlist-1.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:7398c222d1d405e796970320036b1b563892b65809d9e5261487bb2c7f7b5c6a"},
+ {file = "frozenlist-1.8.0-cp39-cp39-win_arm64.whl", hash = "sha256:b4f3b365f31c6cd4af24545ca0a244a53688cad8834e32f56831c4923b50a103"},
+ {file = "frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d"},
+ {file = "frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad"},
+]
+
+[[package]]
+name = "h11"
+version = "0.16.0"
+description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"},
+ {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"},
+]
+
+[[package]]
+name = "httpcore"
+version = "1.0.9"
+description = "A minimal low-level HTTP client."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55"},
+ {file = "httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8"},
+]
+
+[package.dependencies]
+certifi = "*"
+h11 = ">=0.16"
+
+[package.extras]
+asyncio = ["anyio (>=4.0,<5.0)"]
+http2 = ["h2 (>=3,<5)"]
+socks = ["socksio (==1.*)"]
+trio = ["trio (>=0.22.0,<1.0)"]
+
+[[package]]
+name = "httpx"
+version = "0.28.1"
+description = "The next generation HTTP client."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"},
+ {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"},
+]
+
+[package.dependencies]
+anyio = "*"
+certifi = "*"
+httpcore = "==1.*"
+idna = "*"
+
+[package.extras]
+brotli = ["brotli", "brotlicffi"]
+cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"]
+http2 = ["h2 (>=3,<5)"]
+socks = ["socksio (==1.*)"]
+zstd = ["zstandard (>=0.18.0)"]
+
+[[package]]
+name = "httpx-aiohttp"
+version = "0.1.8"
+description = "Aiohttp transport for HTTPX"
+optional = true
+python-versions = ">=3.8"
+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"},
+]
+
+[package.dependencies]
+aiohttp = ">=3.10.0,<4"
+httpx = ">=0.27.0"
+
+[[package]]
+name = "idna"
+version = "3.11"
+description = "Internationalized Domain Names in Applications (IDNA)"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea"},
+ {file = "idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902"},
+]
+
+[package.extras]
+all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"]
+
+[[package]]
+name = "iniconfig"
+version = "2.3.0"
+description = "brain-dead simple config-ini parsing"
+optional = false
+python-versions = ">=3.10"
+files = [
+ {file = "iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12"},
+ {file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"},
+]
+
+[[package]]
+name = "multidict"
+version = "6.7.1"
+description = "multidict implementation"
+optional = true
+python-versions = ">=3.9"
+files = [
+ {file = "multidict-6.7.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c93c3db7ea657dd4637d57e74ab73de31bccefe144d3d4ce370052035bc85fb5"},
+ {file = "multidict-6.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:974e72a2474600827abaeda71af0c53d9ebbc3c2eb7da37b37d7829ae31232d8"},
+ {file = "multidict-6.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdea2e7b2456cfb6694fb113066fd0ec7ea4d67e3a35e1f4cbeea0b448bf5872"},
+ {file = "multidict-6.7.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:17207077e29342fdc2c9a82e4b306f1127bf1ea91f8b71e02d4798a70bb99991"},
+ {file = "multidict-6.7.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4f49cb5661344764e4c7c7973e92a47a59b8fc19b6523649ec9dc4960e58a03"},
+ {file = "multidict-6.7.1-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a9fc4caa29e2e6ae408d1c450ac8bf19892c5fca83ee634ecd88a53332c59981"},
+ {file = "multidict-6.7.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c5f0c21549ab432b57dcc82130f388d84ad8179824cc3f223d5e7cfbfd4143f6"},
+ {file = "multidict-6.7.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7dfb78d966b2c906ae1d28ccf6e6712a3cd04407ee5088cd276fe8cb42186190"},
+ {file = "multidict-6.7.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9b0d9b91d1aa44db9c1f1ecd0d9d2ae610b2f4f856448664e01a3b35899f3f92"},
+ {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dd96c01a9dcd4889dcfcf9eb5544ca0c77603f239e3ffab0524ec17aea9a93ee"},
+ {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:067343c68cd6612d375710f895337b3a98a033c94f14b9a99eff902f205424e2"},
+ {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5884a04f4ff56c6120f6ccf703bdeb8b5079d808ba604d4d53aec0d55dc33568"},
+ {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8affcf1c98b82bc901702eb73b6947a1bfa170823c153fe8a47b5f5f02e48e40"},
+ {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:0d17522c37d03e85c8098ec8431636309b2682cf12e58f4dbc76121fb50e4962"},
+ {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:24c0cf81544ca5e17cfcb6e482e7a82cd475925242b308b890c9452a074d4505"},
+ {file = "multidict-6.7.1-cp310-cp310-win32.whl", hash = "sha256:d82dd730a95e6643802f4454b8fdecdf08667881a9c5670db85bc5a56693f122"},
+ {file = "multidict-6.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:cf37cbe5ced48d417ba045aca1b21bafca67489452debcde94778a576666a1df"},
+ {file = "multidict-6.7.1-cp310-cp310-win_arm64.whl", hash = "sha256:59bc83d3f66b41dac1e7460aac1d196edc70c9ba3094965c467715a70ecb46db"},
+ {file = "multidict-6.7.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7ff981b266af91d7b4b3793ca3382e53229088d193a85dfad6f5f4c27fc73e5d"},
+ {file = "multidict-6.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:844c5bca0b5444adb44a623fb0a1310c2f4cd41f402126bb269cd44c9b3f3e1e"},
+ {file = "multidict-6.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f2a0a924d4c2e9afcd7ec64f9de35fcd96915149b2216e1cb2c10a56df483855"},
+ {file = "multidict-6.7.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8be1802715a8e892c784c0197c2ace276ea52702a0ede98b6310c8f255a5afb3"},
+ {file = "multidict-6.7.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2e2d2ed645ea29f31c4c7ea1552fcfd7cb7ba656e1eafd4134a6620c9f5fdd9e"},
+ {file = "multidict-6.7.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:95922cee9a778659e91db6497596435777bd25ed116701a4c034f8e46544955a"},
+ {file = "multidict-6.7.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6b83cabdc375ffaaa15edd97eb7c0c672ad788e2687004990074d7d6c9b140c8"},
+ {file = "multidict-6.7.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:38fb49540705369bab8484db0689d86c0a33a0a9f2c1b197f506b71b4b6c19b0"},
+ {file = "multidict-6.7.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:439cbebd499f92e9aa6793016a8acaa161dfa749ae86d20960189f5398a19144"},
+ {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6d3bc717b6fe763b8be3f2bee2701d3c8eb1b2a8ae9f60910f1b2860c82b6c49"},
+ {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:619e5a1ac57986dbfec9f0b301d865dddf763696435e2962f6d9cf2fdff2bb71"},
+ {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0b38ebffd9be37c1170d33bc0f36f4f262e0a09bc1aac1c34c7aa51a7293f0b3"},
+ {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:10ae39c9cfe6adedcdb764f5e8411d4a92b055e35573a2eaa88d3323289ef93c"},
+ {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:25167cc263257660290fba06b9318d2026e3c910be240a146e1f66dd114af2b0"},
+ {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:128441d052254f42989ef98b7b6a6ecb1e6f708aa962c7984235316db59f50fa"},
+ {file = "multidict-6.7.1-cp311-cp311-win32.whl", hash = "sha256:d62b7f64ffde3b99d06b707a280db04fb3855b55f5a06df387236051d0668f4a"},
+ {file = "multidict-6.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:bdbf9f3b332abd0cdb306e7c2113818ab1e922dc84b8f8fd06ec89ed2a19ab8b"},
+ {file = "multidict-6.7.1-cp311-cp311-win_arm64.whl", hash = "sha256:b8c990b037d2fff2f4e33d3f21b9b531c5745b33a49a7d6dbe7a177266af44f6"},
+ {file = "multidict-6.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a90f75c956e32891a4eda3639ce6dd86e87105271f43d43442a3aedf3cddf172"},
+ {file = "multidict-6.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fccb473e87eaa1382689053e4a4618e7ba7b9b9b8d6adf2027ee474597128cd"},
+ {file = "multidict-6.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0fa96985700739c4c7853a43c0b3e169360d6855780021bfc6d0f1ce7c123e7"},
+ {file = "multidict-6.7.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cb2a55f408c3043e42b40cc8eecd575afa27b7e0b956dfb190de0f8499a57a53"},
+ {file = "multidict-6.7.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb0ce7b2a32d09892b3dd6cc44877a0d02a33241fafca5f25c8b6b62374f8b75"},
+ {file = "multidict-6.7.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c3a32d23520ee37bf327d1e1a656fec76a2edd5c038bf43eddfa0572ec49c60b"},
+ {file = "multidict-6.7.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9c90fed18bffc0189ba814749fdcc102b536e83a9f738a9003e569acd540a733"},
+ {file = "multidict-6.7.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:da62917e6076f512daccfbbde27f46fed1c98fee202f0559adec8ee0de67f71a"},
+ {file = "multidict-6.7.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bfde23ef6ed9db7eaee6c37dcec08524cb43903c60b285b172b6c094711b3961"},
+ {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3758692429e4e32f1ba0df23219cd0b4fc0a52f476726fff9337d1a57676a582"},
+ {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:398c1478926eca669f2fd6a5856b6de9c0acf23a2cb59a14c0ba5844fa38077e"},
+ {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c102791b1c4f3ab36ce4101154549105a53dc828f016356b3e3bcae2e3a039d3"},
+ {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a088b62bd733e2ad12c50dad01b7d0166c30287c166e137433d3b410add807a6"},
+ {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3d51ff4785d58d3f6c91bdbffcb5e1f7ddfda557727043aa20d20ec4f65e324a"},
+ {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc5907494fccf3e7d3f94f95c91d6336b092b5fc83811720fae5e2765890dfba"},
+ {file = "multidict-6.7.1-cp312-cp312-win32.whl", hash = "sha256:28ca5ce2fd9716631133d0e9a9b9a745ad7f60bac2bccafb56aa380fc0b6c511"},
+ {file = "multidict-6.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcee94dfbd638784645b066074b338bc9cc155d4b4bffa4adce1615c5a426c19"},
+ {file = "multidict-6.7.1-cp312-cp312-win_arm64.whl", hash = "sha256:ba0a9fb644d0c1a2194cf7ffb043bd852cea63a57f66fbd33959f7dae18517bf"},
+ {file = "multidict-6.7.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2b41f5fed0ed563624f1c17630cb9941cf2309d4df00e494b551b5f3e3d67a23"},
+ {file = "multidict-6.7.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84e61e3af5463c19b67ced91f6c634effb89ef8bfc5ca0267f954451ed4bb6a2"},
+ {file = "multidict-6.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:935434b9853c7c112eee7ac891bc4cb86455aa631269ae35442cb316790c1445"},
+ {file = "multidict-6.7.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:432feb25a1cb67fe82a9680b4d65fb542e4635cb3166cd9c01560651ad60f177"},
+ {file = "multidict-6.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e82d14e3c948952a1a85503817e038cba5905a3352de76b9a465075d072fba23"},
+ {file = "multidict-6.7.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4cfb48c6ea66c83bcaaf7e4dfa7ec1b6bbcf751b7db85a328902796dfde4c060"},
+ {file = "multidict-6.7.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1d540e51b7e8e170174555edecddbd5538105443754539193e3e1061864d444d"},
+ {file = "multidict-6.7.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:273d23f4b40f3dce4d6c8a821c741a86dec62cded82e1175ba3d99be128147ed"},
+ {file = "multidict-6.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d624335fd4fa1c08a53f8b4be7676ebde19cd092b3895c421045ca87895b429"},
+ {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:12fad252f8b267cc75b66e8fc51b3079604e8d43a75428ffe193cd9e2195dfd6"},
+ {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:03ede2a6ffbe8ef936b92cb4529f27f42be7f56afcdab5ab739cd5f27fb1cbf9"},
+ {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:90efbcf47dbe33dcf643a1e400d67d59abeac5db07dc3f27d6bdeae497a2198c"},
+ {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c4b9bfc148f5a91be9244d6264c53035c8a0dcd2f51f1c3c6e30e30ebaa1c84"},
+ {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:401c5a650f3add2472d1d288c26deebc540f99e2fb83e9525007a74cd2116f1d"},
+ {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:97891f3b1b3ffbded884e2916cacf3c6fc87b66bb0dde46f7357404750559f33"},
+ {file = "multidict-6.7.1-cp313-cp313-win32.whl", hash = "sha256:e1c5988359516095535c4301af38d8a8838534158f649c05dd1050222321bcb3"},
+ {file = "multidict-6.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:960c83bf01a95b12b08fd54324a4eb1d5b52c88932b5cba5d6e712bb3ed12eb5"},
+ {file = "multidict-6.7.1-cp313-cp313-win_arm64.whl", hash = "sha256:563fe25c678aaba333d5399408f5ec3c383ca5b663e7f774dd179a520b8144df"},
+ {file = "multidict-6.7.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c76c4bec1538375dad9d452d246ca5368ad6e1c9039dadcf007ae59c70619ea1"},
+ {file = "multidict-6.7.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:57b46b24b5d5ebcc978da4ec23a819a9402b4228b8a90d9c656422b4bdd8a963"},
+ {file = "multidict-6.7.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e954b24433c768ce78ab7929e84ccf3422e46deb45a4dc9f93438f8217fa2d34"},
+ {file = "multidict-6.7.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3bd231490fa7217cc832528e1cd8752a96f0125ddd2b5749390f7c3ec8721b65"},
+ {file = "multidict-6.7.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:253282d70d67885a15c8a7716f3a73edf2d635793ceda8173b9ecc21f2fb8292"},
+ {file = "multidict-6.7.1-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b4c48648d7649c9335cf1927a8b87fa692de3dcb15faa676c6a6f1f1aabda43"},
+ {file = "multidict-6.7.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:98bc624954ec4d2c7cb074b8eefc2b5d0ce7d482e410df446414355d158fe4ca"},
+ {file = "multidict-6.7.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1b99af4d9eec0b49927b4402bcbb58dea89d3e0db8806a4086117019939ad3dd"},
+ {file = "multidict-6.7.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6aac4f16b472d5b7dc6f66a0d49dd57b0e0902090be16594dc9ebfd3d17c47e7"},
+ {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:21f830fe223215dffd51f538e78c172ed7c7f60c9b96a2bf05c4848ad49921c3"},
+ {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f5dd81c45b05518b9aa4da4aa74e1c93d715efa234fd3e8a179df611cc85e5f4"},
+ {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:eb304767bca2bb92fb9c5bd33cedc95baee5bb5f6c88e63706533a1c06ad08c8"},
+ {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c9035dde0f916702850ef66460bc4239d89d08df4d02023a5926e7446724212c"},
+ {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:af959b9beeb66c822380f222f0e0a1889331597e81f1ded7f374f3ecb0fd6c52"},
+ {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:41f2952231456154ee479651491e94118229844dd7226541788be783be2b5108"},
+ {file = "multidict-6.7.1-cp313-cp313t-win32.whl", hash = "sha256:df9f19c28adcb40b6aae30bbaa1478c389efd50c28d541d76760199fc1037c32"},
+ {file = "multidict-6.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:d54ecf9f301853f2c5e802da559604b3e95bb7a3b01a9c295c6ee591b9882de8"},
+ {file = "multidict-6.7.1-cp313-cp313t-win_arm64.whl", hash = "sha256:5a37ca18e360377cfda1d62f5f382ff41f2b8c4ccb329ed974cc2e1643440118"},
+ {file = "multidict-6.7.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8f333ec9c5eb1b7105e3b84b53141e66ca05a19a605368c55450b6ba208cb9ee"},
+ {file = "multidict-6.7.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a407f13c188f804c759fc6a9f88286a565c242a76b27626594c133b82883b5c2"},
+ {file = "multidict-6.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0e161ddf326db5577c3a4cc2d8648f81456e8a20d40415541587a71620d7a7d1"},
+ {file = "multidict-6.7.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1e3a8bb24342a8201d178c3b4984c26ba81a577c80d4d525727427460a50c22d"},
+ {file = "multidict-6.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97231140a50f5d447d3164f994b86a0bed7cd016e2682f8650d6a9158e14fd31"},
+ {file = "multidict-6.7.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6b10359683bd8806a200fd2909e7c8ca3a7b24ec1d8132e483d58e791d881048"},
+ {file = "multidict-6.7.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:283ddac99f7ac25a4acadbf004cb5ae34480bbeb063520f70ce397b281859362"},
+ {file = "multidict-6.7.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:538cec1e18c067d0e6103aa9a74f9e832904c957adc260e61cd9d8cf0c3b3d37"},
+ {file = "multidict-6.7.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7eee46ccb30ff48a1e35bb818cc90846c6be2b68240e42a78599166722cea709"},
+ {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa263a02f4f2dd2d11a7b1bb4362aa7cb1049f84a9235d31adf63f30143469a0"},
+ {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:2e1425e2f99ec5bd36c15a01b690a1a2456209c5deed58f95469ffb46039ccbb"},
+ {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:497394b3239fc6f0e13a78a3e1b61296e72bf1c5f94b4c4eb80b265c37a131cd"},
+ {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:233b398c29d3f1b9676b4b6f75c518a06fcb2ea0b925119fb2c1bc35c05e1601"},
+ {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:93b1818e4a6e0930454f0f2af7dfce69307ca03cdcfb3739bf4d91241967b6c1"},
+ {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f33dc2a3abe9249ea5d8360f969ec7f4142e7ac45ee7014d8f8d5acddf178b7b"},
+ {file = "multidict-6.7.1-cp314-cp314-win32.whl", hash = "sha256:3ab8b9d8b75aef9df299595d5388b14530839f6422333357af1339443cff777d"},
+ {file = "multidict-6.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:5e01429a929600e7dab7b166062d9bb54a5eed752384c7384c968c2afab8f50f"},
+ {file = "multidict-6.7.1-cp314-cp314-win_arm64.whl", hash = "sha256:4885cb0e817aef5d00a2e8451d4665c1808378dc27c2705f1bf4ef8505c0d2e5"},
+ {file = "multidict-6.7.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0458c978acd8e6ea53c81eefaddbbee9c6c5e591f41b3f5e8e194780fe026581"},
+ {file = "multidict-6.7.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c0abd12629b0af3cf590982c0b413b1e7395cd4ec026f30986818ab95bfaa94a"},
+ {file = "multidict-6.7.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:14525a5f61d7d0c94b368a42cff4c9a4e7ba2d52e2672a7b23d84dc86fb02b0c"},
+ {file = "multidict-6.7.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:17307b22c217b4cf05033dabefe68255a534d637c6c9b0cc8382718f87be4262"},
+ {file = "multidict-6.7.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a7e590ff876a3eaf1c02a4dfe0724b6e69a9e9de6d8f556816f29c496046e59"},
+ {file = "multidict-6.7.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5fa6a95dfee63893d80a34758cd0e0c118a30b8dcb46372bf75106c591b77889"},
+ {file = "multidict-6.7.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a0543217a6a017692aa6ae5cc39adb75e587af0f3a82288b1492eb73dd6cc2a4"},
+ {file = "multidict-6.7.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f99fe611c312b3c1c0ace793f92464d8cd263cc3b26b5721950d977b006b6c4d"},
+ {file = "multidict-6.7.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9004d8386d133b7e6135679424c91b0b854d2d164af6ea3f289f8f2761064609"},
+ {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e628ef0e6859ffd8273c69412a2465c4be4a9517d07261b33334b5ec6f3c7489"},
+ {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:841189848ba629c3552035a6a7f5bf3b02eb304e9fea7492ca220a8eda6b0e5c"},
+ {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce1bbd7d780bb5a0da032e095c951f7014d6b0a205f8318308140f1a6aba159e"},
+ {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b26684587228afed0d50cf804cc71062cc9c1cdf55051c4c6345d372947b268c"},
+ {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9f9af11306994335398293f9958071019e3ab95e9a707dc1383a35613f6abcb9"},
+ {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b4938326284c4f1224178a560987b6cf8b4d38458b113d9b8c1db1a836e640a2"},
+ {file = "multidict-6.7.1-cp314-cp314t-win32.whl", hash = "sha256:98655c737850c064a65e006a3df7c997cd3b220be4ec8fe26215760b9697d4d7"},
+ {file = "multidict-6.7.1-cp314-cp314t-win_amd64.whl", hash = "sha256:497bde6223c212ba11d462853cfa4f0ae6ef97465033e7dc9940cdb3ab5b48e5"},
+ {file = "multidict-6.7.1-cp314-cp314t-win_arm64.whl", hash = "sha256:2bbd113e0d4af5db41d5ebfe9ccaff89de2120578164f86a5d17d5a576d1e5b2"},
+ {file = "multidict-6.7.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:65573858d27cdeaca41893185677dc82395159aa28875a8867af66532d413a8f"},
+ {file = "multidict-6.7.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c524c6fb8fc342793708ab111c4dbc90ff9abd568de220432500e47e990c0358"},
+ {file = "multidict-6.7.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:aa23b001d968faef416ff70dc0f1ab045517b9b42a90edd3e9bcdb06479e31d5"},
+ {file = "multidict-6.7.1-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6704fa2b7453b2fb121740555fa1ee20cd98c4d011120caf4d2b8d4e7c76eec0"},
+ {file = "multidict-6.7.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:121a34e5bfa410cdf2c8c49716de160de3b1dbcd86b49656f5681e4543bcd1a8"},
+ {file = "multidict-6.7.1-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:026d264228bcd637d4e060844e39cdc60f86c479e463d49075dedc21b18fbbe0"},
+ {file = "multidict-6.7.1-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0e697826df7eb63418ee190fd06ce9f1803593bb4b9517d08c60d9b9a7f69d8f"},
+ {file = "multidict-6.7.1-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bb08271280173720e9fea9ede98e5231defcbad90f1624bea26f32ec8a956e2f"},
+ {file = "multidict-6.7.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c6b3228e1d80af737b72925ce5fb4daf5a335e49cd7ab77ed7b9fdfbf58c526e"},
+ {file = "multidict-6.7.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3943debf0fbb57bdde5901695c11094a9a36723e5c03875f87718ee15ca2f4d2"},
+ {file = "multidict-6.7.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:98c5787b0a0d9a41d9311eae44c3b76e6753def8d8870ab501320efe75a6a5f8"},
+ {file = "multidict-6.7.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:08ccb2a6dc72009093ebe7f3f073e5ec5964cba9a706fa94b1a1484039b87941"},
+ {file = "multidict-6.7.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:eb351f72c26dc9abe338ca7294661aa22969ad8ffe7ef7d5541d19f368dc854a"},
+ {file = "multidict-6.7.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ac1c665bad8b5d762f5f85ebe4d94130c26965f11de70c708c75671297c776de"},
+ {file = "multidict-6.7.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1fa6609d0364f4f6f58351b4659a1f3e0e898ba2a8c5cac04cb2c7bc556b0bc5"},
+ {file = "multidict-6.7.1-cp39-cp39-win32.whl", hash = "sha256:6f77ce314a29263e67adadc7e7c1bc699fcb3a305059ab973d038f87caa42ed0"},
+ {file = "multidict-6.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:f537b55778cd3cbee430abe3131255d3a78202e0f9ea7ffc6ada893a4bcaeea4"},
+ {file = "multidict-6.7.1-cp39-cp39-win_arm64.whl", hash = "sha256:749aa54f578f2e5f439538706a475aa844bfa8ef75854b1401e6e528e4937cf9"},
+ {file = "multidict-6.7.1-py3-none-any.whl", hash = "sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56"},
+ {file = "multidict-6.7.1.tar.gz", hash = "sha256:ec6652a1bee61c53a3e5776b6049172c53b6aaba34f18c9ad04f82712bac623d"},
+]
+
+[package.dependencies]
+typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.11\""}
+
+[[package]]
+name = "mypy"
+version = "1.13.0"
+description = "Optional static typing for Python"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "mypy-1.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6607e0f1dd1fb7f0aca14d936d13fd19eba5e17e1cd2a14f808fa5f8f6d8f60a"},
+ {file = "mypy-1.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8a21be69bd26fa81b1f80a61ee7ab05b076c674d9b18fb56239d72e21d9f4c80"},
+ {file = "mypy-1.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b2353a44d2179846a096e25691d54d59904559f4232519d420d64da6828a3a7"},
+ {file = "mypy-1.13.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0730d1c6a2739d4511dc4253f8274cdd140c55c32dfb0a4cf8b7a43f40abfa6f"},
+ {file = "mypy-1.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:c5fc54dbb712ff5e5a0fca797e6e0aa25726c7e72c6a5850cfd2adbc1eb0a372"},
+ {file = "mypy-1.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:581665e6f3a8a9078f28d5502f4c334c0c8d802ef55ea0e7276a6e409bc0d82d"},
+ {file = "mypy-1.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3ddb5b9bf82e05cc9a627e84707b528e5c7caaa1c55c69e175abb15a761cec2d"},
+ {file = "mypy-1.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20c7ee0bc0d5a9595c46f38beb04201f2620065a93755704e141fcac9f59db2b"},
+ {file = "mypy-1.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3790ded76f0b34bc9c8ba4def8f919dd6a46db0f5a6610fb994fe8efdd447f73"},
+ {file = "mypy-1.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:51f869f4b6b538229c1d1bcc1dd7d119817206e2bc54e8e374b3dfa202defcca"},
+ {file = "mypy-1.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5c7051a3461ae84dfb5dd15eff5094640c61c5f22257c8b766794e6dd85e72d5"},
+ {file = "mypy-1.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39bb21c69a5d6342f4ce526e4584bc5c197fd20a60d14a8624d8743fffb9472e"},
+ {file = "mypy-1.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:164f28cb9d6367439031f4c81e84d3ccaa1e19232d9d05d37cb0bd880d3f93c2"},
+ {file = "mypy-1.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a4c1bfcdbce96ff5d96fc9b08e3831acb30dc44ab02671eca5953eadad07d6d0"},
+ {file = "mypy-1.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0affb3a79a256b4183ba09811e3577c5163ed06685e4d4b46429a271ba174d2"},
+ {file = "mypy-1.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a7b44178c9760ce1a43f544e595d35ed61ac2c3de306599fa59b38a6048e1aa7"},
+ {file = "mypy-1.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d5092efb8516d08440e36626f0153b5006d4088c1d663d88bf79625af3d1d62"},
+ {file = "mypy-1.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2904956dac40ced10931ac967ae63c5089bd498542194b436eb097a9f77bc8"},
+ {file = "mypy-1.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7bfd8836970d33c2105562650656b6846149374dc8ed77d98424b40b09340ba7"},
+ {file = "mypy-1.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:9f73dba9ec77acb86457a8fc04b5239822df0c14a082564737833d2963677dbc"},
+ {file = "mypy-1.13.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:100fac22ce82925f676a734af0db922ecfea991e1d7ec0ceb1e115ebe501301a"},
+ {file = "mypy-1.13.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7bcb0bb7f42a978bb323a7c88f1081d1b5dee77ca86f4100735a6f541299d8fb"},
+ {file = "mypy-1.13.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bde31fc887c213e223bbfc34328070996061b0833b0a4cfec53745ed61f3519b"},
+ {file = "mypy-1.13.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:07de989f89786f62b937851295ed62e51774722e5444a27cecca993fc3f9cd74"},
+ {file = "mypy-1.13.0-cp38-cp38-win_amd64.whl", hash = "sha256:4bde84334fbe19bad704b3f5b78c4abd35ff1026f8ba72b29de70dda0916beb6"},
+ {file = "mypy-1.13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0246bcb1b5de7f08f2826451abd947bf656945209b140d16ed317f65a17dc7dc"},
+ {file = "mypy-1.13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7f5b7deae912cf8b77e990b9280f170381fdfbddf61b4ef80927edd813163732"},
+ {file = "mypy-1.13.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7029881ec6ffb8bc233a4fa364736789582c738217b133f1b55967115288a2bc"},
+ {file = "mypy-1.13.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3e38b980e5681f28f033f3be86b099a247b13c491f14bb8b1e1e134d23bb599d"},
+ {file = "mypy-1.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:a6789be98a2017c912ae6ccb77ea553bbaf13d27605d2ca20a76dfbced631b24"},
+ {file = "mypy-1.13.0-py3-none-any.whl", hash = "sha256:9c250883f9fd81d212e0952c92dbfcc96fc237f4b7c92f56ac81fd48460b3e5a"},
+ {file = "mypy-1.13.0.tar.gz", hash = "sha256:0291a61b6fbf3e6673e3405cfcc0e7650bebc7939659fdca2702958038bd835e"},
+]
+
+[package.dependencies]
+mypy-extensions = ">=1.0.0"
+tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
+typing-extensions = ">=4.6.0"
+
+[package.extras]
+dmypy = ["psutil (>=4.0)"]
+faster-cache = ["orjson"]
+install-types = ["pip"]
+mypyc = ["setuptools (>=50)"]
+reports = ["lxml"]
+
+[[package]]
+name = "mypy-extensions"
+version = "1.1.0"
+description = "Type system extensions for programs checked with the mypy type checker."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"},
+ {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"},
+]
+
+[[package]]
+name = "packaging"
+version = "26.1"
+description = "Core utilities for Python packages"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "packaging-26.1-py3-none-any.whl", hash = "sha256:5d9c0669c6285e491e0ced2eee587eaf67b670d94a19e94e3984a481aba6802f"},
+ {file = "packaging-26.1.tar.gz", hash = "sha256:f042152b681c4bfac5cae2742a55e103d27ab2ec0f3d88037136b6bfe7c9c5de"},
+]
+
+[[package]]
+name = "pluggy"
+version = "1.6.0"
+description = "plugin and hook calling mechanisms for python"
+optional = false
+python-versions = ">=3.9"
+files = [
+ {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"},
+ {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"},
+]
+
+[package.extras]
+dev = ["pre-commit", "tox"]
+testing = ["coverage", "pytest", "pytest-benchmark"]
+
+[[package]]
+name = "propcache"
+version = "0.4.1"
+description = "Accelerated property cache"
+optional = true
+python-versions = ">=3.9"
+files = [
+ {file = "propcache-0.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c2d1fa3201efaf55d730400d945b5b3ab6e672e100ba0f9a409d950ab25d7db"},
+ {file = "propcache-0.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1eb2994229cc8ce7fe9b3db88f5465f5fd8651672840b2e426b88cdb1a30aac8"},
+ {file = "propcache-0.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:66c1f011f45a3b33d7bcb22daed4b29c0c9e2224758b6be00686731e1b46f925"},
+ {file = "propcache-0.4.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9a52009f2adffe195d0b605c25ec929d26b36ef986ba85244891dee3b294df21"},
+ {file = "propcache-0.4.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5d4e2366a9c7b837555cf02fb9be2e3167d333aff716332ef1b7c3a142ec40c5"},
+ {file = "propcache-0.4.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:9d2b6caef873b4f09e26ea7e33d65f42b944837563a47a94719cc3544319a0db"},
+ {file = "propcache-0.4.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b16ec437a8c8a965ecf95739448dd938b5c7f56e67ea009f4300d8df05f32b7"},
+ {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:296f4c8ed03ca7476813fe666c9ea97869a8d7aec972618671b33a38a5182ef4"},
+ {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:1f0978529a418ebd1f49dad413a2b68af33f85d5c5ca5c6ca2a3bed375a7ac60"},
+ {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fd138803047fb4c062b1c1dd95462f5209456bfab55c734458f15d11da288f8f"},
+ {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8c9b3cbe4584636d72ff556d9036e0c9317fa27b3ac1f0f558e7e84d1c9c5900"},
+ {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f93243fdc5657247533273ac4f86ae106cc6445a0efacb9a1bfe982fcfefd90c"},
+ {file = "propcache-0.4.1-cp310-cp310-win32.whl", hash = "sha256:a0ee98db9c5f80785b266eb805016e36058ac72c51a064040f2bc43b61101cdb"},
+ {file = "propcache-0.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:1cdb7988c4e5ac7f6d175a28a9aa0c94cb6f2ebe52756a3c0cda98d2809a9e37"},
+ {file = "propcache-0.4.1-cp310-cp310-win_arm64.whl", hash = "sha256:d82ad62b19645419fe79dd63b3f9253e15b30e955c0170e5cebc350c1844e581"},
+ {file = "propcache-0.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:60a8fda9644b7dfd5dece8c61d8a85e271cb958075bfc4e01083c148b61a7caf"},
+ {file = "propcache-0.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c30b53e7e6bda1d547cabb47c825f3843a0a1a42b0496087bb58d8fedf9f41b5"},
+ {file = "propcache-0.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6918ecbd897443087a3b7cd978d56546a812517dcaaca51b49526720571fa93e"},
+ {file = "propcache-0.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3d902a36df4e5989763425a8ab9e98cd8ad5c52c823b34ee7ef307fd50582566"},
+ {file = "propcache-0.4.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a9695397f85973bb40427dedddf70d8dc4a44b22f1650dd4af9eedf443d45165"},
+ {file = "propcache-0.4.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2bb07ffd7eaad486576430c89f9b215f9e4be68c4866a96e97db9e97fead85dc"},
+ {file = "propcache-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd6f30fdcf9ae2a70abd34da54f18da086160e4d7d9251f81f3da0ff84fc5a48"},
+ {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fc38cba02d1acba4e2869eef1a57a43dfbd3d49a59bf90dda7444ec2be6a5570"},
+ {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:67fad6162281e80e882fb3ec355398cf72864a54069d060321f6cd0ade95fe85"},
+ {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f10207adf04d08bec185bae14d9606a1444715bc99180f9331c9c02093e1959e"},
+ {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e9b0d8d0845bbc4cfcdcbcdbf5086886bc8157aa963c31c777ceff7846c77757"},
+ {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:981333cb2f4c1896a12f4ab92a9cc8f09ea664e9b7dbdc4eff74627af3a11c0f"},
+ {file = "propcache-0.4.1-cp311-cp311-win32.whl", hash = "sha256:f1d2f90aeec838a52f1c1a32fe9a619fefd5e411721a9117fbf82aea638fe8a1"},
+ {file = "propcache-0.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:364426a62660f3f699949ac8c621aad6977be7126c5807ce48c0aeb8e7333ea6"},
+ {file = "propcache-0.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:e53f3a38d3510c11953f3e6a33f205c6d1b001129f972805ca9b42fc308bc239"},
+ {file = "propcache-0.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e153e9cd40cc8945138822807139367f256f89c6810c2634a4f6902b52d3b4e2"},
+ {file = "propcache-0.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cd547953428f7abb73c5ad82cbb32109566204260d98e41e5dfdc682eb7f8403"},
+ {file = "propcache-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f048da1b4f243fc44f205dfd320933a951b8d89e0afd4c7cacc762a8b9165207"},
+ {file = "propcache-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ec17c65562a827bba85e3872ead335f95405ea1674860d96483a02f5c698fa72"},
+ {file = "propcache-0.4.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:405aac25c6394ef275dee4c709be43745d36674b223ba4eb7144bf4d691b7367"},
+ {file = "propcache-0.4.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0013cb6f8dde4b2a2f66903b8ba740bdfe378c943c4377a200551ceb27f379e4"},
+ {file = "propcache-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15932ab57837c3368b024473a525e25d316d8353016e7cc0e5ba9eb343fbb1cf"},
+ {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:031dce78b9dc099f4c29785d9cf5577a3faf9ebf74ecbd3c856a7b92768c3df3"},
+ {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ab08df6c9a035bee56e31af99be621526bd237bea9f32def431c656b29e41778"},
+ {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4d7af63f9f93fe593afbf104c21b3b15868efb2c21d07d8732c0c4287e66b6a6"},
+ {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cfc27c945f422e8b5071b6e93169679e4eb5bf73bbcbf1ba3ae3a83d2f78ebd9"},
+ {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:35c3277624a080cc6ec6f847cbbbb5b49affa3598c4535a0a4682a697aaa5c75"},
+ {file = "propcache-0.4.1-cp312-cp312-win32.whl", hash = "sha256:671538c2262dadb5ba6395e26c1731e1d52534bfe9ae56d0b5573ce539266aa8"},
+ {file = "propcache-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:cb2d222e72399fcf5890d1d5cc1060857b9b236adff2792ff48ca2dfd46c81db"},
+ {file = "propcache-0.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:204483131fb222bdaaeeea9f9e6c6ed0cac32731f75dfc1d4a567fc1926477c1"},
+ {file = "propcache-0.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:43eedf29202c08550aac1d14e0ee619b0430aaef78f85864c1a892294fbc28cf"},
+ {file = "propcache-0.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d62cdfcfd89ccb8de04e0eda998535c406bf5e060ffd56be6c586cbcc05b3311"},
+ {file = "propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cae65ad55793da34db5f54e4029b89d3b9b9490d8abe1b4c7ab5d4b8ec7ebf74"},
+ {file = "propcache-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:333ddb9031d2704a301ee3e506dc46b1fe5f294ec198ed6435ad5b6a085facfe"},
+ {file = "propcache-0.4.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fd0858c20f078a32cf55f7e81473d96dcf3b93fd2ccdb3d40fdf54b8573df3af"},
+ {file = "propcache-0.4.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:678ae89ebc632c5c204c794f8dab2837c5f159aeb59e6ed0539500400577298c"},
+ {file = "propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d472aeb4fbf9865e0c6d622d7f4d54a4e101a89715d8904282bb5f9a2f476c3f"},
+ {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4d3df5fa7e36b3225954fba85589da77a0fe6a53e3976de39caf04a0db4c36f1"},
+ {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ee17f18d2498f2673e432faaa71698032b0127ebf23ae5974eeaf806c279df24"},
+ {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:580e97762b950f993ae618e167e7be9256b8353c2dcd8b99ec100eb50f5286aa"},
+ {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:501d20b891688eb8e7aa903021f0b72d5a55db40ffaab27edefd1027caaafa61"},
+ {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a0bd56e5b100aef69bd8562b74b46254e7c8812918d3baa700c8a8009b0af66"},
+ {file = "propcache-0.4.1-cp313-cp313-win32.whl", hash = "sha256:bcc9aaa5d80322bc2fb24bb7accb4a30f81e90ab8d6ba187aec0744bc302ad81"},
+ {file = "propcache-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:381914df18634f5494334d201e98245c0596067504b9372d8cf93f4bb23e025e"},
+ {file = "propcache-0.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:8873eb4460fd55333ea49b7d189749ecf6e55bf85080f11b1c4530ed3034cba1"},
+ {file = "propcache-0.4.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:92d1935ee1f8d7442da9c0c4fa7ac20d07e94064184811b685f5c4fada64553b"},
+ {file = "propcache-0.4.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:473c61b39e1460d386479b9b2f337da492042447c9b685f28be4f74d3529e566"},
+ {file = "propcache-0.4.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c0ef0aaafc66fbd87842a3fe3902fd889825646bc21149eafe47be6072725835"},
+ {file = "propcache-0.4.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95393b4d66bfae908c3ca8d169d5f79cd65636ae15b5e7a4f6e67af675adb0e"},
+ {file = "propcache-0.4.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c07fda85708bc48578467e85099645167a955ba093be0a2dcba962195676e859"},
+ {file = "propcache-0.4.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:af223b406d6d000830c6f65f1e6431783fc3f713ba3e6cc8c024d5ee96170a4b"},
+ {file = "propcache-0.4.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a78372c932c90ee474559c5ddfffd718238e8673c340dc21fe45c5b8b54559a0"},
+ {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:564d9f0d4d9509e1a870c920a89b2fec951b44bf5ba7d537a9e7c1ccec2c18af"},
+ {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:17612831fda0138059cc5546f4d12a2aacfb9e47068c06af35c400ba58ba7393"},
+ {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:41a89040cb10bd345b3c1a873b2bf36413d48da1def52f268a055f7398514874"},
+ {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e35b88984e7fa64aacecea39236cee32dd9bd8c55f57ba8a75cf2399553f9bd7"},
+ {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f8b465489f927b0df505cbe26ffbeed4d6d8a2bbc61ce90eb074ff129ef0ab1"},
+ {file = "propcache-0.4.1-cp313-cp313t-win32.whl", hash = "sha256:2ad890caa1d928c7c2965b48f3a3815c853180831d0e5503d35cf00c472f4717"},
+ {file = "propcache-0.4.1-cp313-cp313t-win_amd64.whl", hash = "sha256:f7ee0e597f495cf415bcbd3da3caa3bd7e816b74d0d52b8145954c5e6fd3ff37"},
+ {file = "propcache-0.4.1-cp313-cp313t-win_arm64.whl", hash = "sha256:929d7cbe1f01bb7baffb33dc14eb5691c95831450a26354cd210a8155170c93a"},
+ {file = "propcache-0.4.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3f7124c9d820ba5548d431afb4632301acf965db49e666aa21c305cbe8c6de12"},
+ {file = "propcache-0.4.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c0d4b719b7da33599dfe3b22d3db1ef789210a0597bc650b7cee9c77c2be8c5c"},
+ {file = "propcache-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9f302f4783709a78240ebc311b793f123328716a60911d667e0c036bc5dcbded"},
+ {file = "propcache-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c80ee5802e3fb9ea37938e7eecc307fb984837091d5fd262bb37238b1ae97641"},
+ {file = "propcache-0.4.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ed5a841e8bb29a55fb8159ed526b26adc5bdd7e8bd7bf793ce647cb08656cdf4"},
+ {file = "propcache-0.4.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:55c72fd6ea2da4c318e74ffdf93c4fe4e926051133657459131a95c846d16d44"},
+ {file = "propcache-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8326e144341460402713f91df60ade3c999d601e7eb5ff8f6f7862d54de0610d"},
+ {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:060b16ae65bc098da7f6d25bf359f1f31f688384858204fe5d652979e0015e5b"},
+ {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:89eb3fa9524f7bec9de6e83cf3faed9d79bffa560672c118a96a171a6f55831e"},
+ {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:dee69d7015dc235f526fe80a9c90d65eb0039103fe565776250881731f06349f"},
+ {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5558992a00dfd54ccbc64a32726a3357ec93825a418a401f5cc67df0ac5d9e49"},
+ {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c9b822a577f560fbd9554812526831712c1436d2c046cedee4c3796d3543b144"},
+ {file = "propcache-0.4.1-cp314-cp314-win32.whl", hash = "sha256:ab4c29b49d560fe48b696cdcb127dd36e0bc2472548f3bf56cc5cb3da2b2984f"},
+ {file = "propcache-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:5a103c3eb905fcea0ab98be99c3a9a5ab2de60228aa5aceedc614c0281cf6153"},
+ {file = "propcache-0.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:74c1fb26515153e482e00177a1ad654721bf9207da8a494a0c05e797ad27b992"},
+ {file = "propcache-0.4.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:824e908bce90fb2743bd6b59db36eb4f45cd350a39637c9f73b1c1ea66f5b75f"},
+ {file = "propcache-0.4.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2b5e7db5328427c57c8e8831abda175421b709672f6cfc3d630c3b7e2146393"},
+ {file = "propcache-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6f6ff873ed40292cd4969ef5310179afd5db59fdf055897e282485043fc80ad0"},
+ {file = "propcache-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49a2dc67c154db2c1463013594c458881a069fcf98940e61a0569016a583020a"},
+ {file = "propcache-0.4.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:005f08e6a0529984491e37d8dbc3dd86f84bd78a8ceb5fa9a021f4c48d4984be"},
+ {file = "propcache-0.4.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5c3310452e0d31390da9035c348633b43d7e7feb2e37be252be6da45abd1abcc"},
+ {file = "propcache-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c3c70630930447f9ef1caac7728c8ad1c56bc5015338b20fed0d08ea2480b3a"},
+ {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8e57061305815dfc910a3634dcf584f08168a8836e6999983569f51a8544cd89"},
+ {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:521a463429ef54143092c11a77e04056dd00636f72e8c45b70aaa3140d639726"},
+ {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:120c964da3fdc75e3731aa392527136d4ad35868cc556fd09bb6d09172d9a367"},
+ {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:d8f353eb14ee3441ee844ade4277d560cdd68288838673273b978e3d6d2c8f36"},
+ {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ab2943be7c652f09638800905ee1bab2c544e537edb57d527997a24c13dc1455"},
+ {file = "propcache-0.4.1-cp314-cp314t-win32.whl", hash = "sha256:05674a162469f31358c30bcaa8883cb7829fa3110bf9c0991fe27d7896c42d85"},
+ {file = "propcache-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:990f6b3e2a27d683cb7602ed6c86f15ee6b43b1194736f9baaeb93d0016633b1"},
+ {file = "propcache-0.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ecef2343af4cc68e05131e45024ba34f6095821988a9d0a02aa7c73fcc448aa9"},
+ {file = "propcache-0.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3d233076ccf9e450c8b3bc6720af226b898ef5d051a2d145f7d765e6e9f9bcff"},
+ {file = "propcache-0.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:357f5bb5c377a82e105e44bd3d52ba22b616f7b9773714bff93573988ef0a5fb"},
+ {file = "propcache-0.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cbc3b6dfc728105b2a57c06791eb07a94229202ea75c59db644d7d496b698cac"},
+ {file = "propcache-0.4.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:182b51b421f0501952d938dc0b0eb45246a5b5153c50d42b495ad5fb7517c888"},
+ {file = "propcache-0.4.1-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4b536b39c5199b96fc6245eb5fb796c497381d3942f169e44e8e392b29c9ebcc"},
+ {file = "propcache-0.4.1-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:db65d2af507bbfbdcedb254a11149f894169d90488dd3e7190f7cdcb2d6cd57a"},
+ {file = "propcache-0.4.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd2dbc472da1f772a4dae4fa24be938a6c544671a912e30529984dd80400cd88"},
+ {file = "propcache-0.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:daede9cd44e0f8bdd9e6cc9a607fc81feb80fae7a5fc6cecaff0e0bb32e42d00"},
+ {file = "propcache-0.4.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:71b749281b816793678ae7f3d0d84bd36e694953822eaad408d682efc5ca18e0"},
+ {file = "propcache-0.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:0002004213ee1f36cfb3f9a42b5066100c44276b9b72b4e1504cddd3d692e86e"},
+ {file = "propcache-0.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:fe49d0a85038f36ba9e3ffafa1103e61170b28e95b16622e11be0a0ea07c6781"},
+ {file = "propcache-0.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:99d43339c83aaf4d32bda60928231848eee470c6bda8d02599cc4cebe872d183"},
+ {file = "propcache-0.4.1-cp39-cp39-win32.whl", hash = "sha256:a129e76735bc792794d5177069691c3217898b9f5cee2b2661471e52ffe13f19"},
+ {file = "propcache-0.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:948dab269721ae9a87fd16c514a0a2c2a1bdb23a9a61b969b0f9d9ee2968546f"},
+ {file = "propcache-0.4.1-cp39-cp39-win_arm64.whl", hash = "sha256:5fd37c406dd6dc85aa743e214cef35dc54bbdd1419baac4f6ae5e5b1a2976938"},
+ {file = "propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237"},
+ {file = "propcache-0.4.1.tar.gz", hash = "sha256:f48107a8c637e80362555f37ecf49abe20370e557cc4ab374f04ec4423c97c3d"},
+]
+
+[[package]]
+name = "pydantic"
+version = "2.12.5"
+description = "Data validation using Python type hints"
+optional = false
+python-versions = ">=3.9"
+files = [
+ {file = "pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d"},
+ {file = "pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49"},
+]
+
+[package.dependencies]
+annotated-types = ">=0.6.0"
+pydantic-core = "2.41.5"
+typing-extensions = ">=4.14.1"
+typing-inspection = ">=0.4.2"
+
+[package.extras]
+email = ["email-validator (>=2.0.0)"]
+timezone = ["tzdata"]
+
+[[package]]
+name = "pydantic-core"
+version = "2.41.5"
+description = "Core functionality for Pydantic validation and serialization"
+optional = false
+python-versions = ">=3.9"
+files = [
+ {file = "pydantic_core-2.41.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77b63866ca88d804225eaa4af3e664c5faf3568cea95360d21f4725ab6e07146"},
+ {file = "pydantic_core-2.41.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dfa8a0c812ac681395907e71e1274819dec685fec28273a28905df579ef137e2"},
+ {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5921a4d3ca3aee735d9fd163808f5e8dd6c6972101e4adbda9a4667908849b97"},
+ {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25c479382d26a2a41b7ebea1043564a937db462816ea07afa8a44c0866d52f9"},
+ {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f547144f2966e1e16ae626d8ce72b4cfa0caedc7fa28052001c94fb2fcaa1c52"},
+ {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f52298fbd394f9ed112d56f3d11aabd0d5bd27beb3084cc3d8ad069483b8941"},
+ {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:100baa204bb412b74fe285fb0f3a385256dad1d1879f0a5cb1499ed2e83d132a"},
+ {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05a2c8852530ad2812cb7914dc61a1125dc4e06252ee98e5638a12da6cc6fb6c"},
+ {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:29452c56df2ed968d18d7e21f4ab0ac55e71dc59524872f6fc57dcf4a3249ed2"},
+ {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:d5160812ea7a8a2ffbe233d8da666880cad0cbaf5d4de74ae15c313213d62556"},
+ {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:df3959765b553b9440adfd3c795617c352154e497a4eaf3752555cfb5da8fc49"},
+ {file = "pydantic_core-2.41.5-cp310-cp310-win32.whl", hash = "sha256:1f8d33a7f4d5a7889e60dc39856d76d09333d8a6ed0f5f1190635cbec70ec4ba"},
+ {file = "pydantic_core-2.41.5-cp310-cp310-win_amd64.whl", hash = "sha256:62de39db01b8d593e45871af2af9e497295db8d73b085f6bfd0b18c83c70a8f9"},
+ {file = "pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6"},
+ {file = "pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b"},
+ {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a"},
+ {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8"},
+ {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e"},
+ {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1"},
+ {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b"},
+ {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b"},
+ {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284"},
+ {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594"},
+ {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e"},
+ {file = "pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b"},
+ {file = "pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe"},
+ {file = "pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f"},
+ {file = "pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7"},
+ {file = "pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0"},
+ {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69"},
+ {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75"},
+ {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05"},
+ {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc"},
+ {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c"},
+ {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5"},
+ {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c"},
+ {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294"},
+ {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1"},
+ {file = "pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d"},
+ {file = "pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815"},
+ {file = "pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3"},
+ {file = "pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9"},
+ {file = "pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34"},
+ {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0"},
+ {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33"},
+ {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e"},
+ {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2"},
+ {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586"},
+ {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d"},
+ {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740"},
+ {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e"},
+ {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858"},
+ {file = "pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36"},
+ {file = "pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11"},
+ {file = "pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd"},
+ {file = "pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a"},
+ {file = "pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14"},
+ {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1"},
+ {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66"},
+ {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869"},
+ {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2"},
+ {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375"},
+ {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553"},
+ {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90"},
+ {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07"},
+ {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb"},
+ {file = "pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23"},
+ {file = "pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf"},
+ {file = "pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0"},
+ {file = "pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a"},
+ {file = "pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3"},
+ {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c"},
+ {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612"},
+ {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d"},
+ {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9"},
+ {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660"},
+ {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9"},
+ {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3"},
+ {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf"},
+ {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470"},
+ {file = "pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa"},
+ {file = "pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c"},
+ {file = "pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008"},
+ {file = "pydantic_core-2.41.5-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:8bfeaf8735be79f225f3fefab7f941c712aaca36f1128c9d7e2352ee1aa87bdf"},
+ {file = "pydantic_core-2.41.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:346285d28e4c8017da95144c7f3acd42740d637ff41946af5ce6e5e420502dd5"},
+ {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a75dafbf87d6276ddc5b2bf6fae5254e3d0876b626eb24969a574fff9149ee5d"},
+ {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7b93a4d08587e2b7e7882de461e82b6ed76d9026ce91ca7915e740ecc7855f60"},
+ {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e8465ab91a4bd96d36dde3263f06caa6a8a6019e4113f24dc753d79a8b3a3f82"},
+ {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:299e0a22e7ae2b85c1a57f104538b2656e8ab1873511fd718a1c1c6f149b77b5"},
+ {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:707625ef0983fcfb461acfaf14de2067c5942c6bb0f3b4c99158bed6fedd3cf3"},
+ {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f41eb9797986d6ebac5e8edff36d5cef9de40def462311b3eb3eeded1431e425"},
+ {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0384e2e1021894b1ff5a786dbf94771e2986ebe2869533874d7e43bc79c6f504"},
+ {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:f0cd744688278965817fd0839c4a4116add48d23890d468bc436f78beb28abf5"},
+ {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:753e230374206729bf0a807954bcc6c150d3743928a73faffee51ac6557a03c3"},
+ {file = "pydantic_core-2.41.5-cp39-cp39-win32.whl", hash = "sha256:873e0d5b4fb9b89ef7c2d2a963ea7d02879d9da0da8d9d4933dee8ee86a8b460"},
+ {file = "pydantic_core-2.41.5-cp39-cp39-win_amd64.whl", hash = "sha256:e4f4a984405e91527a0d62649ee21138f8e3d0ef103be488c1dc11a80d7f184b"},
+ {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034"},
+ {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c"},
+ {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2"},
+ {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad"},
+ {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd"},
+ {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc"},
+ {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56"},
+ {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b"},
+ {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b5819cd790dbf0c5eb9f82c73c16b39a65dd6dd4d1439dcdea7816ec9adddab8"},
+ {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5a4e67afbc95fa5c34cf27d9089bca7fcab4e51e57278d710320a70b956d1b9a"},
+ {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ece5c59f0ce7d001e017643d8d24da587ea1f74f6993467d85ae8a5ef9d4f42b"},
+ {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16f80f7abe3351f8ea6858914ddc8c77e02578544a0ebc15b4c2e1a0e813b0b2"},
+ {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:33cb885e759a705b426baada1fe68cbb0a2e68e34c5d0d0289a364cf01709093"},
+ {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:c8d8b4eb992936023be7dee581270af5c6e0697a8559895f527f5b7105ecd36a"},
+ {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:242a206cd0318f95cd21bdacff3fcc3aab23e79bba5cac3db5a841c9ef9c6963"},
+ {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d3a978c4f57a597908b7e697229d996d77a6d3c94901e9edee593adada95ce1a"},
+ {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26"},
+ {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808"},
+ {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc"},
+ {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1"},
+ {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84"},
+ {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770"},
+ {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f"},
+ {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51"},
+ {file = "pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e"},
+]
+
+[package.dependencies]
+typing-extensions = ">=4.14.1"
+
+[[package]]
+name = "pygments"
+version = "2.20.0"
+description = "Pygments is a syntax highlighting package written in Python."
+optional = false
+python-versions = ">=3.9"
+files = [
+ {file = "pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176"},
+ {file = "pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f"},
+]
+
+[package.extras]
+windows-terminal = ["colorama (>=0.4.6)"]
+
+[[package]]
+name = "pytest"
+version = "8.4.2"
+description = "pytest: simple powerful testing with Python"
+optional = false
+python-versions = ">=3.9"
+files = [
+ {file = "pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79"},
+ {file = "pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01"},
+]
+
+[package.dependencies]
+colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""}
+exceptiongroup = {version = ">=1", markers = "python_version < \"3.11\""}
+iniconfig = ">=1"
+packaging = ">=20"
+pluggy = ">=1.5,<2"
+pygments = ">=2.7.2"
+tomli = {version = ">=1", markers = "python_version < \"3.11\""}
+
+[package.extras]
+dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"]
+
+[[package]]
+name = "pytest-asyncio"
+version = "1.3.0"
+description = "Pytest support for asyncio"
+optional = false
+python-versions = ">=3.10"
+files = [
+ {file = "pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5"},
+ {file = "pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5"},
+]
+
+[package.dependencies]
+backports-asyncio-runner = {version = ">=1.1,<2", markers = "python_version < \"3.11\""}
+pytest = ">=8.2,<10"
+typing-extensions = {version = ">=4.12", markers = "python_version < \"3.13\""}
+
+[package.extras]
+docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1)"]
+testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"]
+
+[[package]]
+name = "pytest-xdist"
+version = "3.8.0"
+description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs"
+optional = false
+python-versions = ">=3.9"
+files = [
+ {file = "pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88"},
+ {file = "pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1"},
+]
+
+[package.dependencies]
+execnet = ">=2.1"
+pytest = ">=7.0.0"
+
+[package.extras]
+psutil = ["psutil (>=3.0)"]
+setproctitle = ["setproctitle"]
+testing = ["filelock"]
+
+[[package]]
+name = "python-dateutil"
+version = "2.9.0.post0"
+description = "Extensions to the standard Python datetime module"
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
+files = [
+ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"},
+ {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"},
+]
+
+[package.dependencies]
+six = ">=1.5"
+
+[[package]]
+name = "requests"
+version = "2.33.1"
+description = "Python HTTP for Humans."
+optional = false
+python-versions = ">=3.10"
+files = [
+ {file = "requests-2.33.1-py3-none-any.whl", hash = "sha256:4e6d1ef462f3626a1f0a0a9c42dd93c63bad33f9f1c1937509b8c5c8718ab56a"},
+ {file = "requests-2.33.1.tar.gz", hash = "sha256:18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517"},
+]
+
+[package.dependencies]
+certifi = ">=2023.5.7"
+charset_normalizer = ">=2,<4"
+idna = ">=2.5,<4"
+urllib3 = ">=1.26,<3"
+
+[package.extras]
+socks = ["PySocks (>=1.5.6,!=1.5.7)"]
+use-chardet-on-py3 = ["chardet (>=3.0.2,<8)"]
+
+[[package]]
+name = "ruff"
+version = "0.11.5"
+description = "An extremely fast Python linter and code formatter, written in Rust."
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "ruff-0.11.5-py3-none-linux_armv6l.whl", hash = "sha256:2561294e108eb648e50f210671cc56aee590fb6167b594144401532138c66c7b"},
+ {file = "ruff-0.11.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ac12884b9e005c12d0bd121f56ccf8033e1614f736f766c118ad60780882a077"},
+ {file = "ruff-0.11.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:4bfd80a6ec559a5eeb96c33f832418bf0fb96752de0539905cf7b0cc1d31d779"},
+ {file = "ruff-0.11.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0947c0a1afa75dcb5db4b34b070ec2bccee869d40e6cc8ab25aca11a7d527794"},
+ {file = "ruff-0.11.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ad871ff74b5ec9caa66cb725b85d4ef89b53f8170f47c3406e32ef040400b038"},
+ {file = "ruff-0.11.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e6cf918390cfe46d240732d4d72fa6e18e528ca1f60e318a10835cf2fa3dc19f"},
+ {file = "ruff-0.11.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:56145ee1478582f61c08f21076dc59153310d606ad663acc00ea3ab5b2125f82"},
+ {file = "ruff-0.11.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e5f66f8f1e8c9fc594cbd66fbc5f246a8d91f916cb9667e80208663ec3728304"},
+ {file = "ruff-0.11.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80b4df4d335a80315ab9afc81ed1cff62be112bd165e162b5eed8ac55bfc8470"},
+ {file = "ruff-0.11.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3068befab73620b8a0cc2431bd46b3cd619bc17d6f7695a3e1bb166b652c382a"},
+ {file = "ruff-0.11.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f5da2e710a9641828e09aa98b92c9ebbc60518fdf3921241326ca3e8f8e55b8b"},
+ {file = "ruff-0.11.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ef39f19cb8ec98cbc762344921e216f3857a06c47412030374fffd413fb8fd3a"},
+ {file = "ruff-0.11.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:b2a7cedf47244f431fd11aa5a7e2806dda2e0c365873bda7834e8f7d785ae159"},
+ {file = "ruff-0.11.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:81be52e7519f3d1a0beadcf8e974715b2dfc808ae8ec729ecfc79bddf8dbb783"},
+ {file = "ruff-0.11.5-py3-none-win32.whl", hash = "sha256:e268da7b40f56e3eca571508a7e567e794f9bfcc0f412c4b607931d3af9c4afe"},
+ {file = "ruff-0.11.5-py3-none-win_amd64.whl", hash = "sha256:6c6dc38af3cfe2863213ea25b6dc616d679205732dc0fb673356c2d69608f800"},
+ {file = "ruff-0.11.5-py3-none-win_arm64.whl", hash = "sha256:67e241b4314f4eacf14a601d586026a962f4002a475aa702c69980a38087aa4e"},
+ {file = "ruff-0.11.5.tar.gz", hash = "sha256:cae2e2439cb88853e421901ec040a758960b576126dab520fa08e9de431d1bef"},
+]
+
+[[package]]
+name = "six"
+version = "1.17.0"
+description = "Python 2 and 3 compatibility utilities"
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
+files = [
+ {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"},
+ {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"},
+]
+
+[[package]]
+name = "tomli"
+version = "2.4.1"
+description = "A lil' TOML parser"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30"},
+ {file = "tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a"},
+ {file = "tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076"},
+ {file = "tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9"},
+ {file = "tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c"},
+ {file = "tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc"},
+ {file = "tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049"},
+ {file = "tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e"},
+ {file = "tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece"},
+ {file = "tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a"},
+ {file = "tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085"},
+ {file = "tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9"},
+ {file = "tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5"},
+ {file = "tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585"},
+ {file = "tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1"},
+ {file = "tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917"},
+ {file = "tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9"},
+ {file = "tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257"},
+ {file = "tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54"},
+ {file = "tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a"},
+ {file = "tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897"},
+ {file = "tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f"},
+ {file = "tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d"},
+ {file = "tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5"},
+ {file = "tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd"},
+ {file = "tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36"},
+ {file = "tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd"},
+ {file = "tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf"},
+ {file = "tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac"},
+ {file = "tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662"},
+ {file = "tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853"},
+ {file = "tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15"},
+ {file = "tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba"},
+ {file = "tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6"},
+ {file = "tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7"},
+ {file = "tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232"},
+ {file = "tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4"},
+ {file = "tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c"},
+ {file = "tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d"},
+ {file = "tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41"},
+ {file = "tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c"},
+ {file = "tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f"},
+ {file = "tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8"},
+ {file = "tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26"},
+ {file = "tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396"},
+ {file = "tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe"},
+ {file = "tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f"},
+]
+
+[[package]]
+name = "types-python-dateutil"
+version = "2.9.0.20260408"
+description = "Typing stubs for python-dateutil"
+optional = false
+python-versions = ">=3.10"
+files = [
+ {file = "types_python_dateutil-2.9.0.20260408-py3-none-any.whl", hash = "sha256:473139d514a71c9d1fbd8bb328974bedcb1cc3dba57aad04ffa4157f483c216f"},
+ {file = "types_python_dateutil-2.9.0.20260408.tar.gz", hash = "sha256:8b056ec01568674235f64ecbcef928972a5fac412f5aab09c516dfa2acfbb582"},
+]
+
+[[package]]
+name = "types-requests"
+version = "2.33.0.20260408"
+description = "Typing stubs for requests"
+optional = false
+python-versions = ">=3.10"
+files = [
+ {file = "types_requests-2.33.0.20260408-py3-none-any.whl", hash = "sha256:81f31d5ea4acb39f03be7bc8bed569ba6d5a9c5d97e89f45ac43d819b68ca50f"},
+ {file = "types_requests-2.33.0.20260408.tar.gz", hash = "sha256:95b9a86376807a216b2fb412b47617b202091c3ea7c078f47cc358d5528ccb7b"},
+]
+
+[package.dependencies]
+urllib3 = ">=2"
+
+[[package]]
+name = "typing-extensions"
+version = "4.15.0"
+description = "Backported and Experimental Type Hints for Python 3.9+"
+optional = false
+python-versions = ">=3.9"
+files = [
+ {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"},
+ {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"},
+]
+
+[[package]]
+name = "typing-inspection"
+version = "0.4.2"
+description = "Runtime typing introspection tools"
+optional = false
+python-versions = ">=3.9"
+files = [
+ {file = "typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7"},
+ {file = "typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464"},
+]
+
+[package.dependencies]
+typing-extensions = ">=4.12.0"
+
+[[package]]
+name = "urllib3"
+version = "2.6.3"
+description = "HTTP library with thread-safe connection pooling, file post, and more."
+optional = false
+python-versions = ">=3.9"
+files = [
+ {file = "urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4"},
+ {file = "urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed"},
+]
+
+[package.extras]
+brotli = ["brotli (>=1.2.0)", "brotlicffi (>=1.2.0.0)"]
+h2 = ["h2 (>=4,<5)"]
+socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
+zstd = ["backports-zstd (>=1.0.0)"]
+
+[[package]]
+name = "yarl"
+version = "1.23.0"
+description = "Yet another URL library"
+optional = true
+python-versions = ">=3.10"
+files = [
+ {file = "yarl-1.23.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cff6d44cb13d39db2663a22b22305d10855efa0fa8015ddeacc40bc59b9d8107"},
+ {file = "yarl-1.23.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e4c53f8347cd4200f0d70a48ad059cabaf24f5adc6ba08622a23423bc7efa10d"},
+ {file = "yarl-1.23.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a6940a074fb3c48356ed0158a3ca5699c955ee4185b4d7d619be3c327143e05"},
+ {file = "yarl-1.23.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ed5f69ce7be7902e5c70ea19eb72d20abf7d725ab5d49777d696e32d4fc1811d"},
+ {file = "yarl-1.23.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:389871e65468400d6283c0308e791a640b5ab5c83bcee02a2f51295f95e09748"},
+ {file = "yarl-1.23.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dda608c88cf709b1d406bdfcd84d8d63cff7c9e577a403c6108ce8ce9dcc8764"},
+ {file = "yarl-1.23.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8c4fe09e0780c6c3bf2b7d4af02ee2394439d11a523bbcf095cf4747c2932007"},
+ {file = "yarl-1.23.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:31c9921eb8bd12633b41ad27686bbb0b1a2a9b8452bfdf221e34f311e9942ed4"},
+ {file = "yarl-1.23.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5f10fd85e4b75967468af655228fbfd212bdf66db1c0d135065ce288982eda26"},
+ {file = "yarl-1.23.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dbf507e9ef5688bada447a24d68b4b58dd389ba93b7afc065a2ba892bea54769"},
+ {file = "yarl-1.23.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:85e9beda1f591bc73e77ea1c51965c68e98dafd0fec72cdd745f77d727466716"},
+ {file = "yarl-1.23.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:0e1fdaa14ef51366d7757b45bde294e95f6c8c049194e793eedb8387c86d5993"},
+ {file = "yarl-1.23.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:75e3026ab649bf48f9a10c0134512638725b521340293f202a69b567518d94e0"},
+ {file = "yarl-1.23.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:80e6d33a3d42a7549b409f199857b4fb54e2103fc44fb87605b6663b7a7ff750"},
+ {file = "yarl-1.23.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5ec2f42d41ccbd5df0270d7df31618a8ee267bfa50997f5d720ddba86c4a83a6"},
+ {file = "yarl-1.23.0-cp310-cp310-win32.whl", hash = "sha256:debe9c4f41c32990771be5c22b56f810659f9ddf3d63f67abfdcaa2c6c9c5c1d"},
+ {file = "yarl-1.23.0-cp310-cp310-win_amd64.whl", hash = "sha256:ab5f043cb8a2d71c981c09c510da013bc79fd661f5c60139f00dd3c3cc4f2ffb"},
+ {file = "yarl-1.23.0-cp310-cp310-win_arm64.whl", hash = "sha256:263cd4f47159c09b8b685890af949195b51d1aa82ba451c5847ca9bc6413c220"},
+ {file = "yarl-1.23.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b35d13d549077713e4414f927cdc388d62e543987c572baee613bf82f11a4b99"},
+ {file = "yarl-1.23.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cbb0fef01f0c6b38cb0f39b1f78fc90b807e0e3c86a7ff3ce74ad77ce5c7880c"},
+ {file = "yarl-1.23.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dc52310451fc7c629e13c4e061cbe2dd01684d91f2f8ee2821b083c58bd72432"},
+ {file = "yarl-1.23.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b2c6b50c7b0464165472b56b42d4c76a7b864597007d9c085e8b63e185cf4a7a"},
+ {file = "yarl-1.23.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:aafe5dcfda86c8af00386d7781d4c2181b5011b7be3f2add5e99899ea925df05"},
+ {file = "yarl-1.23.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9ee33b875f0b390564c1fb7bc528abf18c8ee6073b201c6ae8524aca778e2d83"},
+ {file = "yarl-1.23.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4c41e021bc6d7affb3364dc1e1e5fa9582b470f283748784bd6ea0558f87f42c"},
+ {file = "yarl-1.23.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:99c8a9ed30f4164bc4c14b37a90208836cbf50d4ce2a57c71d0f52c7fb4f7598"},
+ {file = "yarl-1.23.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f2af5c81a1f124609d5f33507082fc3f739959d4719b56877ab1ee7e7b3d602b"},
+ {file = "yarl-1.23.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6b41389c19b07c760c7e427a3462e8ab83c4bb087d127f0e854c706ce1b9215c"},
+ {file = "yarl-1.23.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:1dc702e42d0684f42d6519c8d581e49c96cefaaab16691f03566d30658ee8788"},
+ {file = "yarl-1.23.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:0e40111274f340d32ebcc0a5668d54d2b552a6cca84c9475859d364b380e3222"},
+ {file = "yarl-1.23.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:4764a6a7588561a9aef92f65bda2c4fb58fe7c675c0883862e6df97559de0bfb"},
+ {file = "yarl-1.23.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:03214408cfa590df47728b84c679ae4ef00be2428e11630277be0727eba2d7cc"},
+ {file = "yarl-1.23.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:170e26584b060879e29fac213e4228ef063f39128723807a312e5c7fec28eff2"},
+ {file = "yarl-1.23.0-cp311-cp311-win32.whl", hash = "sha256:51430653db848d258336cfa0244427b17d12db63d42603a55f0d4546f50f25b5"},
+ {file = "yarl-1.23.0-cp311-cp311-win_amd64.whl", hash = "sha256:bf49a3ae946a87083ef3a34c8f677ae4243f5b824bfc4c69672e72b3d6719d46"},
+ {file = "yarl-1.23.0-cp311-cp311-win_arm64.whl", hash = "sha256:b39cb32a6582750b6cc77bfb3c49c0f8760dc18dc96ec9fb55fbb0f04e08b928"},
+ {file = "yarl-1.23.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1932b6b8bba8d0160a9d1078aae5838a66039e8832d41d2992daa9a3a08f7860"},
+ {file = "yarl-1.23.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:411225bae281f114067578891bc75534cfb3d92a3b4dfef7a6ca78ba354e6069"},
+ {file = "yarl-1.23.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:13a563739ae600a631c36ce096615fe307f131344588b0bc0daec108cdb47b25"},
+ {file = "yarl-1.23.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cbf44c5cb4a7633d078788e1b56387e3d3cf2b8139a3be38040b22d6c3221c8"},
+ {file = "yarl-1.23.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:53ad387048f6f09a8969631e4de3f1bf70c50e93545d64af4f751b2498755072"},
+ {file = "yarl-1.23.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4a59ba56f340334766f3a4442e0efd0af895fae9e2b204741ef885c446b3a1a8"},
+ {file = "yarl-1.23.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:803a3c3ce4acc62eaf01eaca1208dcf0783025ef27572c3336502b9c232005e7"},
+ {file = "yarl-1.23.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3d2bff8f37f8d0f96c7ec554d16945050d54462d6e95414babaa18bfafc7f51"},
+ {file = "yarl-1.23.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c75eb09e8d55bceb4367e83496ff8ef2bc7ea6960efb38e978e8073ea59ecb67"},
+ {file = "yarl-1.23.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877b0738624280e34c55680d6054a307aa94f7d52fa0e3034a9cc6e790871da7"},
+ {file = "yarl-1.23.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b5405bb8f0e783a988172993cfc627e4d9d00432d6bbac65a923041edacf997d"},
+ {file = "yarl-1.23.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1c3a3598a832590c5a3ce56ab5576361b5688c12cb1d39429cf5dba30b510760"},
+ {file = "yarl-1.23.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:8419ebd326430d1cbb7efb5292330a2cf39114e82df5cc3d83c9a0d5ebeaf2f2"},
+ {file = "yarl-1.23.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:be61f6fff406ca40e3b1d84716fde398fc08bc63dd96d15f3a14230a0973ed86"},
+ {file = "yarl-1.23.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3ceb13c5c858d01321b5d9bb65e4cf37a92169ea470b70fec6f236b2c9dd7e34"},
+ {file = "yarl-1.23.0-cp312-cp312-win32.whl", hash = "sha256:fffc45637bcd6538de8b85f51e3df3223e4ad89bccbfca0481c08c7fc8b7ed7d"},
+ {file = "yarl-1.23.0-cp312-cp312-win_amd64.whl", hash = "sha256:f69f57305656a4852f2a7203efc661d8c042e6cc67f7acd97d8667fb448a426e"},
+ {file = "yarl-1.23.0-cp312-cp312-win_arm64.whl", hash = "sha256:6e87a6e8735b44816e7db0b2fbc9686932df473c826b0d9743148432e10bb9b9"},
+ {file = "yarl-1.23.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:16c6994ac35c3e74fb0ae93323bf8b9c2a9088d55946109489667c510a7d010e"},
+ {file = "yarl-1.23.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4a42e651629dafb64fd5b0286a3580613702b5809ad3f24934ea87595804f2c5"},
+ {file = "yarl-1.23.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7c6b9461a2a8b47c65eef63bb1c76a4f1c119618ffa99ea79bc5bb1e46c5821b"},
+ {file = "yarl-1.23.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2569b67d616eab450d262ca7cb9f9e19d2f718c70a8b88712859359d0ab17035"},
+ {file = "yarl-1.23.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e9d9a4d06d3481eab79803beb4d9bd6f6a8e781ec078ac70d7ef2dcc29d1bea5"},
+ {file = "yarl-1.23.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f514f6474e04179d3d33175ed3f3e31434d3130d42ec153540d5b157deefd735"},
+ {file = "yarl-1.23.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fda207c815b253e34f7e1909840fd14299567b1c0eb4908f8c2ce01a41265401"},
+ {file = "yarl-1.23.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34b6cf500e61c90f305094911f9acc9c86da1a05a7a3f5be9f68817043f486e4"},
+ {file = "yarl-1.23.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d7504f2b476d21653e4d143f44a175f7f751cd41233525312696c76aa3dbb23f"},
+ {file = "yarl-1.23.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:578110dd426f0d209d1509244e6d4a3f1a3e9077655d98c5f22583d63252a08a"},
+ {file = "yarl-1.23.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:609d3614d78d74ebe35f54953c5bbd2ac647a7ddb9c30a5d877580f5e86b22f2"},
+ {file = "yarl-1.23.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4966242ec68afc74c122f8459abd597afd7d8a60dc93d695c1334c5fd25f762f"},
+ {file = "yarl-1.23.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:e0fd068364a6759bc794459f0a735ab151d11304346332489c7972bacbe9e72b"},
+ {file = "yarl-1.23.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:39004f0ad156da43e86aa71f44e033de68a44e5a31fc53507b36dd253970054a"},
+ {file = "yarl-1.23.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e5723c01a56c5028c807c701aa66722916d2747ad737a046853f6c46f4875543"},
+ {file = "yarl-1.23.0-cp313-cp313-win32.whl", hash = "sha256:1b6b572edd95b4fa8df75de10b04bc81acc87c1c7d16bcdd2035b09d30acc957"},
+ {file = "yarl-1.23.0-cp313-cp313-win_amd64.whl", hash = "sha256:baaf55442359053c7d62f6f8413a62adba3205119bcb6f49594894d8be47e5e3"},
+ {file = "yarl-1.23.0-cp313-cp313-win_arm64.whl", hash = "sha256:fb4948814a2a98e3912505f09c9e7493b1506226afb1f881825368d6fb776ee3"},
+ {file = "yarl-1.23.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:aecfed0b41aa72b7881712c65cf764e39ce2ec352324f5e0837c7048d9e6daaa"},
+ {file = "yarl-1.23.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a41bcf68efd19073376eb8cf948b8d9be0af26256403e512bb18f3966f1f9120"},
+ {file = "yarl-1.23.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cde9a2ecd91668bcb7f077c4966d8ceddb60af01b52e6e3e2680e4cf00ad1a59"},
+ {file = "yarl-1.23.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5023346c4ee7992febc0068e7593de5fa2bf611848c08404b35ebbb76b1b0512"},
+ {file = "yarl-1.23.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d1009abedb49ae95b136a8904a3f71b342f849ffeced2d3747bf29caeda218c4"},
+ {file = "yarl-1.23.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a8d00f29b42f534cc8aa3931cfe773b13b23e561e10d2b26f27a8d309b0e82a1"},
+ {file = "yarl-1.23.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:95451e6ce06c3e104556d73b559f5da6c34a069b6b62946d3ad66afcd51642ea"},
+ {file = "yarl-1.23.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:531ef597132086b6cf96faa7c6c1dcd0361dd5f1694e5cc30375907b9b7d3ea9"},
+ {file = "yarl-1.23.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:88f9fb0116fbfcefcab70f85cf4b74a2b6ce5d199c41345296f49d974ddb4123"},
+ {file = "yarl-1.23.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e7b0460976dc75cb87ad9cc1f9899a4b97751e7d4e77ab840fc9b6d377b8fd24"},
+ {file = "yarl-1.23.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:115136c4a426f9da976187d238e84139ff6b51a20839aa6e3720cd1026d768de"},
+ {file = "yarl-1.23.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:ead11956716a940c1abc816b7df3fa2b84d06eaed8832ca32f5c5e058c65506b"},
+ {file = "yarl-1.23.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:fe8f8f5e70e6dbdfca9882cd9deaac058729bcf323cf7a58660901e55c9c94f6"},
+ {file = "yarl-1.23.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:a0e317df055958a0c1e79e5d2aa5a5eaa4a6d05a20d4b0c9c3f48918139c9fc6"},
+ {file = "yarl-1.23.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f0fd84de0c957b2d280143522c4f91a73aada1923caee763e24a2b3fda9f8a5"},
+ {file = "yarl-1.23.0-cp313-cp313t-win32.whl", hash = "sha256:93a784271881035ab4406a172edb0faecb6e7d00f4b53dc2f55919d6c9688595"},
+ {file = "yarl-1.23.0-cp313-cp313t-win_amd64.whl", hash = "sha256:dd00607bffbf30250fe108065f07453ec124dbf223420f57f5e749b04295e090"},
+ {file = "yarl-1.23.0-cp313-cp313t-win_arm64.whl", hash = "sha256:ac09d42f48f80c9ee1635b2fcaa819496a44502737660d3c0f2ade7526d29144"},
+ {file = "yarl-1.23.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:21d1b7305a71a15b4794b5ff22e8eef96ff4a6d7f9657155e5aa419444b28912"},
+ {file = "yarl-1.23.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:85610b4f27f69984932a7abbe52703688de3724d9f72bceb1cca667deff27474"},
+ {file = "yarl-1.23.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23f371bd662cf44a7630d4d113101eafc0cfa7518a2760d20760b26021454719"},
+ {file = "yarl-1.23.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4a80f77dc1acaaa61f0934176fccca7096d9b1ff08c8ba9cddf5ae034a24319"},
+ {file = "yarl-1.23.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:bd654fad46d8d9e823afbb4f87c79160b5a374ed1ff5bde24e542e6ba8f41434"},
+ {file = "yarl-1.23.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:682bae25f0a0dd23a056739f23a134db9f52a63e2afd6bfb37ddc76292bbd723"},
+ {file = "yarl-1.23.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a82836cab5f197a0514235aaf7ffccdc886ccdaa2324bc0aafdd4ae898103039"},
+ {file = "yarl-1.23.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c57676bdedc94cd3bc37724cf6f8cd2779f02f6aba48de45feca073e714fe52"},
+ {file = "yarl-1.23.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c7f8dc16c498ff06497c015642333219871effba93e4a2e8604a06264aca5c5c"},
+ {file = "yarl-1.23.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:5ee586fb17ff8f90c91cf73c6108a434b02d69925f44f5f8e0d7f2f260607eae"},
+ {file = "yarl-1.23.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:17235362f580149742739cc3828b80e24029d08cbb9c4bda0242c7b5bc610a8e"},
+ {file = "yarl-1.23.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:0793e2bd0cf14234983bbb371591e6bea9e876ddf6896cdcc93450996b0b5c85"},
+ {file = "yarl-1.23.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:3650dc2480f94f7116c364096bc84b1d602f44224ef7d5c7208425915c0475dd"},
+ {file = "yarl-1.23.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f40e782d49630ad384db66d4d8b73ff4f1b8955dc12e26b09a3e3af064b3b9d6"},
+ {file = "yarl-1.23.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:94f8575fbdf81749008d980c17796097e645574a3b8c28ee313931068dad14fe"},
+ {file = "yarl-1.23.0-cp314-cp314-win32.whl", hash = "sha256:c8aa34a5c864db1087d911a0b902d60d203ea3607d91f615acd3f3108ac32169"},
+ {file = "yarl-1.23.0-cp314-cp314-win_amd64.whl", hash = "sha256:63e92247f383c85ab00dd0091e8c3fa331a96e865459f5ee80353c70a4a42d70"},
+ {file = "yarl-1.23.0-cp314-cp314-win_arm64.whl", hash = "sha256:70efd20be968c76ece7baa8dafe04c5be06abc57f754d6f36f3741f7aa7a208e"},
+ {file = "yarl-1.23.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:9a18d6f9359e45722c064c97464ec883eb0e0366d33eda61cb19a244bf222679"},
+ {file = "yarl-1.23.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:2803ed8b21ca47a43da80a6fd1ed3019d30061f7061daa35ac54f63933409412"},
+ {file = "yarl-1.23.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:394906945aa8b19fc14a61cf69743a868bb8c465efe85eee687109cc540b98f4"},
+ {file = "yarl-1.23.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:71d006bee8397a4a89f469b8deb22469fe7508132d3c17fa6ed871e79832691c"},
+ {file = "yarl-1.23.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:62694e275c93d54f7ccedcfef57d42761b2aad5234b6be1f3e3026cae4001cd4"},
+ {file = "yarl-1.23.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a31de1613658308efdb21ada98cbc86a97c181aa050ba22a808120bb5be3ab94"},
+ {file = "yarl-1.23.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fb1e8b8d66c278b21d13b0a7ca22c41dd757a7c209c6b12c313e445c31dd3b28"},
+ {file = "yarl-1.23.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50f9d8d531dfb767c565f348f33dd5139a6c43f5cbdf3f67da40d54241df93f6"},
+ {file = "yarl-1.23.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:575aa4405a656e61a540f4a80eaa5260f2a38fff7bfdc4b5f611840d76e9e277"},
+ {file = "yarl-1.23.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:041b1a4cefacf65840b4e295c6985f334ba83c30607441ae3cf206a0eed1a2e4"},
+ {file = "yarl-1.23.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:d38c1e8231722c4ce40d7593f28d92b5fc72f3e9774fe73d7e800ec32299f63a"},
+ {file = "yarl-1.23.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:d53834e23c015ee83a99377db6e5e37d8484f333edb03bd15b4bc312cc7254fb"},
+ {file = "yarl-1.23.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:2e27c8841126e017dd2a054a95771569e6070b9ee1b133366d8b31beb5018a41"},
+ {file = "yarl-1.23.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:76855800ac56f878847a09ce6dba727c93ca2d89c9e9d63002d26b916810b0a2"},
+ {file = "yarl-1.23.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e09fd068c2e169a7070d83d3bde728a4d48de0549f975290be3c108c02e499b4"},
+ {file = "yarl-1.23.0-cp314-cp314t-win32.whl", hash = "sha256:73309162a6a571d4cbd3b6a1dcc703c7311843ae0d1578df6f09be4e98df38d4"},
+ {file = "yarl-1.23.0-cp314-cp314t-win_amd64.whl", hash = "sha256:4503053d296bc6e4cbd1fad61cf3b6e33b939886c4f249ba7c78b602214fabe2"},
+ {file = "yarl-1.23.0-cp314-cp314t-win_arm64.whl", hash = "sha256:44bb7bef4ea409384e3f8bc36c063d77ea1b8d4a5b2706956c0d6695f07dcc25"},
+ {file = "yarl-1.23.0-py3-none-any.whl", hash = "sha256:a2df6afe50dea8ae15fa34c9f824a3ee958d785fd5d089063d960bae1daa0a3f"},
+ {file = "yarl-1.23.0.tar.gz", hash = "sha256:53b1ea6ca88ebd4420379c330aea57e258408dd0df9af0992e5de2078dc9f5d5"},
+]
+
+[package.dependencies]
+idna = ">=2.0"
+multidict = ">=4.0"
+propcache = ">=0.2.1"
+
+[extras]
+aiohttp = ["aiohttp", "httpx-aiohttp"]
+
+[metadata]
+lock-version = "2.0"
+python-versions = "^3.10"
+content-hash = "7ac12b5d251e81a278fba2c051cbf4f768fb951f006f23ad0faedb1289539c1a"
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/pyproject.toml b/seed/python-sdk/unions/union-naming-v1-wire-tests/pyproject.toml
new file mode 100644
index 000000000000..6d5ec77b5627
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/pyproject.toml
@@ -0,0 +1,102 @@
+[project]
+name = "fern_unions"
+dynamic = ["version"]
+
+[tool.poetry]
+name = "fern_unions"
+version = "0.0.1"
+description = ""
+readme = "README.md"
+authors = []
+keywords = [
+ "fern",
+ "test"
+]
+
+classifiers = [
+ "Intended Audience :: Developers",
+ "Programming Language :: Python",
+ "Programming Language :: Python :: 3",
+ "Programming Language :: Python :: 3.10",
+ "Programming Language :: Python :: 3.11",
+ "Programming Language :: Python :: 3.12",
+ "Programming Language :: Python :: 3.13",
+ "Programming Language :: Python :: 3.14",
+ "Programming Language :: Python :: 3.15",
+ "Operating System :: OS Independent",
+ "Operating System :: POSIX",
+ "Operating System :: MacOS",
+ "Operating System :: POSIX :: Linux",
+ "Operating System :: Microsoft :: Windows",
+ "Topic :: Software Development :: Libraries :: Python Modules",
+ "Typing :: Typed"
+]
+packages = [
+ { include = "seed", from = "src"}
+]
+
+[tool.poetry.urls]
+Documentation = 'https://buildwithfern.com/learn'
+Homepage = 'https://buildwithfern.com/'
+Repository = 'https://github.com/unions/fern'
+
+[tool.poetry.dependencies]
+python = "^3.10"
+aiohttp = { version = ">=3.10.0,<4", optional = true}
+httpx = ">=0.21.2"
+httpx-aiohttp = { version = "0.1.8", optional = true}
+pydantic = ">= 1.9.2"
+pydantic-core = ">=2.18.2,<2.44.0"
+typing_extensions = ">= 4.0.0"
+
+[tool.poetry.group.dev.dependencies]
+mypy = "==1.13.0"
+pytest = "^8.2.0"
+pytest-asyncio = "^1.0.0"
+pytest-xdist = "^3.6.1"
+python-dateutil = "^2.9.0"
+types-python-dateutil = "^2.9.0.20240316"
+requests = "^2.31.0"
+types-requests = "^2.31.0"
+ruff = "==0.11.5"
+
+[tool.pytest.ini_options]
+testpaths = [ "tests" ]
+asyncio_mode = "auto"
+markers = [
+ "aiohttp: tests that require httpx_aiohttp to be installed",
+]
+
+[tool.mypy]
+plugins = ["pydantic.mypy"]
+
+[tool.ruff]
+line-length = 120
+
+[tool.ruff.lint]
+select = [
+ "E", # pycodestyle errors
+ "F", # pyflakes
+ "I", # isort
+]
+ignore = [
+ "E402", # Module level import not at top of file
+ "E501", # Line too long
+ "E711", # Comparison to `None` should be `cond is not None`
+ "E712", # Avoid equality comparisons to `True`; use `if ...:` checks
+ "E721", # Use `is` and `is not` for type comparisons, or `isinstance()` for insinstance checks
+ "E722", # Do not use bare `except`
+ "E731", # Do not assign a `lambda` expression, use a `def`
+ "F821", # Undefined name
+ "F841" # Local variable ... is assigned to but never used
+]
+
+[tool.ruff.lint.isort]
+section-order = ["future", "standard-library", "third-party", "first-party"]
+
+[build-system]
+requires = ["poetry-core"]
+build-backend = "poetry.core.masonry.api"
+
+[tool.poetry.extras]
+aiohttp=["aiohttp", "httpx-aiohttp"]
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/reference.md b/seed/python-sdk/unions/union-naming-v1-wire-tests/reference.md
new file mode 100644
index 000000000000..96836d74d2bd
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/reference.md
@@ -0,0 +1,314 @@
+# Reference
+## Bigunion
+client.bigunion.get(...) -> BigUnion
+
+-
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from seed import SeedUnions
+
+client = SeedUnions(
+ base_url="https://yourhost.com/path/to/api",
+)
+
+client.bigunion.get(
+ id="id",
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**id:** `str`
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.bigunion.update(...) -> bool
+
+-
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from seed import SeedUnions
+from seed.bigunion import NormalSweetBigUnion
+import datetime
+
+client = SeedUnions(
+ base_url="https://yourhost.com/path/to/api",
+)
+
+client.bigunion.update(
+ request=NormalSweetBigUnion(
+ id="id",
+ created_at=datetime.datetime.fromisoformat("2024-01-15T09:30:00+00:00"),
+ archived_at=datetime.datetime.fromisoformat("2024-01-15T09:30:00+00:00"),
+ value="value",
+ ),
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**request:** `BigUnion`
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.bigunion.update_many(...) -> typing.Dict[str, bool]
+
+-
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from seed import SeedUnions
+from seed.bigunion import NormalSweetBigUnion
+import datetime
+
+client = SeedUnions(
+ base_url="https://yourhost.com/path/to/api",
+)
+
+client.bigunion.update_many(
+ request=[
+ NormalSweetBigUnion(
+ id="id",
+ created_at=datetime.datetime.fromisoformat("2024-01-15T09:30:00+00:00"),
+ archived_at=datetime.datetime.fromisoformat("2024-01-15T09:30:00+00:00"),
+ value="value",
+ ),
+ NormalSweetBigUnion(
+ id="id",
+ created_at=datetime.datetime.fromisoformat("2024-01-15T09:30:00+00:00"),
+ archived_at=datetime.datetime.fromisoformat("2024-01-15T09:30:00+00:00"),
+ value="value",
+ )
+ ],
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**request:** `typing.List[BigUnion]`
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+## Union
+client.union.get(...) -> Shape
+
+-
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from seed import SeedUnions
+
+client = SeedUnions(
+ base_url="https://yourhost.com/path/to/api",
+)
+
+client.bigunion.get(
+ id="id",
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**id:** `str`
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.union.update(...) -> bool
+
+-
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from seed import SeedUnions
+from seed.union import CircleShape
+
+client = SeedUnions(
+ base_url="https://yourhost.com/path/to/api",
+)
+
+client.union.update(
+ request=CircleShape(
+ id="id",
+ radius=1.1,
+ ),
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**request:** `Shape`
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/requirements.txt b/seed/python-sdk/unions/union-naming-v1-wire-tests/requirements.txt
new file mode 100644
index 000000000000..0141a1a5014b
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/requirements.txt
@@ -0,0 +1,4 @@
+httpx>=0.21.2
+pydantic>= 1.9.2
+pydantic-core>=2.18.2,<2.44.0
+typing_extensions>= 4.0.0
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/snippet.json b/seed/python-sdk/unions/union-naming-v1-wire-tests/snippet.json
new file mode 100644
index 000000000000..bb2c36b07e1a
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/snippet.json
@@ -0,0 +1,93 @@
+{
+ "types": {
+ "type_bigunion:BigUnion": "from seed.bigunion import NormalSweetBigUnion\n\nNormalSweetBigUnion(\n value=\"example1\",\n)\n",
+ "type_bigunion:NormalSweet": "from seed.bigunion import NormalSweet\n\nNormalSweet(\n value=\"example1\",\n)\n",
+ "type_bigunion:ThankfulFactor": "from seed.bigunion import ThankfulFactor\n\nThankfulFactor(\n value=\"example1\",\n)\n",
+ "type_types:UnionWithPrimitive": "from seed.types import IntegerUnionWithPrimitive\n\nIntegerUnionWithPrimitive(value=9)\n",
+ "type_types:UnionWithDuplicatePrimitive": "from seed.types import Integer1UnionWithDuplicatePrimitive\n\nInteger1UnionWithDuplicatePrimitive(value=9)\n",
+ "type_types:UnionWithoutKey": "from seed.types import FooUnionWithoutKey\n\nFooUnionWithoutKey(\n name=\"example1\",\n)\n",
+ "type_types:UnionWithNoProperties": "from seed.types import FooUnionWithNoProperties\n\nFooUnionWithNoProperties(\n name=\"example\",\n)\n",
+ "type_types:UnionWithMultipleNoProperties": "from seed.types import FooUnionWithMultipleNoProperties\n\nFooUnionWithMultipleNoProperties(\n name=\"example\",\n)\n",
+ "type_types:UnionWithLiteral": "from seed.types import FernUnionWithLiteral\n\nFernUnionWithLiteral()\n",
+ "type_types:UnionWithBaseProperties": "from seed.types import IntegerUnionWithBaseProperties\n\nIntegerUnionWithBaseProperties(value=5)\n",
+ "type_types:UnionWithTime": "from seed.types import ValueUnionWithTime\n\nValueUnionWithTime(value=5)\n",
+ "type_types:UnionWithOptionalTime": "import datetime\n\nfrom seed.types import DateUnionWithOptionalTime\n\nDateUnionWithOptionalTime(\n value=datetime.date.fromisoformat(\n \"1994-01-01\",\n )\n)\n",
+ "type_types:UnionWithSingleElement": "from seed.types import FooUnionWithSingleElement\n\nFooUnionWithSingleElement(\n name=\"example1\",\n)\n",
+ "type_types:UnionWithDuplicateTypes": "from seed.types import Foo1UnionWithDuplicateTypes\n\nFoo1UnionWithDuplicateTypes(\n name=\"example1\",\n)\n",
+ "type_types:UnionWithSubTypes": "from seed.types import FooUnionWithSubTypes\n\nFooUnionWithSubTypes(\n name=\"example1\",\n)\n",
+ "type_types:Foo": "from seed.types import Foo\n\nFoo(\n name=\"example1\",\n)\n",
+ "type_types:FooExtended": "from seed.types import FooExtended\n\nFooExtended(\n name=\"example1\",\n age=5,\n)\n",
+ "type_types:Bar": "from seed.types import Bar\n\nBar(\n name=\"example1\",\n)\n",
+ "type_types:UnionWithSameStringTypes": "from seed.types import CustomFormatUnionWithSameStringTypes\n\nCustomFormatUnionWithSameStringTypes(value=\"custom-123\")\n",
+ "type_types:UnionWithSameNumberTypes": "from seed.types import PositiveIntUnionWithSameNumberTypes\n\nPositiveIntUnionWithSameNumberTypes(value=100)\n",
+ "type_union:GetShapeRequest": "from seed.union import GetShapeRequest\n\nGetShapeRequest(\n id=\"example\",\n)\n",
+ "type_union:Shape": "from seed.union import CircleShape\n\nCircleShape(\n radius=5.0,\n)\n"
+ },
+ "endpoints": [
+ {
+ "example_identifier": "default",
+ "id": {
+ "path": "/{id}",
+ "method": "GET",
+ "identifier_override": "endpoint_bigunion.get"
+ },
+ "snippet": {
+ "sync_client": "from seed import SeedUnions\n\nclient = SeedUnions(\n base_url=\"https://yourhost.com/path/to/api\",\n)\nclient.bigunion.get(\n id=\"id\",\n)\n",
+ "async_client": "import asyncio\n\nfrom seed import AsyncSeedUnions\n\nclient = AsyncSeedUnions(\n base_url=\"https://yourhost.com/path/to/api\",\n)\n\n\nasync def main() -> None:\n await client.bigunion.get(\n id=\"id\",\n )\n\n\nasyncio.run(main())\n",
+ "type": "python"
+ }
+ },
+ {
+ "example_identifier": "default",
+ "id": {
+ "path": "/",
+ "method": "PATCH",
+ "identifier_override": "endpoint_bigunion.update"
+ },
+ "snippet": {
+ "sync_client": "from seed import SeedUnions\nfrom seed.bigunion import NormalSweetBigUnion\n\nclient = SeedUnions(\n base_url=\"https://yourhost.com/path/to/api\",\n)\nclient.bigunion.update(\n request=NormalSweetBigUnion(\n value=\"value\",\n ),\n)\n",
+ "async_client": "import asyncio\n\nfrom seed import AsyncSeedUnions\nfrom seed.bigunion import NormalSweetBigUnion\n\nclient = AsyncSeedUnions(\n base_url=\"https://yourhost.com/path/to/api\",\n)\n\n\nasync def main() -> None:\n await client.bigunion.update(\n request=NormalSweetBigUnion(\n value=\"value\",\n ),\n )\n\n\nasyncio.run(main())\n",
+ "type": "python"
+ }
+ },
+ {
+ "example_identifier": "default",
+ "id": {
+ "path": "/many",
+ "method": "PATCH",
+ "identifier_override": "endpoint_bigunion.update-many"
+ },
+ "snippet": {
+ "sync_client": "from seed import SeedUnions\nfrom seed.bigunion import NormalSweetBigUnion\n\nclient = SeedUnions(\n base_url=\"https://yourhost.com/path/to/api\",\n)\nclient.bigunion.update_many(\n request=[\n NormalSweetBigUnion(\n value=\"value\",\n ),\n NormalSweetBigUnion(\n value=\"value\",\n ),\n ],\n)\n",
+ "async_client": "import asyncio\n\nfrom seed import AsyncSeedUnions\nfrom seed.bigunion import NormalSweetBigUnion\n\nclient = AsyncSeedUnions(\n base_url=\"https://yourhost.com/path/to/api\",\n)\n\n\nasync def main() -> None:\n await client.bigunion.update_many(\n request=[\n NormalSweetBigUnion(\n value=\"value\",\n ),\n NormalSweetBigUnion(\n value=\"value\",\n ),\n ],\n )\n\n\nasyncio.run(main())\n",
+ "type": "python"
+ }
+ },
+ {
+ "example_identifier": "default",
+ "id": {
+ "path": "/{id}",
+ "method": "GET",
+ "identifier_override": "endpoint_union.get"
+ },
+ "snippet": {
+ "sync_client": "from seed import SeedUnions\n\nclient = SeedUnions(\n base_url=\"https://yourhost.com/path/to/api\",\n)\nclient.union.get(\n id=\"id\",\n)\n",
+ "async_client": "import asyncio\n\nfrom seed import AsyncSeedUnions\n\nclient = AsyncSeedUnions(\n base_url=\"https://yourhost.com/path/to/api\",\n)\n\n\nasync def main() -> None:\n await client.union.get(\n id=\"id\",\n )\n\n\nasyncio.run(main())\n",
+ "type": "python"
+ }
+ },
+ {
+ "example_identifier": "default",
+ "id": {
+ "path": "/",
+ "method": "PATCH",
+ "identifier_override": "endpoint_union.update"
+ },
+ "snippet": {
+ "sync_client": "from seed import SeedUnions\nfrom seed.union import CircleShape\n\nclient = SeedUnions(\n base_url=\"https://yourhost.com/path/to/api\",\n)\nclient.union.update(\n request=CircleShape(\n radius=1.1,\n ),\n)\n",
+ "async_client": "import asyncio\n\nfrom seed import AsyncSeedUnions\nfrom seed.union import CircleShape\n\nclient = AsyncSeedUnions(\n base_url=\"https://yourhost.com/path/to/api\",\n)\n\n\nasync def main() -> None:\n await client.union.update(\n request=CircleShape(\n radius=1.1,\n ),\n )\n\n\nasyncio.run(main())\n",
+ "type": "python"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/__init__.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/__init__.py
new file mode 100644
index 000000000000..cee627c64312
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/__init__.py
@@ -0,0 +1,455 @@
+# This file was auto-generated by Fern from our API Definition.
+
+# isort: skip_file
+
+import typing
+from importlib import import_module
+
+if typing.TYPE_CHECKING:
+ from .types import (
+ AnyNumberUnionWithSameNumberTypes,
+ Bar,
+ BarUnion,
+ BarUnionWithDiscriminant,
+ BarUnionWithNullableReference,
+ BarUnionWithOptionalReference,
+ BarUnionWithoutKey,
+ CustomFormatUnionWithSameStringTypes,
+ DateUnionWithOptionalTime,
+ DateUnionWithTime,
+ DatetimeUnionWithOptionalTime,
+ DatetimeUnionWithTime,
+ Empty1UnionWithMultipleNoProperties,
+ Empty2UnionWithMultipleNoProperties,
+ EmptyUnionWithNoProperties,
+ FernUnionWithLiteral,
+ FirstItemType,
+ FirstItemTypeUnionWithDuplicativeDiscriminants,
+ Foo,
+ Foo1UnionWithDuplicateTypes,
+ Foo2UnionWithDuplicateTypes,
+ FooExtended,
+ FooExtendedUnionWithSubTypes,
+ FooUnion,
+ FooUnionWithBaseProperties,
+ FooUnionWithDiscriminant,
+ FooUnionWithMultipleNoProperties,
+ FooUnionWithNoProperties,
+ FooUnionWithNullableReference,
+ FooUnionWithOptionalReference,
+ FooUnionWithSingleElement,
+ FooUnionWithSubTypes,
+ FooUnionWithoutKey,
+ Integer1UnionWithDuplicatePrimitive,
+ Integer2UnionWithDuplicatePrimitive,
+ IntegerUnionWithBaseProperties,
+ IntegerUnionWithPrimitive,
+ NegativeIntUnionWithSameNumberTypes,
+ PatternStringUnionWithSameStringTypes,
+ PositiveIntUnionWithSameNumberTypes,
+ RegularStringUnionWithSameStringTypes,
+ SecondItemType,
+ SecondItemTypeUnionWithDuplicativeDiscriminants,
+ String1UnionWithDuplicatePrimitive,
+ String2UnionWithDuplicatePrimitive,
+ StringUnionWithBaseProperties,
+ StringUnionWithPrimitive,
+ TypeWithOptionalMap,
+ Union,
+ UnionWithBaseProperties,
+ UnionWithDiscriminant,
+ UnionWithDuplicatePrimitive,
+ UnionWithDuplicateTypes,
+ UnionWithDuplicativeDiscriminants,
+ UnionWithLiteral,
+ UnionWithMultipleNoProperties,
+ UnionWithNoProperties,
+ UnionWithNullableReference,
+ UnionWithOptionalReference,
+ UnionWithOptionalTime,
+ UnionWithPrimitive,
+ UnionWithSameNumberTypes,
+ UnionWithSameStringTypes,
+ UnionWithSingleElement,
+ UnionWithSubTypes,
+ UnionWithTime,
+ UnionWithoutKey,
+ ValueUnionWithTime,
+ )
+ from . import bigunion, types, union
+ from ._default_clients import DefaultAioHttpClient, DefaultAsyncHttpxClient
+ from .bigunion import (
+ ActiveDiamond,
+ ActiveDiamondBigUnion,
+ AttractiveScript,
+ AttractiveScriptBigUnion,
+ BigUnion,
+ CircularCard,
+ CircularCardBigUnion,
+ ColorfulCover,
+ ColorfulCoverBigUnion,
+ DiligentDeal,
+ DiligentDealBigUnion,
+ DisloyalValue,
+ DisloyalValueBigUnion,
+ DistinctFailure,
+ DistinctFailureBigUnion,
+ FalseMirror,
+ FalseMirrorBigUnion,
+ FrozenSleep,
+ FrozenSleepBigUnion,
+ GaseousRoad,
+ GaseousRoadBigUnion,
+ GruesomeCoach,
+ GruesomeCoachBigUnion,
+ HarmoniousPlay,
+ HarmoniousPlayBigUnion,
+ HastyPain,
+ HastyPainBigUnion,
+ HoarseMouse,
+ HoarseMouseBigUnion,
+ JumboEnd,
+ JumboEndBigUnion,
+ LimpingStep,
+ LimpingStepBigUnion,
+ MistySnow,
+ MistySnowBigUnion,
+ NormalSweet,
+ NormalSweetBigUnion,
+ PopularLimit,
+ PopularLimitBigUnion,
+ PotableBad,
+ PotableBadBigUnion,
+ PracticalPrinciple,
+ PracticalPrincipleBigUnion,
+ PrimaryBlock,
+ PrimaryBlockBigUnion,
+ RotatingRatio,
+ RotatingRatioBigUnion,
+ ThankfulFactor,
+ ThankfulFactorBigUnion,
+ TotalWork,
+ TotalWorkBigUnion,
+ TriangularRepair,
+ TriangularRepairBigUnion,
+ UniqueStress,
+ UniqueStressBigUnion,
+ UnwillingSmoke,
+ UnwillingSmokeBigUnion,
+ VibrantExcitement,
+ VibrantExcitementBigUnion,
+ )
+ from .client import AsyncSeedUnions, SeedUnions
+ from .union import Circle, CircleShape, GetShapeRequest, Shape, Square, SquareShape, WithName
+ from .version import __version__
+_dynamic_imports: typing.Dict[str, str] = {
+ "ActiveDiamond": ".bigunion",
+ "ActiveDiamondBigUnion": ".bigunion",
+ "AnyNumberUnionWithSameNumberTypes": ".types",
+ "AsyncSeedUnions": ".client",
+ "AttractiveScript": ".bigunion",
+ "AttractiveScriptBigUnion": ".bigunion",
+ "Bar": ".types",
+ "BarUnion": ".types",
+ "BarUnionWithDiscriminant": ".types",
+ "BarUnionWithNullableReference": ".types",
+ "BarUnionWithOptionalReference": ".types",
+ "BarUnionWithoutKey": ".types",
+ "BigUnion": ".bigunion",
+ "Circle": ".union",
+ "CircleShape": ".union",
+ "CircularCard": ".bigunion",
+ "CircularCardBigUnion": ".bigunion",
+ "ColorfulCover": ".bigunion",
+ "ColorfulCoverBigUnion": ".bigunion",
+ "CustomFormatUnionWithSameStringTypes": ".types",
+ "DateUnionWithOptionalTime": ".types",
+ "DateUnionWithTime": ".types",
+ "DatetimeUnionWithOptionalTime": ".types",
+ "DatetimeUnionWithTime": ".types",
+ "DefaultAioHttpClient": "._default_clients",
+ "DefaultAsyncHttpxClient": "._default_clients",
+ "DiligentDeal": ".bigunion",
+ "DiligentDealBigUnion": ".bigunion",
+ "DisloyalValue": ".bigunion",
+ "DisloyalValueBigUnion": ".bigunion",
+ "DistinctFailure": ".bigunion",
+ "DistinctFailureBigUnion": ".bigunion",
+ "Empty1UnionWithMultipleNoProperties": ".types",
+ "Empty2UnionWithMultipleNoProperties": ".types",
+ "EmptyUnionWithNoProperties": ".types",
+ "FalseMirror": ".bigunion",
+ "FalseMirrorBigUnion": ".bigunion",
+ "FernUnionWithLiteral": ".types",
+ "FirstItemType": ".types",
+ "FirstItemTypeUnionWithDuplicativeDiscriminants": ".types",
+ "Foo": ".types",
+ "Foo1UnionWithDuplicateTypes": ".types",
+ "Foo2UnionWithDuplicateTypes": ".types",
+ "FooExtended": ".types",
+ "FooExtendedUnionWithSubTypes": ".types",
+ "FooUnion": ".types",
+ "FooUnionWithBaseProperties": ".types",
+ "FooUnionWithDiscriminant": ".types",
+ "FooUnionWithMultipleNoProperties": ".types",
+ "FooUnionWithNoProperties": ".types",
+ "FooUnionWithNullableReference": ".types",
+ "FooUnionWithOptionalReference": ".types",
+ "FooUnionWithSingleElement": ".types",
+ "FooUnionWithSubTypes": ".types",
+ "FooUnionWithoutKey": ".types",
+ "FrozenSleep": ".bigunion",
+ "FrozenSleepBigUnion": ".bigunion",
+ "GaseousRoad": ".bigunion",
+ "GaseousRoadBigUnion": ".bigunion",
+ "GetShapeRequest": ".union",
+ "GruesomeCoach": ".bigunion",
+ "GruesomeCoachBigUnion": ".bigunion",
+ "HarmoniousPlay": ".bigunion",
+ "HarmoniousPlayBigUnion": ".bigunion",
+ "HastyPain": ".bigunion",
+ "HastyPainBigUnion": ".bigunion",
+ "HoarseMouse": ".bigunion",
+ "HoarseMouseBigUnion": ".bigunion",
+ "Integer1UnionWithDuplicatePrimitive": ".types",
+ "Integer2UnionWithDuplicatePrimitive": ".types",
+ "IntegerUnionWithBaseProperties": ".types",
+ "IntegerUnionWithPrimitive": ".types",
+ "JumboEnd": ".bigunion",
+ "JumboEndBigUnion": ".bigunion",
+ "LimpingStep": ".bigunion",
+ "LimpingStepBigUnion": ".bigunion",
+ "MistySnow": ".bigunion",
+ "MistySnowBigUnion": ".bigunion",
+ "NegativeIntUnionWithSameNumberTypes": ".types",
+ "NormalSweet": ".bigunion",
+ "NormalSweetBigUnion": ".bigunion",
+ "PatternStringUnionWithSameStringTypes": ".types",
+ "PopularLimit": ".bigunion",
+ "PopularLimitBigUnion": ".bigunion",
+ "PositiveIntUnionWithSameNumberTypes": ".types",
+ "PotableBad": ".bigunion",
+ "PotableBadBigUnion": ".bigunion",
+ "PracticalPrinciple": ".bigunion",
+ "PracticalPrincipleBigUnion": ".bigunion",
+ "PrimaryBlock": ".bigunion",
+ "PrimaryBlockBigUnion": ".bigunion",
+ "RegularStringUnionWithSameStringTypes": ".types",
+ "RotatingRatio": ".bigunion",
+ "RotatingRatioBigUnion": ".bigunion",
+ "SecondItemType": ".types",
+ "SecondItemTypeUnionWithDuplicativeDiscriminants": ".types",
+ "SeedUnions": ".client",
+ "Shape": ".union",
+ "Square": ".union",
+ "SquareShape": ".union",
+ "String1UnionWithDuplicatePrimitive": ".types",
+ "String2UnionWithDuplicatePrimitive": ".types",
+ "StringUnionWithBaseProperties": ".types",
+ "StringUnionWithPrimitive": ".types",
+ "ThankfulFactor": ".bigunion",
+ "ThankfulFactorBigUnion": ".bigunion",
+ "TotalWork": ".bigunion",
+ "TotalWorkBigUnion": ".bigunion",
+ "TriangularRepair": ".bigunion",
+ "TriangularRepairBigUnion": ".bigunion",
+ "TypeWithOptionalMap": ".types",
+ "Union": ".types",
+ "UnionWithBaseProperties": ".types",
+ "UnionWithDiscriminant": ".types",
+ "UnionWithDuplicatePrimitive": ".types",
+ "UnionWithDuplicateTypes": ".types",
+ "UnionWithDuplicativeDiscriminants": ".types",
+ "UnionWithLiteral": ".types",
+ "UnionWithMultipleNoProperties": ".types",
+ "UnionWithNoProperties": ".types",
+ "UnionWithNullableReference": ".types",
+ "UnionWithOptionalReference": ".types",
+ "UnionWithOptionalTime": ".types",
+ "UnionWithPrimitive": ".types",
+ "UnionWithSameNumberTypes": ".types",
+ "UnionWithSameStringTypes": ".types",
+ "UnionWithSingleElement": ".types",
+ "UnionWithSubTypes": ".types",
+ "UnionWithTime": ".types",
+ "UnionWithoutKey": ".types",
+ "UniqueStress": ".bigunion",
+ "UniqueStressBigUnion": ".bigunion",
+ "UnwillingSmoke": ".bigunion",
+ "UnwillingSmokeBigUnion": ".bigunion",
+ "ValueUnionWithTime": ".types",
+ "VibrantExcitement": ".bigunion",
+ "VibrantExcitementBigUnion": ".bigunion",
+ "WithName": ".union",
+ "__version__": ".version",
+ "bigunion": ".bigunion",
+ "types": ".types",
+ "union": ".union",
+}
+
+
+def __getattr__(attr_name: str) -> typing.Any:
+ module_name = _dynamic_imports.get(attr_name)
+ if module_name is None:
+ raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}")
+ try:
+ module = import_module(module_name, __package__)
+ if module_name == f".{attr_name}":
+ return module
+ else:
+ return getattr(module, attr_name)
+ except ImportError as e:
+ raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e
+ except AttributeError as e:
+ raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e
+
+
+def __dir__():
+ lazy_attrs = list(_dynamic_imports.keys())
+ return sorted(lazy_attrs)
+
+
+__all__ = [
+ "ActiveDiamond",
+ "ActiveDiamondBigUnion",
+ "AnyNumberUnionWithSameNumberTypes",
+ "AsyncSeedUnions",
+ "AttractiveScript",
+ "AttractiveScriptBigUnion",
+ "Bar",
+ "BarUnion",
+ "BarUnionWithDiscriminant",
+ "BarUnionWithNullableReference",
+ "BarUnionWithOptionalReference",
+ "BarUnionWithoutKey",
+ "BigUnion",
+ "Circle",
+ "CircleShape",
+ "CircularCard",
+ "CircularCardBigUnion",
+ "ColorfulCover",
+ "ColorfulCoverBigUnion",
+ "CustomFormatUnionWithSameStringTypes",
+ "DateUnionWithOptionalTime",
+ "DateUnionWithTime",
+ "DatetimeUnionWithOptionalTime",
+ "DatetimeUnionWithTime",
+ "DefaultAioHttpClient",
+ "DefaultAsyncHttpxClient",
+ "DiligentDeal",
+ "DiligentDealBigUnion",
+ "DisloyalValue",
+ "DisloyalValueBigUnion",
+ "DistinctFailure",
+ "DistinctFailureBigUnion",
+ "Empty1UnionWithMultipleNoProperties",
+ "Empty2UnionWithMultipleNoProperties",
+ "EmptyUnionWithNoProperties",
+ "FalseMirror",
+ "FalseMirrorBigUnion",
+ "FernUnionWithLiteral",
+ "FirstItemType",
+ "FirstItemTypeUnionWithDuplicativeDiscriminants",
+ "Foo",
+ "Foo1UnionWithDuplicateTypes",
+ "Foo2UnionWithDuplicateTypes",
+ "FooExtended",
+ "FooExtendedUnionWithSubTypes",
+ "FooUnion",
+ "FooUnionWithBaseProperties",
+ "FooUnionWithDiscriminant",
+ "FooUnionWithMultipleNoProperties",
+ "FooUnionWithNoProperties",
+ "FooUnionWithNullableReference",
+ "FooUnionWithOptionalReference",
+ "FooUnionWithSingleElement",
+ "FooUnionWithSubTypes",
+ "FooUnionWithoutKey",
+ "FrozenSleep",
+ "FrozenSleepBigUnion",
+ "GaseousRoad",
+ "GaseousRoadBigUnion",
+ "GetShapeRequest",
+ "GruesomeCoach",
+ "GruesomeCoachBigUnion",
+ "HarmoniousPlay",
+ "HarmoniousPlayBigUnion",
+ "HastyPain",
+ "HastyPainBigUnion",
+ "HoarseMouse",
+ "HoarseMouseBigUnion",
+ "Integer1UnionWithDuplicatePrimitive",
+ "Integer2UnionWithDuplicatePrimitive",
+ "IntegerUnionWithBaseProperties",
+ "IntegerUnionWithPrimitive",
+ "JumboEnd",
+ "JumboEndBigUnion",
+ "LimpingStep",
+ "LimpingStepBigUnion",
+ "MistySnow",
+ "MistySnowBigUnion",
+ "NegativeIntUnionWithSameNumberTypes",
+ "NormalSweet",
+ "NormalSweetBigUnion",
+ "PatternStringUnionWithSameStringTypes",
+ "PopularLimit",
+ "PopularLimitBigUnion",
+ "PositiveIntUnionWithSameNumberTypes",
+ "PotableBad",
+ "PotableBadBigUnion",
+ "PracticalPrinciple",
+ "PracticalPrincipleBigUnion",
+ "PrimaryBlock",
+ "PrimaryBlockBigUnion",
+ "RegularStringUnionWithSameStringTypes",
+ "RotatingRatio",
+ "RotatingRatioBigUnion",
+ "SecondItemType",
+ "SecondItemTypeUnionWithDuplicativeDiscriminants",
+ "SeedUnions",
+ "Shape",
+ "Square",
+ "SquareShape",
+ "String1UnionWithDuplicatePrimitive",
+ "String2UnionWithDuplicatePrimitive",
+ "StringUnionWithBaseProperties",
+ "StringUnionWithPrimitive",
+ "ThankfulFactor",
+ "ThankfulFactorBigUnion",
+ "TotalWork",
+ "TotalWorkBigUnion",
+ "TriangularRepair",
+ "TriangularRepairBigUnion",
+ "TypeWithOptionalMap",
+ "Union",
+ "UnionWithBaseProperties",
+ "UnionWithDiscriminant",
+ "UnionWithDuplicatePrimitive",
+ "UnionWithDuplicateTypes",
+ "UnionWithDuplicativeDiscriminants",
+ "UnionWithLiteral",
+ "UnionWithMultipleNoProperties",
+ "UnionWithNoProperties",
+ "UnionWithNullableReference",
+ "UnionWithOptionalReference",
+ "UnionWithOptionalTime",
+ "UnionWithPrimitive",
+ "UnionWithSameNumberTypes",
+ "UnionWithSameStringTypes",
+ "UnionWithSingleElement",
+ "UnionWithSubTypes",
+ "UnionWithTime",
+ "UnionWithoutKey",
+ "UniqueStress",
+ "UniqueStressBigUnion",
+ "UnwillingSmoke",
+ "UnwillingSmokeBigUnion",
+ "ValueUnionWithTime",
+ "VibrantExcitement",
+ "VibrantExcitementBigUnion",
+ "WithName",
+ "__version__",
+ "bigunion",
+ "types",
+ "union",
+]
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/_default_clients.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/_default_clients.py
new file mode 100644
index 000000000000..d2858ce48a93
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/_default_clients.py
@@ -0,0 +1,30 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import httpx
+
+SDK_DEFAULT_TIMEOUT = 60
+
+try:
+ import httpx_aiohttp # type: ignore[import-not-found]
+except ImportError:
+
+ class DefaultAioHttpClient(httpx.AsyncClient): # type: ignore
+ def __init__(self, **kwargs: typing.Any) -> None:
+ raise RuntimeError("To use the aiohttp client, install the aiohttp extra: pip install fern_unions[aiohttp]")
+
+else:
+
+ class DefaultAioHttpClient(httpx_aiohttp.HttpxAiohttpClient): # type: ignore
+ def __init__(self, **kwargs: typing.Any) -> None:
+ kwargs.setdefault("timeout", SDK_DEFAULT_TIMEOUT)
+ kwargs.setdefault("follow_redirects", True)
+ super().__init__(**kwargs)
+
+
+class DefaultAsyncHttpxClient(httpx.AsyncClient):
+ def __init__(self, **kwargs: typing.Any) -> None:
+ kwargs.setdefault("timeout", SDK_DEFAULT_TIMEOUT)
+ kwargs.setdefault("follow_redirects", True)
+ super().__init__(**kwargs)
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/__init__.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/__init__.py
new file mode 100644
index 000000000000..e918bbd31d2e
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/__init__.py
@@ -0,0 +1,214 @@
+# This file was auto-generated by Fern from our API Definition.
+
+# isort: skip_file
+
+import typing
+from importlib import import_module
+
+if typing.TYPE_CHECKING:
+ from .types import (
+ ActiveDiamond,
+ ActiveDiamondBigUnion,
+ AttractiveScript,
+ AttractiveScriptBigUnion,
+ BigUnion,
+ CircularCard,
+ CircularCardBigUnion,
+ ColorfulCover,
+ ColorfulCoverBigUnion,
+ DiligentDeal,
+ DiligentDealBigUnion,
+ DisloyalValue,
+ DisloyalValueBigUnion,
+ DistinctFailure,
+ DistinctFailureBigUnion,
+ FalseMirror,
+ FalseMirrorBigUnion,
+ FrozenSleep,
+ FrozenSleepBigUnion,
+ GaseousRoad,
+ GaseousRoadBigUnion,
+ GruesomeCoach,
+ GruesomeCoachBigUnion,
+ HarmoniousPlay,
+ HarmoniousPlayBigUnion,
+ HastyPain,
+ HastyPainBigUnion,
+ HoarseMouse,
+ HoarseMouseBigUnion,
+ JumboEnd,
+ JumboEndBigUnion,
+ LimpingStep,
+ LimpingStepBigUnion,
+ MistySnow,
+ MistySnowBigUnion,
+ NormalSweet,
+ NormalSweetBigUnion,
+ PopularLimit,
+ PopularLimitBigUnion,
+ PotableBad,
+ PotableBadBigUnion,
+ PracticalPrinciple,
+ PracticalPrincipleBigUnion,
+ PrimaryBlock,
+ PrimaryBlockBigUnion,
+ RotatingRatio,
+ RotatingRatioBigUnion,
+ ThankfulFactor,
+ ThankfulFactorBigUnion,
+ TotalWork,
+ TotalWorkBigUnion,
+ TriangularRepair,
+ TriangularRepairBigUnion,
+ UniqueStress,
+ UniqueStressBigUnion,
+ UnwillingSmoke,
+ UnwillingSmokeBigUnion,
+ VibrantExcitement,
+ VibrantExcitementBigUnion,
+ )
+_dynamic_imports: typing.Dict[str, str] = {
+ "ActiveDiamond": ".types",
+ "ActiveDiamondBigUnion": ".types",
+ "AttractiveScript": ".types",
+ "AttractiveScriptBigUnion": ".types",
+ "BigUnion": ".types",
+ "CircularCard": ".types",
+ "CircularCardBigUnion": ".types",
+ "ColorfulCover": ".types",
+ "ColorfulCoverBigUnion": ".types",
+ "DiligentDeal": ".types",
+ "DiligentDealBigUnion": ".types",
+ "DisloyalValue": ".types",
+ "DisloyalValueBigUnion": ".types",
+ "DistinctFailure": ".types",
+ "DistinctFailureBigUnion": ".types",
+ "FalseMirror": ".types",
+ "FalseMirrorBigUnion": ".types",
+ "FrozenSleep": ".types",
+ "FrozenSleepBigUnion": ".types",
+ "GaseousRoad": ".types",
+ "GaseousRoadBigUnion": ".types",
+ "GruesomeCoach": ".types",
+ "GruesomeCoachBigUnion": ".types",
+ "HarmoniousPlay": ".types",
+ "HarmoniousPlayBigUnion": ".types",
+ "HastyPain": ".types",
+ "HastyPainBigUnion": ".types",
+ "HoarseMouse": ".types",
+ "HoarseMouseBigUnion": ".types",
+ "JumboEnd": ".types",
+ "JumboEndBigUnion": ".types",
+ "LimpingStep": ".types",
+ "LimpingStepBigUnion": ".types",
+ "MistySnow": ".types",
+ "MistySnowBigUnion": ".types",
+ "NormalSweet": ".types",
+ "NormalSweetBigUnion": ".types",
+ "PopularLimit": ".types",
+ "PopularLimitBigUnion": ".types",
+ "PotableBad": ".types",
+ "PotableBadBigUnion": ".types",
+ "PracticalPrinciple": ".types",
+ "PracticalPrincipleBigUnion": ".types",
+ "PrimaryBlock": ".types",
+ "PrimaryBlockBigUnion": ".types",
+ "RotatingRatio": ".types",
+ "RotatingRatioBigUnion": ".types",
+ "ThankfulFactor": ".types",
+ "ThankfulFactorBigUnion": ".types",
+ "TotalWork": ".types",
+ "TotalWorkBigUnion": ".types",
+ "TriangularRepair": ".types",
+ "TriangularRepairBigUnion": ".types",
+ "UniqueStress": ".types",
+ "UniqueStressBigUnion": ".types",
+ "UnwillingSmoke": ".types",
+ "UnwillingSmokeBigUnion": ".types",
+ "VibrantExcitement": ".types",
+ "VibrantExcitementBigUnion": ".types",
+}
+
+
+def __getattr__(attr_name: str) -> typing.Any:
+ module_name = _dynamic_imports.get(attr_name)
+ if module_name is None:
+ raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}")
+ try:
+ module = import_module(module_name, __package__)
+ if module_name == f".{attr_name}":
+ return module
+ else:
+ return getattr(module, attr_name)
+ except ImportError as e:
+ raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e
+ except AttributeError as e:
+ raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e
+
+
+def __dir__():
+ lazy_attrs = list(_dynamic_imports.keys())
+ return sorted(lazy_attrs)
+
+
+__all__ = [
+ "ActiveDiamond",
+ "ActiveDiamondBigUnion",
+ "AttractiveScript",
+ "AttractiveScriptBigUnion",
+ "BigUnion",
+ "CircularCard",
+ "CircularCardBigUnion",
+ "ColorfulCover",
+ "ColorfulCoverBigUnion",
+ "DiligentDeal",
+ "DiligentDealBigUnion",
+ "DisloyalValue",
+ "DisloyalValueBigUnion",
+ "DistinctFailure",
+ "DistinctFailureBigUnion",
+ "FalseMirror",
+ "FalseMirrorBigUnion",
+ "FrozenSleep",
+ "FrozenSleepBigUnion",
+ "GaseousRoad",
+ "GaseousRoadBigUnion",
+ "GruesomeCoach",
+ "GruesomeCoachBigUnion",
+ "HarmoniousPlay",
+ "HarmoniousPlayBigUnion",
+ "HastyPain",
+ "HastyPainBigUnion",
+ "HoarseMouse",
+ "HoarseMouseBigUnion",
+ "JumboEnd",
+ "JumboEndBigUnion",
+ "LimpingStep",
+ "LimpingStepBigUnion",
+ "MistySnow",
+ "MistySnowBigUnion",
+ "NormalSweet",
+ "NormalSweetBigUnion",
+ "PopularLimit",
+ "PopularLimitBigUnion",
+ "PotableBad",
+ "PotableBadBigUnion",
+ "PracticalPrinciple",
+ "PracticalPrincipleBigUnion",
+ "PrimaryBlock",
+ "PrimaryBlockBigUnion",
+ "RotatingRatio",
+ "RotatingRatioBigUnion",
+ "ThankfulFactor",
+ "ThankfulFactorBigUnion",
+ "TotalWork",
+ "TotalWorkBigUnion",
+ "TriangularRepair",
+ "TriangularRepairBigUnion",
+ "UniqueStress",
+ "UniqueStressBigUnion",
+ "UnwillingSmoke",
+ "UnwillingSmokeBigUnion",
+ "VibrantExcitement",
+ "VibrantExcitementBigUnion",
+]
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/client.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/client.py
new file mode 100644
index 000000000000..4134e30de3bc
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/client.py
@@ -0,0 +1,255 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper
+from ..core.request_options import RequestOptions
+from .raw_client import AsyncRawBigunionClient, RawBigunionClient
+from .types.big_union import BigUnion
+
+# this is used as the default value for optional parameters
+OMIT = typing.cast(typing.Any, ...)
+
+
+class BigunionClient:
+ def __init__(self, *, client_wrapper: SyncClientWrapper):
+ self._raw_client = RawBigunionClient(client_wrapper=client_wrapper)
+
+ @property
+ def with_raw_response(self) -> RawBigunionClient:
+ """
+ Retrieves a raw implementation of this client that returns raw responses.
+
+ Returns
+ -------
+ RawBigunionClient
+ """
+ return self._raw_client
+
+ def get(self, id: str, *, request_options: typing.Optional[RequestOptions] = None) -> BigUnion:
+ """
+ Parameters
+ ----------
+ id : str
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ BigUnion
+
+ Examples
+ --------
+ from seed import SeedUnions
+
+ client = SeedUnions(
+ base_url="https://yourhost.com/path/to/api",
+ )
+ client.bigunion.get(
+ id="id",
+ )
+ """
+ _response = self._raw_client.get(id, request_options=request_options)
+ return _response.data
+
+ def update(self, *, request: BigUnion, request_options: typing.Optional[RequestOptions] = None) -> bool:
+ """
+ Parameters
+ ----------
+ request : BigUnion
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ bool
+
+ Examples
+ --------
+ from seed import SeedUnions
+ from seed.bigunion import NormalSweetBigUnion
+
+ client = SeedUnions(
+ base_url="https://yourhost.com/path/to/api",
+ )
+ client.bigunion.update(
+ request=NormalSweetBigUnion(
+ value="value",
+ ),
+ )
+ """
+ _response = self._raw_client.update(request=request, request_options=request_options)
+ return _response.data
+
+ def update_many(
+ self, *, request: typing.Sequence[BigUnion], request_options: typing.Optional[RequestOptions] = None
+ ) -> typing.Dict[str, bool]:
+ """
+ Parameters
+ ----------
+ request : typing.Sequence[BigUnion]
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ typing.Dict[str, bool]
+
+ Examples
+ --------
+ from seed import SeedUnions
+ from seed.bigunion import NormalSweetBigUnion
+
+ client = SeedUnions(
+ base_url="https://yourhost.com/path/to/api",
+ )
+ client.bigunion.update_many(
+ request=[
+ NormalSweetBigUnion(
+ value="value",
+ ),
+ NormalSweetBigUnion(
+ value="value",
+ ),
+ ],
+ )
+ """
+ _response = self._raw_client.update_many(request=request, request_options=request_options)
+ return _response.data
+
+
+class AsyncBigunionClient:
+ def __init__(self, *, client_wrapper: AsyncClientWrapper):
+ self._raw_client = AsyncRawBigunionClient(client_wrapper=client_wrapper)
+
+ @property
+ def with_raw_response(self) -> AsyncRawBigunionClient:
+ """
+ Retrieves a raw implementation of this client that returns raw responses.
+
+ Returns
+ -------
+ AsyncRawBigunionClient
+ """
+ return self._raw_client
+
+ async def get(self, id: str, *, request_options: typing.Optional[RequestOptions] = None) -> BigUnion:
+ """
+ Parameters
+ ----------
+ id : str
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ BigUnion
+
+ Examples
+ --------
+ import asyncio
+
+ from seed import AsyncSeedUnions
+
+ client = AsyncSeedUnions(
+ base_url="https://yourhost.com/path/to/api",
+ )
+
+
+ async def main() -> None:
+ await client.bigunion.get(
+ id="id",
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.get(id, request_options=request_options)
+ return _response.data
+
+ async def update(self, *, request: BigUnion, request_options: typing.Optional[RequestOptions] = None) -> bool:
+ """
+ Parameters
+ ----------
+ request : BigUnion
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ bool
+
+ Examples
+ --------
+ import asyncio
+
+ from seed import AsyncSeedUnions
+ from seed.bigunion import NormalSweetBigUnion
+
+ client = AsyncSeedUnions(
+ base_url="https://yourhost.com/path/to/api",
+ )
+
+
+ async def main() -> None:
+ await client.bigunion.update(
+ request=NormalSweetBigUnion(
+ value="value",
+ ),
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.update(request=request, request_options=request_options)
+ return _response.data
+
+ async def update_many(
+ self, *, request: typing.Sequence[BigUnion], request_options: typing.Optional[RequestOptions] = None
+ ) -> typing.Dict[str, bool]:
+ """
+ Parameters
+ ----------
+ request : typing.Sequence[BigUnion]
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ typing.Dict[str, bool]
+
+ Examples
+ --------
+ import asyncio
+
+ from seed import AsyncSeedUnions
+ from seed.bigunion import NormalSweetBigUnion
+
+ client = AsyncSeedUnions(
+ base_url="https://yourhost.com/path/to/api",
+ )
+
+
+ async def main() -> None:
+ await client.bigunion.update_many(
+ request=[
+ NormalSweetBigUnion(
+ value="value",
+ ),
+ NormalSweetBigUnion(
+ value="value",
+ ),
+ ],
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.update_many(request=request, request_options=request_options)
+ return _response.data
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/raw_client.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/raw_client.py
new file mode 100644
index 000000000000..5daf655747d5
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/raw_client.py
@@ -0,0 +1,270 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+from json.decoder import JSONDecodeError
+
+from ..core.api_error import ApiError
+from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper
+from ..core.http_response import AsyncHttpResponse, HttpResponse
+from ..core.jsonable_encoder import encode_path_param
+from ..core.parse_error import ParsingError
+from ..core.pydantic_utilities import parse_obj_as
+from ..core.request_options import RequestOptions
+from ..core.serialization import convert_and_respect_annotation_metadata
+from .types.big_union import BigUnion
+from pydantic import ValidationError
+
+# this is used as the default value for optional parameters
+OMIT = typing.cast(typing.Any, ...)
+
+
+class RawBigunionClient:
+ def __init__(self, *, client_wrapper: SyncClientWrapper):
+ self._client_wrapper = client_wrapper
+
+ def get(self, id: str, *, request_options: typing.Optional[RequestOptions] = None) -> HttpResponse[BigUnion]:
+ """
+ Parameters
+ ----------
+ id : str
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[BigUnion]
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"{encode_path_param(id)}",
+ method="GET",
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ BigUnion,
+ parse_obj_as(
+ type_=BigUnion, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ _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 update(
+ self, *, request: BigUnion, request_options: typing.Optional[RequestOptions] = None
+ ) -> HttpResponse[bool]:
+ """
+ Parameters
+ ----------
+ request : BigUnion
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[bool]
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ method="PATCH",
+ json=convert_and_respect_annotation_metadata(object_=request, annotation=BigUnion, direction="write"),
+ request_options=request_options,
+ omit=OMIT,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ bool,
+ parse_obj_as(
+ type_=bool, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ _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 update_many(
+ self, *, request: typing.Sequence[BigUnion], request_options: typing.Optional[RequestOptions] = None
+ ) -> HttpResponse[typing.Dict[str, bool]]:
+ """
+ Parameters
+ ----------
+ request : typing.Sequence[BigUnion]
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[typing.Dict[str, bool]]
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ "many",
+ method="PATCH",
+ json=convert_and_respect_annotation_metadata(
+ object_=request, annotation=typing.Sequence[BigUnion], direction="write"
+ ),
+ request_options=request_options,
+ omit=OMIT,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ typing.Dict[str, bool],
+ parse_obj_as(
+ type_=typing.Dict[str, bool], # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ _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 AsyncRawBigunionClient:
+ def __init__(self, *, client_wrapper: AsyncClientWrapper):
+ self._client_wrapper = client_wrapper
+
+ async def get(
+ self, id: str, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> AsyncHttpResponse[BigUnion]:
+ """
+ Parameters
+ ----------
+ id : str
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[BigUnion]
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"{encode_path_param(id)}",
+ method="GET",
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ BigUnion,
+ parse_obj_as(
+ type_=BigUnion, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ _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 update(
+ self, *, request: BigUnion, request_options: typing.Optional[RequestOptions] = None
+ ) -> AsyncHttpResponse[bool]:
+ """
+ Parameters
+ ----------
+ request : BigUnion
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[bool]
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ method="PATCH",
+ json=convert_and_respect_annotation_metadata(object_=request, annotation=BigUnion, direction="write"),
+ request_options=request_options,
+ omit=OMIT,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ bool,
+ parse_obj_as(
+ type_=bool, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ _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 update_many(
+ self, *, request: typing.Sequence[BigUnion], request_options: typing.Optional[RequestOptions] = None
+ ) -> AsyncHttpResponse[typing.Dict[str, bool]]:
+ """
+ Parameters
+ ----------
+ request : typing.Sequence[BigUnion]
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[typing.Dict[str, bool]]
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ "many",
+ method="PATCH",
+ json=convert_and_respect_annotation_metadata(
+ object_=request, annotation=typing.Sequence[BigUnion], direction="write"
+ ),
+ request_options=request_options,
+ omit=OMIT,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ typing.Dict[str, bool],
+ parse_obj_as(
+ type_=typing.Dict[str, bool], # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ _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/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/__init__.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/__init__.py
new file mode 100644
index 000000000000..afaf4ad0990c
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/__init__.py
@@ -0,0 +1,214 @@
+# This file was auto-generated by Fern from our API Definition.
+
+# isort: skip_file
+
+import typing
+from importlib import import_module
+
+if typing.TYPE_CHECKING:
+ from .active_diamond import ActiveDiamond
+ from .attractive_script import AttractiveScript
+ from .big_union import (
+ ActiveDiamondBigUnion,
+ AttractiveScriptBigUnion,
+ BigUnion,
+ CircularCardBigUnion,
+ ColorfulCoverBigUnion,
+ DiligentDealBigUnion,
+ DisloyalValueBigUnion,
+ DistinctFailureBigUnion,
+ FalseMirrorBigUnion,
+ FrozenSleepBigUnion,
+ GaseousRoadBigUnion,
+ GruesomeCoachBigUnion,
+ HarmoniousPlayBigUnion,
+ HastyPainBigUnion,
+ HoarseMouseBigUnion,
+ JumboEndBigUnion,
+ LimpingStepBigUnion,
+ MistySnowBigUnion,
+ NormalSweetBigUnion,
+ PopularLimitBigUnion,
+ PotableBadBigUnion,
+ PracticalPrincipleBigUnion,
+ PrimaryBlockBigUnion,
+ RotatingRatioBigUnion,
+ ThankfulFactorBigUnion,
+ TotalWorkBigUnion,
+ TriangularRepairBigUnion,
+ UniqueStressBigUnion,
+ UnwillingSmokeBigUnion,
+ VibrantExcitementBigUnion,
+ )
+ from .circular_card import CircularCard
+ from .colorful_cover import ColorfulCover
+ from .diligent_deal import DiligentDeal
+ from .disloyal_value import DisloyalValue
+ from .distinct_failure import DistinctFailure
+ from .false_mirror import FalseMirror
+ from .frozen_sleep import FrozenSleep
+ from .gaseous_road import GaseousRoad
+ from .gruesome_coach import GruesomeCoach
+ from .harmonious_play import HarmoniousPlay
+ from .hasty_pain import HastyPain
+ from .hoarse_mouse import HoarseMouse
+ from .jumbo_end import JumboEnd
+ from .limping_step import LimpingStep
+ from .misty_snow import MistySnow
+ from .normal_sweet import NormalSweet
+ from .popular_limit import PopularLimit
+ from .potable_bad import PotableBad
+ from .practical_principle import PracticalPrinciple
+ from .primary_block import PrimaryBlock
+ from .rotating_ratio import RotatingRatio
+ from .thankful_factor import ThankfulFactor
+ from .total_work import TotalWork
+ from .triangular_repair import TriangularRepair
+ from .unique_stress import UniqueStress
+ from .unwilling_smoke import UnwillingSmoke
+ from .vibrant_excitement import VibrantExcitement
+_dynamic_imports: typing.Dict[str, str] = {
+ "ActiveDiamond": ".active_diamond",
+ "ActiveDiamondBigUnion": ".big_union",
+ "AttractiveScript": ".attractive_script",
+ "AttractiveScriptBigUnion": ".big_union",
+ "BigUnion": ".big_union",
+ "CircularCard": ".circular_card",
+ "CircularCardBigUnion": ".big_union",
+ "ColorfulCover": ".colorful_cover",
+ "ColorfulCoverBigUnion": ".big_union",
+ "DiligentDeal": ".diligent_deal",
+ "DiligentDealBigUnion": ".big_union",
+ "DisloyalValue": ".disloyal_value",
+ "DisloyalValueBigUnion": ".big_union",
+ "DistinctFailure": ".distinct_failure",
+ "DistinctFailureBigUnion": ".big_union",
+ "FalseMirror": ".false_mirror",
+ "FalseMirrorBigUnion": ".big_union",
+ "FrozenSleep": ".frozen_sleep",
+ "FrozenSleepBigUnion": ".big_union",
+ "GaseousRoad": ".gaseous_road",
+ "GaseousRoadBigUnion": ".big_union",
+ "GruesomeCoach": ".gruesome_coach",
+ "GruesomeCoachBigUnion": ".big_union",
+ "HarmoniousPlay": ".harmonious_play",
+ "HarmoniousPlayBigUnion": ".big_union",
+ "HastyPain": ".hasty_pain",
+ "HastyPainBigUnion": ".big_union",
+ "HoarseMouse": ".hoarse_mouse",
+ "HoarseMouseBigUnion": ".big_union",
+ "JumboEnd": ".jumbo_end",
+ "JumboEndBigUnion": ".big_union",
+ "LimpingStep": ".limping_step",
+ "LimpingStepBigUnion": ".big_union",
+ "MistySnow": ".misty_snow",
+ "MistySnowBigUnion": ".big_union",
+ "NormalSweet": ".normal_sweet",
+ "NormalSweetBigUnion": ".big_union",
+ "PopularLimit": ".popular_limit",
+ "PopularLimitBigUnion": ".big_union",
+ "PotableBad": ".potable_bad",
+ "PotableBadBigUnion": ".big_union",
+ "PracticalPrinciple": ".practical_principle",
+ "PracticalPrincipleBigUnion": ".big_union",
+ "PrimaryBlock": ".primary_block",
+ "PrimaryBlockBigUnion": ".big_union",
+ "RotatingRatio": ".rotating_ratio",
+ "RotatingRatioBigUnion": ".big_union",
+ "ThankfulFactor": ".thankful_factor",
+ "ThankfulFactorBigUnion": ".big_union",
+ "TotalWork": ".total_work",
+ "TotalWorkBigUnion": ".big_union",
+ "TriangularRepair": ".triangular_repair",
+ "TriangularRepairBigUnion": ".big_union",
+ "UniqueStress": ".unique_stress",
+ "UniqueStressBigUnion": ".big_union",
+ "UnwillingSmoke": ".unwilling_smoke",
+ "UnwillingSmokeBigUnion": ".big_union",
+ "VibrantExcitement": ".vibrant_excitement",
+ "VibrantExcitementBigUnion": ".big_union",
+}
+
+
+def __getattr__(attr_name: str) -> typing.Any:
+ module_name = _dynamic_imports.get(attr_name)
+ if module_name is None:
+ raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}")
+ try:
+ module = import_module(module_name, __package__)
+ if module_name == f".{attr_name}":
+ return module
+ else:
+ return getattr(module, attr_name)
+ except ImportError as e:
+ raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e
+ except AttributeError as e:
+ raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e
+
+
+def __dir__():
+ lazy_attrs = list(_dynamic_imports.keys())
+ return sorted(lazy_attrs)
+
+
+__all__ = [
+ "ActiveDiamond",
+ "ActiveDiamondBigUnion",
+ "AttractiveScript",
+ "AttractiveScriptBigUnion",
+ "BigUnion",
+ "CircularCard",
+ "CircularCardBigUnion",
+ "ColorfulCover",
+ "ColorfulCoverBigUnion",
+ "DiligentDeal",
+ "DiligentDealBigUnion",
+ "DisloyalValue",
+ "DisloyalValueBigUnion",
+ "DistinctFailure",
+ "DistinctFailureBigUnion",
+ "FalseMirror",
+ "FalseMirrorBigUnion",
+ "FrozenSleep",
+ "FrozenSleepBigUnion",
+ "GaseousRoad",
+ "GaseousRoadBigUnion",
+ "GruesomeCoach",
+ "GruesomeCoachBigUnion",
+ "HarmoniousPlay",
+ "HarmoniousPlayBigUnion",
+ "HastyPain",
+ "HastyPainBigUnion",
+ "HoarseMouse",
+ "HoarseMouseBigUnion",
+ "JumboEnd",
+ "JumboEndBigUnion",
+ "LimpingStep",
+ "LimpingStepBigUnion",
+ "MistySnow",
+ "MistySnowBigUnion",
+ "NormalSweet",
+ "NormalSweetBigUnion",
+ "PopularLimit",
+ "PopularLimitBigUnion",
+ "PotableBad",
+ "PotableBadBigUnion",
+ "PracticalPrinciple",
+ "PracticalPrincipleBigUnion",
+ "PrimaryBlock",
+ "PrimaryBlockBigUnion",
+ "RotatingRatio",
+ "RotatingRatioBigUnion",
+ "ThankfulFactor",
+ "ThankfulFactorBigUnion",
+ "TotalWork",
+ "TotalWorkBigUnion",
+ "TriangularRepair",
+ "TriangularRepairBigUnion",
+ "UniqueStress",
+ "UniqueStressBigUnion",
+ "UnwillingSmoke",
+ "UnwillingSmokeBigUnion",
+ "VibrantExcitement",
+ "VibrantExcitementBigUnion",
+]
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/active_diamond.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/active_diamond.py
new file mode 100644
index 000000000000..0e4b77a2e70f
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/active_diamond.py
@@ -0,0 +1,19 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class ActiveDiamond(UniversalBaseModel):
+ value: str
+
+ 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/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/attractive_script.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/attractive_script.py
new file mode 100644
index 000000000000..eed322ce1ca9
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/attractive_script.py
@@ -0,0 +1,19 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class AttractiveScript(UniversalBaseModel):
+ value: str
+
+ 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/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/big_union.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/big_union.py
new file mode 100644
index 000000000000..97dbc873615c
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/big_union.py
@@ -0,0 +1,779 @@
+# This file was auto-generated by Fern from our API Definition.
+
+from __future__ import annotations
+
+import datetime as dt
+import typing
+
+import pydantic
+import typing_extensions
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ...core.serialization import FieldMetadata
+
+
+class Base(UniversalBaseModel):
+ """
+ Examples
+ --------
+ from seed.bigunion import NormalSweetBigUnion
+
+ NormalSweetBigUnion(
+ value="example1",
+ )
+ """
+
+ id: str
+ created_at: typing_extensions.Annotated[
+ dt.datetime, FieldMetadata(alias="created-at"), pydantic.Field(alias="created-at")
+ ]
+ archived_at: typing_extensions.Annotated[
+ typing.Optional[dt.datetime], FieldMetadata(alias="archived-at"), pydantic.Field(alias="archived-at")
+ ] = 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
+
+
+class NormalSweetBigUnion(Base):
+ """
+ Examples
+ --------
+ from seed.bigunion import NormalSweetBigUnion
+
+ NormalSweetBigUnion(
+ value="example1",
+ )
+ """
+
+ type: typing.Literal["normalSweet"] = "normalSweet"
+ value: str
+
+ 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
+
+
+class ThankfulFactorBigUnion(Base):
+ """
+ Examples
+ --------
+ from seed.bigunion import NormalSweetBigUnion
+
+ NormalSweetBigUnion(
+ value="example1",
+ )
+ """
+
+ type: typing.Literal["thankfulFactor"] = "thankfulFactor"
+ value: str
+
+ 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
+
+
+class JumboEndBigUnion(Base):
+ """
+ Examples
+ --------
+ from seed.bigunion import NormalSweetBigUnion
+
+ NormalSweetBigUnion(
+ value="example1",
+ )
+ """
+
+ type: typing.Literal["jumboEnd"] = "jumboEnd"
+ value: str
+
+ 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
+
+
+class HastyPainBigUnion(Base):
+ """
+ Examples
+ --------
+ from seed.bigunion import NormalSweetBigUnion
+
+ NormalSweetBigUnion(
+ value="example1",
+ )
+ """
+
+ type: typing.Literal["hastyPain"] = "hastyPain"
+ value: str
+
+ 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
+
+
+class MistySnowBigUnion(Base):
+ """
+ Examples
+ --------
+ from seed.bigunion import NormalSweetBigUnion
+
+ NormalSweetBigUnion(
+ value="example1",
+ )
+ """
+
+ type: typing.Literal["mistySnow"] = "mistySnow"
+ value: str
+
+ 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
+
+
+class DistinctFailureBigUnion(Base):
+ """
+ Examples
+ --------
+ from seed.bigunion import NormalSweetBigUnion
+
+ NormalSweetBigUnion(
+ value="example1",
+ )
+ """
+
+ type: typing.Literal["distinctFailure"] = "distinctFailure"
+ value: str
+
+ 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
+
+
+class PracticalPrincipleBigUnion(Base):
+ """
+ Examples
+ --------
+ from seed.bigunion import NormalSweetBigUnion
+
+ NormalSweetBigUnion(
+ value="example1",
+ )
+ """
+
+ type: typing.Literal["practicalPrinciple"] = "practicalPrinciple"
+ value: str
+
+ 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
+
+
+class LimpingStepBigUnion(Base):
+ """
+ Examples
+ --------
+ from seed.bigunion import NormalSweetBigUnion
+
+ NormalSweetBigUnion(
+ value="example1",
+ )
+ """
+
+ type: typing.Literal["limpingStep"] = "limpingStep"
+ value: str
+
+ 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
+
+
+class VibrantExcitementBigUnion(Base):
+ """
+ Examples
+ --------
+ from seed.bigunion import NormalSweetBigUnion
+
+ NormalSweetBigUnion(
+ value="example1",
+ )
+ """
+
+ type: typing.Literal["vibrantExcitement"] = "vibrantExcitement"
+ value: str
+
+ 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
+
+
+class ActiveDiamondBigUnion(Base):
+ """
+ Examples
+ --------
+ from seed.bigunion import NormalSweetBigUnion
+
+ NormalSweetBigUnion(
+ value="example1",
+ )
+ """
+
+ type: typing.Literal["activeDiamond"] = "activeDiamond"
+ value: str
+
+ 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
+
+
+class PopularLimitBigUnion(Base):
+ """
+ Examples
+ --------
+ from seed.bigunion import NormalSweetBigUnion
+
+ NormalSweetBigUnion(
+ value="example1",
+ )
+ """
+
+ type: typing.Literal["popularLimit"] = "popularLimit"
+ value: str
+
+ 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
+
+
+class FalseMirrorBigUnion(Base):
+ """
+ Examples
+ --------
+ from seed.bigunion import NormalSweetBigUnion
+
+ NormalSweetBigUnion(
+ value="example1",
+ )
+ """
+
+ type: typing.Literal["falseMirror"] = "falseMirror"
+ value: str
+
+ 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
+
+
+class PrimaryBlockBigUnion(Base):
+ """
+ Examples
+ --------
+ from seed.bigunion import NormalSweetBigUnion
+
+ NormalSweetBigUnion(
+ value="example1",
+ )
+ """
+
+ type: typing.Literal["primaryBlock"] = "primaryBlock"
+ value: str
+
+ 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
+
+
+class RotatingRatioBigUnion(Base):
+ """
+ Examples
+ --------
+ from seed.bigunion import NormalSweetBigUnion
+
+ NormalSweetBigUnion(
+ value="example1",
+ )
+ """
+
+ type: typing.Literal["rotatingRatio"] = "rotatingRatio"
+ value: str
+
+ 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
+
+
+class ColorfulCoverBigUnion(Base):
+ """
+ Examples
+ --------
+ from seed.bigunion import NormalSweetBigUnion
+
+ NormalSweetBigUnion(
+ value="example1",
+ )
+ """
+
+ type: typing.Literal["colorfulCover"] = "colorfulCover"
+ value: str
+
+ 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
+
+
+class DisloyalValueBigUnion(Base):
+ """
+ Examples
+ --------
+ from seed.bigunion import NormalSweetBigUnion
+
+ NormalSweetBigUnion(
+ value="example1",
+ )
+ """
+
+ type: typing.Literal["disloyalValue"] = "disloyalValue"
+ value: str
+
+ 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
+
+
+class GruesomeCoachBigUnion(Base):
+ """
+ Examples
+ --------
+ from seed.bigunion import NormalSweetBigUnion
+
+ NormalSweetBigUnion(
+ value="example1",
+ )
+ """
+
+ type: typing.Literal["gruesomeCoach"] = "gruesomeCoach"
+ value: str
+
+ 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
+
+
+class TotalWorkBigUnion(Base):
+ """
+ Examples
+ --------
+ from seed.bigunion import NormalSweetBigUnion
+
+ NormalSweetBigUnion(
+ value="example1",
+ )
+ """
+
+ type: typing.Literal["totalWork"] = "totalWork"
+ value: str
+
+ 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
+
+
+class HarmoniousPlayBigUnion(Base):
+ """
+ Examples
+ --------
+ from seed.bigunion import NormalSweetBigUnion
+
+ NormalSweetBigUnion(
+ value="example1",
+ )
+ """
+
+ type: typing.Literal["harmoniousPlay"] = "harmoniousPlay"
+ value: str
+
+ 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
+
+
+class UniqueStressBigUnion(Base):
+ """
+ Examples
+ --------
+ from seed.bigunion import NormalSweetBigUnion
+
+ NormalSweetBigUnion(
+ value="example1",
+ )
+ """
+
+ type: typing.Literal["uniqueStress"] = "uniqueStress"
+ value: str
+
+ 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
+
+
+class UnwillingSmokeBigUnion(Base):
+ """
+ Examples
+ --------
+ from seed.bigunion import NormalSweetBigUnion
+
+ NormalSweetBigUnion(
+ value="example1",
+ )
+ """
+
+ type: typing.Literal["unwillingSmoke"] = "unwillingSmoke"
+ value: str
+
+ 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
+
+
+class FrozenSleepBigUnion(Base):
+ """
+ Examples
+ --------
+ from seed.bigunion import NormalSweetBigUnion
+
+ NormalSweetBigUnion(
+ value="example1",
+ )
+ """
+
+ type: typing.Literal["frozenSleep"] = "frozenSleep"
+ value: str
+
+ 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
+
+
+class DiligentDealBigUnion(Base):
+ """
+ Examples
+ --------
+ from seed.bigunion import NormalSweetBigUnion
+
+ NormalSweetBigUnion(
+ value="example1",
+ )
+ """
+
+ type: typing.Literal["diligentDeal"] = "diligentDeal"
+ value: str
+
+ 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
+
+
+class AttractiveScriptBigUnion(Base):
+ """
+ Examples
+ --------
+ from seed.bigunion import NormalSweetBigUnion
+
+ NormalSweetBigUnion(
+ value="example1",
+ )
+ """
+
+ type: typing.Literal["attractiveScript"] = "attractiveScript"
+ value: str
+
+ 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
+
+
+class HoarseMouseBigUnion(Base):
+ """
+ Examples
+ --------
+ from seed.bigunion import NormalSweetBigUnion
+
+ NormalSweetBigUnion(
+ value="example1",
+ )
+ """
+
+ type: typing.Literal["hoarseMouse"] = "hoarseMouse"
+ value: str
+
+ 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
+
+
+class CircularCardBigUnion(Base):
+ """
+ Examples
+ --------
+ from seed.bigunion import NormalSweetBigUnion
+
+ NormalSweetBigUnion(
+ value="example1",
+ )
+ """
+
+ type: typing.Literal["circularCard"] = "circularCard"
+ value: str
+
+ 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
+
+
+class PotableBadBigUnion(Base):
+ """
+ Examples
+ --------
+ from seed.bigunion import NormalSweetBigUnion
+
+ NormalSweetBigUnion(
+ value="example1",
+ )
+ """
+
+ type: typing.Literal["potableBad"] = "potableBad"
+ value: str
+
+ 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
+
+
+class TriangularRepairBigUnion(Base):
+ """
+ Examples
+ --------
+ from seed.bigunion import NormalSweetBigUnion
+
+ NormalSweetBigUnion(
+ value="example1",
+ )
+ """
+
+ type: typing.Literal["triangularRepair"] = "triangularRepair"
+ value: str
+
+ 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
+
+
+class GaseousRoadBigUnion(Base):
+ """
+ Examples
+ --------
+ from seed.bigunion import NormalSweetBigUnion
+
+ NormalSweetBigUnion(
+ value="example1",
+ )
+ """
+
+ type: typing.Literal["gaseousRoad"] = "gaseousRoad"
+ value: str
+
+ 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
+
+
+"""
+from seed.bigunion import NormalSweetBigUnion
+
+NormalSweetBigUnion(
+ value="example1",
+)
+"""
+BigUnion = typing_extensions.Annotated[
+ typing.Union[
+ NormalSweetBigUnion,
+ ThankfulFactorBigUnion,
+ JumboEndBigUnion,
+ HastyPainBigUnion,
+ MistySnowBigUnion,
+ DistinctFailureBigUnion,
+ PracticalPrincipleBigUnion,
+ LimpingStepBigUnion,
+ VibrantExcitementBigUnion,
+ ActiveDiamondBigUnion,
+ PopularLimitBigUnion,
+ FalseMirrorBigUnion,
+ PrimaryBlockBigUnion,
+ RotatingRatioBigUnion,
+ ColorfulCoverBigUnion,
+ DisloyalValueBigUnion,
+ GruesomeCoachBigUnion,
+ TotalWorkBigUnion,
+ HarmoniousPlayBigUnion,
+ UniqueStressBigUnion,
+ UnwillingSmokeBigUnion,
+ FrozenSleepBigUnion,
+ DiligentDealBigUnion,
+ AttractiveScriptBigUnion,
+ HoarseMouseBigUnion,
+ CircularCardBigUnion,
+ PotableBadBigUnion,
+ TriangularRepairBigUnion,
+ GaseousRoadBigUnion,
+ ],
+ pydantic.Field(discriminator="type"),
+]
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/circular_card.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/circular_card.py
new file mode 100644
index 000000000000..d884b0929a98
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/circular_card.py
@@ -0,0 +1,19 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class CircularCard(UniversalBaseModel):
+ value: str
+
+ 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/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/colorful_cover.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/colorful_cover.py
new file mode 100644
index 000000000000..b21d525b6a76
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/colorful_cover.py
@@ -0,0 +1,19 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class ColorfulCover(UniversalBaseModel):
+ value: str
+
+ 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/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/diligent_deal.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/diligent_deal.py
new file mode 100644
index 000000000000..45f5c2e67637
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/diligent_deal.py
@@ -0,0 +1,19 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class DiligentDeal(UniversalBaseModel):
+ value: str
+
+ 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/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/disloyal_value.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/disloyal_value.py
new file mode 100644
index 000000000000..cf02ae574266
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/disloyal_value.py
@@ -0,0 +1,19 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class DisloyalValue(UniversalBaseModel):
+ value: str
+
+ 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/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/distinct_failure.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/distinct_failure.py
new file mode 100644
index 000000000000..3ca8d28e40c2
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/distinct_failure.py
@@ -0,0 +1,19 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class DistinctFailure(UniversalBaseModel):
+ value: str
+
+ 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/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/false_mirror.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/false_mirror.py
new file mode 100644
index 000000000000..1e506ede0061
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/false_mirror.py
@@ -0,0 +1,19 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class FalseMirror(UniversalBaseModel):
+ value: str
+
+ 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/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/frozen_sleep.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/frozen_sleep.py
new file mode 100644
index 000000000000..6f5060657ee7
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/frozen_sleep.py
@@ -0,0 +1,19 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class FrozenSleep(UniversalBaseModel):
+ value: str
+
+ 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/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/gaseous_road.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/gaseous_road.py
new file mode 100644
index 000000000000..4def6aad7e89
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/gaseous_road.py
@@ -0,0 +1,19 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class GaseousRoad(UniversalBaseModel):
+ value: str
+
+ 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/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/gruesome_coach.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/gruesome_coach.py
new file mode 100644
index 000000000000..048ebeee82c3
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/gruesome_coach.py
@@ -0,0 +1,19 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class GruesomeCoach(UniversalBaseModel):
+ value: str
+
+ 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/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/harmonious_play.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/harmonious_play.py
new file mode 100644
index 000000000000..47db998a112b
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/harmonious_play.py
@@ -0,0 +1,19 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class HarmoniousPlay(UniversalBaseModel):
+ value: str
+
+ 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/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/hasty_pain.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/hasty_pain.py
new file mode 100644
index 000000000000..0f8064e7ebe6
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/hasty_pain.py
@@ -0,0 +1,19 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class HastyPain(UniversalBaseModel):
+ value: str
+
+ 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/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/hoarse_mouse.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/hoarse_mouse.py
new file mode 100644
index 000000000000..1b3628a5f9bc
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/hoarse_mouse.py
@@ -0,0 +1,19 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class HoarseMouse(UniversalBaseModel):
+ value: str
+
+ 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/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/jumbo_end.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/jumbo_end.py
new file mode 100644
index 000000000000..38586b8eaa95
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/jumbo_end.py
@@ -0,0 +1,19 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class JumboEnd(UniversalBaseModel):
+ value: str
+
+ 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/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/limping_step.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/limping_step.py
new file mode 100644
index 000000000000..3d7bbc7fd3bf
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/limping_step.py
@@ -0,0 +1,19 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class LimpingStep(UniversalBaseModel):
+ value: str
+
+ 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/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/misty_snow.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/misty_snow.py
new file mode 100644
index 000000000000..5436bdedf14e
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/misty_snow.py
@@ -0,0 +1,19 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class MistySnow(UniversalBaseModel):
+ value: str
+
+ 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/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/normal_sweet.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/normal_sweet.py
new file mode 100644
index 000000000000..2dd9efacf00d
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/normal_sweet.py
@@ -0,0 +1,29 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class NormalSweet(UniversalBaseModel):
+ """
+ Examples
+ --------
+ from seed.bigunion import NormalSweet
+
+ NormalSweet(
+ value="example1",
+ )
+ """
+
+ value: str
+
+ 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/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/popular_limit.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/popular_limit.py
new file mode 100644
index 000000000000..59a17270b490
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/popular_limit.py
@@ -0,0 +1,19 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class PopularLimit(UniversalBaseModel):
+ value: str
+
+ 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/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/potable_bad.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/potable_bad.py
new file mode 100644
index 000000000000..4435f2d7e394
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/potable_bad.py
@@ -0,0 +1,19 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class PotableBad(UniversalBaseModel):
+ value: str
+
+ 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/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/practical_principle.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/practical_principle.py
new file mode 100644
index 000000000000..3e8e94b65fe6
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/practical_principle.py
@@ -0,0 +1,19 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class PracticalPrinciple(UniversalBaseModel):
+ value: str
+
+ 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/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/primary_block.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/primary_block.py
new file mode 100644
index 000000000000..962d018a5496
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/primary_block.py
@@ -0,0 +1,19 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class PrimaryBlock(UniversalBaseModel):
+ value: str
+
+ 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/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/rotating_ratio.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/rotating_ratio.py
new file mode 100644
index 000000000000..4e24b4a1b021
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/rotating_ratio.py
@@ -0,0 +1,19 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class RotatingRatio(UniversalBaseModel):
+ value: str
+
+ 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/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/thankful_factor.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/thankful_factor.py
new file mode 100644
index 000000000000..c9469ab08fa5
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/thankful_factor.py
@@ -0,0 +1,29 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class ThankfulFactor(UniversalBaseModel):
+ """
+ Examples
+ --------
+ from seed.bigunion import ThankfulFactor
+
+ ThankfulFactor(
+ value="example1",
+ )
+ """
+
+ value: str
+
+ 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/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/total_work.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/total_work.py
new file mode 100644
index 000000000000..73aaaeb4a7ca
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/total_work.py
@@ -0,0 +1,19 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class TotalWork(UniversalBaseModel):
+ value: str
+
+ 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/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/triangular_repair.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/triangular_repair.py
new file mode 100644
index 000000000000..ebcb3de72233
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/triangular_repair.py
@@ -0,0 +1,19 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class TriangularRepair(UniversalBaseModel):
+ value: str
+
+ 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/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/unique_stress.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/unique_stress.py
new file mode 100644
index 000000000000..8d9b2ea1fac9
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/unique_stress.py
@@ -0,0 +1,19 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class UniqueStress(UniversalBaseModel):
+ value: str
+
+ 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/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/unwilling_smoke.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/unwilling_smoke.py
new file mode 100644
index 000000000000..38411a0b1446
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/unwilling_smoke.py
@@ -0,0 +1,19 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class UnwillingSmoke(UniversalBaseModel):
+ value: str
+
+ 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/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/vibrant_excitement.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/vibrant_excitement.py
new file mode 100644
index 000000000000..36c9e7a6f2d6
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/bigunion/types/vibrant_excitement.py
@@ -0,0 +1,19 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class VibrantExcitement(UniversalBaseModel):
+ value: str
+
+ 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/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/client.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/client.py
new file mode 100644
index 000000000000..18391d638e87
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/client.py
@@ -0,0 +1,183 @@
+# This file was auto-generated by Fern from our API Definition.
+
+from __future__ import annotations
+
+import typing
+
+import httpx
+from .core.client_wrapper import AsyncClientWrapper, SyncClientWrapper
+from .core.logging import LogConfig, Logger
+
+if typing.TYPE_CHECKING:
+ from .bigunion.client import AsyncBigunionClient, BigunionClient
+ from .union.client import AsyncUnionClient, UnionClient
+
+
+class SeedUnions:
+ """
+ Use this class to access the different functions within the SDK. You can instantiate any number of clients with different configuration that will propagate to these functions.
+
+ Parameters
+ ----------
+ base_url : str
+ The base url to use for requests from the client.
+
+ headers : typing.Optional[typing.Dict[str, str]]
+ Additional headers to send with every request.
+
+ 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.
+
+ follow_redirects : typing.Optional[bool]
+ Whether the default httpx client follows redirects or not, this is irrelevant if a custom httpx client is passed in.
+
+ httpx_client : typing.Optional[httpx.Client]
+ The httpx client to use for making requests, a preconfigured client is used by default, however this is useful should you want to pass in any custom httpx configuration.
+
+ logging : typing.Optional[typing.Union[LogConfig, Logger]]
+ Configure logging for the SDK. Accepts a LogConfig dict with 'level' (debug/info/warn/error), 'logger' (custom logger implementation), and 'silent' (boolean, defaults to True) fields. You can also pass a pre-configured Logger instance.
+
+ Examples
+ --------
+ from seed import SeedUnions
+
+ client = SeedUnions(
+ base_url="https://yourhost.com/path/to/api",
+ )
+ """
+
+ def __init__(
+ self,
+ *,
+ base_url: str,
+ headers: typing.Optional[typing.Dict[str, str]] = None,
+ timeout: typing.Optional[float] = None,
+ follow_redirects: typing.Optional[bool] = True,
+ httpx_client: typing.Optional[httpx.Client] = None,
+ logging: typing.Optional[typing.Union[LogConfig, Logger]] = None,
+ ):
+ _defaulted_timeout = (
+ timeout if timeout is not None else 60 if httpx_client is None else httpx_client.timeout.read
+ )
+ self._client_wrapper = SyncClientWrapper(
+ base_url=base_url,
+ headers=headers,
+ httpx_client=httpx_client
+ if httpx_client is not None
+ else httpx.Client(timeout=_defaulted_timeout, follow_redirects=follow_redirects)
+ if follow_redirects is not None
+ else httpx.Client(timeout=_defaulted_timeout),
+ timeout=_defaulted_timeout,
+ logging=logging,
+ )
+ self._bigunion: typing.Optional[BigunionClient] = None
+ self._union: typing.Optional[UnionClient] = None
+
+ @property
+ def bigunion(self):
+ if self._bigunion is None:
+ from .bigunion.client import BigunionClient # noqa: E402
+
+ self._bigunion = BigunionClient(client_wrapper=self._client_wrapper)
+ return self._bigunion
+
+ @property
+ def union(self):
+ if self._union is None:
+ from .union.client import UnionClient # noqa: E402
+
+ self._union = UnionClient(client_wrapper=self._client_wrapper)
+ return self._union
+
+
+def _make_default_async_client(
+ timeout: typing.Optional[float],
+ follow_redirects: typing.Optional[bool],
+) -> httpx.AsyncClient:
+ try:
+ import httpx_aiohttp # type: ignore[import-not-found]
+ except ImportError:
+ pass
+ else:
+ if follow_redirects is not None:
+ return httpx_aiohttp.HttpxAiohttpClient(timeout=timeout, follow_redirects=follow_redirects)
+ return httpx_aiohttp.HttpxAiohttpClient(timeout=timeout)
+
+ if follow_redirects is not None:
+ return httpx.AsyncClient(timeout=timeout, follow_redirects=follow_redirects)
+ return httpx.AsyncClient(timeout=timeout)
+
+
+class AsyncSeedUnions:
+ """
+ Use this class to access the different functions within the SDK. You can instantiate any number of clients with different configuration that will propagate to these functions.
+
+ Parameters
+ ----------
+ base_url : str
+ The base url to use for requests from the client.
+
+ headers : typing.Optional[typing.Dict[str, str]]
+ Additional headers to send with every request.
+
+ 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.
+
+ follow_redirects : typing.Optional[bool]
+ Whether the default httpx client follows redirects or not, this is irrelevant if a custom httpx client is passed in.
+
+ httpx_client : typing.Optional[httpx.AsyncClient]
+ The httpx client to use for making requests, a preconfigured client is used by default, however this is useful should you want to pass in any custom httpx configuration.
+
+ logging : typing.Optional[typing.Union[LogConfig, Logger]]
+ Configure logging for the SDK. Accepts a LogConfig dict with 'level' (debug/info/warn/error), 'logger' (custom logger implementation), and 'silent' (boolean, defaults to True) fields. You can also pass a pre-configured Logger instance.
+
+ Examples
+ --------
+ from seed import AsyncSeedUnions
+
+ client = AsyncSeedUnions(
+ base_url="https://yourhost.com/path/to/api",
+ )
+ """
+
+ def __init__(
+ self,
+ *,
+ base_url: str,
+ headers: typing.Optional[typing.Dict[str, str]] = None,
+ timeout: typing.Optional[float] = None,
+ follow_redirects: typing.Optional[bool] = True,
+ httpx_client: typing.Optional[httpx.AsyncClient] = None,
+ logging: typing.Optional[typing.Union[LogConfig, Logger]] = None,
+ ):
+ _defaulted_timeout = (
+ timeout if timeout is not None else 60 if httpx_client is None else httpx_client.timeout.read
+ )
+ self._client_wrapper = AsyncClientWrapper(
+ base_url=base_url,
+ headers=headers,
+ httpx_client=httpx_client
+ if httpx_client is not None
+ else _make_default_async_client(timeout=_defaulted_timeout, follow_redirects=follow_redirects),
+ timeout=_defaulted_timeout,
+ logging=logging,
+ )
+ self._bigunion: typing.Optional[AsyncBigunionClient] = None
+ self._union: typing.Optional[AsyncUnionClient] = None
+
+ @property
+ def bigunion(self):
+ if self._bigunion is None:
+ from .bigunion.client import AsyncBigunionClient # noqa: E402
+
+ self._bigunion = AsyncBigunionClient(client_wrapper=self._client_wrapper)
+ return self._bigunion
+
+ @property
+ def union(self):
+ if self._union is None:
+ from .union.client import AsyncUnionClient # noqa: E402
+
+ self._union = AsyncUnionClient(client_wrapper=self._client_wrapper)
+ return self._union
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/core/__init__.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/core/__init__.py
new file mode 100644
index 000000000000..5bc159a110f2
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/core/__init__.py
@@ -0,0 +1,127 @@
+# This file was auto-generated by Fern from our API Definition.
+
+# isort: skip_file
+
+import typing
+from importlib import import_module
+
+if typing.TYPE_CHECKING:
+ from .api_error import ApiError
+ from .client_wrapper import AsyncClientWrapper, BaseClientWrapper, SyncClientWrapper
+ from .datetime_utils import Rfc2822DateTime, parse_rfc2822_datetime, serialize_datetime
+ from .file import File, convert_file_dict_to_httpx_tuples, with_content_type
+ from .http_client import AsyncHttpClient, HttpClient
+ from .http_response import AsyncHttpResponse, HttpResponse
+ from .jsonable_encoder import encode_path_param, jsonable_encoder
+ from .logging import ConsoleLogger, ILogger, LogConfig, LogLevel, Logger, create_logger
+ from .parse_error import ParsingError
+ from .pydantic_utilities import (
+ IS_PYDANTIC_V2,
+ UniversalBaseModel,
+ UniversalRootModel,
+ parse_obj_as,
+ universal_field_validator,
+ universal_root_validator,
+ update_forward_refs,
+ )
+ from .query_encoder import encode_query
+ from .remove_none_from_dict import remove_none_from_dict
+ from .request_options import RequestOptions
+ from .serialization import FieldMetadata, convert_and_respect_annotation_metadata
+_dynamic_imports: typing.Dict[str, str] = {
+ "ApiError": ".api_error",
+ "AsyncClientWrapper": ".client_wrapper",
+ "AsyncHttpClient": ".http_client",
+ "AsyncHttpResponse": ".http_response",
+ "BaseClientWrapper": ".client_wrapper",
+ "ConsoleLogger": ".logging",
+ "FieldMetadata": ".serialization",
+ "File": ".file",
+ "HttpClient": ".http_client",
+ "HttpResponse": ".http_response",
+ "ILogger": ".logging",
+ "IS_PYDANTIC_V2": ".pydantic_utilities",
+ "LogConfig": ".logging",
+ "LogLevel": ".logging",
+ "Logger": ".logging",
+ "ParsingError": ".parse_error",
+ "RequestOptions": ".request_options",
+ "Rfc2822DateTime": ".datetime_utils",
+ "SyncClientWrapper": ".client_wrapper",
+ "UniversalBaseModel": ".pydantic_utilities",
+ "UniversalRootModel": ".pydantic_utilities",
+ "convert_and_respect_annotation_metadata": ".serialization",
+ "convert_file_dict_to_httpx_tuples": ".file",
+ "create_logger": ".logging",
+ "encode_path_param": ".jsonable_encoder",
+ "encode_query": ".query_encoder",
+ "jsonable_encoder": ".jsonable_encoder",
+ "parse_obj_as": ".pydantic_utilities",
+ "parse_rfc2822_datetime": ".datetime_utils",
+ "remove_none_from_dict": ".remove_none_from_dict",
+ "serialize_datetime": ".datetime_utils",
+ "universal_field_validator": ".pydantic_utilities",
+ "universal_root_validator": ".pydantic_utilities",
+ "update_forward_refs": ".pydantic_utilities",
+ "with_content_type": ".file",
+}
+
+
+def __getattr__(attr_name: str) -> typing.Any:
+ module_name = _dynamic_imports.get(attr_name)
+ if module_name is None:
+ raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}")
+ try:
+ module = import_module(module_name, __package__)
+ if module_name == f".{attr_name}":
+ return module
+ else:
+ return getattr(module, attr_name)
+ except ImportError as e:
+ raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e
+ except AttributeError as e:
+ raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e
+
+
+def __dir__():
+ lazy_attrs = list(_dynamic_imports.keys())
+ return sorted(lazy_attrs)
+
+
+__all__ = [
+ "ApiError",
+ "AsyncClientWrapper",
+ "AsyncHttpClient",
+ "AsyncHttpResponse",
+ "BaseClientWrapper",
+ "ConsoleLogger",
+ "FieldMetadata",
+ "File",
+ "HttpClient",
+ "HttpResponse",
+ "ILogger",
+ "IS_PYDANTIC_V2",
+ "LogConfig",
+ "LogLevel",
+ "Logger",
+ "ParsingError",
+ "RequestOptions",
+ "Rfc2822DateTime",
+ "SyncClientWrapper",
+ "UniversalBaseModel",
+ "UniversalRootModel",
+ "convert_and_respect_annotation_metadata",
+ "convert_file_dict_to_httpx_tuples",
+ "create_logger",
+ "encode_path_param",
+ "encode_query",
+ "jsonable_encoder",
+ "parse_obj_as",
+ "parse_rfc2822_datetime",
+ "remove_none_from_dict",
+ "serialize_datetime",
+ "universal_field_validator",
+ "universal_root_validator",
+ "update_forward_refs",
+ "with_content_type",
+]
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/core/api_error.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/core/api_error.py
new file mode 100644
index 000000000000..6f850a60cba3
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/core/api_error.py
@@ -0,0 +1,23 @@
+# This file was auto-generated by Fern from our API Definition.
+
+from typing import Any, Dict, Optional
+
+
+class ApiError(Exception):
+ headers: Optional[Dict[str, str]]
+ status_code: Optional[int]
+ body: Any
+
+ def __init__(
+ self,
+ *,
+ headers: Optional[Dict[str, str]] = None,
+ status_code: Optional[int] = None,
+ body: Any = None,
+ ) -> None:
+ self.headers = headers
+ self.status_code = status_code
+ self.body = body
+
+ def __str__(self) -> str:
+ return f"headers: {self.headers}, status_code: {self.status_code}, body: {self.body}"
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/core/client_wrapper.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/core/client_wrapper.py
new file mode 100644
index 000000000000..8640d9fea182
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/core/client_wrapper.py
@@ -0,0 +1,95 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import httpx
+from .http_client import AsyncHttpClient, HttpClient
+from .logging import LogConfig, Logger
+
+
+class BaseClientWrapper:
+ def __init__(
+ self,
+ *,
+ headers: typing.Optional[typing.Dict[str, str]] = None,
+ base_url: str,
+ timeout: typing.Optional[float] = None,
+ logging: typing.Optional[typing.Union[LogConfig, Logger]] = None,
+ ):
+ self._headers = headers
+ self._base_url = base_url
+ self._timeout = timeout
+ self._logging = logging
+
+ def get_headers(self) -> typing.Dict[str, str]:
+ import platform
+
+ headers: typing.Dict[str, str] = {
+ "User-Agent": "fern_unions/0.0.1",
+ "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": "fern_unions",
+ "X-Fern-SDK-Version": "0.0.1",
+ **(self.get_custom_headers() or {}),
+ }
+ return headers
+
+ def get_custom_headers(self) -> typing.Optional[typing.Dict[str, str]]:
+ return self._headers
+
+ def get_base_url(self) -> str:
+ return self._base_url
+
+ def get_timeout(self) -> typing.Optional[float]:
+ return self._timeout
+
+
+class SyncClientWrapper(BaseClientWrapper):
+ def __init__(
+ self,
+ *,
+ headers: typing.Optional[typing.Dict[str, str]] = None,
+ base_url: str,
+ timeout: typing.Optional[float] = None,
+ logging: typing.Optional[typing.Union[LogConfig, Logger]] = None,
+ httpx_client: httpx.Client,
+ ):
+ super().__init__(headers=headers, base_url=base_url, timeout=timeout, logging=logging)
+ self.httpx_client = HttpClient(
+ httpx_client=httpx_client,
+ base_headers=self.get_headers,
+ base_timeout=self.get_timeout,
+ base_url=self.get_base_url,
+ logging_config=self._logging,
+ )
+
+
+class AsyncClientWrapper(BaseClientWrapper):
+ def __init__(
+ self,
+ *,
+ headers: typing.Optional[typing.Dict[str, str]] = None,
+ base_url: str,
+ timeout: typing.Optional[float] = None,
+ logging: typing.Optional[typing.Union[LogConfig, Logger]] = None,
+ async_token: typing.Optional[typing.Callable[[], typing.Awaitable[str]]] = None,
+ httpx_client: httpx.AsyncClient,
+ ):
+ super().__init__(headers=headers, base_url=base_url, timeout=timeout, logging=logging)
+ self._async_token = async_token
+ self.httpx_client = AsyncHttpClient(
+ httpx_client=httpx_client,
+ base_headers=self.get_headers,
+ base_timeout=self.get_timeout,
+ base_url=self.get_base_url,
+ async_base_headers=self.async_get_headers,
+ logging_config=self._logging,
+ )
+
+ async def async_get_headers(self) -> typing.Dict[str, str]:
+ headers = self.get_headers()
+ if self._async_token is not None:
+ token = await self._async_token()
+ headers["Authorization"] = f"Bearer {token}"
+ return headers
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/core/datetime_utils.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/core/datetime_utils.py
new file mode 100644
index 000000000000..a12b2ad03c53
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/core/datetime_utils.py
@@ -0,0 +1,70 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import datetime as dt
+from email.utils import parsedate_to_datetime
+from typing import Any
+
+import pydantic
+
+IS_PYDANTIC_V2 = pydantic.VERSION.startswith("2.")
+
+
+def parse_rfc2822_datetime(v: Any) -> dt.datetime:
+ """
+ Parse an RFC 2822 datetime string (e.g., "Wed, 02 Oct 2002 13:00:00 GMT")
+ into a datetime object. If the value is already a datetime, return it as-is.
+ Falls back to ISO 8601 parsing if RFC 2822 parsing fails.
+ """
+ if isinstance(v, dt.datetime):
+ return v
+ if isinstance(v, str):
+ try:
+ return parsedate_to_datetime(v)
+ except Exception:
+ pass
+ # Fallback to ISO 8601 parsing
+ return dt.datetime.fromisoformat(v.replace("Z", "+00:00"))
+ raise ValueError(f"Expected str or datetime, got {type(v)}")
+
+
+class Rfc2822DateTime(dt.datetime):
+ """A datetime subclass that parses RFC 2822 date strings.
+
+ On Pydantic V1, uses __get_validators__ for pre-validation.
+ On Pydantic V2, uses __get_pydantic_core_schema__ for BeforeValidator-style parsing.
+ """
+
+ @classmethod
+ def __get_validators__(cls): # type: ignore[no-untyped-def]
+ yield parse_rfc2822_datetime
+
+ @classmethod
+ def __get_pydantic_core_schema__(cls, _source_type: Any, _handler: Any) -> Any: # type: ignore[override]
+ from pydantic_core import core_schema
+
+ return core_schema.no_info_before_validator_function(parse_rfc2822_datetime, core_schema.datetime_schema())
+
+
+def serialize_datetime(v: dt.datetime) -> str:
+ """
+ Serialize a datetime including timezone info.
+
+ Uses the timezone info provided if present, otherwise uses the current runtime's timezone info.
+
+ UTC datetimes end in "Z" while all other timezones are represented as offset from UTC, e.g. +05:00.
+ """
+
+ def _serialize_zoned_datetime(v: dt.datetime) -> str:
+ if v.tzinfo is not None and v.tzinfo.tzname(None) == dt.timezone.utc.tzname(None):
+ # UTC is a special case where we use "Z" at the end instead of "+00:00"
+ return v.isoformat().replace("+00:00", "Z")
+ else:
+ # Delegate to the typical +/- offset format
+ return v.isoformat()
+
+ if v.tzinfo is not None:
+ return _serialize_zoned_datetime(v)
+ else:
+ local_tz = dt.datetime.now().astimezone().tzinfo
+ localized_dt = v.replace(tzinfo=local_tz)
+ return _serialize_zoned_datetime(localized_dt)
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/core/file.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/core/file.py
new file mode 100644
index 000000000000..44b0d27c0895
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/core/file.py
@@ -0,0 +1,67 @@
+# This file was auto-generated by Fern from our API Definition.
+
+from typing import IO, Dict, List, Mapping, Optional, Tuple, Union, cast
+
+# File typing inspired by the flexibility of types within the httpx library
+# https://github.com/encode/httpx/blob/master/httpx/_types.py
+FileContent = Union[IO[bytes], bytes, str]
+File = Union[
+ # file (or bytes)
+ FileContent,
+ # (filename, file (or bytes))
+ Tuple[Optional[str], FileContent],
+ # (filename, file (or bytes), content_type)
+ Tuple[Optional[str], FileContent, Optional[str]],
+ # (filename, file (or bytes), content_type, headers)
+ Tuple[
+ Optional[str],
+ FileContent,
+ Optional[str],
+ Mapping[str, str],
+ ],
+]
+
+
+def convert_file_dict_to_httpx_tuples(
+ d: Dict[str, Union[File, List[File]]],
+) -> List[Tuple[str, File]]:
+ """
+ The format we use is a list of tuples, where the first element is the
+ name of the file and the second is the file object. Typically HTTPX wants
+ a dict, but to be able to send lists of files, you have to use the list
+ approach (which also works for non-lists)
+ https://github.com/encode/httpx/pull/1032
+ """
+
+ httpx_tuples = []
+ for key, file_like in d.items():
+ if isinstance(file_like, list):
+ for file_like_item in file_like:
+ httpx_tuples.append((key, file_like_item))
+ else:
+ httpx_tuples.append((key, file_like))
+ return httpx_tuples
+
+
+def with_content_type(*, file: File, default_content_type: str) -> File:
+ """
+ This function resolves to the file's content type, if provided, and defaults
+ to the default_content_type value if not.
+ """
+ if isinstance(file, tuple):
+ if len(file) == 2:
+ filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore
+ return (filename, content, default_content_type)
+ elif len(file) == 3:
+ filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore
+ out_content_type = file_content_type or default_content_type
+ return (filename, content, out_content_type)
+ elif len(file) == 4:
+ filename, content, file_content_type, headers = cast( # type: ignore
+ Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file
+ )
+ out_content_type = file_content_type or default_content_type
+ return (filename, content, out_content_type, headers)
+ else:
+ raise ValueError(f"Unexpected tuple length: {len(file)}")
+ return (None, file, default_content_type)
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/core/force_multipart.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/core/force_multipart.py
new file mode 100644
index 000000000000..5440913fd4bc
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/core/force_multipart.py
@@ -0,0 +1,18 @@
+# This file was auto-generated by Fern from our API Definition.
+
+from typing import Any, Dict
+
+
+class ForceMultipartDict(Dict[str, Any]):
+ """
+ A dictionary subclass that always evaluates to True in boolean contexts.
+
+ This is used to force multipart/form-data encoding in HTTP requests even when
+ the dictionary is empty, which would normally evaluate to False.
+ """
+
+ def __bool__(self) -> bool:
+ return True
+
+
+FORCE_MULTIPART = ForceMultipartDict()
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/core/http_client.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/core/http_client.py
new file mode 100644
index 000000000000..f0a39ca8243a
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/core/http_client.py
@@ -0,0 +1,840 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import asyncio
+import email.utils
+import re
+import time
+import typing
+from contextlib import asynccontextmanager, contextmanager
+from random import random
+
+import httpx
+from .file import File, convert_file_dict_to_httpx_tuples
+from .force_multipart import FORCE_MULTIPART
+from .jsonable_encoder import jsonable_encoder
+from .logging import LogConfig, Logger, create_logger
+from .query_encoder import encode_query
+from .remove_none_from_dict import remove_none_from_dict as remove_none_from_dict
+from .request_options import RequestOptions
+from httpx._types import RequestFiles
+
+INITIAL_RETRY_DELAY_SECONDS = 1.0
+MAX_RETRY_DELAY_SECONDS = 60.0
+JITTER_FACTOR = 0.2 # 20% random jitter
+
+
+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.
+
+ Inspired by the urllib3 retry implementation.
+ """
+ retry_after_ms = response_headers.get("retry-after-ms")
+ if retry_after_ms is not None:
+ try:
+ return int(retry_after_ms) / 1000 if retry_after_ms > 0 else 0
+ except Exception:
+ pass
+
+ retry_after = response_headers.get("retry-after")
+ if retry_after is None:
+ return None
+
+ # Attempt to parse the header as an int.
+ if re.match(r"^\s*[0-9]+\s*$", retry_after):
+ seconds = float(retry_after)
+ # Fallback to parsing it as a date.
+ else:
+ retry_date_tuple = email.utils.parsedate_tz(retry_after)
+ if retry_date_tuple is None:
+ return None
+ if retry_date_tuple[9] is None: # Python 2
+ # Assume UTC if no timezone was specified
+ # On Python2.7, parsedate_tz returns None for a timezone offset
+ # instead of 0 if no timezone is given, where mktime_tz treats
+ # a None timezone offset as local time.
+ retry_date_tuple = retry_date_tuple[:9] + (0,) + retry_date_tuple[10:]
+
+ retry_date = email.utils.mktime_tz(retry_date_tuple)
+ seconds = retry_date - time.time()
+
+ if seconds < 0:
+ seconds = 0
+
+ return seconds
+
+
+def _add_positive_jitter(delay: float) -> float:
+ """Add positive jitter (0-20%) to prevent thundering herd."""
+ jitter_multiplier = 1 + random() * JITTER_FACTOR
+ return delay * jitter_multiplier
+
+
+def _add_symmetric_jitter(delay: float) -> float:
+ """Add symmetric jitter (±10%) for exponential backoff."""
+ jitter_multiplier = 1 + (random() - 0.5) * JITTER_FACTOR
+ return delay * jitter_multiplier
+
+
+def _parse_x_ratelimit_reset(response_headers: httpx.Headers) -> typing.Optional[float]:
+ """
+ Parse the X-RateLimit-Reset header (Unix timestamp in seconds).
+ Returns seconds to wait, or None if header is missing/invalid.
+ """
+ reset_time_str = response_headers.get("x-ratelimit-reset")
+ if reset_time_str is None:
+ return None
+
+ try:
+ reset_time = int(reset_time_str)
+ delay = reset_time - time.time()
+ if delay > 0:
+ return delay
+ except (ValueError, TypeError):
+ pass
+
+ return None
+
+
+def _retry_timeout(response: httpx.Response, retries: int) -> float:
+ """
+ Determine the amount of time to wait before retrying a request.
+ This function begins by trying to parse a retry-after header from the response, and then proceeds to use exponential backoff
+ with a jitter to determine the number of seconds to wait.
+ """
+
+ # 1. Check Retry-After header first
+ retry_after = _parse_retry_after(response.headers)
+ if retry_after is not None and retry_after > 0:
+ return min(retry_after, MAX_RETRY_DELAY_SECONDS)
+
+ # 2. Check X-RateLimit-Reset header (with positive jitter)
+ ratelimit_reset = _parse_x_ratelimit_reset(response.headers)
+ if ratelimit_reset is not None:
+ return _add_positive_jitter(min(ratelimit_reset, MAX_RETRY_DELAY_SECONDS))
+
+ # 3. Fall back to exponential backoff (with symmetric jitter)
+ backoff = min(INITIAL_RETRY_DELAY_SECONDS * pow(2.0, retries), MAX_RETRY_DELAY_SECONDS)
+ return _add_symmetric_jitter(backoff)
+
+
+def _retry_timeout_from_retries(retries: int) -> float:
+ """Determine retry timeout using exponential backoff when no response is available."""
+ backoff = min(INITIAL_RETRY_DELAY_SECONDS * pow(2.0, retries), MAX_RETRY_DELAY_SECONDS)
+ return _add_symmetric_jitter(backoff)
+
+
+def _should_retry(response: httpx.Response) -> bool:
+ retryable_400s = [429, 408, 409]
+ return response.status_code >= 500 or response.status_code in retryable_400s
+
+
+_SENSITIVE_HEADERS = frozenset(
+ {
+ "authorization",
+ "www-authenticate",
+ "x-api-key",
+ "api-key",
+ "apikey",
+ "x-api-token",
+ "x-auth-token",
+ "auth-token",
+ "cookie",
+ "set-cookie",
+ "proxy-authorization",
+ "proxy-authenticate",
+ "x-csrf-token",
+ "x-xsrf-token",
+ "x-session-token",
+ "x-access-token",
+ }
+)
+
+
+def _redact_headers(headers: typing.Dict[str, str]) -> typing.Dict[str, str]:
+ return {k: ("[REDACTED]" if k.lower() in _SENSITIVE_HEADERS else v) for k, v in headers.items()}
+
+
+def _build_url(base_url: str, path: typing.Optional[str]) -> str:
+ """
+ Build a full URL by joining a base URL with a path.
+
+ This function correctly handles base URLs that contain path prefixes (e.g., tenant-based URLs)
+ by using string concatenation instead of urllib.parse.urljoin(), which would incorrectly
+ strip path components when the path starts with '/'.
+
+ Example:
+ >>> _build_url("https://cloud.example.com/org/tenant/api", "/users")
+ 'https://cloud.example.com/org/tenant/api/users'
+
+ Args:
+ base_url: The base URL, which may contain path prefixes.
+ path: The path to append. Can be None or empty string.
+
+ Returns:
+ The full URL with base_url and path properly joined.
+ """
+ if not path:
+ return base_url
+ return f"{base_url.rstrip('/')}/{path.lstrip('/')}"
+
+
+def _maybe_filter_none_from_multipart_data(
+ data: typing.Optional[typing.Any],
+ request_files: typing.Optional[RequestFiles],
+ force_multipart: typing.Optional[bool],
+) -> typing.Optional[typing.Any]:
+ """
+ Filter None values from data body for multipart/form requests.
+ This prevents httpx from converting None to empty strings in multipart encoding.
+ Only applies when files are present or force_multipart is True.
+ """
+ if data is not None and isinstance(data, typing.Mapping) and (request_files or force_multipart):
+ return remove_none_from_dict(data)
+ return data
+
+
+def remove_omit_from_dict(
+ original: typing.Dict[str, typing.Optional[typing.Any]],
+ omit: typing.Optional[typing.Any],
+) -> typing.Dict[str, typing.Any]:
+ if omit is None:
+ return original
+ new: typing.Dict[str, typing.Any] = {}
+ for key, value in original.items():
+ if value is not omit:
+ new[key] = value
+ return new
+
+
+def maybe_filter_request_body(
+ data: typing.Optional[typing.Any],
+ request_options: typing.Optional[RequestOptions],
+ omit: typing.Optional[typing.Any],
+) -> typing.Optional[typing.Any]:
+ if data is None:
+ return (
+ jsonable_encoder(request_options.get("additional_body_parameters", {})) or {}
+ if request_options is not None
+ else None
+ )
+ elif not isinstance(data, typing.Mapping):
+ data_content = jsonable_encoder(data)
+ else:
+ data_content = {
+ **(jsonable_encoder(remove_omit_from_dict(data, omit))), # type: ignore
+ **(
+ jsonable_encoder(request_options.get("additional_body_parameters", {})) or {}
+ if request_options is not None
+ else {}
+ ),
+ }
+ return data_content
+
+
+# Abstracted out for testing purposes
+def get_request_body(
+ *,
+ json: typing.Optional[typing.Any],
+ data: typing.Optional[typing.Any],
+ request_options: typing.Optional[RequestOptions],
+ omit: typing.Optional[typing.Any],
+) -> typing.Tuple[typing.Optional[typing.Any], typing.Optional[typing.Any]]:
+ json_body = None
+ data_body = None
+ if data is not None:
+ data_body = maybe_filter_request_body(data, request_options, omit)
+ else:
+ # If both data and json are None, we send json data in the event extra properties are specified
+ json_body = maybe_filter_request_body(json, request_options, omit)
+
+ has_additional_body_parameters = bool(
+ request_options is not None and request_options.get("additional_body_parameters")
+ )
+
+ # 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:
+ json_body = None
+ if data_body == {} and data is None and not has_additional_body_parameters:
+ data_body = None
+
+ return json_body, data_body
+
+
+class HttpClient:
+ def __init__(
+ self,
+ *,
+ httpx_client: httpx.Client,
+ base_timeout: typing.Callable[[], typing.Optional[float]],
+ base_headers: typing.Callable[[], typing.Dict[str, str]],
+ base_url: typing.Optional[typing.Callable[[], str]] = None,
+ base_max_retries: int = 2,
+ logging_config: typing.Optional[typing.Union[LogConfig, Logger]] = None,
+ ):
+ self.base_url = base_url
+ self.base_timeout = base_timeout
+ self.base_headers = base_headers
+ self.base_max_retries = base_max_retries
+ self.httpx_client = httpx_client
+ self.logger = create_logger(logging_config)
+
+ def get_base_url(self, maybe_base_url: typing.Optional[str]) -> str:
+ base_url = maybe_base_url
+ if self.base_url is not None and base_url is None:
+ base_url = self.base_url()
+
+ if base_url is None:
+ raise ValueError("A base_url is required to make this request, please provide one and try again.")
+ return base_url
+
+ def request(
+ self,
+ path: typing.Optional[str] = None,
+ *,
+ method: str,
+ base_url: typing.Optional[str] = None,
+ params: typing.Optional[typing.Dict[str, typing.Any]] = None,
+ json: typing.Optional[typing.Any] = None,
+ data: typing.Optional[typing.Any] = None,
+ content: typing.Optional[typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]] = None,
+ files: typing.Optional[
+ typing.Union[
+ typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]],
+ typing.List[typing.Tuple[str, File]],
+ ]
+ ] = None,
+ headers: typing.Optional[typing.Dict[str, typing.Any]] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ retries: int = 0,
+ omit: typing.Optional[typing.Any] = None,
+ force_multipart: typing.Optional[bool] = None,
+ ) -> httpx.Response:
+ base_url = self.get_base_url(base_url)
+ timeout = (
+ request_options.get("timeout_in_seconds")
+ if request_options is not None and request_options.get("timeout_in_seconds") is not None
+ else self.base_timeout()
+ )
+
+ json_body, data_body = get_request_body(json=json, data=data, request_options=request_options, omit=omit)
+
+ request_files: typing.Optional[RequestFiles] = (
+ convert_file_dict_to_httpx_tuples(remove_omit_from_dict(remove_none_from_dict(files), omit))
+ if (files is not None and files is not omit and isinstance(files, dict))
+ else None
+ )
+
+ if (request_files is None or len(request_files) == 0) and force_multipart:
+ request_files = FORCE_MULTIPART
+
+ data_body = _maybe_filter_none_from_multipart_data(data_body, request_files, force_multipart)
+
+ # Compute encoded params separately to avoid passing empty list to httpx
+ # (httpx strips existing query params from URL when params=[] is passed)
+ _encoded_params = encode_query(
+ jsonable_encoder(
+ remove_none_from_dict(
+ remove_omit_from_dict(
+ {
+ **(params if params is not None else {}),
+ **(
+ request_options.get("additional_query_parameters", {}) or {}
+ if request_options is not None
+ else {}
+ ),
+ },
+ omit,
+ )
+ )
+ )
+ )
+
+ _request_url = _build_url(base_url, path)
+ _request_headers = jsonable_encoder(
+ remove_none_from_dict(
+ {
+ **self.base_headers(),
+ **(headers if headers is not None else {}),
+ **(request_options.get("additional_headers", {}) or {} if request_options is not None else {}),
+ }
+ )
+ )
+
+ if self.logger.is_debug():
+ self.logger.debug(
+ "Making HTTP request",
+ method=method,
+ url=_request_url,
+ headers=_redact_headers(_request_headers),
+ has_body=json_body is not None or data_body is not None,
+ )
+
+ max_retries: int = (
+ request_options.get("max_retries", self.base_max_retries)
+ if request_options is not None
+ else self.base_max_retries
+ )
+
+ try:
+ response = self.httpx_client.request(
+ method=method,
+ url=_request_url,
+ headers=_request_headers,
+ params=_encoded_params if _encoded_params else None,
+ json=json_body,
+ data=data_body,
+ content=content,
+ files=request_files,
+ timeout=timeout,
+ )
+ except (httpx.ConnectError, httpx.RemoteProtocolError):
+ if retries < max_retries:
+ time.sleep(_retry_timeout_from_retries(retries=retries))
+ return self.request(
+ path=path,
+ method=method,
+ base_url=base_url,
+ params=params,
+ json=json,
+ data=data,
+ content=content,
+ files=files,
+ headers=headers,
+ request_options=request_options,
+ retries=retries + 1,
+ omit=omit,
+ force_multipart=force_multipart,
+ )
+ raise
+
+ if _should_retry(response=response):
+ if retries < max_retries:
+ time.sleep(_retry_timeout(response=response, retries=retries))
+ return self.request(
+ path=path,
+ method=method,
+ base_url=base_url,
+ params=params,
+ json=json,
+ data=data,
+ content=content,
+ files=files,
+ headers=headers,
+ request_options=request_options,
+ retries=retries + 1,
+ omit=omit,
+ force_multipart=force_multipart,
+ )
+
+ if self.logger.is_debug():
+ if 200 <= response.status_code < 400:
+ self.logger.debug(
+ "HTTP request succeeded",
+ method=method,
+ url=_request_url,
+ status_code=response.status_code,
+ )
+
+ if self.logger.is_error():
+ if response.status_code >= 400:
+ self.logger.error(
+ "HTTP request failed with error status",
+ method=method,
+ url=_request_url,
+ status_code=response.status_code,
+ )
+
+ return response
+
+ @contextmanager
+ def stream(
+ self,
+ path: typing.Optional[str] = None,
+ *,
+ method: str,
+ base_url: typing.Optional[str] = None,
+ params: typing.Optional[typing.Dict[str, typing.Any]] = None,
+ json: typing.Optional[typing.Any] = None,
+ data: typing.Optional[typing.Any] = None,
+ content: typing.Optional[typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]] = None,
+ files: typing.Optional[
+ typing.Union[
+ typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]],
+ typing.List[typing.Tuple[str, File]],
+ ]
+ ] = None,
+ headers: typing.Optional[typing.Dict[str, typing.Any]] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ retries: int = 0,
+ omit: typing.Optional[typing.Any] = None,
+ force_multipart: typing.Optional[bool] = None,
+ ) -> typing.Iterator[httpx.Response]:
+ base_url = self.get_base_url(base_url)
+ timeout = (
+ request_options.get("timeout_in_seconds")
+ if request_options is not None and request_options.get("timeout_in_seconds") is not None
+ else self.base_timeout()
+ )
+
+ request_files: typing.Optional[RequestFiles] = (
+ convert_file_dict_to_httpx_tuples(remove_omit_from_dict(remove_none_from_dict(files), omit))
+ if (files is not None and files is not omit and isinstance(files, dict))
+ else None
+ )
+
+ 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)
+
+ data_body = _maybe_filter_none_from_multipart_data(data_body, request_files, force_multipart)
+
+ # Compute encoded params separately to avoid passing empty list to httpx
+ # (httpx strips existing query params from URL when params=[] is passed)
+ _encoded_params = encode_query(
+ jsonable_encoder(
+ remove_none_from_dict(
+ remove_omit_from_dict(
+ {
+ **(params if params is not None else {}),
+ **(
+ request_options.get("additional_query_parameters", {})
+ if request_options is not None
+ else {}
+ ),
+ },
+ omit,
+ )
+ )
+ )
+ )
+
+ _request_url = _build_url(base_url, path)
+ _request_headers = jsonable_encoder(
+ remove_none_from_dict(
+ {
+ **self.base_headers(),
+ **(headers if headers is not None else {}),
+ **(request_options.get("additional_headers", {}) if request_options is not None else {}),
+ }
+ )
+ )
+
+ if self.logger.is_debug():
+ self.logger.debug(
+ "Making streaming HTTP request",
+ method=method,
+ url=_request_url,
+ headers=_redact_headers(_request_headers),
+ )
+
+ with self.httpx_client.stream(
+ method=method,
+ url=_request_url,
+ headers=_request_headers,
+ params=_encoded_params if _encoded_params else None,
+ json=json_body,
+ data=data_body,
+ content=content,
+ files=request_files,
+ timeout=timeout,
+ ) as stream:
+ yield stream
+
+
+class AsyncHttpClient:
+ def __init__(
+ self,
+ *,
+ httpx_client: httpx.AsyncClient,
+ base_timeout: typing.Callable[[], typing.Optional[float]],
+ base_headers: typing.Callable[[], typing.Dict[str, str]],
+ base_url: typing.Optional[typing.Callable[[], str]] = None,
+ base_max_retries: int = 2,
+ async_base_headers: typing.Optional[typing.Callable[[], typing.Awaitable[typing.Dict[str, str]]]] = None,
+ logging_config: typing.Optional[typing.Union[LogConfig, Logger]] = None,
+ ):
+ self.base_url = base_url
+ self.base_timeout = base_timeout
+ self.base_headers = base_headers
+ self.base_max_retries = base_max_retries
+ self.async_base_headers = async_base_headers
+ self.httpx_client = httpx_client
+ self.logger = create_logger(logging_config)
+
+ async def _get_headers(self) -> typing.Dict[str, str]:
+ if self.async_base_headers is not None:
+ return await self.async_base_headers()
+ return self.base_headers()
+
+ def get_base_url(self, maybe_base_url: typing.Optional[str]) -> str:
+ base_url = maybe_base_url
+ if self.base_url is not None and base_url is None:
+ base_url = self.base_url()
+
+ if base_url is None:
+ raise ValueError("A base_url is required to make this request, please provide one and try again.")
+ return base_url
+
+ async def request(
+ self,
+ path: typing.Optional[str] = None,
+ *,
+ method: str,
+ base_url: typing.Optional[str] = None,
+ params: typing.Optional[typing.Dict[str, typing.Any]] = None,
+ json: typing.Optional[typing.Any] = None,
+ data: typing.Optional[typing.Any] = None,
+ content: typing.Optional[typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]] = None,
+ files: typing.Optional[
+ typing.Union[
+ typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]],
+ typing.List[typing.Tuple[str, File]],
+ ]
+ ] = None,
+ headers: typing.Optional[typing.Dict[str, typing.Any]] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ retries: int = 0,
+ omit: typing.Optional[typing.Any] = None,
+ force_multipart: typing.Optional[bool] = None,
+ ) -> httpx.Response:
+ base_url = self.get_base_url(base_url)
+ timeout = (
+ request_options.get("timeout_in_seconds")
+ if request_options is not None and request_options.get("timeout_in_seconds") is not None
+ else self.base_timeout()
+ )
+
+ request_files: typing.Optional[RequestFiles] = (
+ convert_file_dict_to_httpx_tuples(remove_omit_from_dict(remove_none_from_dict(files), omit))
+ if (files is not None and files is not omit and isinstance(files, dict))
+ else None
+ )
+
+ 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)
+
+ data_body = _maybe_filter_none_from_multipart_data(data_body, request_files, force_multipart)
+
+ # Get headers (supports async token providers)
+ _headers = await self._get_headers()
+
+ # Compute encoded params separately to avoid passing empty list to httpx
+ # (httpx strips existing query params from URL when params=[] is passed)
+ _encoded_params = encode_query(
+ jsonable_encoder(
+ remove_none_from_dict(
+ remove_omit_from_dict(
+ {
+ **(params if params is not None else {}),
+ **(
+ request_options.get("additional_query_parameters", {}) or {}
+ if request_options is not None
+ else {}
+ ),
+ },
+ omit,
+ )
+ )
+ )
+ )
+
+ _request_url = _build_url(base_url, path)
+ _request_headers = jsonable_encoder(
+ remove_none_from_dict(
+ {
+ **_headers,
+ **(headers if headers is not None else {}),
+ **(request_options.get("additional_headers", {}) or {} if request_options is not None else {}),
+ }
+ )
+ )
+
+ if self.logger.is_debug():
+ self.logger.debug(
+ "Making HTTP request",
+ method=method,
+ url=_request_url,
+ headers=_redact_headers(_request_headers),
+ has_body=json_body is not None or data_body is not None,
+ )
+
+ max_retries: int = (
+ request_options.get("max_retries", self.base_max_retries)
+ if request_options is not None
+ else self.base_max_retries
+ )
+
+ try:
+ response = await self.httpx_client.request(
+ method=method,
+ url=_request_url,
+ headers=_request_headers,
+ params=_encoded_params if _encoded_params else None,
+ json=json_body,
+ data=data_body,
+ content=content,
+ files=request_files,
+ timeout=timeout,
+ )
+ except (httpx.ConnectError, httpx.RemoteProtocolError):
+ if retries < max_retries:
+ await asyncio.sleep(_retry_timeout_from_retries(retries=retries))
+ return await self.request(
+ path=path,
+ method=method,
+ base_url=base_url,
+ params=params,
+ json=json,
+ data=data,
+ content=content,
+ files=files,
+ headers=headers,
+ request_options=request_options,
+ retries=retries + 1,
+ omit=omit,
+ force_multipart=force_multipart,
+ )
+ raise
+
+ if _should_retry(response=response):
+ if retries < max_retries:
+ await asyncio.sleep(_retry_timeout(response=response, retries=retries))
+ return await self.request(
+ path=path,
+ method=method,
+ base_url=base_url,
+ params=params,
+ json=json,
+ data=data,
+ content=content,
+ files=files,
+ headers=headers,
+ request_options=request_options,
+ retries=retries + 1,
+ omit=omit,
+ force_multipart=force_multipart,
+ )
+
+ if self.logger.is_debug():
+ if 200 <= response.status_code < 400:
+ self.logger.debug(
+ "HTTP request succeeded",
+ method=method,
+ url=_request_url,
+ status_code=response.status_code,
+ )
+
+ if self.logger.is_error():
+ if response.status_code >= 400:
+ self.logger.error(
+ "HTTP request failed with error status",
+ method=method,
+ url=_request_url,
+ status_code=response.status_code,
+ )
+
+ return response
+
+ @asynccontextmanager
+ async def stream(
+ self,
+ path: typing.Optional[str] = None,
+ *,
+ method: str,
+ base_url: typing.Optional[str] = None,
+ params: typing.Optional[typing.Dict[str, typing.Any]] = None,
+ json: typing.Optional[typing.Any] = None,
+ data: typing.Optional[typing.Any] = None,
+ content: typing.Optional[typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]] = None,
+ files: typing.Optional[
+ typing.Union[
+ typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]],
+ typing.List[typing.Tuple[str, File]],
+ ]
+ ] = None,
+ headers: typing.Optional[typing.Dict[str, typing.Any]] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ retries: int = 0,
+ omit: typing.Optional[typing.Any] = None,
+ force_multipart: typing.Optional[bool] = None,
+ ) -> typing.AsyncIterator[httpx.Response]:
+ base_url = self.get_base_url(base_url)
+ timeout = (
+ request_options.get("timeout_in_seconds")
+ if request_options is not None and request_options.get("timeout_in_seconds") is not None
+ else self.base_timeout()
+ )
+
+ request_files: typing.Optional[RequestFiles] = (
+ convert_file_dict_to_httpx_tuples(remove_omit_from_dict(remove_none_from_dict(files), omit))
+ if (files is not None and files is not omit and isinstance(files, dict))
+ else None
+ )
+
+ 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)
+
+ data_body = _maybe_filter_none_from_multipart_data(data_body, request_files, force_multipart)
+
+ # Get headers (supports async token providers)
+ _headers = await self._get_headers()
+
+ # Compute encoded params separately to avoid passing empty list to httpx
+ # (httpx strips existing query params from URL when params=[] is passed)
+ _encoded_params = encode_query(
+ jsonable_encoder(
+ remove_none_from_dict(
+ remove_omit_from_dict(
+ {
+ **(params if params is not None else {}),
+ **(
+ request_options.get("additional_query_parameters", {})
+ if request_options is not None
+ else {}
+ ),
+ },
+ omit=omit,
+ )
+ )
+ )
+ )
+
+ _request_url = _build_url(base_url, path)
+ _request_headers = jsonable_encoder(
+ remove_none_from_dict(
+ {
+ **_headers,
+ **(headers if headers is not None else {}),
+ **(request_options.get("additional_headers", {}) if request_options is not None else {}),
+ }
+ )
+ )
+
+ if self.logger.is_debug():
+ self.logger.debug(
+ "Making streaming HTTP request",
+ method=method,
+ url=_request_url,
+ headers=_redact_headers(_request_headers),
+ )
+
+ async with self.httpx_client.stream(
+ method=method,
+ url=_request_url,
+ headers=_request_headers,
+ params=_encoded_params if _encoded_params else None,
+ json=json_body,
+ data=data_body,
+ content=content,
+ files=request_files,
+ timeout=timeout,
+ ) as stream:
+ yield stream
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/core/http_response.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/core/http_response.py
new file mode 100644
index 000000000000..00bb1096d2d0
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/core/http_response.py
@@ -0,0 +1,59 @@
+# This file was auto-generated by Fern from our API Definition.
+
+from typing import Dict, Generic, TypeVar
+
+import httpx
+
+# Generic to represent the underlying type of the data wrapped by the HTTP response.
+T = TypeVar("T")
+
+
+class BaseHttpResponse:
+ """Minimalist HTTP response wrapper that exposes response headers and status code."""
+
+ _response: httpx.Response
+
+ def __init__(self, response: httpx.Response):
+ self._response = response
+
+ @property
+ def headers(self) -> Dict[str, str]:
+ return dict(self._response.headers)
+
+ @property
+ def status_code(self) -> int:
+ return self._response.status_code
+
+
+class HttpResponse(Generic[T], BaseHttpResponse):
+ """HTTP response wrapper that exposes response headers and data."""
+
+ _data: T
+
+ def __init__(self, response: httpx.Response, data: T):
+ super().__init__(response)
+ self._data = data
+
+ @property
+ def data(self) -> T:
+ return self._data
+
+ def close(self) -> None:
+ self._response.close()
+
+
+class AsyncHttpResponse(Generic[T], BaseHttpResponse):
+ """HTTP response wrapper that exposes response headers and data."""
+
+ _data: T
+
+ def __init__(self, response: httpx.Response, data: T):
+ super().__init__(response)
+ self._data = data
+
+ @property
+ def data(self) -> T:
+ return self._data
+
+ async def close(self) -> None:
+ await self._response.aclose()
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/core/http_sse/__init__.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/core/http_sse/__init__.py
new file mode 100644
index 000000000000..730e5a3382eb
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/core/http_sse/__init__.py
@@ -0,0 +1,42 @@
+# This file was auto-generated by Fern from our API Definition.
+
+# isort: skip_file
+
+import typing
+from importlib import import_module
+
+if typing.TYPE_CHECKING:
+ from ._api import EventSource, aconnect_sse, connect_sse
+ from ._exceptions import SSEError
+ from ._models import ServerSentEvent
+_dynamic_imports: typing.Dict[str, str] = {
+ "EventSource": "._api",
+ "SSEError": "._exceptions",
+ "ServerSentEvent": "._models",
+ "aconnect_sse": "._api",
+ "connect_sse": "._api",
+}
+
+
+def __getattr__(attr_name: str) -> typing.Any:
+ module_name = _dynamic_imports.get(attr_name)
+ if module_name is None:
+ raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}")
+ try:
+ module = import_module(module_name, __package__)
+ if module_name == f".{attr_name}":
+ return module
+ else:
+ return getattr(module, attr_name)
+ except ImportError as e:
+ raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e
+ except AttributeError as e:
+ raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e
+
+
+def __dir__():
+ lazy_attrs = list(_dynamic_imports.keys())
+ return sorted(lazy_attrs)
+
+
+__all__ = ["EventSource", "SSEError", "ServerSentEvent", "aconnect_sse", "connect_sse"]
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/core/http_sse/_api.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/core/http_sse/_api.py
new file mode 100644
index 000000000000..f900b3b686de
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/core/http_sse/_api.py
@@ -0,0 +1,112 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import re
+from contextlib import asynccontextmanager, contextmanager
+from typing import Any, AsyncGenerator, AsyncIterator, Iterator, cast
+
+import httpx
+from ._decoders import SSEDecoder
+from ._exceptions import SSEError
+from ._models import ServerSentEvent
+
+
+class EventSource:
+ def __init__(self, response: httpx.Response) -> None:
+ self._response = response
+
+ def _check_content_type(self) -> None:
+ content_type = self._response.headers.get("content-type", "").partition(";")[0]
+ if "text/event-stream" not in content_type:
+ raise SSEError(
+ f"Expected response header Content-Type to contain 'text/event-stream', got {content_type!r}"
+ )
+
+ def _get_charset(self) -> str:
+ """Extract charset from Content-Type header, fallback to UTF-8."""
+ content_type = self._response.headers.get("content-type", "")
+
+ # Parse charset parameter using regex
+ charset_match = re.search(r"charset=([^;\s]+)", content_type, re.IGNORECASE)
+ if charset_match:
+ charset = charset_match.group(1).strip("\"'")
+ # Validate that it's a known encoding
+ try:
+ # Test if the charset is valid by trying to encode/decode
+ "test".encode(charset).decode(charset)
+ return charset
+ except (LookupError, UnicodeError):
+ # If charset is invalid, fall back to UTF-8
+ pass
+
+ # Default to UTF-8 if no charset specified or invalid charset
+ return "utf-8"
+
+ @property
+ def response(self) -> httpx.Response:
+ return self._response
+
+ def iter_sse(self) -> Iterator[ServerSentEvent]:
+ self._check_content_type()
+ decoder = SSEDecoder()
+ charset = self._get_charset()
+
+ buffer = ""
+ for chunk in self._response.iter_bytes():
+ # Decode chunk using detected charset
+ text_chunk = chunk.decode(charset, errors="replace")
+ buffer += text_chunk
+
+ # Process complete lines
+ while "\n" in buffer:
+ line, buffer = buffer.split("\n", 1)
+ line = line.rstrip("\r")
+ sse = decoder.decode(line)
+ # when we reach a "\n\n" => line = ''
+ # => decoder will attempt to return an SSE Event
+ if sse is not None:
+ yield sse
+
+ # Process any remaining data in buffer
+ if buffer.strip():
+ line = buffer.rstrip("\r")
+ sse = decoder.decode(line)
+ if sse is not None:
+ yield sse
+
+ async def aiter_sse(self) -> AsyncGenerator[ServerSentEvent, None]:
+ self._check_content_type()
+ decoder = SSEDecoder()
+ lines = cast(AsyncGenerator[str, None], self._response.aiter_lines())
+ try:
+ async for line in lines:
+ line = line.rstrip("\n")
+ sse = decoder.decode(line)
+ if sse is not None:
+ yield sse
+ finally:
+ await lines.aclose()
+
+
+@contextmanager
+def connect_sse(client: httpx.Client, method: str, url: str, **kwargs: Any) -> Iterator[EventSource]:
+ headers = kwargs.pop("headers", {})
+ headers["Accept"] = "text/event-stream"
+ headers["Cache-Control"] = "no-store"
+
+ with client.stream(method, url, headers=headers, **kwargs) as response:
+ yield EventSource(response)
+
+
+@asynccontextmanager
+async def aconnect_sse(
+ client: httpx.AsyncClient,
+ method: str,
+ url: str,
+ **kwargs: Any,
+) -> AsyncIterator[EventSource]:
+ headers = kwargs.pop("headers", {})
+ headers["Accept"] = "text/event-stream"
+ headers["Cache-Control"] = "no-store"
+
+ async with client.stream(method, url, headers=headers, **kwargs) as response:
+ yield EventSource(response)
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/core/http_sse/_decoders.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/core/http_sse/_decoders.py
new file mode 100644
index 000000000000..339b08901381
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/core/http_sse/_decoders.py
@@ -0,0 +1,61 @@
+# This file was auto-generated by Fern from our API Definition.
+
+from typing import List, Optional
+
+from ._models import ServerSentEvent
+
+
+class SSEDecoder:
+ def __init__(self) -> None:
+ self._event = ""
+ self._data: List[str] = []
+ self._last_event_id = ""
+ self._retry: Optional[int] = None
+
+ def decode(self, line: str) -> Optional[ServerSentEvent]:
+ # See: https://html.spec.whatwg.org/multipage/server-sent-events.html#event-stream-interpretation # noqa: E501
+
+ if not line:
+ if not self._event and not self._data and not self._last_event_id and self._retry is None:
+ return None
+
+ sse = ServerSentEvent(
+ event=self._event,
+ data="\n".join(self._data),
+ id=self._last_event_id,
+ retry=self._retry,
+ )
+
+ # NOTE: as per the SSE spec, do not reset last_event_id.
+ self._event = ""
+ self._data = []
+ self._retry = None
+
+ return sse
+
+ if line.startswith(":"):
+ return None
+
+ fieldname, _, value = line.partition(":")
+
+ if value.startswith(" "):
+ value = value[1:]
+
+ if fieldname == "event":
+ self._event = value
+ elif fieldname == "data":
+ self._data.append(value)
+ elif fieldname == "id":
+ if "\0" in value:
+ pass
+ else:
+ self._last_event_id = value
+ elif fieldname == "retry":
+ try:
+ self._retry = int(value)
+ except (TypeError, ValueError):
+ pass
+ else:
+ pass # Field is ignored.
+
+ return None
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/core/http_sse/_exceptions.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/core/http_sse/_exceptions.py
new file mode 100644
index 000000000000..81605a8a65ed
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/core/http_sse/_exceptions.py
@@ -0,0 +1,7 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import httpx
+
+
+class SSEError(httpx.TransportError):
+ pass
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/core/http_sse/_models.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/core/http_sse/_models.py
new file mode 100644
index 000000000000..1af57f8fd0d2
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/core/http_sse/_models.py
@@ -0,0 +1,17 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import json
+from dataclasses import dataclass
+from typing import Any, Optional
+
+
+@dataclass(frozen=True)
+class ServerSentEvent:
+ event: str = "message"
+ data: str = ""
+ id: str = ""
+ retry: Optional[int] = None
+
+ def json(self) -> Any:
+ """Parse the data field as JSON."""
+ return json.loads(self.data)
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/core/jsonable_encoder.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/core/jsonable_encoder.py
new file mode 100644
index 000000000000..5b0902ebcde3
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/core/jsonable_encoder.py
@@ -0,0 +1,120 @@
+# This file was auto-generated by Fern from our API Definition.
+
+"""
+jsonable_encoder converts a Python object to a JSON-friendly dict
+(e.g. datetimes to strings, Pydantic models to dicts).
+
+Taken from FastAPI, and made a bit simpler
+https://github.com/tiangolo/fastapi/blob/master/fastapi/encoders.py
+"""
+
+import base64
+import dataclasses
+import datetime as dt
+from enum import Enum
+from pathlib import PurePath
+from types import GeneratorType
+from typing import Any, Callable, Dict, List, Optional, Set, Union
+
+import pydantic
+from .datetime_utils import serialize_datetime
+from .pydantic_utilities import (
+ IS_PYDANTIC_V2,
+ encode_by_type,
+ to_jsonable_with_fallback,
+)
+
+SetIntStr = Set[Union[int, str]]
+DictIntStrAny = Dict[Union[int, str], Any]
+
+
+def jsonable_encoder(obj: Any, custom_encoder: Optional[Dict[Any, Callable[[Any], Any]]] = None) -> Any:
+ custom_encoder = custom_encoder or {}
+ # Generated SDKs use Ellipsis (`...`) as the sentinel value for "OMIT".
+ # OMIT values should be excluded from serialized payloads.
+ if obj is Ellipsis:
+ return None
+ if custom_encoder:
+ if type(obj) in custom_encoder:
+ return custom_encoder[type(obj)](obj)
+ else:
+ for encoder_type, encoder_instance in custom_encoder.items():
+ if isinstance(obj, encoder_type):
+ return encoder_instance(obj)
+ if isinstance(obj, pydantic.BaseModel):
+ if IS_PYDANTIC_V2:
+ encoder = getattr(obj.model_config, "json_encoders", {}) # type: ignore # Pydantic v2
+ else:
+ encoder = getattr(obj.__config__, "json_encoders", {}) # type: ignore # Pydantic v1
+ if custom_encoder:
+ encoder.update(custom_encoder)
+ obj_dict = obj.dict(by_alias=True)
+ if "__root__" in obj_dict:
+ obj_dict = obj_dict["__root__"]
+ if "root" in obj_dict:
+ obj_dict = obj_dict["root"]
+ return jsonable_encoder(obj_dict, custom_encoder=encoder)
+ if dataclasses.is_dataclass(obj):
+ obj_dict = dataclasses.asdict(obj) # type: ignore
+ return jsonable_encoder(obj_dict, custom_encoder=custom_encoder)
+ if isinstance(obj, bytes):
+ return base64.b64encode(obj).decode("utf-8")
+ if isinstance(obj, Enum):
+ return obj.value
+ if isinstance(obj, PurePath):
+ return str(obj)
+ if isinstance(obj, (str, int, float, type(None))):
+ return obj
+ if isinstance(obj, dt.datetime):
+ return serialize_datetime(obj)
+ if isinstance(obj, dt.date):
+ return str(obj)
+ if isinstance(obj, dict):
+ encoded_dict = {}
+ allowed_keys = set(obj.keys())
+ for key, value in obj.items():
+ if key in allowed_keys:
+ if value is Ellipsis:
+ continue
+ encoded_key = jsonable_encoder(key, custom_encoder=custom_encoder)
+ encoded_value = jsonable_encoder(value, custom_encoder=custom_encoder)
+ encoded_dict[encoded_key] = encoded_value
+ return encoded_dict
+ if isinstance(obj, (list, set, frozenset, GeneratorType, tuple)):
+ encoded_list = []
+ for item in obj:
+ if item is Ellipsis:
+ continue
+ encoded_list.append(jsonable_encoder(item, custom_encoder=custom_encoder))
+ return encoded_list
+
+ def fallback_serializer(o: Any) -> Any:
+ attempt_encode = encode_by_type(o)
+ if attempt_encode is not None:
+ return attempt_encode
+
+ try:
+ data = dict(o)
+ except Exception as e:
+ errors: List[Exception] = []
+ errors.append(e)
+ try:
+ data = vars(o)
+ except Exception as e:
+ errors.append(e)
+ raise ValueError(errors) from e
+ return jsonable_encoder(data, custom_encoder=custom_encoder)
+
+ return to_jsonable_with_fallback(obj, fallback_serializer)
+
+
+def encode_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment.
+
+ Ensures proper string conversion for all types, including
+ booleans which need lowercase 'true'/'false' rather than
+ Python's 'True'/'False'.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return str(jsonable_encoder(obj))
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/core/logging.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/core/logging.py
new file mode 100644
index 000000000000..e5e572458bc8
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/core/logging.py
@@ -0,0 +1,107 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import logging
+import typing
+
+LogLevel = typing.Literal["debug", "info", "warn", "error"]
+
+_LOG_LEVEL_MAP: typing.Dict[LogLevel, int] = {
+ "debug": 1,
+ "info": 2,
+ "warn": 3,
+ "error": 4,
+}
+
+
+class ILogger(typing.Protocol):
+ def debug(self, message: str, **kwargs: typing.Any) -> None: ...
+ def info(self, message: str, **kwargs: typing.Any) -> None: ...
+ def warn(self, message: str, **kwargs: typing.Any) -> None: ...
+ def error(self, message: str, **kwargs: typing.Any) -> None: ...
+
+
+class ConsoleLogger:
+ _logger: logging.Logger
+
+ def __init__(self) -> None:
+ self._logger = logging.getLogger("fern")
+ if not self._logger.handlers:
+ handler = logging.StreamHandler()
+ handler.setFormatter(logging.Formatter("%(levelname)s - %(message)s"))
+ self._logger.addHandler(handler)
+ self._logger.setLevel(logging.DEBUG)
+
+ def debug(self, message: str, **kwargs: typing.Any) -> None:
+ self._logger.debug(message, extra=kwargs)
+
+ def info(self, message: str, **kwargs: typing.Any) -> None:
+ self._logger.info(message, extra=kwargs)
+
+ def warn(self, message: str, **kwargs: typing.Any) -> None:
+ self._logger.warning(message, extra=kwargs)
+
+ def error(self, message: str, **kwargs: typing.Any) -> None:
+ self._logger.error(message, extra=kwargs)
+
+
+class LogConfig(typing.TypedDict, total=False):
+ level: LogLevel
+ logger: ILogger
+ silent: bool
+
+
+class Logger:
+ _level: int
+ _logger: ILogger
+ _silent: bool
+
+ def __init__(self, *, level: LogLevel, logger: ILogger, silent: bool) -> None:
+ self._level = _LOG_LEVEL_MAP[level]
+ self._logger = logger
+ self._silent = silent
+
+ def _should_log(self, level: LogLevel) -> bool:
+ return not self._silent and self._level <= _LOG_LEVEL_MAP[level]
+
+ def is_debug(self) -> bool:
+ return self._should_log("debug")
+
+ def is_info(self) -> bool:
+ return self._should_log("info")
+
+ def is_warn(self) -> bool:
+ return self._should_log("warn")
+
+ def is_error(self) -> bool:
+ return self._should_log("error")
+
+ def debug(self, message: str, **kwargs: typing.Any) -> None:
+ if self.is_debug():
+ self._logger.debug(message, **kwargs)
+
+ def info(self, message: str, **kwargs: typing.Any) -> None:
+ if self.is_info():
+ self._logger.info(message, **kwargs)
+
+ def warn(self, message: str, **kwargs: typing.Any) -> None:
+ if self.is_warn():
+ self._logger.warn(message, **kwargs)
+
+ def error(self, message: str, **kwargs: typing.Any) -> None:
+ if self.is_error():
+ self._logger.error(message, **kwargs)
+
+
+_default_logger: Logger = Logger(level="info", logger=ConsoleLogger(), silent=True)
+
+
+def create_logger(config: typing.Optional[typing.Union[LogConfig, Logger]] = None) -> Logger:
+ if config is None:
+ return _default_logger
+ if isinstance(config, Logger):
+ return config
+ return Logger(
+ level=config.get("level", "info"),
+ logger=config.get("logger", ConsoleLogger()),
+ silent=config.get("silent", True),
+ )
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/core/parse_error.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/core/parse_error.py
new file mode 100644
index 000000000000..4527c6a8adec
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/core/parse_error.py
@@ -0,0 +1,36 @@
+# This file was auto-generated by Fern from our API Definition.
+
+from typing import Any, Dict, Optional
+
+
+class ParsingError(Exception):
+ """
+ Raised when the SDK fails to parse/validate a response from the server.
+ This typically indicates that the server returned a response whose shape
+ does not match the expected schema.
+ """
+
+ headers: Optional[Dict[str, str]]
+ status_code: Optional[int]
+ body: Any
+ cause: Optional[Exception]
+
+ def __init__(
+ self,
+ *,
+ headers: Optional[Dict[str, str]] = None,
+ status_code: Optional[int] = None,
+ body: Any = None,
+ cause: Optional[Exception] = None,
+ ) -> None:
+ self.headers = headers
+ self.status_code = status_code
+ self.body = body
+ self.cause = cause
+ super().__init__()
+ if cause is not None:
+ self.__cause__ = cause
+
+ def __str__(self) -> str:
+ cause_str = f", cause: {self.cause}" if self.cause is not None else ""
+ return f"headers: {self.headers}, status_code: {self.status_code}, body: {self.body}{cause_str}"
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/core/pydantic_utilities.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/core/pydantic_utilities.py
new file mode 100644
index 000000000000..fea3a08d3268
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/core/pydantic_utilities.py
@@ -0,0 +1,634 @@
+# This file was auto-generated by Fern from our API Definition.
+
+# nopycln: file
+import datetime as dt
+import inspect
+import json
+import logging
+from collections import defaultdict
+from dataclasses import asdict
+from typing import (
+ TYPE_CHECKING,
+ Any,
+ Callable,
+ ClassVar,
+ Dict,
+ List,
+ Mapping,
+ Optional,
+ Set,
+ Tuple,
+ Type,
+ TypeVar,
+ Union,
+ cast,
+)
+
+import pydantic
+import typing_extensions
+from pydantic.fields import FieldInfo as _FieldInfo
+
+_logger = logging.getLogger(__name__)
+
+if TYPE_CHECKING:
+ from .http_sse._models import ServerSentEvent
+
+IS_PYDANTIC_V2 = pydantic.VERSION.startswith("2.")
+
+if IS_PYDANTIC_V2:
+ _datetime_adapter = pydantic.TypeAdapter(dt.datetime) # type: ignore[attr-defined]
+ _date_adapter = pydantic.TypeAdapter(dt.date) # type: ignore[attr-defined]
+
+ def parse_datetime(value: Any) -> dt.datetime: # type: ignore[misc]
+ if isinstance(value, dt.datetime):
+ return value
+ return _datetime_adapter.validate_python(value)
+
+ def parse_date(value: Any) -> dt.date: # type: ignore[misc]
+ if isinstance(value, dt.datetime):
+ return value.date()
+ if isinstance(value, dt.date):
+ return value
+ return _date_adapter.validate_python(value)
+
+ # Avoid importing from pydantic.v1 to maintain Python 3.14 compatibility.
+ from typing import get_args as get_args # type: ignore[assignment]
+ from typing import get_origin as get_origin # type: ignore[assignment]
+
+ def is_literal_type(tp: Optional[Type[Any]]) -> bool: # type: ignore[misc]
+ return typing_extensions.get_origin(tp) is typing_extensions.Literal
+
+ def is_union(tp: Optional[Type[Any]]) -> bool: # type: ignore[misc]
+ return tp is Union or typing_extensions.get_origin(tp) is Union # type: ignore[comparison-overlap]
+
+ # Inline encoders_by_type to avoid importing from pydantic.v1.json
+ import re as _re
+ from collections import deque as _deque
+ from decimal import Decimal as _Decimal
+ from enum import Enum as _Enum
+ from ipaddress import (
+ IPv4Address as _IPv4Address,
+ )
+ from ipaddress import (
+ IPv4Interface as _IPv4Interface,
+ )
+ from ipaddress import (
+ IPv4Network as _IPv4Network,
+ )
+ from ipaddress import (
+ IPv6Address as _IPv6Address,
+ )
+ from ipaddress import (
+ IPv6Interface as _IPv6Interface,
+ )
+ from ipaddress import (
+ IPv6Network as _IPv6Network,
+ )
+ from pathlib import Path as _Path
+ from types import GeneratorType as _GeneratorType
+ from uuid import UUID as _UUID
+
+ from pydantic.fields import FieldInfo as ModelField # type: ignore[no-redef, assignment]
+
+ def _decimal_encoder(dec_value: Any) -> Any:
+ if dec_value.as_tuple().exponent >= 0:
+ return int(dec_value)
+ return float(dec_value)
+
+ encoders_by_type: Dict[Type[Any], Callable[[Any], Any]] = { # type: ignore[no-redef]
+ bytes: lambda o: o.decode(),
+ dt.date: lambda o: o.isoformat(),
+ dt.datetime: lambda o: o.isoformat(),
+ dt.time: lambda o: o.isoformat(),
+ dt.timedelta: lambda td: td.total_seconds(),
+ _Decimal: _decimal_encoder,
+ _Enum: lambda o: o.value,
+ frozenset: list,
+ _deque: list,
+ _GeneratorType: list,
+ _IPv4Address: str,
+ _IPv4Interface: str,
+ _IPv4Network: str,
+ _IPv6Address: str,
+ _IPv6Interface: str,
+ _IPv6Network: str,
+ _Path: str,
+ _re.Pattern: lambda o: o.pattern,
+ set: list,
+ _UUID: str,
+ }
+else:
+ from pydantic.datetime_parse import parse_date as parse_date # type: ignore[no-redef]
+ from pydantic.datetime_parse import parse_datetime as parse_datetime # type: ignore[no-redef]
+ from pydantic.fields import ModelField as ModelField # type: ignore[attr-defined, no-redef, assignment]
+ from pydantic.json import ENCODERS_BY_TYPE as encoders_by_type # type: ignore[no-redef]
+ from pydantic.typing import get_args as get_args # type: ignore[no-redef]
+ from pydantic.typing import get_origin as get_origin # type: ignore[no-redef]
+ from pydantic.typing import is_literal_type as is_literal_type # type: ignore[no-redef, assignment]
+ from pydantic.typing import is_union as is_union # type: ignore[no-redef]
+
+from .datetime_utils import serialize_datetime
+from .serialization import convert_and_respect_annotation_metadata
+from typing_extensions import TypeAlias
+
+T = TypeVar("T")
+Model = TypeVar("Model", bound=pydantic.BaseModel)
+
+
+def _get_discriminator_and_variants(type_: Type[Any]) -> Tuple[Optional[str], Optional[List[Type[Any]]]]:
+ """
+ Extract the discriminator field name and union variants from a discriminated union type.
+ Supports Annotated[Union[...], Field(discriminator=...)] patterns.
+ Returns (discriminator, variants) or (None, None) if not a discriminated union.
+ """
+ origin = typing_extensions.get_origin(type_)
+
+ if origin is typing_extensions.Annotated:
+ args = typing_extensions.get_args(type_)
+ if len(args) >= 2:
+ inner_type = args[0]
+ # Check annotations for discriminator
+ discriminator = None
+ for annotation in args[1:]:
+ if hasattr(annotation, "discriminator"):
+ discriminator = getattr(annotation, "discriminator", None)
+ break
+
+ if discriminator:
+ inner_origin = typing_extensions.get_origin(inner_type)
+ if inner_origin is Union:
+ variants = list(typing_extensions.get_args(inner_type))
+ return discriminator, variants
+ return None, None
+
+
+def _get_field_annotation(model: Type[Any], field_name: str) -> Optional[Type[Any]]:
+ """Get the type annotation of a field from a Pydantic model."""
+ if IS_PYDANTIC_V2:
+ fields = getattr(model, "model_fields", {})
+ field_info = fields.get(field_name)
+ if field_info:
+ return cast(Optional[Type[Any]], field_info.annotation)
+ else:
+ fields = getattr(model, "__fields__", {})
+ field_info = fields.get(field_name)
+ if field_info:
+ return cast(Optional[Type[Any]], field_info.outer_type_)
+ return None
+
+
+def _find_variant_by_discriminator(
+ variants: List[Type[Any]],
+ discriminator: str,
+ discriminator_value: Any,
+) -> Optional[Type[Any]]:
+ """Find the union variant that matches the discriminator value."""
+ for variant in variants:
+ if not (inspect.isclass(variant) and issubclass(variant, pydantic.BaseModel)):
+ continue
+
+ disc_annotation = _get_field_annotation(variant, discriminator)
+ if disc_annotation and is_literal_type(disc_annotation):
+ literal_args = get_args(disc_annotation)
+ if literal_args and literal_args[0] == discriminator_value:
+ return variant
+ return None
+
+
+def _is_string_type(type_: Type[Any]) -> bool:
+ """Check if a type is str or Optional[str]."""
+ if type_ is str:
+ return True
+
+ origin = typing_extensions.get_origin(type_)
+ if origin is Union:
+ args = typing_extensions.get_args(type_)
+ # Optional[str] = Union[str, None]
+ non_none_args = [a for a in args if a is not type(None)]
+ if len(non_none_args) == 1 and non_none_args[0] is str:
+ return True
+
+ return False
+
+
+def parse_sse_obj(sse: "ServerSentEvent", type_: Type[T]) -> T:
+ """
+ Parse a ServerSentEvent into the appropriate type.
+
+ Handles two scenarios based on where the discriminator field is located:
+
+ 1. Data-level discrimination: The discriminator (e.g., 'type') is inside the 'data' payload.
+ The union describes the data content, not the SSE envelope.
+ -> Returns: json.loads(data) parsed into the type
+
+ Example: ChatStreamResponse with discriminator='type'
+ Input: ServerSentEvent(event="message", data='{"type": "content-delta", ...}', id="")
+ Output: ContentDeltaEvent (parsed from data, SSE envelope stripped)
+
+ 2. Event-level discrimination: The discriminator (e.g., 'event') is at the SSE event level.
+ The union describes the full SSE event structure.
+ -> Returns: SSE envelope with 'data' field JSON-parsed only if the variant expects non-string
+
+ Example: JobStreamResponse with discriminator='event'
+ Input: ServerSentEvent(event="ERROR", data='{"code": "FAILED", ...}', id="123")
+ Output: JobStreamResponse_Error with data as ErrorData object
+
+ But for variants where data is str (like STATUS_UPDATE):
+ Input: ServerSentEvent(event="STATUS_UPDATE", data='{"status": "processing"}', id="1")
+ Output: JobStreamResponse_StatusUpdate with data as string (not parsed)
+
+ Args:
+ sse: The ServerSentEvent object to parse
+ type_: The target discriminated union type
+
+ Returns:
+ The parsed object of type T
+
+ Note:
+ This function is only available in SDK contexts where http_sse module exists.
+ """
+ sse_event = asdict(sse)
+ discriminator, variants = _get_discriminator_and_variants(type_)
+
+ if discriminator is None or variants is None:
+ # Not a discriminated union - parse the data field as JSON
+ data_value = sse_event.get("data")
+ if isinstance(data_value, str) and data_value:
+ try:
+ parsed_data = json.loads(data_value)
+ return parse_obj_as(type_, parsed_data)
+ except json.JSONDecodeError as e:
+ _logger.warning(
+ "Failed to parse SSE data field as JSON: %s, data: %s",
+ e,
+ data_value[:100] if len(data_value) > 100 else data_value,
+ )
+ return parse_obj_as(type_, sse_event)
+
+ data_value = sse_event.get("data")
+
+ # Check if discriminator is at the top level (event-level discrimination)
+ if discriminator in sse_event:
+ # Case 2: Event-level discrimination
+ # Find the matching variant to check if 'data' field needs JSON parsing
+ disc_value = sse_event.get(discriminator)
+ matching_variant = _find_variant_by_discriminator(variants, discriminator, disc_value)
+
+ if matching_variant is not None:
+ # Check what type the variant expects for 'data'
+ data_type = _get_field_annotation(matching_variant, "data")
+ if data_type is not None and not _is_string_type(data_type):
+ # Variant expects non-string data - parse JSON
+ if isinstance(data_value, str) and data_value:
+ try:
+ parsed_data = json.loads(data_value)
+ new_object = dict(sse_event)
+ new_object["data"] = parsed_data
+ return parse_obj_as(type_, new_object)
+ except json.JSONDecodeError as e:
+ _logger.warning(
+ "Failed to parse SSE data field as JSON for event-level discrimination: %s, data: %s",
+ e,
+ data_value[:100] if len(data_value) > 100 else data_value,
+ )
+ # Either no matching variant, data is string type, or JSON parse failed
+ return parse_obj_as(type_, sse_event)
+
+ else:
+ # Case 1: Data-level discrimination
+ # The discriminator is inside the data payload - extract and parse data only
+ if isinstance(data_value, str) and data_value:
+ try:
+ parsed_data = json.loads(data_value)
+ return parse_obj_as(type_, parsed_data)
+ except json.JSONDecodeError as e:
+ _logger.warning(
+ "Failed to parse SSE data field as JSON for data-level discrimination: %s, data: %s",
+ e,
+ data_value[:100] if len(data_value) > 100 else data_value,
+ )
+ return parse_obj_as(type_, sse_event)
+
+
+def parse_obj_as(type_: Type[T], object_: Any) -> T:
+ # convert_and_respect_annotation_metadata is required for TypedDict aliasing.
+ #
+ # For Pydantic models, whether we should pre-dealias depends on how the model encodes aliasing:
+ # - If the model uses real Pydantic aliases (pydantic.Field(alias=...)), then we must pass wire keys through
+ # unchanged so Pydantic can validate them.
+ # - 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
+
+ dealiased_object = (
+ object_
+ if has_pydantic_aliases
+ else convert_and_respect_annotation_metadata(object_=object_, annotation=type_, direction="read")
+ )
+ else:
+ dealiased_object = convert_and_respect_annotation_metadata(object_=object_, annotation=type_, direction="read")
+ if IS_PYDANTIC_V2:
+ adapter = pydantic.TypeAdapter(type_) # type: ignore[attr-defined]
+ return adapter.validate_python(dealiased_object)
+ return pydantic.parse_obj_as(type_, dealiased_object)
+
+
+def to_jsonable_with_fallback(obj: Any, fallback_serializer: Callable[[Any], Any]) -> Any:
+ if IS_PYDANTIC_V2:
+ from pydantic_core import to_jsonable_python
+
+ return to_jsonable_python(obj, fallback=fallback_serializer)
+ return fallback_serializer(obj)
+
+
+class UniversalBaseModel(pydantic.BaseModel):
+ if IS_PYDANTIC_V2:
+ model_config: ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( # type: ignore[typeddict-unknown-key]
+ # Allow fields beginning with `model_` to be used in the model
+ protected_namespaces=(),
+ )
+
+ @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
+
+ @pydantic.model_serializer(mode="plain", when_used="json") # type: ignore[attr-defined]
+ def serialize_model(self) -> Any: # type: ignore[name-defined]
+ serialized = self.dict() # type: ignore[attr-defined]
+ data = {k: serialize_datetime(v) if isinstance(v, dt.datetime) else v for k, v in serialized.items()}
+ return data
+
+ else:
+
+ class Config:
+ smart_union = True
+ json_encoders = {dt.datetime: serialize_datetime}
+
+ @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
+
+ @classmethod
+ def model_construct(cls: Type["Model"], _fields_set: Optional[Set[str]] = None, **values: Any) -> "Model":
+ dealiased_object = convert_and_respect_annotation_metadata(object_=values, annotation=cls, direction="read")
+ return cls.construct(_fields_set, **dealiased_object)
+
+ @classmethod
+ def construct(cls: Type["Model"], _fields_set: Optional[Set[str]] = None, **values: Any) -> "Model":
+ dealiased_object = convert_and_respect_annotation_metadata(object_=values, annotation=cls, direction="read")
+ if IS_PYDANTIC_V2:
+ return super().model_construct(_fields_set, **dealiased_object) # type: ignore[misc]
+ return super().construct(_fields_set, **dealiased_object)
+
+ def json(self, **kwargs: Any) -> str:
+ kwargs_with_defaults = {
+ "by_alias": True,
+ "exclude_unset": True,
+ **kwargs,
+ }
+ if IS_PYDANTIC_V2:
+ return super().model_dump_json(**kwargs_with_defaults) # type: ignore[misc]
+ return super().json(**kwargs_with_defaults)
+
+ def dict(self, **kwargs: Any) -> Dict[str, Any]:
+ """
+ Override the default dict method to `exclude_unset` by default. This function patches
+ `exclude_unset` to work include fields within non-None default values.
+ """
+ # Note: the logic here is multiplexed given the levers exposed in Pydantic V1 vs V2
+ # Pydantic V1's .dict can be extremely slow, so we do not want to call it twice.
+ #
+ # We'd ideally do the same for Pydantic V2, but it shells out to a library to serialize models
+ # that we have less control over, and this is less intrusive than custom serializers for now.
+ if IS_PYDANTIC_V2:
+ kwargs_with_defaults_exclude_unset = {
+ **kwargs,
+ "by_alias": True,
+ "exclude_unset": True,
+ "exclude_none": False,
+ }
+ kwargs_with_defaults_exclude_none = {
+ **kwargs,
+ "by_alias": True,
+ "exclude_none": True,
+ "exclude_unset": False,
+ }
+ dict_dump = deep_union_pydantic_dicts(
+ super().model_dump(**kwargs_with_defaults_exclude_unset), # type: ignore[misc]
+ super().model_dump(**kwargs_with_defaults_exclude_none), # type: ignore[misc]
+ )
+
+ else:
+ _fields_set = self.__fields_set__.copy()
+
+ fields = _get_model_fields(self.__class__)
+ for name, field in fields.items():
+ if name not in _fields_set:
+ default = _get_field_default(field)
+
+ # If the default values are non-null act like they've been set
+ # This effectively allows exclude_unset to work like exclude_none where
+ # the latter passes through intentionally set none values.
+ if default is not None or ("exclude_unset" in kwargs and not kwargs["exclude_unset"]):
+ _fields_set.add(name)
+
+ if default is not None:
+ self.__fields_set__.add(name)
+
+ kwargs_with_defaults_exclude_unset_include_fields = {
+ "by_alias": True,
+ "exclude_unset": True,
+ "include": _fields_set,
+ **kwargs,
+ }
+
+ dict_dump = super().dict(**kwargs_with_defaults_exclude_unset_include_fields)
+
+ return cast(
+ Dict[str, Any],
+ convert_and_respect_annotation_metadata(object_=dict_dump, annotation=self.__class__, direction="write"),
+ )
+
+
+def _union_list_of_pydantic_dicts(source: List[Any], destination: List[Any]) -> List[Any]:
+ converted_list: List[Any] = []
+ for i, item in enumerate(source):
+ destination_value = destination[i]
+ if isinstance(item, dict):
+ converted_list.append(deep_union_pydantic_dicts(item, destination_value))
+ elif isinstance(item, list):
+ converted_list.append(_union_list_of_pydantic_dicts(item, destination_value))
+ else:
+ converted_list.append(item)
+ return converted_list
+
+
+def deep_union_pydantic_dicts(source: Dict[str, Any], destination: Dict[str, Any]) -> Dict[str, Any]:
+ for key, value in source.items():
+ node = destination.setdefault(key, {})
+ if isinstance(value, dict):
+ deep_union_pydantic_dicts(value, node)
+ # Note: we do not do this same processing for sets given we do not have sets of models
+ # and given the sets are unordered, the processing of the set and matching objects would
+ # be non-trivial.
+ elif isinstance(value, list):
+ destination[key] = _union_list_of_pydantic_dicts(value, node)
+ else:
+ destination[key] = value
+
+ return destination
+
+
+if IS_PYDANTIC_V2:
+
+ class V2RootModel(UniversalBaseModel, pydantic.RootModel): # type: ignore[misc, name-defined, type-arg]
+ pass
+
+ UniversalRootModel: TypeAlias = V2RootModel # type: ignore[misc]
+else:
+ UniversalRootModel: TypeAlias = UniversalBaseModel # type: ignore[misc, no-redef]
+
+
+def encode_by_type(o: Any) -> Any:
+ encoders_by_class_tuples: Dict[Callable[[Any], Any], Tuple[Any, ...]] = defaultdict(tuple)
+ for type_, encoder in encoders_by_type.items():
+ encoders_by_class_tuples[encoder] += (type_,)
+
+ if type(o) in encoders_by_type:
+ return encoders_by_type[type(o)](o)
+ for encoder, classes_tuple in encoders_by_class_tuples.items():
+ if isinstance(o, classes_tuple):
+ return encoder(o)
+
+
+def update_forward_refs(model: Type["Model"], **localns: Any) -> None:
+ if IS_PYDANTIC_V2:
+ model.model_rebuild(raise_errors=False) # type: ignore[attr-defined]
+ else:
+ model.update_forward_refs(**localns)
+
+
+# Mirrors Pydantic's internal typing
+AnyCallable = Callable[..., Any]
+
+
+def universal_root_validator(
+ pre: bool = False,
+) -> Callable[[AnyCallable], AnyCallable]:
+ def decorator(func: AnyCallable) -> AnyCallable:
+ if IS_PYDANTIC_V2:
+ # In Pydantic v2, for RootModel we always use "before" mode
+ # The custom validators transform the input value before the model is created
+ return cast(AnyCallable, pydantic.model_validator(mode="before")(func)) # type: ignore[attr-defined]
+ return cast(AnyCallable, pydantic.root_validator(pre=pre)(func)) # type: ignore[call-overload]
+
+ return decorator
+
+
+def universal_field_validator(field_name: str, pre: bool = False) -> Callable[[AnyCallable], AnyCallable]:
+ def decorator(func: AnyCallable) -> AnyCallable:
+ if IS_PYDANTIC_V2:
+ return cast(AnyCallable, pydantic.field_validator(field_name, mode="before" if pre else "after")(func)) # type: ignore[attr-defined]
+ return cast(AnyCallable, pydantic.validator(field_name, pre=pre)(func))
+
+ return decorator
+
+
+PydanticField = Union[ModelField, _FieldInfo]
+
+
+def _get_model_fields(model: Type["Model"]) -> Mapping[str, PydanticField]:
+ if IS_PYDANTIC_V2:
+ return cast(Mapping[str, PydanticField], model.model_fields) # type: ignore[attr-defined]
+ return cast(Mapping[str, PydanticField], model.__fields__)
+
+
+def _get_field_default(field: PydanticField) -> Any:
+ try:
+ value = field.get_default() # type: ignore[union-attr]
+ except:
+ value = field.default
+ if IS_PYDANTIC_V2:
+ from pydantic_core import PydanticUndefined
+
+ if value == PydanticUndefined:
+ return None
+ return value
+ return value
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/core/query_encoder.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/core/query_encoder.py
new file mode 100644
index 000000000000..3183001d4046
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/core/query_encoder.py
@@ -0,0 +1,58 @@
+# This file was auto-generated by Fern from our API Definition.
+
+from typing import Any, Dict, List, Optional, Tuple
+
+import pydantic
+
+
+# Flattens dicts to be of the form {"key[subkey][subkey2]": value} where value is not a dict
+def traverse_query_dict(dict_flat: Dict[str, Any], key_prefix: Optional[str] = None) -> List[Tuple[str, Any]]:
+ result = []
+ for k, v in dict_flat.items():
+ key = f"{key_prefix}[{k}]" if key_prefix is not None else k
+ if isinstance(v, dict):
+ result.extend(traverse_query_dict(v, key))
+ elif isinstance(v, list):
+ for arr_v in v:
+ if isinstance(arr_v, dict):
+ result.extend(traverse_query_dict(arr_v, key))
+ else:
+ result.append((key, arr_v))
+ else:
+ result.append((key, v))
+ return result
+
+
+def single_query_encoder(query_key: str, query_value: Any) -> List[Tuple[str, Any]]:
+ if isinstance(query_value, pydantic.BaseModel) or isinstance(query_value, dict):
+ if isinstance(query_value, pydantic.BaseModel):
+ obj_dict = query_value.dict(by_alias=True)
+ else:
+ obj_dict = query_value
+ return traverse_query_dict(obj_dict, query_key)
+ elif isinstance(query_value, list):
+ encoded_values: List[Tuple[str, Any]] = []
+ for value in query_value:
+ if isinstance(value, pydantic.BaseModel) or isinstance(value, dict):
+ if isinstance(value, pydantic.BaseModel):
+ obj_dict = value.dict(by_alias=True)
+ elif isinstance(value, dict):
+ obj_dict = value
+
+ encoded_values.extend(single_query_encoder(query_key, obj_dict))
+ else:
+ encoded_values.append((query_key, value))
+
+ return encoded_values
+
+ return [(query_key, query_value)]
+
+
+def encode_query(query: Optional[Dict[str, Any]]) -> Optional[List[Tuple[str, Any]]]:
+ if query is None:
+ return None
+
+ encoded_query = []
+ for k, v in query.items():
+ encoded_query.extend(single_query_encoder(k, v))
+ return encoded_query
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/core/remove_none_from_dict.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/core/remove_none_from_dict.py
new file mode 100644
index 000000000000..c2298143f14a
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/core/remove_none_from_dict.py
@@ -0,0 +1,11 @@
+# This file was auto-generated by Fern from our API Definition.
+
+from typing import Any, Dict, Mapping, Optional
+
+
+def remove_none_from_dict(original: Mapping[str, Optional[Any]]) -> Dict[str, Any]:
+ new: Dict[str, Any] = {}
+ for key, value in original.items():
+ if value is not None:
+ new[key] = value
+ return new
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/core/request_options.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/core/request_options.py
new file mode 100644
index 000000000000..1b38804432ba
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/core/request_options.py
@@ -0,0 +1,35 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+try:
+ from typing import NotRequired # type: ignore
+except ImportError:
+ from typing_extensions import NotRequired
+
+
+class RequestOptions(typing.TypedDict, total=False):
+ """
+ Additional options for request-specific configuration when calling APIs via the SDK.
+ This is used primarily as an optional final parameter for service functions.
+
+ Attributes:
+ - timeout_in_seconds: int. The number of seconds to await an API call before timing out.
+
+ - max_retries: int. The max number of retries to attempt if the API call fails.
+
+ - additional_headers: typing.Dict[str, typing.Any]. A dictionary containing additional parameters to spread into the request's header dict
+
+ - additional_query_parameters: typing.Dict[str, typing.Any]. A dictionary containing additional parameters to spread into the request's query parameters dict
+
+ - additional_body_parameters: typing.Dict[str, typing.Any]. A dictionary containing additional parameters to spread into the request's body parameters dict
+
+ - chunk_size: int. The size, in bytes, to process each chunk of data being streamed back within the response. This equates to leveraging `chunk_size` within `requests` or `httpx`, and is only leveraged for file downloads.
+ """
+
+ timeout_in_seconds: NotRequired[int]
+ max_retries: NotRequired[int]
+ additional_headers: NotRequired[typing.Dict[str, typing.Any]]
+ additional_query_parameters: NotRequired[typing.Dict[str, typing.Any]]
+ additional_body_parameters: NotRequired[typing.Dict[str, typing.Any]]
+ chunk_size: NotRequired[int]
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/core/serialization.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/core/serialization.py
new file mode 100644
index 000000000000..c36e865cc729
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/core/serialization.py
@@ -0,0 +1,276 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import collections
+import inspect
+import typing
+
+import pydantic
+import typing_extensions
+
+
+class FieldMetadata:
+ """
+ Metadata class used to annotate fields to provide additional information.
+
+ Example:
+ class MyDict(TypedDict):
+ field: typing.Annotated[str, FieldMetadata(alias="field_name")]
+
+ Will serialize: `{"field": "value"}`
+ To: `{"field_name": "value"}`
+ """
+
+ alias: str
+
+ def __init__(self, *, alias: str) -> None:
+ self.alias = alias
+
+
+def convert_and_respect_annotation_metadata(
+ *,
+ object_: typing.Any,
+ annotation: typing.Any,
+ inner_type: typing.Optional[typing.Any] = None,
+ direction: typing.Literal["read", "write"],
+) -> typing.Any:
+ """
+ Respect the metadata annotations on a field, such as aliasing. This function effectively
+ manipulates the dict-form of an object to respect the metadata annotations. This is primarily used for
+ TypedDicts, which cannot support aliasing out of the box, and can be extended for additional
+ utilities, such as defaults.
+
+ Parameters
+ ----------
+ object_ : typing.Any
+
+ annotation : type
+ The type we're looking to apply typing annotations from
+
+ inner_type : typing.Optional[type]
+
+ Returns
+ -------
+ typing.Any
+ """
+
+ if object_ is None:
+ return None
+ if inner_type is None:
+ inner_type = annotation
+
+ clean_type = _remove_annotations(inner_type)
+ # Pydantic models
+ if (
+ inspect.isclass(clean_type)
+ and issubclass(clean_type, pydantic.BaseModel)
+ and isinstance(object_, typing.Mapping)
+ ):
+ return _convert_mapping(object_, clean_type, direction)
+ # TypedDicts
+ if typing_extensions.is_typeddict(clean_type) and isinstance(object_, typing.Mapping):
+ return _convert_mapping(object_, clean_type, direction)
+
+ if (
+ typing_extensions.get_origin(clean_type) == typing.Dict
+ or typing_extensions.get_origin(clean_type) == dict
+ or clean_type == typing.Dict
+ ) and isinstance(object_, typing.Dict):
+ key_type = typing_extensions.get_args(clean_type)[0]
+ value_type = typing_extensions.get_args(clean_type)[1]
+
+ return {
+ key: convert_and_respect_annotation_metadata(
+ object_=value,
+ annotation=annotation,
+ inner_type=value_type,
+ direction=direction,
+ )
+ for key, value in object_.items()
+ }
+
+ # If you're iterating on a string, do not bother to coerce it to a sequence.
+ if not isinstance(object_, str):
+ if (
+ typing_extensions.get_origin(clean_type) == typing.Set
+ or typing_extensions.get_origin(clean_type) == set
+ or clean_type == typing.Set
+ ) and isinstance(object_, typing.Set):
+ inner_type = typing_extensions.get_args(clean_type)[0]
+ return {
+ convert_and_respect_annotation_metadata(
+ object_=item,
+ annotation=annotation,
+ inner_type=inner_type,
+ direction=direction,
+ )
+ for item in object_
+ }
+ elif (
+ (
+ typing_extensions.get_origin(clean_type) == typing.List
+ or typing_extensions.get_origin(clean_type) == list
+ or clean_type == typing.List
+ )
+ and isinstance(object_, typing.List)
+ ) or (
+ (
+ typing_extensions.get_origin(clean_type) == typing.Sequence
+ or typing_extensions.get_origin(clean_type) == collections.abc.Sequence
+ or clean_type == typing.Sequence
+ )
+ and isinstance(object_, typing.Sequence)
+ ):
+ inner_type = typing_extensions.get_args(clean_type)[0]
+ return [
+ convert_and_respect_annotation_metadata(
+ object_=item,
+ annotation=annotation,
+ inner_type=inner_type,
+ direction=direction,
+ )
+ for item in object_
+ ]
+
+ if typing_extensions.get_origin(clean_type) == typing.Union:
+ # We should be able to ~relatively~ safely try to convert keys against all
+ # member types in the union, the edge case here is if one member aliases a field
+ # of the same name to a different name from another member
+ # Or if another member aliases a field of the same name that another member does not.
+ for member in typing_extensions.get_args(clean_type):
+ object_ = convert_and_respect_annotation_metadata(
+ object_=object_,
+ annotation=annotation,
+ inner_type=member,
+ direction=direction,
+ )
+ return object_
+
+ annotated_type = _get_annotation(annotation)
+ if annotated_type is None:
+ return object_
+
+ # If the object is not a TypedDict, a Union, or other container (list, set, sequence, etc.)
+ # Then we can safely call it on the recursive conversion.
+ return object_
+
+
+def _convert_mapping(
+ object_: typing.Mapping[str, object],
+ expected_type: typing.Any,
+ direction: typing.Literal["read", "write"],
+) -> typing.Mapping[str, object]:
+ converted_object: typing.Dict[str, object] = {}
+ try:
+ annotations = typing_extensions.get_type_hints(expected_type, include_extras=True)
+ except NameError:
+ # The TypedDict contains a circular reference, so
+ # we use the __annotations__ attribute directly.
+ annotations = getattr(expected_type, "__annotations__", {})
+ aliases_to_field_names = _get_alias_to_field_name(annotations)
+ for key, value in object_.items():
+ if direction == "read" and key in aliases_to_field_names:
+ dealiased_key = aliases_to_field_names.get(key)
+ if dealiased_key is not None:
+ type_ = annotations.get(dealiased_key)
+ else:
+ type_ = annotations.get(key)
+ # Note you can't get the annotation by the field name if you're in read mode, so you must check the aliases map
+ #
+ # So this is effectively saying if we're in write mode, and we don't have a type, or if we're in read mode and we don't have an alias
+ # then we can just pass the value through as is
+ if type_ is None:
+ converted_object[key] = value
+ elif direction == "read" and key not in aliases_to_field_names:
+ converted_object[key] = convert_and_respect_annotation_metadata(
+ object_=value, annotation=type_, direction=direction
+ )
+ else:
+ converted_object[_alias_key(key, type_, direction, aliases_to_field_names)] = (
+ convert_and_respect_annotation_metadata(object_=value, annotation=type_, direction=direction)
+ )
+ return converted_object
+
+
+def _get_annotation(type_: typing.Any) -> typing.Optional[typing.Any]:
+ maybe_annotated_type = typing_extensions.get_origin(type_)
+ if maybe_annotated_type is None:
+ return None
+
+ if maybe_annotated_type == typing_extensions.NotRequired:
+ type_ = typing_extensions.get_args(type_)[0]
+ maybe_annotated_type = typing_extensions.get_origin(type_)
+
+ if maybe_annotated_type == typing_extensions.Annotated:
+ return type_
+
+ return None
+
+
+def _remove_annotations(type_: typing.Any) -> typing.Any:
+ maybe_annotated_type = typing_extensions.get_origin(type_)
+ if maybe_annotated_type is None:
+ return type_
+
+ if maybe_annotated_type == typing_extensions.NotRequired:
+ return _remove_annotations(typing_extensions.get_args(type_)[0])
+
+ if maybe_annotated_type == typing_extensions.Annotated:
+ return _remove_annotations(typing_extensions.get_args(type_)[0])
+
+ return type_
+
+
+def get_alias_to_field_mapping(type_: typing.Any) -> typing.Dict[str, str]:
+ annotations = typing_extensions.get_type_hints(type_, include_extras=True)
+ return _get_alias_to_field_name(annotations)
+
+
+def get_field_to_alias_mapping(type_: typing.Any) -> typing.Dict[str, str]:
+ annotations = typing_extensions.get_type_hints(type_, include_extras=True)
+ return _get_field_to_alias_name(annotations)
+
+
+def _get_alias_to_field_name(
+ field_to_hint: typing.Dict[str, typing.Any],
+) -> typing.Dict[str, str]:
+ aliases = {}
+ for field, hint in field_to_hint.items():
+ maybe_alias = _get_alias_from_type(hint)
+ if maybe_alias is not None:
+ aliases[maybe_alias] = field
+ return aliases
+
+
+def _get_field_to_alias_name(
+ field_to_hint: typing.Dict[str, typing.Any],
+) -> typing.Dict[str, str]:
+ aliases = {}
+ for field, hint in field_to_hint.items():
+ maybe_alias = _get_alias_from_type(hint)
+ if maybe_alias is not None:
+ aliases[field] = maybe_alias
+ return aliases
+
+
+def _get_alias_from_type(type_: typing.Any) -> typing.Optional[str]:
+ maybe_annotated_type = _get_annotation(type_)
+
+ if maybe_annotated_type is not None:
+ # The actual annotations are 1 onward, the first is the annotated type
+ annotations = typing_extensions.get_args(maybe_annotated_type)[1:]
+
+ for annotation in annotations:
+ if isinstance(annotation, FieldMetadata) and annotation.alias is not None:
+ return annotation.alias
+ return None
+
+
+def _alias_key(
+ key: str,
+ type_: typing.Any,
+ direction: typing.Literal["read", "write"],
+ aliases_to_field_names: typing.Dict[str, str],
+) -> str:
+ if direction == "read":
+ return aliases_to_field_names.get(key, key)
+ return _get_alias_from_type(type_=type_) or key
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/py.typed b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/py.typed
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/__init__.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/__init__.py
new file mode 100644
index 000000000000..3c33147d88ac
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/__init__.py
@@ -0,0 +1,241 @@
+# This file was auto-generated by Fern from our API Definition.
+
+# isort: skip_file
+
+import typing
+from importlib import import_module
+
+if typing.TYPE_CHECKING:
+ from .types import (
+ AnyNumberUnionWithSameNumberTypes,
+ Bar,
+ BarUnion,
+ BarUnionWithDiscriminant,
+ BarUnionWithNullableReference,
+ BarUnionWithOptionalReference,
+ BarUnionWithoutKey,
+ CustomFormatUnionWithSameStringTypes,
+ DateUnionWithOptionalTime,
+ DateUnionWithTime,
+ DatetimeUnionWithOptionalTime,
+ DatetimeUnionWithTime,
+ Empty1UnionWithMultipleNoProperties,
+ Empty2UnionWithMultipleNoProperties,
+ EmptyUnionWithNoProperties,
+ FernUnionWithLiteral,
+ FirstItemType,
+ FirstItemTypeUnionWithDuplicativeDiscriminants,
+ Foo,
+ Foo1UnionWithDuplicateTypes,
+ Foo2UnionWithDuplicateTypes,
+ FooExtended,
+ FooExtendedUnionWithSubTypes,
+ FooUnion,
+ FooUnionWithBaseProperties,
+ FooUnionWithDiscriminant,
+ FooUnionWithMultipleNoProperties,
+ FooUnionWithNoProperties,
+ FooUnionWithNullableReference,
+ FooUnionWithOptionalReference,
+ FooUnionWithSingleElement,
+ FooUnionWithSubTypes,
+ FooUnionWithoutKey,
+ Integer1UnionWithDuplicatePrimitive,
+ Integer2UnionWithDuplicatePrimitive,
+ IntegerUnionWithBaseProperties,
+ IntegerUnionWithPrimitive,
+ NegativeIntUnionWithSameNumberTypes,
+ PatternStringUnionWithSameStringTypes,
+ PositiveIntUnionWithSameNumberTypes,
+ RegularStringUnionWithSameStringTypes,
+ SecondItemType,
+ SecondItemTypeUnionWithDuplicativeDiscriminants,
+ String1UnionWithDuplicatePrimitive,
+ String2UnionWithDuplicatePrimitive,
+ StringUnionWithBaseProperties,
+ StringUnionWithPrimitive,
+ TypeWithOptionalMap,
+ Union,
+ UnionWithBaseProperties,
+ UnionWithDiscriminant,
+ UnionWithDuplicatePrimitive,
+ UnionWithDuplicateTypes,
+ UnionWithDuplicativeDiscriminants,
+ UnionWithLiteral,
+ UnionWithMultipleNoProperties,
+ UnionWithNoProperties,
+ UnionWithNullableReference,
+ UnionWithOptionalReference,
+ UnionWithOptionalTime,
+ UnionWithPrimitive,
+ UnionWithSameNumberTypes,
+ UnionWithSameStringTypes,
+ UnionWithSingleElement,
+ UnionWithSubTypes,
+ UnionWithTime,
+ UnionWithoutKey,
+ ValueUnionWithTime,
+ )
+_dynamic_imports: typing.Dict[str, str] = {
+ "AnyNumberUnionWithSameNumberTypes": ".types",
+ "Bar": ".types",
+ "BarUnion": ".types",
+ "BarUnionWithDiscriminant": ".types",
+ "BarUnionWithNullableReference": ".types",
+ "BarUnionWithOptionalReference": ".types",
+ "BarUnionWithoutKey": ".types",
+ "CustomFormatUnionWithSameStringTypes": ".types",
+ "DateUnionWithOptionalTime": ".types",
+ "DateUnionWithTime": ".types",
+ "DatetimeUnionWithOptionalTime": ".types",
+ "DatetimeUnionWithTime": ".types",
+ "Empty1UnionWithMultipleNoProperties": ".types",
+ "Empty2UnionWithMultipleNoProperties": ".types",
+ "EmptyUnionWithNoProperties": ".types",
+ "FernUnionWithLiteral": ".types",
+ "FirstItemType": ".types",
+ "FirstItemTypeUnionWithDuplicativeDiscriminants": ".types",
+ "Foo": ".types",
+ "Foo1UnionWithDuplicateTypes": ".types",
+ "Foo2UnionWithDuplicateTypes": ".types",
+ "FooExtended": ".types",
+ "FooExtendedUnionWithSubTypes": ".types",
+ "FooUnion": ".types",
+ "FooUnionWithBaseProperties": ".types",
+ "FooUnionWithDiscriminant": ".types",
+ "FooUnionWithMultipleNoProperties": ".types",
+ "FooUnionWithNoProperties": ".types",
+ "FooUnionWithNullableReference": ".types",
+ "FooUnionWithOptionalReference": ".types",
+ "FooUnionWithSingleElement": ".types",
+ "FooUnionWithSubTypes": ".types",
+ "FooUnionWithoutKey": ".types",
+ "Integer1UnionWithDuplicatePrimitive": ".types",
+ "Integer2UnionWithDuplicatePrimitive": ".types",
+ "IntegerUnionWithBaseProperties": ".types",
+ "IntegerUnionWithPrimitive": ".types",
+ "NegativeIntUnionWithSameNumberTypes": ".types",
+ "PatternStringUnionWithSameStringTypes": ".types",
+ "PositiveIntUnionWithSameNumberTypes": ".types",
+ "RegularStringUnionWithSameStringTypes": ".types",
+ "SecondItemType": ".types",
+ "SecondItemTypeUnionWithDuplicativeDiscriminants": ".types",
+ "String1UnionWithDuplicatePrimitive": ".types",
+ "String2UnionWithDuplicatePrimitive": ".types",
+ "StringUnionWithBaseProperties": ".types",
+ "StringUnionWithPrimitive": ".types",
+ "TypeWithOptionalMap": ".types",
+ "Union": ".types",
+ "UnionWithBaseProperties": ".types",
+ "UnionWithDiscriminant": ".types",
+ "UnionWithDuplicatePrimitive": ".types",
+ "UnionWithDuplicateTypes": ".types",
+ "UnionWithDuplicativeDiscriminants": ".types",
+ "UnionWithLiteral": ".types",
+ "UnionWithMultipleNoProperties": ".types",
+ "UnionWithNoProperties": ".types",
+ "UnionWithNullableReference": ".types",
+ "UnionWithOptionalReference": ".types",
+ "UnionWithOptionalTime": ".types",
+ "UnionWithPrimitive": ".types",
+ "UnionWithSameNumberTypes": ".types",
+ "UnionWithSameStringTypes": ".types",
+ "UnionWithSingleElement": ".types",
+ "UnionWithSubTypes": ".types",
+ "UnionWithTime": ".types",
+ "UnionWithoutKey": ".types",
+ "ValueUnionWithTime": ".types",
+}
+
+
+def __getattr__(attr_name: str) -> typing.Any:
+ module_name = _dynamic_imports.get(attr_name)
+ if module_name is None:
+ raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}")
+ try:
+ module = import_module(module_name, __package__)
+ if module_name == f".{attr_name}":
+ return module
+ else:
+ return getattr(module, attr_name)
+ except ImportError as e:
+ raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e
+ except AttributeError as e:
+ raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e
+
+
+def __dir__():
+ lazy_attrs = list(_dynamic_imports.keys())
+ return sorted(lazy_attrs)
+
+
+__all__ = [
+ "AnyNumberUnionWithSameNumberTypes",
+ "Bar",
+ "BarUnion",
+ "BarUnionWithDiscriminant",
+ "BarUnionWithNullableReference",
+ "BarUnionWithOptionalReference",
+ "BarUnionWithoutKey",
+ "CustomFormatUnionWithSameStringTypes",
+ "DateUnionWithOptionalTime",
+ "DateUnionWithTime",
+ "DatetimeUnionWithOptionalTime",
+ "DatetimeUnionWithTime",
+ "Empty1UnionWithMultipleNoProperties",
+ "Empty2UnionWithMultipleNoProperties",
+ "EmptyUnionWithNoProperties",
+ "FernUnionWithLiteral",
+ "FirstItemType",
+ "FirstItemTypeUnionWithDuplicativeDiscriminants",
+ "Foo",
+ "Foo1UnionWithDuplicateTypes",
+ "Foo2UnionWithDuplicateTypes",
+ "FooExtended",
+ "FooExtendedUnionWithSubTypes",
+ "FooUnion",
+ "FooUnionWithBaseProperties",
+ "FooUnionWithDiscriminant",
+ "FooUnionWithMultipleNoProperties",
+ "FooUnionWithNoProperties",
+ "FooUnionWithNullableReference",
+ "FooUnionWithOptionalReference",
+ "FooUnionWithSingleElement",
+ "FooUnionWithSubTypes",
+ "FooUnionWithoutKey",
+ "Integer1UnionWithDuplicatePrimitive",
+ "Integer2UnionWithDuplicatePrimitive",
+ "IntegerUnionWithBaseProperties",
+ "IntegerUnionWithPrimitive",
+ "NegativeIntUnionWithSameNumberTypes",
+ "PatternStringUnionWithSameStringTypes",
+ "PositiveIntUnionWithSameNumberTypes",
+ "RegularStringUnionWithSameStringTypes",
+ "SecondItemType",
+ "SecondItemTypeUnionWithDuplicativeDiscriminants",
+ "String1UnionWithDuplicatePrimitive",
+ "String2UnionWithDuplicatePrimitive",
+ "StringUnionWithBaseProperties",
+ "StringUnionWithPrimitive",
+ "TypeWithOptionalMap",
+ "Union",
+ "UnionWithBaseProperties",
+ "UnionWithDiscriminant",
+ "UnionWithDuplicatePrimitive",
+ "UnionWithDuplicateTypes",
+ "UnionWithDuplicativeDiscriminants",
+ "UnionWithLiteral",
+ "UnionWithMultipleNoProperties",
+ "UnionWithNoProperties",
+ "UnionWithNullableReference",
+ "UnionWithOptionalReference",
+ "UnionWithOptionalTime",
+ "UnionWithPrimitive",
+ "UnionWithSameNumberTypes",
+ "UnionWithSameStringTypes",
+ "UnionWithSingleElement",
+ "UnionWithSubTypes",
+ "UnionWithTime",
+ "UnionWithoutKey",
+ "ValueUnionWithTime",
+]
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/__init__.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/__init__.py
new file mode 100644
index 000000000000..db56fd8eb53c
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/__init__.py
@@ -0,0 +1,242 @@
+# This file was auto-generated by Fern from our API Definition.
+
+# isort: skip_file
+
+import typing
+from importlib import import_module
+
+if typing.TYPE_CHECKING:
+ from .bar import Bar
+ from .first_item_type import FirstItemType
+ from .foo import Foo
+ from .foo_extended import FooExtended
+ from .second_item_type import SecondItemType
+ from .type_with_optional_map import TypeWithOptionalMap
+ from .union import BarUnion, FooUnion, Union
+ from .union_with_base_properties import (
+ FooUnionWithBaseProperties,
+ IntegerUnionWithBaseProperties,
+ StringUnionWithBaseProperties,
+ UnionWithBaseProperties,
+ )
+ from .union_with_discriminant import BarUnionWithDiscriminant, FooUnionWithDiscriminant, UnionWithDiscriminant
+ from .union_with_duplicate_primitive import (
+ Integer1UnionWithDuplicatePrimitive,
+ Integer2UnionWithDuplicatePrimitive,
+ String1UnionWithDuplicatePrimitive,
+ String2UnionWithDuplicatePrimitive,
+ UnionWithDuplicatePrimitive,
+ )
+ from .union_with_duplicate_types import (
+ Foo1UnionWithDuplicateTypes,
+ Foo2UnionWithDuplicateTypes,
+ UnionWithDuplicateTypes,
+ )
+ from .union_with_duplicative_discriminants import (
+ FirstItemTypeUnionWithDuplicativeDiscriminants,
+ SecondItemTypeUnionWithDuplicativeDiscriminants,
+ UnionWithDuplicativeDiscriminants,
+ )
+ from .union_with_literal import FernUnionWithLiteral, UnionWithLiteral
+ from .union_with_multiple_no_properties import (
+ Empty1UnionWithMultipleNoProperties,
+ Empty2UnionWithMultipleNoProperties,
+ FooUnionWithMultipleNoProperties,
+ UnionWithMultipleNoProperties,
+ )
+ from .union_with_no_properties import EmptyUnionWithNoProperties, FooUnionWithNoProperties, UnionWithNoProperties
+ from .union_with_nullable_reference import (
+ BarUnionWithNullableReference,
+ FooUnionWithNullableReference,
+ UnionWithNullableReference,
+ )
+ from .union_with_optional_reference import (
+ BarUnionWithOptionalReference,
+ FooUnionWithOptionalReference,
+ UnionWithOptionalReference,
+ )
+ from .union_with_optional_time import (
+ DateUnionWithOptionalTime,
+ DatetimeUnionWithOptionalTime,
+ UnionWithOptionalTime,
+ )
+ from .union_with_primitive import IntegerUnionWithPrimitive, StringUnionWithPrimitive, UnionWithPrimitive
+ from .union_with_same_number_types import (
+ AnyNumberUnionWithSameNumberTypes,
+ NegativeIntUnionWithSameNumberTypes,
+ PositiveIntUnionWithSameNumberTypes,
+ UnionWithSameNumberTypes,
+ )
+ from .union_with_same_string_types import (
+ CustomFormatUnionWithSameStringTypes,
+ PatternStringUnionWithSameStringTypes,
+ RegularStringUnionWithSameStringTypes,
+ UnionWithSameStringTypes,
+ )
+ from .union_with_single_element import FooUnionWithSingleElement, UnionWithSingleElement
+ from .union_with_sub_types import FooExtendedUnionWithSubTypes, FooUnionWithSubTypes, UnionWithSubTypes
+ from .union_with_time import DateUnionWithTime, DatetimeUnionWithTime, UnionWithTime, ValueUnionWithTime
+ from .union_without_key import BarUnionWithoutKey, FooUnionWithoutKey, UnionWithoutKey
+_dynamic_imports: typing.Dict[str, str] = {
+ "AnyNumberUnionWithSameNumberTypes": ".union_with_same_number_types",
+ "Bar": ".bar",
+ "BarUnion": ".union",
+ "BarUnionWithDiscriminant": ".union_with_discriminant",
+ "BarUnionWithNullableReference": ".union_with_nullable_reference",
+ "BarUnionWithOptionalReference": ".union_with_optional_reference",
+ "BarUnionWithoutKey": ".union_without_key",
+ "CustomFormatUnionWithSameStringTypes": ".union_with_same_string_types",
+ "DateUnionWithOptionalTime": ".union_with_optional_time",
+ "DateUnionWithTime": ".union_with_time",
+ "DatetimeUnionWithOptionalTime": ".union_with_optional_time",
+ "DatetimeUnionWithTime": ".union_with_time",
+ "Empty1UnionWithMultipleNoProperties": ".union_with_multiple_no_properties",
+ "Empty2UnionWithMultipleNoProperties": ".union_with_multiple_no_properties",
+ "EmptyUnionWithNoProperties": ".union_with_no_properties",
+ "FernUnionWithLiteral": ".union_with_literal",
+ "FirstItemType": ".first_item_type",
+ "FirstItemTypeUnionWithDuplicativeDiscriminants": ".union_with_duplicative_discriminants",
+ "Foo": ".foo",
+ "Foo1UnionWithDuplicateTypes": ".union_with_duplicate_types",
+ "Foo2UnionWithDuplicateTypes": ".union_with_duplicate_types",
+ "FooExtended": ".foo_extended",
+ "FooExtendedUnionWithSubTypes": ".union_with_sub_types",
+ "FooUnion": ".union",
+ "FooUnionWithBaseProperties": ".union_with_base_properties",
+ "FooUnionWithDiscriminant": ".union_with_discriminant",
+ "FooUnionWithMultipleNoProperties": ".union_with_multiple_no_properties",
+ "FooUnionWithNoProperties": ".union_with_no_properties",
+ "FooUnionWithNullableReference": ".union_with_nullable_reference",
+ "FooUnionWithOptionalReference": ".union_with_optional_reference",
+ "FooUnionWithSingleElement": ".union_with_single_element",
+ "FooUnionWithSubTypes": ".union_with_sub_types",
+ "FooUnionWithoutKey": ".union_without_key",
+ "Integer1UnionWithDuplicatePrimitive": ".union_with_duplicate_primitive",
+ "Integer2UnionWithDuplicatePrimitive": ".union_with_duplicate_primitive",
+ "IntegerUnionWithBaseProperties": ".union_with_base_properties",
+ "IntegerUnionWithPrimitive": ".union_with_primitive",
+ "NegativeIntUnionWithSameNumberTypes": ".union_with_same_number_types",
+ "PatternStringUnionWithSameStringTypes": ".union_with_same_string_types",
+ "PositiveIntUnionWithSameNumberTypes": ".union_with_same_number_types",
+ "RegularStringUnionWithSameStringTypes": ".union_with_same_string_types",
+ "SecondItemType": ".second_item_type",
+ "SecondItemTypeUnionWithDuplicativeDiscriminants": ".union_with_duplicative_discriminants",
+ "String1UnionWithDuplicatePrimitive": ".union_with_duplicate_primitive",
+ "String2UnionWithDuplicatePrimitive": ".union_with_duplicate_primitive",
+ "StringUnionWithBaseProperties": ".union_with_base_properties",
+ "StringUnionWithPrimitive": ".union_with_primitive",
+ "TypeWithOptionalMap": ".type_with_optional_map",
+ "Union": ".union",
+ "UnionWithBaseProperties": ".union_with_base_properties",
+ "UnionWithDiscriminant": ".union_with_discriminant",
+ "UnionWithDuplicatePrimitive": ".union_with_duplicate_primitive",
+ "UnionWithDuplicateTypes": ".union_with_duplicate_types",
+ "UnionWithDuplicativeDiscriminants": ".union_with_duplicative_discriminants",
+ "UnionWithLiteral": ".union_with_literal",
+ "UnionWithMultipleNoProperties": ".union_with_multiple_no_properties",
+ "UnionWithNoProperties": ".union_with_no_properties",
+ "UnionWithNullableReference": ".union_with_nullable_reference",
+ "UnionWithOptionalReference": ".union_with_optional_reference",
+ "UnionWithOptionalTime": ".union_with_optional_time",
+ "UnionWithPrimitive": ".union_with_primitive",
+ "UnionWithSameNumberTypes": ".union_with_same_number_types",
+ "UnionWithSameStringTypes": ".union_with_same_string_types",
+ "UnionWithSingleElement": ".union_with_single_element",
+ "UnionWithSubTypes": ".union_with_sub_types",
+ "UnionWithTime": ".union_with_time",
+ "UnionWithoutKey": ".union_without_key",
+ "ValueUnionWithTime": ".union_with_time",
+}
+
+
+def __getattr__(attr_name: str) -> typing.Any:
+ module_name = _dynamic_imports.get(attr_name)
+ if module_name is None:
+ raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}")
+ try:
+ module = import_module(module_name, __package__)
+ if module_name == f".{attr_name}":
+ return module
+ else:
+ return getattr(module, attr_name)
+ except ImportError as e:
+ raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e
+ except AttributeError as e:
+ raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e
+
+
+def __dir__():
+ lazy_attrs = list(_dynamic_imports.keys())
+ return sorted(lazy_attrs)
+
+
+__all__ = [
+ "AnyNumberUnionWithSameNumberTypes",
+ "Bar",
+ "BarUnion",
+ "BarUnionWithDiscriminant",
+ "BarUnionWithNullableReference",
+ "BarUnionWithOptionalReference",
+ "BarUnionWithoutKey",
+ "CustomFormatUnionWithSameStringTypes",
+ "DateUnionWithOptionalTime",
+ "DateUnionWithTime",
+ "DatetimeUnionWithOptionalTime",
+ "DatetimeUnionWithTime",
+ "Empty1UnionWithMultipleNoProperties",
+ "Empty2UnionWithMultipleNoProperties",
+ "EmptyUnionWithNoProperties",
+ "FernUnionWithLiteral",
+ "FirstItemType",
+ "FirstItemTypeUnionWithDuplicativeDiscriminants",
+ "Foo",
+ "Foo1UnionWithDuplicateTypes",
+ "Foo2UnionWithDuplicateTypes",
+ "FooExtended",
+ "FooExtendedUnionWithSubTypes",
+ "FooUnion",
+ "FooUnionWithBaseProperties",
+ "FooUnionWithDiscriminant",
+ "FooUnionWithMultipleNoProperties",
+ "FooUnionWithNoProperties",
+ "FooUnionWithNullableReference",
+ "FooUnionWithOptionalReference",
+ "FooUnionWithSingleElement",
+ "FooUnionWithSubTypes",
+ "FooUnionWithoutKey",
+ "Integer1UnionWithDuplicatePrimitive",
+ "Integer2UnionWithDuplicatePrimitive",
+ "IntegerUnionWithBaseProperties",
+ "IntegerUnionWithPrimitive",
+ "NegativeIntUnionWithSameNumberTypes",
+ "PatternStringUnionWithSameStringTypes",
+ "PositiveIntUnionWithSameNumberTypes",
+ "RegularStringUnionWithSameStringTypes",
+ "SecondItemType",
+ "SecondItemTypeUnionWithDuplicativeDiscriminants",
+ "String1UnionWithDuplicatePrimitive",
+ "String2UnionWithDuplicatePrimitive",
+ "StringUnionWithBaseProperties",
+ "StringUnionWithPrimitive",
+ "TypeWithOptionalMap",
+ "Union",
+ "UnionWithBaseProperties",
+ "UnionWithDiscriminant",
+ "UnionWithDuplicatePrimitive",
+ "UnionWithDuplicateTypes",
+ "UnionWithDuplicativeDiscriminants",
+ "UnionWithLiteral",
+ "UnionWithMultipleNoProperties",
+ "UnionWithNoProperties",
+ "UnionWithNullableReference",
+ "UnionWithOptionalReference",
+ "UnionWithOptionalTime",
+ "UnionWithPrimitive",
+ "UnionWithSameNumberTypes",
+ "UnionWithSameStringTypes",
+ "UnionWithSingleElement",
+ "UnionWithSubTypes",
+ "UnionWithTime",
+ "UnionWithoutKey",
+ "ValueUnionWithTime",
+]
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/bar.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/bar.py
new file mode 100644
index 000000000000..79756d8a7f61
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/bar.py
@@ -0,0 +1,29 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class Bar(UniversalBaseModel):
+ """
+ Examples
+ --------
+ from seed.types import Bar
+
+ Bar(
+ name="example1",
+ )
+ """
+
+ name: str
+
+ 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/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/first_item_type.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/first_item_type.py
new file mode 100644
index 000000000000..9d6f05fd670d
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/first_item_type.py
@@ -0,0 +1,20 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class FirstItemType(UniversalBaseModel):
+ type: typing.Optional[typing.Literal["firstItemType"]] = None
+ name: str
+
+ 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/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/foo.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/foo.py
new file mode 100644
index 000000000000..577841969f62
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/foo.py
@@ -0,0 +1,29 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class Foo(UniversalBaseModel):
+ """
+ Examples
+ --------
+ from seed.types import Foo
+
+ Foo(
+ name="example1",
+ )
+ """
+
+ name: str
+
+ 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/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/foo_extended.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/foo_extended.py
new file mode 100644
index 000000000000..93bda09fc539
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/foo_extended.py
@@ -0,0 +1,31 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class FooExtended(UniversalBaseModel):
+ """
+ Examples
+ --------
+ from seed.types import FooExtended
+
+ FooExtended(
+ name="example1",
+ age=5,
+ )
+ """
+
+ age: int
+ name: str
+
+ 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/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/second_item_type.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/second_item_type.py
new file mode 100644
index 000000000000..e362608ab78c
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/second_item_type.py
@@ -0,0 +1,20 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class SecondItemType(UniversalBaseModel):
+ type: typing.Optional[typing.Literal["secondItemType"]] = None
+ title: str
+
+ 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/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/type_with_optional_map.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/type_with_optional_map.py
new file mode 100644
index 000000000000..363f95fb0ba2
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/type_with_optional_map.py
@@ -0,0 +1,26 @@
+# 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 TypeWithOptionalMap(UniversalBaseModel):
+ key: str
+ column_values: typing_extensions.Annotated[
+ typing.Dict[str, typing.Optional[str]],
+ FieldMetadata(alias="columnValues"),
+ pydantic.Field(alias="columnValues"),
+ ]
+
+ 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/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/union.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/union.py
new file mode 100644
index 000000000000..5dd00a82a315
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/union.py
@@ -0,0 +1,40 @@
+# This file was auto-generated by Fern from our API Definition.
+
+from __future__ import annotations
+
+import typing
+
+import pydantic
+import typing_extensions
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from .bar import Bar
+from .foo import Foo
+
+
+class FooUnion(UniversalBaseModel):
+ foo: Foo
+ type: typing.Literal["foo"] = "foo"
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+
+
+class BarUnion(UniversalBaseModel):
+ bar: Bar
+ type: typing.Literal["bar"] = "bar"
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+
+
+Union = typing_extensions.Annotated[typing.Union[FooUnion, BarUnion], pydantic.Field(discriminator="type")]
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/union_with_base_properties.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/union_with_base_properties.py
new file mode 100644
index 000000000000..a24785e2c6d5
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/union_with_base_properties.py
@@ -0,0 +1,89 @@
+# This file was auto-generated by Fern from our API Definition.
+
+from __future__ import annotations
+
+import typing
+
+import pydantic
+import typing_extensions
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class Base(UniversalBaseModel):
+ """
+ Examples
+ --------
+ from seed.types import IntegerUnionWithBaseProperties
+
+ IntegerUnionWithBaseProperties(value=5)
+ """
+
+ id: str
+
+ 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
+
+
+class IntegerUnionWithBaseProperties(Base):
+ value: int
+ type: typing.Literal["integer"] = "integer"
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+
+
+class StringUnionWithBaseProperties(Base):
+ value: str
+ type: typing.Literal["string"] = "string"
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+
+
+class FooUnionWithBaseProperties(Base):
+ """
+ Examples
+ --------
+ from seed.types import IntegerUnionWithBaseProperties
+
+ IntegerUnionWithBaseProperties(value=5)
+ """
+
+ type: typing.Literal["foo"] = "foo"
+ name: str
+
+ 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
+
+
+"""
+from seed.types import IntegerUnionWithBaseProperties
+
+IntegerUnionWithBaseProperties(value=5)
+"""
+UnionWithBaseProperties = typing_extensions.Annotated[
+ typing.Union[IntegerUnionWithBaseProperties, StringUnionWithBaseProperties, FooUnionWithBaseProperties],
+ pydantic.Field(discriminator="type"),
+]
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/union_with_discriminant.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/union_with_discriminant.py
new file mode 100644
index 000000000000..2ccd5fdb2e1d
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/union_with_discriminant.py
@@ -0,0 +1,47 @@
+# This file was auto-generated by Fern from our API Definition.
+
+from __future__ import annotations
+
+import typing
+
+import pydantic
+import typing_extensions
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ...core.serialization import FieldMetadata
+from .bar import Bar
+from .foo import Foo
+
+
+class FooUnionWithDiscriminant(UniversalBaseModel):
+ foo: Foo
+ type: typing_extensions.Annotated[
+ typing.Literal["foo"], FieldMetadata(alias="_type"), pydantic.Field(alias="_type")
+ ] = "foo"
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+
+
+class BarUnionWithDiscriminant(UniversalBaseModel):
+ bar: Bar
+ type: typing_extensions.Annotated[
+ typing.Literal["bar"], FieldMetadata(alias="_type"), pydantic.Field(alias="_type")
+ ] = "bar"
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+
+
+UnionWithDiscriminant = typing_extensions.Annotated[
+ typing.Union[FooUnionWithDiscriminant, BarUnionWithDiscriminant], pydantic.Field(discriminator="type")
+]
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/union_with_duplicate_primitive.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/union_with_duplicate_primitive.py
new file mode 100644
index 000000000000..303bcec31917
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/union_with_duplicate_primitive.py
@@ -0,0 +1,77 @@
+# This file was auto-generated by Fern from our API Definition.
+
+from __future__ import annotations
+
+import typing
+
+import pydantic
+import typing_extensions
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class Integer1UnionWithDuplicatePrimitive(UniversalBaseModel):
+ value: int
+ type: typing.Literal["integer1"] = "integer1"
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+
+
+class Integer2UnionWithDuplicatePrimitive(UniversalBaseModel):
+ value: int
+ type: typing.Literal["integer2"] = "integer2"
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+
+
+class String1UnionWithDuplicatePrimitive(UniversalBaseModel):
+ value: str
+ type: typing.Literal["string1"] = "string1"
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+
+
+class String2UnionWithDuplicatePrimitive(UniversalBaseModel):
+ value: str
+ type: typing.Literal["string2"] = "string2"
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+
+
+"""
+from seed.types import Integer1UnionWithDuplicatePrimitive
+
+Integer1UnionWithDuplicatePrimitive(value=9)
+"""
+UnionWithDuplicatePrimitive = typing_extensions.Annotated[
+ typing.Union[
+ Integer1UnionWithDuplicatePrimitive,
+ Integer2UnionWithDuplicatePrimitive,
+ String1UnionWithDuplicatePrimitive,
+ String2UnionWithDuplicatePrimitive,
+ ],
+ pydantic.Field(discriminator="type"),
+]
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/union_with_duplicate_types.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/union_with_duplicate_types.py
new file mode 100644
index 000000000000..edafb87d9865
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/union_with_duplicate_types.py
@@ -0,0 +1,69 @@
+# This file was auto-generated by Fern from our API Definition.
+
+from __future__ import annotations
+
+import typing
+
+import pydantic
+import typing_extensions
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class Foo1UnionWithDuplicateTypes(UniversalBaseModel):
+ """
+ Examples
+ --------
+ from seed.types import Foo1UnionWithDuplicateTypes
+
+ Foo1UnionWithDuplicateTypes(
+ name="example1",
+ )
+ """
+
+ type: typing.Literal["foo1"] = "foo1"
+ name: str
+
+ 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
+
+
+class Foo2UnionWithDuplicateTypes(UniversalBaseModel):
+ """
+ Examples
+ --------
+ from seed.types import Foo1UnionWithDuplicateTypes
+
+ Foo1UnionWithDuplicateTypes(
+ name="example1",
+ )
+ """
+
+ type: typing.Literal["foo2"] = "foo2"
+ name: str
+
+ 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
+
+
+"""
+from seed.types import Foo1UnionWithDuplicateTypes
+
+Foo1UnionWithDuplicateTypes(
+ name="example1",
+)
+"""
+UnionWithDuplicateTypes = typing_extensions.Annotated[
+ typing.Union[Foo1UnionWithDuplicateTypes, Foo2UnionWithDuplicateTypes], pydantic.Field(discriminator="type")
+]
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/union_with_duplicative_discriminants.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/union_with_duplicative_discriminants.py
new file mode 100644
index 000000000000..18c32e903b3b
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/union_with_duplicative_discriminants.py
@@ -0,0 +1,43 @@
+# This file was auto-generated by Fern from our API Definition.
+
+from __future__ import annotations
+
+import typing
+
+import pydantic
+import typing_extensions
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class FirstItemTypeUnionWithDuplicativeDiscriminants(UniversalBaseModel):
+ type: typing.Literal["firstItemType"] = "firstItemType"
+ name: str
+
+ 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
+
+
+class SecondItemTypeUnionWithDuplicativeDiscriminants(UniversalBaseModel):
+ type: typing.Literal["secondItemType"] = "secondItemType"
+ title: str
+
+ 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
+
+
+UnionWithDuplicativeDiscriminants = typing_extensions.Annotated[
+ typing.Union[FirstItemTypeUnionWithDuplicativeDiscriminants, SecondItemTypeUnionWithDuplicativeDiscriminants],
+ pydantic.Field(discriminator="type"),
+]
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/union_with_literal.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/union_with_literal.py
new file mode 100644
index 000000000000..ed8b0823e190
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/union_with_literal.py
@@ -0,0 +1,50 @@
+# This file was auto-generated by Fern from our API Definition.
+
+from __future__ import annotations
+
+import typing
+
+import pydantic
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class Base(UniversalBaseModel):
+ """
+ Examples
+ --------
+ from seed.types import FernUnionWithLiteral
+
+ FernUnionWithLiteral()
+ """
+
+ base: typing.Literal["base"] = "base"
+
+ 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
+
+
+class FernUnionWithLiteral(Base):
+ value: typing.Literal["fern"]
+ type: typing.Literal["fern"] = "fern"
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+
+
+"""
+from seed.types import FernUnionWithLiteral
+
+FernUnionWithLiteral()
+"""
+UnionWithLiteral = FernUnionWithLiteral
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/union_with_multiple_no_properties.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/union_with_multiple_no_properties.py
new file mode 100644
index 000000000000..b2721f724e50
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/union_with_multiple_no_properties.py
@@ -0,0 +1,94 @@
+# This file was auto-generated by Fern from our API Definition.
+
+from __future__ import annotations
+
+import typing
+
+import pydantic
+import typing_extensions
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class FooUnionWithMultipleNoProperties(UniversalBaseModel):
+ """
+ Examples
+ --------
+ from seed.types import FooUnionWithMultipleNoProperties
+
+ FooUnionWithMultipleNoProperties(
+ name="example",
+ )
+ """
+
+ type: typing.Literal["foo"] = "foo"
+ name: str
+
+ 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
+
+
+class Empty1UnionWithMultipleNoProperties(UniversalBaseModel):
+ """
+ Examples
+ --------
+ from seed.types import FooUnionWithMultipleNoProperties
+
+ FooUnionWithMultipleNoProperties(
+ name="example",
+ )
+ """
+
+ type: typing.Literal["empty1"] = "empty1"
+
+ 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
+
+
+class Empty2UnionWithMultipleNoProperties(UniversalBaseModel):
+ """
+ Examples
+ --------
+ from seed.types import FooUnionWithMultipleNoProperties
+
+ FooUnionWithMultipleNoProperties(
+ name="example",
+ )
+ """
+
+ type: typing.Literal["empty2"] = "empty2"
+
+ 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
+
+
+"""
+from seed.types import FooUnionWithMultipleNoProperties
+
+FooUnionWithMultipleNoProperties(
+ name="example",
+)
+"""
+UnionWithMultipleNoProperties = typing_extensions.Annotated[
+ typing.Union[
+ FooUnionWithMultipleNoProperties, Empty1UnionWithMultipleNoProperties, Empty2UnionWithMultipleNoProperties
+ ],
+ pydantic.Field(discriminator="type"),
+]
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/union_with_no_properties.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/union_with_no_properties.py
new file mode 100644
index 000000000000..60729355e856
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/union_with_no_properties.py
@@ -0,0 +1,68 @@
+# This file was auto-generated by Fern from our API Definition.
+
+from __future__ import annotations
+
+import typing
+
+import pydantic
+import typing_extensions
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class FooUnionWithNoProperties(UniversalBaseModel):
+ """
+ Examples
+ --------
+ from seed.types import FooUnionWithNoProperties
+
+ FooUnionWithNoProperties(
+ name="example",
+ )
+ """
+
+ type: typing.Literal["foo"] = "foo"
+ name: str
+
+ 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
+
+
+class EmptyUnionWithNoProperties(UniversalBaseModel):
+ """
+ Examples
+ --------
+ from seed.types import FooUnionWithNoProperties
+
+ FooUnionWithNoProperties(
+ name="example",
+ )
+ """
+
+ type: typing.Literal["empty"] = "empty"
+
+ 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
+
+
+"""
+from seed.types import FooUnionWithNoProperties
+
+FooUnionWithNoProperties(
+ name="example",
+)
+"""
+UnionWithNoProperties = typing_extensions.Annotated[
+ typing.Union[FooUnionWithNoProperties, EmptyUnionWithNoProperties], pydantic.Field(discriminator="type")
+]
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/union_with_nullable_reference.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/union_with_nullable_reference.py
new file mode 100644
index 000000000000..facb98a808e2
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/union_with_nullable_reference.py
@@ -0,0 +1,42 @@
+# This file was auto-generated by Fern from our API Definition.
+
+from __future__ import annotations
+
+import typing
+
+import pydantic
+import typing_extensions
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from .bar import Bar
+from .foo import Foo
+
+
+class FooUnionWithNullableReference(UniversalBaseModel):
+ value: typing.Optional[Foo] = None
+ type: typing.Literal["foo"] = "foo"
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+
+
+class BarUnionWithNullableReference(UniversalBaseModel):
+ value: typing.Optional[Bar] = None
+ type: typing.Literal["bar"] = "bar"
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+
+
+UnionWithNullableReference = typing_extensions.Annotated[
+ typing.Union[FooUnionWithNullableReference, BarUnionWithNullableReference], pydantic.Field(discriminator="type")
+]
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/union_with_optional_reference.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/union_with_optional_reference.py
new file mode 100644
index 000000000000..999b9ba7536e
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/union_with_optional_reference.py
@@ -0,0 +1,42 @@
+# This file was auto-generated by Fern from our API Definition.
+
+from __future__ import annotations
+
+import typing
+
+import pydantic
+import typing_extensions
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from .bar import Bar
+from .foo import Foo
+
+
+class FooUnionWithOptionalReference(UniversalBaseModel):
+ value: typing.Optional[Foo] = None
+ type: typing.Literal["foo"] = "foo"
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+
+
+class BarUnionWithOptionalReference(UniversalBaseModel):
+ value: typing.Optional[Bar] = None
+ type: typing.Literal["bar"] = "bar"
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+
+
+UnionWithOptionalReference = typing_extensions.Annotated[
+ typing.Union[FooUnionWithOptionalReference, BarUnionWithOptionalReference], pydantic.Field(discriminator="type")
+]
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/union_with_optional_time.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/union_with_optional_time.py
new file mode 100644
index 000000000000..c641c9d2aa02
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/union_with_optional_time.py
@@ -0,0 +1,52 @@
+# This file was auto-generated by Fern from our API Definition.
+
+from __future__ import annotations
+
+import datetime as dt
+import typing
+
+import pydantic
+import typing_extensions
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class DateUnionWithOptionalTime(UniversalBaseModel):
+ value: typing.Optional[dt.date] = None
+ type: typing.Literal["date"] = "date"
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+
+
+class DatetimeUnionWithOptionalTime(UniversalBaseModel):
+ value: typing.Optional[dt.datetime] = None
+ type: typing.Literal["datetime"] = "datetime"
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+
+
+"""
+import datetime
+
+from seed.types import DateUnionWithOptionalTime
+
+DateUnionWithOptionalTime(
+ value=datetime.date.fromisoformat(
+ "1994-01-01",
+ )
+)
+"""
+UnionWithOptionalTime = typing_extensions.Annotated[
+ typing.Union[DateUnionWithOptionalTime, DatetimeUnionWithOptionalTime], pydantic.Field(discriminator="type")
+]
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/union_with_primitive.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/union_with_primitive.py
new file mode 100644
index 000000000000..61b3d7648b4a
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/union_with_primitive.py
@@ -0,0 +1,45 @@
+# This file was auto-generated by Fern from our API Definition.
+
+from __future__ import annotations
+
+import typing
+
+import pydantic
+import typing_extensions
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class IntegerUnionWithPrimitive(UniversalBaseModel):
+ value: int
+ type: typing.Literal["integer"] = "integer"
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+
+
+class StringUnionWithPrimitive(UniversalBaseModel):
+ value: str
+ type: typing.Literal["string"] = "string"
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+
+
+"""
+from seed.types import IntegerUnionWithPrimitive
+
+IntegerUnionWithPrimitive(value=9)
+"""
+UnionWithPrimitive = typing_extensions.Annotated[
+ typing.Union[IntegerUnionWithPrimitive, StringUnionWithPrimitive], pydantic.Field(discriminator="type")
+]
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/union_with_same_number_types.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/union_with_same_number_types.py
new file mode 100644
index 000000000000..18ab1202f7f5
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/union_with_same_number_types.py
@@ -0,0 +1,61 @@
+# This file was auto-generated by Fern from our API Definition.
+
+from __future__ import annotations
+
+import typing
+
+import pydantic
+import typing_extensions
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class PositiveIntUnionWithSameNumberTypes(UniversalBaseModel):
+ value: int
+ type: typing.Literal["positiveInt"] = "positiveInt"
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+
+
+class NegativeIntUnionWithSameNumberTypes(UniversalBaseModel):
+ value: int
+ type: typing.Literal["negativeInt"] = "negativeInt"
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+
+
+class AnyNumberUnionWithSameNumberTypes(UniversalBaseModel):
+ value: float
+ type: typing.Literal["anyNumber"] = "anyNumber"
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+
+
+"""
+from seed.types import PositiveIntUnionWithSameNumberTypes
+
+PositiveIntUnionWithSameNumberTypes(value=100)
+"""
+UnionWithSameNumberTypes = typing_extensions.Annotated[
+ typing.Union[
+ PositiveIntUnionWithSameNumberTypes, NegativeIntUnionWithSameNumberTypes, AnyNumberUnionWithSameNumberTypes
+ ],
+ pydantic.Field(discriminator="type"),
+]
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/union_with_same_string_types.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/union_with_same_string_types.py
new file mode 100644
index 000000000000..4e15ef4fa7ef
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/union_with_same_string_types.py
@@ -0,0 +1,63 @@
+# This file was auto-generated by Fern from our API Definition.
+
+from __future__ import annotations
+
+import typing
+
+import pydantic
+import typing_extensions
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class CustomFormatUnionWithSameStringTypes(UniversalBaseModel):
+ value: str
+ type: typing.Literal["customFormat"] = "customFormat"
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+
+
+class RegularStringUnionWithSameStringTypes(UniversalBaseModel):
+ value: str
+ type: typing.Literal["regularString"] = "regularString"
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+
+
+class PatternStringUnionWithSameStringTypes(UniversalBaseModel):
+ value: str
+ type: typing.Literal["patternString"] = "patternString"
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+
+
+"""
+from seed.types import CustomFormatUnionWithSameStringTypes
+
+CustomFormatUnionWithSameStringTypes(value="custom-123")
+"""
+UnionWithSameStringTypes = typing_extensions.Annotated[
+ typing.Union[
+ CustomFormatUnionWithSameStringTypes,
+ RegularStringUnionWithSameStringTypes,
+ PatternStringUnionWithSameStringTypes,
+ ],
+ pydantic.Field(discriminator="type"),
+]
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/union_with_single_element.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/union_with_single_element.py
new file mode 100644
index 000000000000..66210d446a7f
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/union_with_single_element.py
@@ -0,0 +1,42 @@
+# This file was auto-generated by Fern from our API Definition.
+
+from __future__ import annotations
+
+import typing
+
+import pydantic
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class FooUnionWithSingleElement(UniversalBaseModel):
+ """
+ Examples
+ --------
+ from seed.types import FooUnionWithSingleElement
+
+ FooUnionWithSingleElement(
+ name="example1",
+ )
+ """
+
+ type: typing.Literal["foo"] = "foo"
+ name: str
+
+ 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
+
+
+"""
+from seed.types import FooUnionWithSingleElement
+
+FooUnionWithSingleElement(
+ name="example1",
+)
+"""
+UnionWithSingleElement = FooUnionWithSingleElement
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/union_with_sub_types.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/union_with_sub_types.py
new file mode 100644
index 000000000000..24138b377866
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/union_with_sub_types.py
@@ -0,0 +1,70 @@
+# This file was auto-generated by Fern from our API Definition.
+
+from __future__ import annotations
+
+import typing
+
+import pydantic
+import typing_extensions
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class FooUnionWithSubTypes(UniversalBaseModel):
+ """
+ Examples
+ --------
+ from seed.types import FooUnionWithSubTypes
+
+ FooUnionWithSubTypes(
+ name="example1",
+ )
+ """
+
+ type: typing.Literal["foo"] = "foo"
+ name: str
+
+ 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
+
+
+class FooExtendedUnionWithSubTypes(UniversalBaseModel):
+ """
+ Examples
+ --------
+ from seed.types import FooUnionWithSubTypes
+
+ FooUnionWithSubTypes(
+ name="example1",
+ )
+ """
+
+ type: typing.Literal["fooExtended"] = "fooExtended"
+ age: int
+ name: str
+
+ 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
+
+
+"""
+from seed.types import FooUnionWithSubTypes
+
+FooUnionWithSubTypes(
+ name="example1",
+)
+"""
+UnionWithSubTypes = typing_extensions.Annotated[
+ typing.Union[FooUnionWithSubTypes, FooExtendedUnionWithSubTypes], pydantic.Field(discriminator="type")
+]
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/union_with_time.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/union_with_time.py
new file mode 100644
index 000000000000..7255608ad2e8
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/union_with_time.py
@@ -0,0 +1,59 @@
+# This file was auto-generated by Fern from our API Definition.
+
+from __future__ import annotations
+
+import datetime as dt
+import typing
+
+import pydantic
+import typing_extensions
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class ValueUnionWithTime(UniversalBaseModel):
+ value: int
+ type: typing.Literal["value"] = "value"
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+
+
+class DateUnionWithTime(UniversalBaseModel):
+ value: dt.date
+ type: typing.Literal["date"] = "date"
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+
+
+class DatetimeUnionWithTime(UniversalBaseModel):
+ value: dt.datetime
+ type: typing.Literal["datetime"] = "datetime"
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+
+
+"""
+from seed.types import ValueUnionWithTime
+
+ValueUnionWithTime(value=5)
+"""
+UnionWithTime = typing_extensions.Annotated[
+ typing.Union[ValueUnionWithTime, DateUnionWithTime, DatetimeUnionWithTime], pydantic.Field(discriminator="type")
+]
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/union_without_key.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/union_without_key.py
new file mode 100644
index 000000000000..8708f05b7aa7
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/types/types/union_without_key.py
@@ -0,0 +1,69 @@
+# This file was auto-generated by Fern from our API Definition.
+
+from __future__ import annotations
+
+import typing
+
+import pydantic
+import typing_extensions
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class FooUnionWithoutKey(UniversalBaseModel):
+ """
+ Examples
+ --------
+ from seed.types import FooUnionWithoutKey
+
+ FooUnionWithoutKey(
+ name="example1",
+ )
+ """
+
+ type: typing.Literal["foo"] = "foo"
+ name: str
+
+ 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
+
+
+class BarUnionWithoutKey(UniversalBaseModel):
+ """
+ Examples
+ --------
+ from seed.types import FooUnionWithoutKey
+
+ FooUnionWithoutKey(
+ name="example1",
+ )
+ """
+
+ type: typing.Literal["bar"] = "bar"
+ name: str
+
+ 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
+
+
+"""
+from seed.types import FooUnionWithoutKey
+
+FooUnionWithoutKey(
+ name="example1",
+)
+"""
+UnionWithoutKey = typing_extensions.Annotated[
+ typing.Union[FooUnionWithoutKey, BarUnionWithoutKey], pydantic.Field(discriminator="type")
+]
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/union/__init__.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/union/__init__.py
new file mode 100644
index 000000000000..40dcfa3a404a
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/union/__init__.py
@@ -0,0 +1,42 @@
+# This file was auto-generated by Fern from our API Definition.
+
+# isort: skip_file
+
+import typing
+from importlib import import_module
+
+if typing.TYPE_CHECKING:
+ from .types import Circle, CircleShape, GetShapeRequest, Shape, Square, SquareShape, WithName
+_dynamic_imports: typing.Dict[str, str] = {
+ "Circle": ".types",
+ "CircleShape": ".types",
+ "GetShapeRequest": ".types",
+ "Shape": ".types",
+ "Square": ".types",
+ "SquareShape": ".types",
+ "WithName": ".types",
+}
+
+
+def __getattr__(attr_name: str) -> typing.Any:
+ module_name = _dynamic_imports.get(attr_name)
+ if module_name is None:
+ raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}")
+ try:
+ module = import_module(module_name, __package__)
+ if module_name == f".{attr_name}":
+ return module
+ else:
+ return getattr(module, attr_name)
+ except ImportError as e:
+ raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e
+ except AttributeError as e:
+ raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e
+
+
+def __dir__():
+ lazy_attrs = list(_dynamic_imports.keys())
+ return sorted(lazy_attrs)
+
+
+__all__ = ["Circle", "CircleShape", "GetShapeRequest", "Shape", "Square", "SquareShape", "WithName"]
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/union/client.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/union/client.py
new file mode 100644
index 000000000000..ac58a42f3c8b
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/union/client.py
@@ -0,0 +1,173 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper
+from ..core.request_options import RequestOptions
+from .raw_client import AsyncRawUnionClient, RawUnionClient
+from .types.shape import Shape
+
+# this is used as the default value for optional parameters
+OMIT = typing.cast(typing.Any, ...)
+
+
+class UnionClient:
+ def __init__(self, *, client_wrapper: SyncClientWrapper):
+ self._raw_client = RawUnionClient(client_wrapper=client_wrapper)
+
+ @property
+ def with_raw_response(self) -> RawUnionClient:
+ """
+ Retrieves a raw implementation of this client that returns raw responses.
+
+ Returns
+ -------
+ RawUnionClient
+ """
+ return self._raw_client
+
+ def get(self, id: str, *, request_options: typing.Optional[RequestOptions] = None) -> Shape:
+ """
+ Parameters
+ ----------
+ id : str
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ Shape
+
+ Examples
+ --------
+ from seed import SeedUnions
+
+ client = SeedUnions(
+ base_url="https://yourhost.com/path/to/api",
+ )
+ client.union.get(
+ id="id",
+ )
+ """
+ _response = self._raw_client.get(id, request_options=request_options)
+ return _response.data
+
+ def update(self, *, request: Shape, request_options: typing.Optional[RequestOptions] = None) -> bool:
+ """
+ Parameters
+ ----------
+ request : Shape
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ bool
+
+ Examples
+ --------
+ from seed import SeedUnions
+ from seed.union import CircleShape
+
+ client = SeedUnions(
+ base_url="https://yourhost.com/path/to/api",
+ )
+ client.union.update(
+ request=CircleShape(
+ radius=1.1,
+ ),
+ )
+ """
+ _response = self._raw_client.update(request=request, request_options=request_options)
+ return _response.data
+
+
+class AsyncUnionClient:
+ def __init__(self, *, client_wrapper: AsyncClientWrapper):
+ self._raw_client = AsyncRawUnionClient(client_wrapper=client_wrapper)
+
+ @property
+ def with_raw_response(self) -> AsyncRawUnionClient:
+ """
+ Retrieves a raw implementation of this client that returns raw responses.
+
+ Returns
+ -------
+ AsyncRawUnionClient
+ """
+ return self._raw_client
+
+ async def get(self, id: str, *, request_options: typing.Optional[RequestOptions] = None) -> Shape:
+ """
+ Parameters
+ ----------
+ id : str
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ Shape
+
+ Examples
+ --------
+ import asyncio
+
+ from seed import AsyncSeedUnions
+
+ client = AsyncSeedUnions(
+ base_url="https://yourhost.com/path/to/api",
+ )
+
+
+ async def main() -> None:
+ await client.union.get(
+ id="id",
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.get(id, request_options=request_options)
+ return _response.data
+
+ async def update(self, *, request: Shape, request_options: typing.Optional[RequestOptions] = None) -> bool:
+ """
+ Parameters
+ ----------
+ request : Shape
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ bool
+
+ Examples
+ --------
+ import asyncio
+
+ from seed import AsyncSeedUnions
+ from seed.union import CircleShape
+
+ client = AsyncSeedUnions(
+ base_url="https://yourhost.com/path/to/api",
+ )
+
+
+ async def main() -> None:
+ await client.union.update(
+ request=CircleShape(
+ radius=1.1,
+ ),
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.update(request=request, request_options=request_options)
+ return _response.data
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/union/raw_client.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/union/raw_client.py
new file mode 100644
index 000000000000..a53e4834a2f4
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/union/raw_client.py
@@ -0,0 +1,182 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+from json.decoder import JSONDecodeError
+
+from ..core.api_error import ApiError
+from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper
+from ..core.http_response import AsyncHttpResponse, HttpResponse
+from ..core.jsonable_encoder import encode_path_param
+from ..core.parse_error import ParsingError
+from ..core.pydantic_utilities import parse_obj_as
+from ..core.request_options import RequestOptions
+from ..core.serialization import convert_and_respect_annotation_metadata
+from .types.shape import Shape
+from pydantic import ValidationError
+
+# this is used as the default value for optional parameters
+OMIT = typing.cast(typing.Any, ...)
+
+
+class RawUnionClient:
+ def __init__(self, *, client_wrapper: SyncClientWrapper):
+ self._client_wrapper = client_wrapper
+
+ def get(self, id: str, *, request_options: typing.Optional[RequestOptions] = None) -> HttpResponse[Shape]:
+ """
+ Parameters
+ ----------
+ id : str
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[Shape]
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"{encode_path_param(id)}",
+ method="GET",
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ Shape,
+ parse_obj_as(
+ type_=Shape, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ _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 update(self, *, request: Shape, request_options: typing.Optional[RequestOptions] = None) -> HttpResponse[bool]:
+ """
+ Parameters
+ ----------
+ request : Shape
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[bool]
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ method="PATCH",
+ json=convert_and_respect_annotation_metadata(object_=request, annotation=Shape, direction="write"),
+ request_options=request_options,
+ omit=OMIT,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ bool,
+ parse_obj_as(
+ type_=bool, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ _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 AsyncRawUnionClient:
+ def __init__(self, *, client_wrapper: AsyncClientWrapper):
+ self._client_wrapper = client_wrapper
+
+ async def get(
+ self, id: str, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> AsyncHttpResponse[Shape]:
+ """
+ Parameters
+ ----------
+ id : str
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[Shape]
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"{encode_path_param(id)}",
+ method="GET",
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ Shape,
+ parse_obj_as(
+ type_=Shape, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ _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 update(
+ self, *, request: Shape, request_options: typing.Optional[RequestOptions] = None
+ ) -> AsyncHttpResponse[bool]:
+ """
+ Parameters
+ ----------
+ request : Shape
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[bool]
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ method="PATCH",
+ json=convert_and_respect_annotation_metadata(object_=request, annotation=Shape, direction="write"),
+ request_options=request_options,
+ omit=OMIT,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ bool,
+ parse_obj_as(
+ type_=bool, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ _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/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/union/types/__init__.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/union/types/__init__.py
new file mode 100644
index 000000000000..923b0c8d8bb0
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/union/types/__init__.py
@@ -0,0 +1,46 @@
+# This file was auto-generated by Fern from our API Definition.
+
+# isort: skip_file
+
+import typing
+from importlib import import_module
+
+if typing.TYPE_CHECKING:
+ from .circle import Circle
+ from .get_shape_request import GetShapeRequest
+ from .shape import CircleShape, Shape, SquareShape
+ from .square import Square
+ from .with_name import WithName
+_dynamic_imports: typing.Dict[str, str] = {
+ "Circle": ".circle",
+ "CircleShape": ".shape",
+ "GetShapeRequest": ".get_shape_request",
+ "Shape": ".shape",
+ "Square": ".square",
+ "SquareShape": ".shape",
+ "WithName": ".with_name",
+}
+
+
+def __getattr__(attr_name: str) -> typing.Any:
+ module_name = _dynamic_imports.get(attr_name)
+ if module_name is None:
+ raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}")
+ try:
+ module = import_module(module_name, __package__)
+ if module_name == f".{attr_name}":
+ return module
+ else:
+ return getattr(module, attr_name)
+ except ImportError as e:
+ raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e
+ except AttributeError as e:
+ raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e
+
+
+def __dir__():
+ lazy_attrs = list(_dynamic_imports.keys())
+ return sorted(lazy_attrs)
+
+
+__all__ = ["Circle", "CircleShape", "GetShapeRequest", "Shape", "Square", "SquareShape", "WithName"]
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/union/types/circle.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/union/types/circle.py
new file mode 100644
index 000000000000..d58f9ecfbf7b
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/union/types/circle.py
@@ -0,0 +1,19 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class Circle(UniversalBaseModel):
+ radius: float
+
+ 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/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/union/types/get_shape_request.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/union/types/get_shape_request.py
new file mode 100644
index 000000000000..90e62e26660b
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/union/types/get_shape_request.py
@@ -0,0 +1,29 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class GetShapeRequest(UniversalBaseModel):
+ """
+ Examples
+ --------
+ from seed.union import GetShapeRequest
+
+ GetShapeRequest(
+ id="example",
+ )
+ """
+
+ id: str
+
+ 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/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/union/types/shape.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/union/types/shape.py
new file mode 100644
index 000000000000..d906fb34a14b
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/union/types/shape.py
@@ -0,0 +1,90 @@
+# This file was auto-generated by Fern from our API Definition.
+
+from __future__ import annotations
+
+import typing
+
+import pydantic
+import typing_extensions
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class Base(UniversalBaseModel):
+ """
+ Examples
+ --------
+ from seed.union import CircleShape
+
+ CircleShape(
+ radius=5.0,
+ )
+ """
+
+ id: str
+
+ 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
+
+
+class CircleShape(Base):
+ """
+ Examples
+ --------
+ from seed.union import CircleShape
+
+ CircleShape(
+ radius=5.0,
+ )
+ """
+
+ type: typing.Literal["circle"] = "circle"
+ radius: float
+
+ 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
+
+
+class SquareShape(Base):
+ """
+ Examples
+ --------
+ from seed.union import CircleShape
+
+ CircleShape(
+ radius=5.0,
+ )
+ """
+
+ type: typing.Literal["square"] = "square"
+ length: float
+
+ 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
+
+
+"""
+from seed.union import CircleShape
+
+CircleShape(
+ radius=5.0,
+)
+"""
+Shape = typing_extensions.Annotated[typing.Union[CircleShape, SquareShape], pydantic.Field(discriminator="type")]
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/union/types/square.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/union/types/square.py
new file mode 100644
index 000000000000..86ad20367b26
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/union/types/square.py
@@ -0,0 +1,19 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class Square(UniversalBaseModel):
+ length: float
+
+ 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/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/union/types/with_name.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/union/types/with_name.py
new file mode 100644
index 000000000000..15fb8573a8dd
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/union/types/with_name.py
@@ -0,0 +1,19 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class WithName(UniversalBaseModel):
+ name: str
+
+ 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/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/version.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/version.py
new file mode 100644
index 000000000000..7b884a244da5
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/src/seed/version.py
@@ -0,0 +1,3 @@
+from importlib import metadata
+
+__version__ = metadata.version("fern_unions")
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/tests/conftest.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/tests/conftest.py
new file mode 100644
index 000000000000..e8a0c6c4757e
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/tests/conftest.py
@@ -0,0 +1,147 @@
+"""
+Pytest plugin that manages the WireMock container lifecycle for wire tests.
+
+This plugin is loaded globally for the test suite and is responsible for
+starting and stopping the WireMock container exactly once per test run,
+including when running with pytest-xdist over the entire project.
+
+It lives under tests/ (as tests/conftest.py) and is discovered automatically
+by pytest's normal test collection rules.
+"""
+
+import os
+import subprocess
+
+import pytest
+
+_STARTED: bool = False
+_EXTERNAL: bool = False # True when using an external WireMock instance (skip container lifecycle)
+_WIREMOCK_URL: str = "http://localhost:8080" # Default, will be updated after container starts
+_PROJECT_NAME: str = "seed-unions"
+
+# This file lives at tests/conftest.py, so the project root is one level up.
+_PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
+_COMPOSE_FILE = os.path.join(_PROJECT_ROOT, "wiremock", "docker-compose.test.yml")
+
+
+def _get_wiremock_port() -> str:
+ """Gets the dynamically assigned port for the WireMock container."""
+ try:
+ result = subprocess.run(
+ ["docker", "compose", "-f", _COMPOSE_FILE, "-p", _PROJECT_NAME, "port", "wiremock", "8080"],
+ check=True,
+ capture_output=True,
+ text=True,
+ )
+ # Output is like "0.0.0.0:32768" or "[::]:32768"
+ port = result.stdout.strip().split(":")[-1]
+ return port
+ except subprocess.CalledProcessError:
+ return "8080" # Fallback to default
+
+
+def _start_wiremock() -> None:
+ """Starts the WireMock container using docker-compose."""
+ global _STARTED, _EXTERNAL, _WIREMOCK_URL
+ if _STARTED:
+ return
+
+ # If WIREMOCK_URL is already set (e.g., by CI/CD pipeline), skip container management
+ existing_url = os.environ.get("WIREMOCK_URL")
+ if existing_url:
+ _WIREMOCK_URL = existing_url
+ _EXTERNAL = True
+ _STARTED = True
+ print(f"\nUsing external WireMock at {_WIREMOCK_URL} (container management skipped)")
+ return
+
+ print(f"\nStarting WireMock container (project: {_PROJECT_NAME})...")
+ try:
+ subprocess.run(
+ ["docker", "compose", "-f", _COMPOSE_FILE, "-p", _PROJECT_NAME, "up", "-d", "--wait"],
+ check=True,
+ capture_output=True,
+ text=True,
+ )
+ _WIREMOCK_PORT = _get_wiremock_port()
+ _WIREMOCK_URL = f"http://localhost:{_WIREMOCK_PORT}"
+ os.environ["WIREMOCK_URL"] = _WIREMOCK_URL
+ print(f"WireMock container is ready at {_WIREMOCK_URL}")
+ _STARTED = True
+ except subprocess.CalledProcessError as e:
+ print(f"Failed to start WireMock: {e.stderr}")
+ raise
+
+
+def _stop_wiremock() -> None:
+ """Stops and removes the WireMock container."""
+ if _EXTERNAL:
+ # Container is managed externally; nothing to tear down.
+ return
+
+ print("\nStopping WireMock container...")
+ subprocess.run(
+ ["docker", "compose", "-f", _COMPOSE_FILE, "-p", _PROJECT_NAME, "down", "-v"],
+ check=False,
+ capture_output=True,
+ )
+
+
+def _is_xdist_worker(config: pytest.Config) -> bool:
+ """
+ Determines if the current process is an xdist worker.
+
+ In pytest-xdist, worker processes have a 'workerinput' attribute
+ on the config object, while the controller process does not.
+ """
+ return hasattr(config, "workerinput")
+
+
+def _has_httpx_aiohttp() -> bool:
+ """Check if httpx_aiohttp is importable."""
+ try:
+ import httpx_aiohttp # type: ignore[import-not-found] # noqa: F401
+
+ return True
+ except ImportError:
+ return False
+
+
+def pytest_collection_modifyitems(config: pytest.Config, items: list) -> None:
+ """Auto-skip @pytest.mark.aiohttp tests when httpx_aiohttp is not installed."""
+ if _has_httpx_aiohttp():
+ return
+ skip_aiohttp = pytest.mark.skip(reason="httpx_aiohttp not installed")
+ for item in items:
+ if "aiohttp" in item.keywords:
+ item.add_marker(skip_aiohttp)
+
+
+def pytest_configure(config: pytest.Config) -> None:
+ """
+ Pytest hook that runs during test session setup.
+
+ Starts WireMock container only from the controller process (xdist)
+ or the single process (non-xdist). This ensures only one container
+ is started regardless of the number of worker processes.
+ """
+ if _is_xdist_worker(config):
+ # Workers never manage the container lifecycle.
+ return
+
+ _start_wiremock()
+
+
+def pytest_unconfigure(config: pytest.Config) -> None:
+ """
+ Pytest hook that runs during test session teardown.
+
+ Stops WireMock container only from the controller process (xdist)
+ or the single process (non-xdist). This ensures the container is
+ cleaned up after all workers have finished.
+ """
+ if _is_xdist_worker(config):
+ # Workers never manage the container lifecycle.
+ return
+
+ _stop_wiremock()
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/tests/custom/test_client.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/tests/custom/test_client.py
new file mode 100644
index 000000000000..ab04ce6393ef
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/tests/custom/test_client.py
@@ -0,0 +1,7 @@
+import pytest
+
+
+# Get started with writing tests with pytest at https://docs.pytest.org
+@pytest.mark.skip(reason="Unimplemented")
+def test_client() -> None:
+ assert True
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/tests/test_aiohttp_autodetect.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/tests/test_aiohttp_autodetect.py
new file mode 100644
index 000000000000..a15bc9cc80b6
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/tests/test_aiohttp_autodetect.py
@@ -0,0 +1,116 @@
+import importlib
+import sys
+import unittest
+from unittest import mock
+
+import httpx
+import pytest
+
+
+class TestMakeDefaultAsyncClientWithoutAiohttp(unittest.TestCase):
+ """Tests for _make_default_async_client when httpx_aiohttp is NOT installed."""
+
+ def test_returns_httpx_async_client(self) -> None:
+ """When httpx_aiohttp is not installed, returns plain httpx.AsyncClient."""
+ with mock.patch.dict(sys.modules, {"httpx_aiohttp": None}):
+ from seed.client import _make_default_async_client
+
+ client = _make_default_async_client(timeout=60, follow_redirects=True)
+ self.assertIsInstance(client, httpx.AsyncClient)
+ self.assertEqual(client.timeout.read, 60)
+ self.assertTrue(client.follow_redirects)
+
+ def test_follow_redirects_none(self) -> None:
+ """When follow_redirects is None, omits it from httpx.AsyncClient."""
+ with mock.patch.dict(sys.modules, {"httpx_aiohttp": None}):
+ from seed.client import _make_default_async_client
+
+ client = _make_default_async_client(timeout=60, follow_redirects=None)
+ self.assertIsInstance(client, httpx.AsyncClient)
+ self.assertFalse(client.follow_redirects)
+
+ def test_explicit_httpx_client_bypasses_autodetect(self) -> None:
+ """When user passes httpx_client explicitly, _make_default_async_client is not called."""
+
+ explicit_client = httpx.AsyncClient(timeout=120)
+ with mock.patch("seed.client._make_default_async_client") as mock_make:
+ # Replicate the generated conditional: httpx_client if httpx_client is not None else _make_default_async_client(...)
+ result = explicit_client if explicit_client is not None else mock_make(timeout=60, follow_redirects=True)
+ mock_make.assert_not_called()
+ self.assertIs(result, explicit_client)
+
+
+@pytest.mark.aiohttp
+class TestMakeDefaultAsyncClientWithAiohttp(unittest.TestCase):
+ """Tests for _make_default_async_client when httpx_aiohttp IS installed."""
+
+ def test_returns_aiohttp_client(self) -> None:
+ """When httpx_aiohttp is installed, returns HttpxAiohttpClient."""
+ import httpx_aiohttp # type: ignore[import-not-found]
+
+ from seed.client import _make_default_async_client
+
+ client = _make_default_async_client(timeout=60, follow_redirects=True)
+ self.assertIsInstance(client, httpx_aiohttp.HttpxAiohttpClient)
+ self.assertEqual(client.timeout.read, 60)
+ self.assertTrue(client.follow_redirects)
+
+ def test_follow_redirects_none(self) -> None:
+ """When httpx_aiohttp is installed and follow_redirects is None, omits it."""
+ import httpx_aiohttp # type: ignore[import-not-found]
+
+ from seed.client import _make_default_async_client
+
+ client = _make_default_async_client(timeout=60, follow_redirects=None)
+ self.assertIsInstance(client, httpx_aiohttp.HttpxAiohttpClient)
+ self.assertFalse(client.follow_redirects)
+
+
+class TestDefaultClientsWithoutAiohttp(unittest.TestCase):
+ """Tests for _default_clients.py convenience classes (no aiohttp)."""
+
+ def test_default_async_httpx_client_defaults(self) -> None:
+ """DefaultAsyncHttpxClient applies SDK defaults."""
+ from seed._default_clients import SDK_DEFAULT_TIMEOUT, DefaultAsyncHttpxClient
+
+ client = DefaultAsyncHttpxClient()
+ self.assertIsInstance(client, httpx.AsyncClient)
+ self.assertEqual(client.timeout.read, SDK_DEFAULT_TIMEOUT)
+ self.assertTrue(client.follow_redirects)
+
+ def test_default_async_httpx_client_overrides(self) -> None:
+ """DefaultAsyncHttpxClient allows overriding defaults."""
+ from seed._default_clients import DefaultAsyncHttpxClient
+
+ client = DefaultAsyncHttpxClient(timeout=30, follow_redirects=False)
+ self.assertEqual(client.timeout.read, 30)
+ self.assertFalse(client.follow_redirects)
+
+ def test_default_aiohttp_client_raises_without_package(self) -> None:
+ """DefaultAioHttpClient raises RuntimeError when httpx_aiohttp not installed."""
+ import seed._default_clients
+
+ with mock.patch.dict(sys.modules, {"httpx_aiohttp": None}):
+ importlib.reload(seed._default_clients)
+
+ with self.assertRaises(RuntimeError) as ctx:
+ seed._default_clients.DefaultAioHttpClient()
+ self.assertIn("pip install fern_unions[aiohttp]", str(ctx.exception))
+
+ importlib.reload(seed._default_clients)
+
+
+@pytest.mark.aiohttp
+class TestDefaultClientsWithAiohttp(unittest.TestCase):
+ """Tests for _default_clients.py when httpx_aiohttp IS installed."""
+
+ def test_default_aiohttp_client_defaults(self) -> None:
+ """DefaultAioHttpClient works when httpx_aiohttp is installed."""
+ import httpx_aiohttp # type: ignore[import-not-found]
+
+ from seed._default_clients import SDK_DEFAULT_TIMEOUT, DefaultAioHttpClient
+
+ client = DefaultAioHttpClient()
+ self.assertIsInstance(client, httpx_aiohttp.HttpxAiohttpClient)
+ self.assertEqual(client.timeout.read, SDK_DEFAULT_TIMEOUT)
+ self.assertTrue(client.follow_redirects)
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/tests/utils/__init__.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/tests/utils/__init__.py
new file mode 100644
index 000000000000..f3ea2659bb1c
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/tests/utils/__init__.py
@@ -0,0 +1,2 @@
+# This file was auto-generated by Fern from our API Definition.
+
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/tests/utils/assets/models/__init__.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/tests/utils/assets/models/__init__.py
new file mode 100644
index 000000000000..2cf01263529d
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/tests/utils/assets/models/__init__.py
@@ -0,0 +1,21 @@
+# This file was auto-generated by Fern from our API Definition.
+
+# This file was auto-generated by Fern from our API Definition.
+
+from .circle import CircleParams
+from .object_with_defaults import ObjectWithDefaultsParams
+from .object_with_optional_field import ObjectWithOptionalFieldParams
+from .shape import Shape_CircleParams, Shape_SquareParams, ShapeParams
+from .square import SquareParams
+from .undiscriminated_shape import UndiscriminatedShapeParams
+
+__all__ = [
+ "CircleParams",
+ "ObjectWithDefaultsParams",
+ "ObjectWithOptionalFieldParams",
+ "ShapeParams",
+ "Shape_CircleParams",
+ "Shape_SquareParams",
+ "SquareParams",
+ "UndiscriminatedShapeParams",
+]
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/tests/utils/assets/models/circle.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/tests/utils/assets/models/circle.py
new file mode 100644
index 000000000000..74ecf38c308b
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/tests/utils/assets/models/circle.py
@@ -0,0 +1,11 @@
+# This file was auto-generated by Fern from our API Definition.
+
+# This file was auto-generated by Fern from our API Definition.
+
+import typing_extensions
+
+from seed.core.serialization import FieldMetadata
+
+
+class CircleParams(typing_extensions.TypedDict):
+ radius_measurement: typing_extensions.Annotated[float, FieldMetadata(alias="radiusMeasurement")]
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/tests/utils/assets/models/color.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/tests/utils/assets/models/color.py
new file mode 100644
index 000000000000..2aa2c4c52f0c
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/tests/utils/assets/models/color.py
@@ -0,0 +1,7 @@
+# This file was auto-generated by Fern from our API Definition.
+
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+Color = typing.Union[typing.Literal["red", "blue"], typing.Any]
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/tests/utils/assets/models/object_with_defaults.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/tests/utils/assets/models/object_with_defaults.py
new file mode 100644
index 000000000000..a977b1d2aa1c
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/tests/utils/assets/models/object_with_defaults.py
@@ -0,0 +1,15 @@
+# This file was auto-generated by Fern from our API Definition.
+
+# This file was auto-generated by Fern from our API Definition.
+
+import typing_extensions
+
+
+class ObjectWithDefaultsParams(typing_extensions.TypedDict):
+ """
+ Defines properties with default values and validation rules.
+ """
+
+ decimal: typing_extensions.NotRequired[float]
+ string: typing_extensions.NotRequired[str]
+ required_string: str
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/tests/utils/assets/models/object_with_optional_field.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/tests/utils/assets/models/object_with_optional_field.py
new file mode 100644
index 000000000000..6b5608bc05b6
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/tests/utils/assets/models/object_with_optional_field.py
@@ -0,0 +1,35 @@
+# This file was auto-generated by Fern from our API Definition.
+
+# This file was auto-generated by Fern from our API Definition.
+
+import datetime as dt
+import typing
+import uuid
+
+import typing_extensions
+from .color import Color
+from .shape import ShapeParams
+from .undiscriminated_shape import UndiscriminatedShapeParams
+
+from seed.core.serialization import FieldMetadata
+
+
+class ObjectWithOptionalFieldParams(typing_extensions.TypedDict):
+ literal: typing.Literal["lit_one"]
+ string: typing_extensions.NotRequired[str]
+ integer: typing_extensions.NotRequired[int]
+ long_: typing_extensions.NotRequired[typing_extensions.Annotated[int, FieldMetadata(alias="long")]]
+ double: typing_extensions.NotRequired[float]
+ bool_: typing_extensions.NotRequired[typing_extensions.Annotated[bool, FieldMetadata(alias="bool")]]
+ datetime: typing_extensions.NotRequired[dt.datetime]
+ date: typing_extensions.NotRequired[dt.date]
+ uuid_: typing_extensions.NotRequired[typing_extensions.Annotated[uuid.UUID, FieldMetadata(alias="uuid")]]
+ base_64: typing_extensions.NotRequired[typing_extensions.Annotated[str, FieldMetadata(alias="base64")]]
+ list_: typing_extensions.NotRequired[typing_extensions.Annotated[typing.Sequence[str], FieldMetadata(alias="list")]]
+ set_: typing_extensions.NotRequired[typing_extensions.Annotated[typing.Set[str], FieldMetadata(alias="set")]]
+ map_: typing_extensions.NotRequired[typing_extensions.Annotated[typing.Dict[int, str], FieldMetadata(alias="map")]]
+ enum: typing_extensions.NotRequired[Color]
+ union: typing_extensions.NotRequired[ShapeParams]
+ second_union: typing_extensions.NotRequired[ShapeParams]
+ undiscriminated_union: typing_extensions.NotRequired[UndiscriminatedShapeParams]
+ any: typing.Optional[typing.Any]
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/tests/utils/assets/models/shape.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/tests/utils/assets/models/shape.py
new file mode 100644
index 000000000000..7e70010a251f
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/tests/utils/assets/models/shape.py
@@ -0,0 +1,28 @@
+# This file was auto-generated by Fern from our API Definition.
+
+# This file was auto-generated by Fern from our API Definition.
+
+from __future__ import annotations
+
+import typing
+
+import typing_extensions
+
+from seed.core.serialization import FieldMetadata
+
+
+class Base(typing_extensions.TypedDict):
+ id: str
+
+
+class Shape_CircleParams(Base):
+ shape_type: typing_extensions.Annotated[typing.Literal["circle"], FieldMetadata(alias="shapeType")]
+ radius_measurement: typing_extensions.Annotated[float, FieldMetadata(alias="radiusMeasurement")]
+
+
+class Shape_SquareParams(Base):
+ shape_type: typing_extensions.Annotated[typing.Literal["square"], FieldMetadata(alias="shapeType")]
+ length_measurement: typing_extensions.Annotated[float, FieldMetadata(alias="lengthMeasurement")]
+
+
+ShapeParams = typing.Union[Shape_CircleParams, Shape_SquareParams]
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/tests/utils/assets/models/square.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/tests/utils/assets/models/square.py
new file mode 100644
index 000000000000..71c7d25fd4ad
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/tests/utils/assets/models/square.py
@@ -0,0 +1,11 @@
+# This file was auto-generated by Fern from our API Definition.
+
+# This file was auto-generated by Fern from our API Definition.
+
+import typing_extensions
+
+from seed.core.serialization import FieldMetadata
+
+
+class SquareParams(typing_extensions.TypedDict):
+ length_measurement: typing_extensions.Annotated[float, FieldMetadata(alias="lengthMeasurement")]
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/tests/utils/assets/models/undiscriminated_shape.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/tests/utils/assets/models/undiscriminated_shape.py
new file mode 100644
index 000000000000..99f12b300d1d
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/tests/utils/assets/models/undiscriminated_shape.py
@@ -0,0 +1,10 @@
+# This file was auto-generated by Fern from our API Definition.
+
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+from .circle import CircleParams
+from .square import SquareParams
+
+UndiscriminatedShapeParams = typing.Union[CircleParams, SquareParams]
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/tests/utils/test_http_client.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/tests/utils/test_http_client.py
new file mode 100644
index 000000000000..aa2a8b4e4700
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/tests/utils/test_http_client.py
@@ -0,0 +1,662 @@
+# This file was auto-generated by Fern from our API Definition.
+
+from typing import Any, Dict
+from unittest.mock import AsyncMock, MagicMock, patch
+
+import httpx
+import pytest
+
+from seed.core.http_client import (
+ AsyncHttpClient,
+ HttpClient,
+ _build_url,
+ get_request_body,
+ remove_none_from_dict,
+)
+from seed.core.request_options import RequestOptions
+
+
+# Stub clients for testing HttpClient and AsyncHttpClient
+class _DummySyncClient:
+ """A minimal stub for httpx.Client that records request arguments."""
+
+ def __init__(self) -> None:
+ self.last_request_kwargs: Dict[str, Any] = {}
+
+ def request(self, **kwargs: Any) -> "_DummyResponse":
+ self.last_request_kwargs = kwargs
+ return _DummyResponse()
+
+
+class _DummyAsyncClient:
+ """A minimal stub for httpx.AsyncClient that records request arguments."""
+
+ def __init__(self) -> None:
+ self.last_request_kwargs: Dict[str, Any] = {}
+
+ async def request(self, **kwargs: Any) -> "_DummyResponse":
+ self.last_request_kwargs = kwargs
+ return _DummyResponse()
+
+
+class _DummyResponse:
+ """A minimal stub for httpx.Response."""
+
+ status_code = 200
+ headers: Dict[str, str] = {}
+
+
+def get_request_options() -> RequestOptions:
+ return {"additional_body_parameters": {"see you": "later"}}
+
+
+def get_request_options_with_none() -> RequestOptions:
+ return {"additional_body_parameters": {"see you": "later", "optional": None}}
+
+
+def test_get_json_request_body() -> None:
+ json_body, data_body = get_request_body(json={"hello": "world"}, data=None, request_options=None, omit=None)
+ assert json_body == {"hello": "world"}
+ assert data_body is None
+
+ json_body_extras, data_body_extras = get_request_body(
+ json={"goodbye": "world"}, data=None, request_options=get_request_options(), omit=None
+ )
+
+ assert json_body_extras == {"goodbye": "world", "see you": "later"}
+ assert data_body_extras is None
+
+
+def test_get_files_request_body() -> None:
+ json_body, data_body = get_request_body(json=None, data={"hello": "world"}, request_options=None, omit=None)
+ assert data_body == {"hello": "world"}
+ assert json_body is None
+
+ json_body_extras, data_body_extras = get_request_body(
+ json=None, data={"goodbye": "world"}, request_options=get_request_options(), omit=None
+ )
+
+ assert data_body_extras == {"goodbye": "world", "see you": "later"}
+ assert json_body_extras is None
+
+
+def test_get_none_request_body() -> None:
+ json_body, data_body = get_request_body(json=None, data=None, request_options=None, omit=None)
+ assert data_body is None
+ assert json_body is None
+
+ json_body_extras, data_body_extras = get_request_body(
+ json=None, data=None, request_options=get_request_options(), omit=None
+ )
+
+ assert json_body_extras == {"see you": "later"}
+ assert data_body_extras is None
+
+
+def test_get_empty_json_request_body() -> None:
+ """Test that implicit empty bodies (json=None) are collapsed to None."""
+ unrelated_request_options: RequestOptions = {"max_retries": 3}
+ json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None)
+ assert json_body is None
+ assert data_body is None
+
+
+def test_explicit_empty_json_body_is_preserved() -> None:
+ """Test that explicit empty bodies (json={}) are preserved and sent as {}.
+
+ This is important for endpoints where the request body is required but all
+ fields are optional. The server expects valid JSON ({}) not an empty body.
+ """
+ unrelated_request_options: RequestOptions = {"max_retries": 3}
+
+ # Explicit json={} should be preserved
+ json_body, data_body = get_request_body(json={}, data=None, request_options=unrelated_request_options, omit=None)
+ assert json_body == {}
+ assert data_body is None
+
+ # Explicit data={} should also be preserved
+ json_body2, data_body2 = get_request_body(json=None, data={}, request_options=unrelated_request_options, omit=None)
+ assert json_body2 is None
+ assert data_body2 == {}
+
+
+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(
+ json={"hello": "world", "optional": None}, data=None, request_options=None, omit=None
+ )
+ # JSON bodies should preserve None values
+ assert json_body == {"hello": "world", "optional": None}
+ assert data_body is None
+
+
+def test_data_body_preserves_none_values_without_multipart() -> None:
+ """Test that data bodies preserve None values when not using multipart.
+
+ The filtering of None values happens in HttpClient.request/stream methods,
+ not in get_request_body. This test verifies get_request_body doesn't filter None.
+ """
+ json_body, data_body = get_request_body(
+ json=None, data={"hello": "world", "optional": None}, request_options=None, omit=None
+ )
+ # get_request_body should preserve None values in data body
+ # The filtering happens later in HttpClient.request when multipart is detected
+ assert data_body == {"hello": "world", "optional": None}
+ assert json_body is None
+
+
+def test_remove_none_from_dict_filters_none_values() -> None:
+ """Test that remove_none_from_dict correctly filters out None values."""
+ original = {"hello": "world", "optional": None, "another": "value", "also_none": None}
+ filtered = remove_none_from_dict(original)
+ assert filtered == {"hello": "world", "another": "value"}
+ # Original should not be modified
+ assert original == {"hello": "world", "optional": None, "another": "value", "also_none": None}
+
+
+def test_remove_none_from_dict_empty_dict() -> None:
+ """Test that remove_none_from_dict handles empty dict."""
+ assert remove_none_from_dict({}) == {}
+
+
+def test_remove_none_from_dict_all_none() -> None:
+ """Test that remove_none_from_dict handles dict with all None values."""
+ assert remove_none_from_dict({"a": None, "b": None}) == {}
+
+
+def test_http_client_does_not_pass_empty_params_list() -> None:
+ """Test that HttpClient passes params=None when params are empty.
+
+ This prevents httpx from stripping existing query parameters from the URL,
+ which happens when params=[] or params={} is passed.
+ """
+ dummy_client = _DummySyncClient()
+ http_client = HttpClient(
+ httpx_client=dummy_client, # type: ignore[arg-type]
+ base_timeout=lambda: None,
+ base_headers=lambda: {},
+ base_url=lambda: "https://example.com",
+ )
+
+ # Use a path with query params (e.g., pagination cursor URL)
+ http_client.request(
+ path="resource?after=123",
+ method="GET",
+ params=None,
+ request_options=None,
+ )
+
+ # We care that httpx receives params=None, not [] or {}
+ assert "params" in dummy_client.last_request_kwargs
+ assert dummy_client.last_request_kwargs["params"] is None
+
+ # Verify the query string in the URL is preserved
+ url = str(dummy_client.last_request_kwargs["url"])
+ assert "after=123" in url, f"Expected query param 'after=123' in URL, got: {url}"
+
+
+def test_http_client_passes_encoded_params_when_present() -> None:
+ """Test that HttpClient passes encoded params when params are provided."""
+ dummy_client = _DummySyncClient()
+ http_client = HttpClient(
+ httpx_client=dummy_client, # type: ignore[arg-type]
+ base_timeout=lambda: None,
+ base_headers=lambda: {},
+ base_url=lambda: "https://example.com/resource",
+ )
+
+ http_client.request(
+ path="",
+ method="GET",
+ params={"after": "456"},
+ request_options=None,
+ )
+
+ params = dummy_client.last_request_kwargs["params"]
+ # For a simple dict, encode_query should give a single (key, value) tuple
+ assert params == [("after", "456")]
+
+
+@pytest.mark.asyncio
+async def test_async_http_client_does_not_pass_empty_params_list() -> None:
+ """Test that AsyncHttpClient passes params=None when params are empty.
+
+ This prevents httpx from stripping existing query parameters from the URL,
+ which happens when params=[] or params={} is passed.
+ """
+ dummy_client = _DummyAsyncClient()
+ http_client = AsyncHttpClient(
+ httpx_client=dummy_client, # type: ignore[arg-type]
+ base_timeout=lambda: None,
+ base_headers=lambda: {},
+ base_url=lambda: "https://example.com",
+ async_base_headers=None,
+ )
+
+ # Use a path with query params (e.g., pagination cursor URL)
+ await http_client.request(
+ path="resource?after=123",
+ method="GET",
+ params=None,
+ request_options=None,
+ )
+
+ # We care that httpx receives params=None, not [] or {}
+ assert "params" in dummy_client.last_request_kwargs
+ assert dummy_client.last_request_kwargs["params"] is None
+
+ # Verify the query string in the URL is preserved
+ url = str(dummy_client.last_request_kwargs["url"])
+ assert "after=123" in url, f"Expected query param 'after=123' in URL, got: {url}"
+
+
+@pytest.mark.asyncio
+async def test_async_http_client_passes_encoded_params_when_present() -> None:
+ """Test that AsyncHttpClient passes encoded params when params are provided."""
+ dummy_client = _DummyAsyncClient()
+ http_client = AsyncHttpClient(
+ httpx_client=dummy_client, # type: ignore[arg-type]
+ base_timeout=lambda: None,
+ base_headers=lambda: {},
+ base_url=lambda: "https://example.com/resource",
+ async_base_headers=None,
+ )
+
+ await http_client.request(
+ path="",
+ method="GET",
+ params={"after": "456"},
+ request_options=None,
+ )
+
+ params = dummy_client.last_request_kwargs["params"]
+ # For a simple dict, encode_query should give a single (key, value) tuple
+ assert params == [("after", "456")]
+
+
+def test_basic_url_joining() -> None:
+ """Test basic URL joining with a simple base URL and path."""
+ result = _build_url("https://api.example.com", "/users")
+ assert result == "https://api.example.com/users"
+
+
+def test_basic_url_joining_trailing_slash() -> None:
+ """Test basic URL joining with a simple base URL and path."""
+ result = _build_url("https://api.example.com/", "/users")
+ assert result == "https://api.example.com/users"
+
+
+def test_preserves_base_url_path_prefix() -> None:
+ """Test that path prefixes in base URL are preserved.
+
+ This is the critical bug fix - urllib.parse.urljoin() would strip
+ the path prefix when the path starts with '/'.
+ """
+ result = _build_url("https://cloud.example.com/org/tenant/api", "/users")
+ assert result == "https://cloud.example.com/org/tenant/api/users"
+
+
+def test_preserves_base_url_path_prefix_trailing_slash() -> None:
+ """Test that path prefixes in base URL are preserved."""
+ result = _build_url("https://cloud.example.com/org/tenant/api/", "/users")
+ assert result == "https://cloud.example.com/org/tenant/api/users"
+
+
+# ---------------------------------------------------------------------------
+# Connection error retry tests
+# ---------------------------------------------------------------------------
+
+
+def _make_sync_http_client(mock_client: Any) -> HttpClient:
+ return HttpClient(
+ httpx_client=mock_client, # type: ignore[arg-type]
+ base_timeout=lambda: None,
+ base_headers=lambda: {},
+ base_url=lambda: "https://example.com",
+ )
+
+
+def _make_async_http_client(mock_client: Any) -> AsyncHttpClient:
+ return AsyncHttpClient(
+ httpx_client=mock_client, # type: ignore[arg-type]
+ base_timeout=lambda: None,
+ base_headers=lambda: {},
+ base_url=lambda: "https://example.com",
+ async_base_headers=None,
+ )
+
+
+@patch("seed.core.http_client.time.sleep", return_value=None)
+def test_sync_retries_on_connect_error(mock_sleep: MagicMock) -> None:
+ """Sync: connection error retries on httpx.ConnectError."""
+ mock_client = MagicMock()
+ mock_client.request.side_effect = [
+ httpx.ConnectError("connection failed"),
+ _DummyResponse(),
+ ]
+ http_client = _make_sync_http_client(mock_client)
+
+ response = http_client.request(path="/test", method="GET")
+
+ assert response.status_code == 200
+ assert mock_client.request.call_count == 2
+ mock_sleep.assert_called_once()
+
+
+@patch("seed.core.http_client.time.sleep", return_value=None)
+def test_sync_retries_on_remote_protocol_error(mock_sleep: MagicMock) -> None:
+ """Sync: connection error retries on httpx.RemoteProtocolError."""
+ mock_client = MagicMock()
+ mock_client.request.side_effect = [
+ httpx.RemoteProtocolError("Remote end closed connection without response"),
+ _DummyResponse(),
+ ]
+ http_client = _make_sync_http_client(mock_client)
+
+ response = http_client.request(path="/test", method="GET")
+
+ assert response.status_code == 200
+ assert mock_client.request.call_count == 2
+ mock_sleep.assert_called_once()
+
+
+@patch("seed.core.http_client.time.sleep", return_value=None)
+def test_sync_connection_error_exhausts_retries(mock_sleep: MagicMock) -> None:
+ """Sync: connection error exhausts retries then raises."""
+ mock_client = MagicMock()
+ mock_client.request.side_effect = httpx.ConnectError("connection failed")
+ http_client = _make_sync_http_client(mock_client)
+
+ with pytest.raises(httpx.ConnectError):
+ http_client.request(
+ path="/test",
+ method="GET",
+ request_options={"max_retries": 2},
+ )
+
+ # 1 initial + 2 retries = 3 total attempts
+ assert mock_client.request.call_count == 3
+ assert mock_sleep.call_count == 2
+
+
+@patch("seed.core.http_client.time.sleep", return_value=None)
+def test_sync_connection_error_respects_max_retries_zero(mock_sleep: MagicMock) -> None:
+ """Sync: connection error respects max_retries=0."""
+ mock_client = MagicMock()
+ mock_client.request.side_effect = httpx.ConnectError("connection failed")
+ http_client = _make_sync_http_client(mock_client)
+
+ with pytest.raises(httpx.ConnectError):
+ http_client.request(
+ path="/test",
+ method="GET",
+ request_options={"max_retries": 0},
+ )
+
+ # No retries, just the initial attempt
+ assert mock_client.request.call_count == 1
+ mock_sleep.assert_not_called()
+
+
+@pytest.mark.asyncio
+@patch("seed.core.http_client.asyncio.sleep", new_callable=AsyncMock)
+async def test_async_retries_on_connect_error(mock_sleep: AsyncMock) -> None:
+ """Async: connection error retries on httpx.ConnectError."""
+ mock_client = MagicMock()
+ mock_client.request = AsyncMock(
+ side_effect=[
+ httpx.ConnectError("connection failed"),
+ _DummyResponse(),
+ ]
+ )
+ http_client = _make_async_http_client(mock_client)
+
+ response = await http_client.request(path="/test", method="GET")
+
+ assert response.status_code == 200
+ assert mock_client.request.call_count == 2
+ mock_sleep.assert_called_once()
+
+
+@pytest.mark.asyncio
+@patch("seed.core.http_client.asyncio.sleep", new_callable=AsyncMock)
+async def test_async_retries_on_remote_protocol_error(mock_sleep: AsyncMock) -> None:
+ """Async: connection error retries on httpx.RemoteProtocolError."""
+ mock_client = MagicMock()
+ mock_client.request = AsyncMock(
+ side_effect=[
+ httpx.RemoteProtocolError("Remote end closed connection without response"),
+ _DummyResponse(),
+ ]
+ )
+ http_client = _make_async_http_client(mock_client)
+
+ response = await http_client.request(path="/test", method="GET")
+
+ assert response.status_code == 200
+ assert mock_client.request.call_count == 2
+ mock_sleep.assert_called_once()
+
+
+@pytest.mark.asyncio
+@patch("seed.core.http_client.asyncio.sleep", new_callable=AsyncMock)
+async def test_async_connection_error_exhausts_retries(mock_sleep: AsyncMock) -> None:
+ """Async: connection error exhausts retries then raises."""
+ mock_client = MagicMock()
+ mock_client.request = AsyncMock(side_effect=httpx.ConnectError("connection failed"))
+ http_client = _make_async_http_client(mock_client)
+
+ with pytest.raises(httpx.ConnectError):
+ await http_client.request(
+ path="/test",
+ method="GET",
+ request_options={"max_retries": 2},
+ )
+
+ # 1 initial + 2 retries = 3 total attempts
+ assert mock_client.request.call_count == 3
+ assert mock_sleep.call_count == 2
+
+
+# ---------------------------------------------------------------------------
+# base_max_retries constructor parameter tests
+# ---------------------------------------------------------------------------
+
+
+def test_sync_http_client_default_base_max_retries() -> None:
+ """HttpClient defaults to base_max_retries=2."""
+ http_client = HttpClient(
+ httpx_client=MagicMock(), # type: ignore[arg-type]
+ base_timeout=lambda: None,
+ base_headers=lambda: {},
+ )
+ assert http_client.base_max_retries == 2
+
+
+def test_async_http_client_default_base_max_retries() -> None:
+ """AsyncHttpClient defaults to base_max_retries=2."""
+ http_client = AsyncHttpClient(
+ httpx_client=MagicMock(), # type: ignore[arg-type]
+ base_timeout=lambda: None,
+ base_headers=lambda: {},
+ )
+ assert http_client.base_max_retries == 2
+
+
+def test_sync_http_client_custom_base_max_retries() -> None:
+ """HttpClient accepts a custom base_max_retries value."""
+ http_client = HttpClient(
+ httpx_client=MagicMock(), # type: ignore[arg-type]
+ base_timeout=lambda: None,
+ base_headers=lambda: {},
+ base_max_retries=5,
+ )
+ assert http_client.base_max_retries == 5
+
+
+def test_async_http_client_custom_base_max_retries() -> None:
+ """AsyncHttpClient accepts a custom base_max_retries value."""
+ http_client = AsyncHttpClient(
+ httpx_client=MagicMock(), # type: ignore[arg-type]
+ base_timeout=lambda: None,
+ base_headers=lambda: {},
+ base_max_retries=5,
+ )
+ assert http_client.base_max_retries == 5
+
+
+@patch("seed.core.http_client.time.sleep", return_value=None)
+def test_sync_base_max_retries_zero_disables_retries(mock_sleep: MagicMock) -> None:
+ """Sync: base_max_retries=0 disables retries when no request_options override."""
+ mock_client = MagicMock()
+ mock_client.request.side_effect = httpx.ConnectError("connection failed")
+ http_client = HttpClient(
+ httpx_client=mock_client, # type: ignore[arg-type]
+ base_timeout=lambda: None,
+ base_headers=lambda: {},
+ base_url=lambda: "https://example.com",
+ base_max_retries=0,
+ )
+
+ with pytest.raises(httpx.ConnectError):
+ http_client.request(path="/test", method="GET")
+
+ # No retries, just the initial attempt
+ assert mock_client.request.call_count == 1
+ mock_sleep.assert_not_called()
+
+
+@pytest.mark.asyncio
+@patch("seed.core.http_client.asyncio.sleep", new_callable=AsyncMock)
+async def test_async_base_max_retries_zero_disables_retries(mock_sleep: AsyncMock) -> None:
+ """Async: base_max_retries=0 disables retries when no request_options override."""
+ mock_client = MagicMock()
+ mock_client.request = AsyncMock(side_effect=httpx.ConnectError("connection failed"))
+ http_client = AsyncHttpClient(
+ httpx_client=mock_client, # type: ignore[arg-type]
+ base_timeout=lambda: None,
+ base_headers=lambda: {},
+ base_url=lambda: "https://example.com",
+ base_max_retries=0,
+ )
+
+ with pytest.raises(httpx.ConnectError):
+ await http_client.request(path="/test", method="GET")
+
+ # No retries, just the initial attempt
+ assert mock_client.request.call_count == 1
+ mock_sleep.assert_not_called()
+
+
+@patch("seed.core.http_client.time.sleep", return_value=None)
+def test_sync_request_options_override_base_max_retries(mock_sleep: MagicMock) -> None:
+ """Sync: request_options max_retries overrides base_max_retries."""
+ mock_client = MagicMock()
+ mock_client.request.side_effect = [
+ httpx.ConnectError("connection failed"),
+ httpx.ConnectError("connection failed"),
+ _DummyResponse(),
+ ]
+ http_client = HttpClient(
+ httpx_client=mock_client, # type: ignore[arg-type]
+ base_timeout=lambda: None,
+ base_headers=lambda: {},
+ base_url=lambda: "https://example.com",
+ base_max_retries=0, # base says no retries
+ )
+
+ # But request_options overrides to allow 2 retries
+ response = http_client.request(
+ path="/test",
+ method="GET",
+ request_options={"max_retries": 2},
+ )
+
+ assert response.status_code == 200
+ # 1 initial + 2 retries = 3 total attempts
+ assert mock_client.request.call_count == 3
+
+
+@pytest.mark.asyncio
+@patch("seed.core.http_client.asyncio.sleep", new_callable=AsyncMock)
+async def test_async_request_options_override_base_max_retries(mock_sleep: AsyncMock) -> None:
+ """Async: request_options max_retries overrides base_max_retries."""
+ mock_client = MagicMock()
+ mock_client.request = AsyncMock(
+ side_effect=[
+ httpx.ConnectError("connection failed"),
+ httpx.ConnectError("connection failed"),
+ _DummyResponse(),
+ ]
+ )
+ http_client = AsyncHttpClient(
+ httpx_client=mock_client, # type: ignore[arg-type]
+ base_timeout=lambda: None,
+ base_headers=lambda: {},
+ base_url=lambda: "https://example.com",
+ base_max_retries=0, # base says no retries
+ )
+
+ # But request_options overrides to allow 2 retries
+ response = await http_client.request(
+ path="/test",
+ method="GET",
+ request_options={"max_retries": 2},
+ )
+
+ assert response.status_code == 200
+ # 1 initial + 2 retries = 3 total attempts
+ assert mock_client.request.call_count == 3
+
+
+@patch("seed.core.http_client.time.sleep", return_value=None)
+def test_sync_base_max_retries_used_as_default(mock_sleep: MagicMock) -> None:
+ """Sync: base_max_retries is used when request_options has no max_retries."""
+ mock_client = MagicMock()
+ mock_client.request.side_effect = [
+ httpx.ConnectError("fail"),
+ httpx.ConnectError("fail"),
+ httpx.ConnectError("fail"),
+ _DummyResponse(),
+ ]
+ http_client = HttpClient(
+ httpx_client=mock_client, # type: ignore[arg-type]
+ base_timeout=lambda: None,
+ base_headers=lambda: {},
+ base_url=lambda: "https://example.com",
+ base_max_retries=3,
+ )
+
+ response = http_client.request(path="/test", method="GET")
+
+ assert response.status_code == 200
+ # 1 initial + 3 retries = 4 total attempts
+ assert mock_client.request.call_count == 4
+
+
+@pytest.mark.asyncio
+@patch("seed.core.http_client.asyncio.sleep", new_callable=AsyncMock)
+async def test_async_base_max_retries_used_as_default(mock_sleep: AsyncMock) -> None:
+ """Async: base_max_retries is used when request_options has no max_retries."""
+ mock_client = MagicMock()
+ mock_client.request = AsyncMock(
+ side_effect=[
+ httpx.ConnectError("fail"),
+ httpx.ConnectError("fail"),
+ httpx.ConnectError("fail"),
+ _DummyResponse(),
+ ]
+ )
+ http_client = AsyncHttpClient(
+ httpx_client=mock_client, # type: ignore[arg-type]
+ base_timeout=lambda: None,
+ base_headers=lambda: {},
+ base_url=lambda: "https://example.com",
+ base_max_retries=3,
+ )
+
+ response = await http_client.request(path="/test", method="GET")
+
+ assert response.status_code == 200
+ # 1 initial + 3 retries = 4 total attempts
+ assert mock_client.request.call_count == 4
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/tests/utils/test_query_encoding.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/tests/utils/test_query_encoding.py
new file mode 100644
index 000000000000..ef5fd7094f9b
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/tests/utils/test_query_encoding.py
@@ -0,0 +1,36 @@
+# This file was auto-generated by Fern from our API Definition.
+
+from seed.core.query_encoder import encode_query
+
+
+def test_query_encoding_deep_objects() -> None:
+ assert encode_query({"hello world": "hello world"}) == [("hello world", "hello world")]
+ assert encode_query({"hello_world": {"hello": "world"}}) == [("hello_world[hello]", "world")]
+ assert encode_query({"hello_world": {"hello": {"world": "today"}, "test": "this"}, "hi": "there"}) == [
+ ("hello_world[hello][world]", "today"),
+ ("hello_world[test]", "this"),
+ ("hi", "there"),
+ ]
+
+
+def test_query_encoding_deep_object_arrays() -> None:
+ assert encode_query({"objects": [{"key": "hello", "value": "world"}, {"key": "foo", "value": "bar"}]}) == [
+ ("objects[key]", "hello"),
+ ("objects[value]", "world"),
+ ("objects[key]", "foo"),
+ ("objects[value]", "bar"),
+ ]
+ assert encode_query(
+ {"users": [{"name": "string", "tags": ["string"]}, {"name": "string2", "tags": ["string2", "string3"]}]}
+ ) == [
+ ("users[name]", "string"),
+ ("users[tags]", "string"),
+ ("users[name]", "string2"),
+ ("users[tags]", "string2"),
+ ("users[tags]", "string3"),
+ ]
+
+
+def test_encode_query_with_none() -> None:
+ encoded = encode_query(None)
+ assert encoded is None
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/tests/utils/test_serialization.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/tests/utils/test_serialization.py
new file mode 100644
index 000000000000..b298db89c4bd
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/tests/utils/test_serialization.py
@@ -0,0 +1,72 @@
+# This file was auto-generated by Fern from our API Definition.
+
+from typing import Any, List
+
+from .assets.models import ObjectWithOptionalFieldParams, ShapeParams
+
+from seed.core.serialization import convert_and_respect_annotation_metadata
+
+UNION_TEST: ShapeParams = {"radius_measurement": 1.0, "shape_type": "circle", "id": "1"}
+UNION_TEST_CONVERTED = {"shapeType": "circle", "radiusMeasurement": 1.0, "id": "1"}
+
+
+def test_convert_and_respect_annotation_metadata() -> None:
+ data: ObjectWithOptionalFieldParams = {
+ "string": "string",
+ "long_": 12345,
+ "bool_": True,
+ "literal": "lit_one",
+ "any": "any",
+ }
+ converted = convert_and_respect_annotation_metadata(
+ object_=data, annotation=ObjectWithOptionalFieldParams, direction="write"
+ )
+ assert converted == {"string": "string", "long": 12345, "bool": True, "literal": "lit_one", "any": "any"}
+
+
+def test_convert_and_respect_annotation_metadata_in_list() -> None:
+ data: List[ObjectWithOptionalFieldParams] = [
+ {"string": "string", "long_": 12345, "bool_": True, "literal": "lit_one", "any": "any"},
+ {"string": "another string", "long_": 67890, "list_": [], "literal": "lit_one", "any": "any"},
+ ]
+ converted = convert_and_respect_annotation_metadata(
+ object_=data, annotation=List[ObjectWithOptionalFieldParams], direction="write"
+ )
+
+ assert converted == [
+ {"string": "string", "long": 12345, "bool": True, "literal": "lit_one", "any": "any"},
+ {"string": "another string", "long": 67890, "list": [], "literal": "lit_one", "any": "any"},
+ ]
+
+
+def test_convert_and_respect_annotation_metadata_in_nested_object() -> None:
+ data: ObjectWithOptionalFieldParams = {
+ "string": "string",
+ "long_": 12345,
+ "union": UNION_TEST,
+ "literal": "lit_one",
+ "any": "any",
+ }
+ converted = convert_and_respect_annotation_metadata(
+ object_=data, annotation=ObjectWithOptionalFieldParams, direction="write"
+ )
+
+ assert converted == {
+ "string": "string",
+ "long": 12345,
+ "union": UNION_TEST_CONVERTED,
+ "literal": "lit_one",
+ "any": "any",
+ }
+
+
+def test_convert_and_respect_annotation_metadata_in_union() -> None:
+ converted = convert_and_respect_annotation_metadata(object_=UNION_TEST, annotation=ShapeParams, direction="write")
+
+ assert converted == UNION_TEST_CONVERTED
+
+
+def test_convert_and_respect_annotation_metadata_with_empty_object() -> None:
+ data: Any = {}
+ converted = convert_and_respect_annotation_metadata(object_=data, annotation=ShapeParams, direction="write")
+ assert converted == data
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/tests/wire/__init__.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/tests/wire/__init__.py
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/tests/wire/conftest.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/tests/wire/conftest.py
new file mode 100644
index 000000000000..a7120f41e0a4
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/tests/wire/conftest.py
@@ -0,0 +1,78 @@
+"""
+Pytest configuration for wire tests.
+
+This module provides helpers for creating a configured client that talks to
+WireMock and for verifying requests in WireMock.
+
+The WireMock container lifecycle itself is managed by a top-level pytest
+plugin (tests/conftest.py) so that the container is started exactly once
+per test run, even when using pytest-xdist.
+"""
+
+import inspect
+import os
+from typing import Any, Dict, Optional
+
+import httpx
+
+from seed.client import SeedUnions
+
+# Check once at import time whether the client constructor accepts a headers kwarg.
+try:
+ _CLIENT_SUPPORTS_HEADERS: bool = "headers" in inspect.signature(SeedUnions).parameters
+except (TypeError, ValueError):
+ _CLIENT_SUPPORTS_HEADERS = False
+
+
+def _get_wiremock_base_url() -> str:
+ """Returns the WireMock base URL from the WIREMOCK_URL environment variable."""
+ return os.environ.get("WIREMOCK_URL", "http://localhost:8080")
+
+
+def get_client(test_id: str) -> SeedUnions:
+ """
+ Creates a configured client instance for wire tests.
+
+ Args:
+ test_id: Unique identifier for the test, used for request tracking.
+
+ Returns:
+ A configured client instance with all required auth parameters.
+ """
+ test_headers = {"X-Test-Id": test_id}
+ base_url = _get_wiremock_base_url()
+
+ if _CLIENT_SUPPORTS_HEADERS:
+ return SeedUnions(
+ base_url=base_url,
+ headers=test_headers,
+ )
+
+ return SeedUnions(
+ base_url=base_url,
+ httpx_client=httpx.Client(headers=test_headers),
+ )
+
+
+def verify_request_count(
+ test_id: str,
+ method: str,
+ url_path: str,
+ query_params: Optional[Dict[str, str]],
+ expected: int,
+) -> None:
+ """Verifies the number of requests made to WireMock filtered by test ID for concurrency safety."""
+ wiremock_admin_url = f"{_get_wiremock_base_url()}/__admin"
+ request_body: Dict[str, Any] = {
+ "method": method,
+ "urlPath": url_path,
+ "headers": {"X-Test-Id": {"equalTo": test_id}},
+ }
+ if query_params:
+ query_parameters = {k: {"equalTo": v} for k, v in query_params.items()}
+ request_body["queryParameters"] = query_parameters
+ response = httpx.post(f"{wiremock_admin_url}/requests/find", json=request_body)
+ assert response.status_code == 200, "Failed to query WireMock requests"
+ result = response.json()
+ requests_found = len(result.get("requests", []))
+ assert requests_found == expected, f"Expected {expected} requests, found {requests_found}"
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/tests/wire/test_bigunion.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/tests/wire/test_bigunion.py
new file mode 100644
index 000000000000..79b278060331
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/tests/wire/test_bigunion.py
@@ -0,0 +1,53 @@
+import datetime
+
+from .conftest import get_client, verify_request_count
+
+from seed.bigunion import NormalSweetBigUnion
+
+
+def test_bigunion_get() -> None:
+ """Test get endpoint with WireMock"""
+ test_id = "bigunion.get.0"
+ client = get_client(test_id)
+ client.bigunion.get(
+ id="id",
+ )
+ verify_request_count(test_id, "GET", "/id", None, 1)
+
+
+def test_bigunion_update() -> None:
+ """Test update endpoint with WireMock"""
+ test_id = "bigunion.update.0"
+ client = get_client(test_id)
+ client.bigunion.update(
+ request=NormalSweetBigUnion(
+ id="id",
+ created_at=datetime.datetime.fromisoformat("2024-01-15T09:30:00+00:00"),
+ archived_at=datetime.datetime.fromisoformat("2024-01-15T09:30:00+00:00"),
+ value="value",
+ ),
+ )
+ verify_request_count(test_id, "PATCH", "/", None, 1)
+
+
+def test_bigunion_update_many() -> None:
+ """Test update-many endpoint with WireMock"""
+ test_id = "bigunion.update_many.0"
+ client = get_client(test_id)
+ client.bigunion.update_many(
+ request=[
+ NormalSweetBigUnion(
+ id="id",
+ created_at=datetime.datetime.fromisoformat("2024-01-15T09:30:00+00:00"),
+ archived_at=datetime.datetime.fromisoformat("2024-01-15T09:30:00+00:00"),
+ value="value",
+ ),
+ NormalSweetBigUnion(
+ id="id",
+ created_at=datetime.datetime.fromisoformat("2024-01-15T09:30:00+00:00"),
+ archived_at=datetime.datetime.fromisoformat("2024-01-15T09:30:00+00:00"),
+ value="value",
+ ),
+ ],
+ )
+ verify_request_count(test_id, "PATCH", "/many", None, 1)
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/tests/wire/test_union.py b/seed/python-sdk/unions/union-naming-v1-wire-tests/tests/wire/test_union.py
new file mode 100644
index 000000000000..e2ff50960a1d
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/tests/wire/test_union.py
@@ -0,0 +1,26 @@
+from .conftest import get_client, verify_request_count
+
+from seed.union import CircleShape
+
+
+def test_union_get() -> None:
+ """Test get endpoint with WireMock"""
+ test_id = "union.get.0"
+ client = get_client(test_id)
+ client.union.get(
+ id="id",
+ )
+ verify_request_count(test_id, "GET", "/id", None, 1)
+
+
+def test_union_update() -> None:
+ """Test update endpoint with WireMock"""
+ test_id = "union.update.0"
+ client = get_client(test_id)
+ client.union.update(
+ request=CircleShape(
+ id="id",
+ radius=1.1,
+ ),
+ )
+ verify_request_count(test_id, "PATCH", "/", None, 1)
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/wiremock/docker-compose.test.yml b/seed/python-sdk/unions/union-naming-v1-wire-tests/wiremock/docker-compose.test.yml
new file mode 100644
index 000000000000..58747d54a46b
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/wiremock/docker-compose.test.yml
@@ -0,0 +1,14 @@
+services:
+ wiremock:
+ image: wiremock/wiremock:3.9.1
+ ports:
+ - "0:8080" # Use dynamic port to avoid conflicts with concurrent tests
+ volumes:
+ - ./wiremock-mappings.json:/home/wiremock/mappings/wiremock-mappings.json
+ command: ["--global-response-templating", "--verbose"]
+ healthcheck:
+ test: ["CMD", "curl", "-f", "http://localhost:8080/__admin/health"]
+ interval: 2s
+ timeout: 5s
+ retries: 15
+ start_period: 5s
diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/wiremock/wiremock-mappings.json b/seed/python-sdk/unions/union-naming-v1-wire-tests/wiremock/wiremock-mappings.json
new file mode 100644
index 000000000000..2e8a75f621d2
--- /dev/null
+++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/wiremock/wiremock-mappings.json
@@ -0,0 +1,147 @@
+{
+ "mappings": [
+ {
+ "id": "79c47b13-99df-4bee-a249-4ce00eb110db",
+ "name": "get - default",
+ "request": {
+ "urlPathTemplate": "/{id}",
+ "method": "GET",
+ "pathParameters": {
+ "id": {
+ "equalTo": "id"
+ }
+ }
+ },
+ "response": {
+ "status": 200,
+ "body": "{\n \"type\": \"normalSweet\",\n \"value\": \"value\",\n \"id\": \"id\",\n \"created-at\": \"2024-01-15T09:30:00Z\",\n \"archived-at\": \"2024-01-15T09:30:00Z\"\n}",
+ "headers": {
+ "Content-Type": "application/json"
+ }
+ },
+ "uuid": "79c47b13-99df-4bee-a249-4ce00eb110db",
+ "persistent": true,
+ "priority": 3,
+ "metadata": {
+ "mocklab": {
+ "created": {
+ "at": "2020-01-01T00:00:00.000Z",
+ "via": "SYSTEM"
+ }
+ }
+ }
+ },
+ {
+ "id": "5f1f346e-183d-4637-acbe-e1b61414f5e4",
+ "name": "update - default",
+ "request": {
+ "urlPathTemplate": "/",
+ "method": "PATCH"
+ },
+ "response": {
+ "status": 200,
+ "body": "true",
+ "headers": {
+ "Content-Type": "application/json"
+ }
+ },
+ "uuid": "5f1f346e-183d-4637-acbe-e1b61414f5e4",
+ "persistent": true,
+ "priority": 3,
+ "metadata": {
+ "mocklab": {
+ "created": {
+ "at": "2020-01-01T00:00:00.000Z",
+ "via": "SYSTEM"
+ }
+ }
+ }
+ },
+ {
+ "id": "4950f13e-46d4-4dd3-9513-62d558c1061b",
+ "name": "update-many - default",
+ "request": {
+ "urlPathTemplate": "/many",
+ "method": "PATCH"
+ },
+ "response": {
+ "status": 200,
+ "body": "{\n \"string\": true\n}",
+ "headers": {
+ "Content-Type": "application/json"
+ }
+ },
+ "uuid": "4950f13e-46d4-4dd3-9513-62d558c1061b",
+ "persistent": true,
+ "priority": 3,
+ "metadata": {
+ "mocklab": {
+ "created": {
+ "at": "2020-01-01T00:00:00.000Z",
+ "via": "SYSTEM"
+ }
+ }
+ }
+ },
+ {
+ "id": "c355b619-2495-4caf-b85f-77766995a146",
+ "name": "get - default",
+ "request": {
+ "urlPathTemplate": "/{id}",
+ "method": "GET",
+ "pathParameters": {
+ "id": {
+ "equalTo": "id"
+ }
+ }
+ },
+ "response": {
+ "status": 200,
+ "body": "{\n \"type\": \"circle\",\n \"radius\": 1.1,\n \"id\": \"id\"\n}",
+ "headers": {
+ "Content-Type": "application/json"
+ }
+ },
+ "uuid": "c355b619-2495-4caf-b85f-77766995a146",
+ "persistent": true,
+ "priority": 3,
+ "metadata": {
+ "mocklab": {
+ "created": {
+ "at": "2020-01-01T00:00:00.000Z",
+ "via": "SYSTEM"
+ }
+ }
+ }
+ },
+ {
+ "id": "7b5d90dc-ff29-4059-a8d7-e30f4286d795",
+ "name": "update - default",
+ "request": {
+ "urlPathTemplate": "/",
+ "method": "PATCH"
+ },
+ "response": {
+ "status": 200,
+ "body": "true",
+ "headers": {
+ "Content-Type": "application/json"
+ }
+ },
+ "uuid": "7b5d90dc-ff29-4059-a8d7-e30f4286d795",
+ "persistent": true,
+ "priority": 3,
+ "metadata": {
+ "mocklab": {
+ "created": {
+ "at": "2020-01-01T00:00:00.000Z",
+ "via": "SYSTEM"
+ }
+ }
+ }
+ }
+ ],
+ "meta": {
+ "total": 5
+ }
+}
\ No newline at end of file
diff --git a/seed/python-sdk/unions/union-naming-v1/reference.md b/seed/python-sdk/unions/union-naming-v1/reference.md
index ef52dd4c84c7..96836d74d2bd 100644
--- a/seed/python-sdk/unions/union-naming-v1/reference.md
+++ b/seed/python-sdk/unions/union-naming-v1/reference.md
@@ -71,7 +71,7 @@ client.bigunion.get(
```python
from seed import SeedUnions
-from seed.bigunion import BigUnion_NormalSweet
+from seed.bigunion import NormalSweetBigUnion
import datetime
client = SeedUnions(
@@ -79,7 +79,7 @@ client = SeedUnions(
)
client.bigunion.update(
- request=BigUnion_NormalSweet(
+ request=NormalSweetBigUnion(
id="id",
created_at=datetime.datetime.fromisoformat("2024-01-15T09:30:00+00:00"),
archived_at=datetime.datetime.fromisoformat("2024-01-15T09:30:00+00:00"),
@@ -135,7 +135,7 @@ client.bigunion.update(
```python
from seed import SeedUnions
-from seed.bigunion import BigUnion_NormalSweet
+from seed.bigunion import NormalSweetBigUnion
import datetime
client = SeedUnions(
@@ -144,13 +144,13 @@ client = SeedUnions(
client.bigunion.update_many(
request=[
- BigUnion_NormalSweet(
+ NormalSweetBigUnion(
id="id",
created_at=datetime.datetime.fromisoformat("2024-01-15T09:30:00+00:00"),
archived_at=datetime.datetime.fromisoformat("2024-01-15T09:30:00+00:00"),
value="value",
),
- BigUnion_NormalSweet(
+ NormalSweetBigUnion(
id="id",
created_at=datetime.datetime.fromisoformat("2024-01-15T09:30:00+00:00"),
archived_at=datetime.datetime.fromisoformat("2024-01-15T09:30:00+00:00"),
@@ -265,14 +265,14 @@ client.bigunion.get(
```python
from seed import SeedUnions
-from seed.union import Shape_Circle
+from seed.union import CircleShape
client = SeedUnions(
base_url="https://yourhost.com/path/to/api",
)
client.union.update(
- request=Shape_Circle(
+ request=CircleShape(
id="id",
radius=1.1,
),