diff --git a/generators/python/core_utilities/shared/jsonable_encoder.py b/generators/python/core_utilities/shared/jsonable_encoder.py
index ee2b36b91d33..460d3737c617 100644
--- a/generators/python/core_utilities/shared/jsonable_encoder.py
+++ b/generators/python/core_utilities/shared/jsonable_encoder.py
@@ -13,6 +13,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -116,3 +117,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/generators/python/sdk/changes/unreleased/encode-path-parameters.yml b/generators/python/sdk/changes/unreleased/encode-path-parameters.yml
new file mode 100644
index 000000000000..6768d4106e9f
--- /dev/null
+++ b/generators/python/sdk/changes/unreleased/encode-path-parameters.yml
@@ -0,0 +1,7 @@
+- summary: |
+ Add an `encode_path_params` config option (default `false`). When enabled, path parameter
+ values are percent-encoded when substituted into the request path, so a value containing `/`
+ or `..` can no longer change which endpoint the request resolves to. The default `false`
+ preserves the existing (unencoded) behavior; TypeScript, Go, Java, and C# already encode path
+ params.
+ type: feat
diff --git a/generators/python/src/fern_python/generators/sdk/core_utilities/core_utilities.py b/generators/python/src/fern_python/generators/sdk/core_utilities/core_utilities.py
index a4e5d64eef5a..f2680d1b81b2 100644
--- a/generators/python/src/fern_python/generators/sdk/core_utilities/core_utilities.py
+++ b/generators/python/src/fern_python/generators/sdk/core_utilities/core_utilities.py
@@ -44,6 +44,7 @@ def __init__(
self._version = custom_config.pydantic_config.version
self._project_module_path = project_module_path
self._use_pydantic_field_aliases = custom_config.pydantic_config.use_pydantic_field_aliases
+ self._encode_path_params = custom_config.encode_path_params
self._should_generate_websocket_clients = custom_config.should_generate_websocket_clients
self._exclude_types_from_init_exports = custom_config.exclude_types_from_init_exports
self._custom_pager_base_name = self._sanitize_pager_name(custom_config.custom_pager_name or "CustomPager")
@@ -682,7 +683,7 @@ def encode_path_param(self, obj: AST.Expression) -> AST.Expression:
qualified_name_excluding_import=(),
import_=AST.ReferenceImport(
module=AST.Module.local(*self._module_path, "jsonable_encoder"),
- named_import="encode_path_param",
+ named_import="quote_path_param" if self._encode_path_params else "encode_path_param",
),
),
args=[obj],
diff --git a/generators/python/src/fern_python/generators/sdk/custom_config.py b/generators/python/src/fern_python/generators/sdk/custom_config.py
index 87fb1c9dd814..a167d66cedc2 100644
--- a/generators/python/src/fern_python/generators/sdk/custom_config.py
+++ b/generators/python/src/fern_python/generators/sdk/custom_config.py
@@ -129,6 +129,11 @@ class SDKCustomConfig(pydantic.BaseModel):
# If true, treats path parameters as named parameters in endpoint functions
inline_path_params: bool = False
+ # If true, path parameter values are percent-encoded when substituted into the
+ # request path, so a value containing "/" or ".." cannot change which endpoint
+ # the request resolves to. Off by default so existing output is unchanged.
+ encode_path_params: bool = False
+
# Feature flag that enables generation of Python websocket clients
should_generate_websocket_clients: bool = False
diff --git a/generators/python/tests/sdk/test_jsonable_encoder.py b/generators/python/tests/sdk/test_jsonable_encoder.py
index e60d81c92849..0e49c3c10aba 100644
--- a/generators/python/tests/sdk/test_jsonable_encoder.py
+++ b/generators/python/tests/sdk/test_jsonable_encoder.py
@@ -2,10 +2,31 @@
from fern.generator_exec.logging import GeneratorUpdate, InitUpdateV2
-from core_utilities.shared.jsonable_encoder import jsonable_encoder
+from core_utilities.shared.jsonable_encoder import encode_path_param, jsonable_encoder, quote_path_param
def test_jsonable_encoder() -> None:
updates: List[GeneratorUpdate] = [GeneratorUpdate.factory.init_v_2(InitUpdateV2(publishing_to_registry=None))]
serialized = jsonable_encoder(updates)
assert serialized == [{"_type": "initV2", "publishingToRegistry": None}]
+
+
+def test_encode_path_param() -> None:
+ assert encode_path_param("../connections") == "../connections"
+ assert encode_path_param("user_1") == "user_1"
+ assert encode_path_param(42) == "42"
+ assert encode_path_param(True) == "true"
+ assert encode_path_param(False) == "false"
+
+
+def test_quote_path_param() -> None:
+ assert quote_path_param("../connections") == "..%2Fconnections"
+ assert quote_path_param("user id?") == "user%20id%3F"
+ assert quote_path_param("user_1") == "user_1"
+ assert quote_path_param(42) == "42"
+ assert quote_path_param(True) == "true"
+ assert quote_path_param(False) == "false"
+ # every "/" is encoded, so a multi-segment value cannot change the endpoint
+ assert quote_path_param("a/b/c") == "a%2Fb%2Fc"
+ # already-encoded input is encoded again rather than passed through
+ assert quote_path_param("a%2Fb") == "a%252Fb"
diff --git a/seed/python-sdk/accept-header/src/seed/core/jsonable_encoder.py b/seed/python-sdk/accept-header/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/accept-header/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/accept-header/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/alias-extends/no-custom-config/src/seed/core/jsonable_encoder.py b/seed/python-sdk/alias-extends/no-custom-config/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/alias-extends/no-custom-config/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/alias-extends/no-custom-config/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/alias-extends/no-inheritance-for-extended-models/src/seed/core/jsonable_encoder.py b/seed/python-sdk/alias-extends/no-inheritance-for-extended-models/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/alias-extends/no-inheritance-for-extended-models/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/alias-extends/no-inheritance-for-extended-models/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/alias/src/seed/core/jsonable_encoder.py b/seed/python-sdk/alias/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/alias/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/alias/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/jsonable_encoder.py b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/allof-inline/no-custom-config/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/allof/no-custom-config/src/seed/core/jsonable_encoder.py b/seed/python-sdk/allof/no-custom-config/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/allof/no-custom-config/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/allof/no-custom-config/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/any-auth/src/seed/core/jsonable_encoder.py b/seed/python-sdk/any-auth/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/any-auth/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/any-auth/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/api-wide-base-path-with-default/src/seed/core/jsonable_encoder.py b/seed/python-sdk/api-wide-base-path-with-default/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/api-wide-base-path-with-default/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/api-wide-base-path-with-default/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/api-wide-base-path/src/seed/core/jsonable_encoder.py b/seed/python-sdk/api-wide-base-path/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/api-wide-base-path/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/api-wide-base-path/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/audiences/src/seed/core/jsonable_encoder.py b/seed/python-sdk/audiences/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/audiences/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/audiences/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/basic-auth-environment-variables/src/seed/core/jsonable_encoder.py b/seed/python-sdk/basic-auth-environment-variables/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/basic-auth-environment-variables/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/basic-auth-environment-variables/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/basic-auth-pw-omitted/with-wire-tests/src/seed/core/jsonable_encoder.py b/seed/python-sdk/basic-auth-pw-omitted/with-wire-tests/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/basic-auth-pw-omitted/with-wire-tests/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/basic-auth-pw-omitted/with-wire-tests/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/basic-auth/src/seed/core/jsonable_encoder.py b/seed/python-sdk/basic-auth/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/basic-auth/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/basic-auth/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/bearer-token-environment-variable/src/seed/core/jsonable_encoder.py b/seed/python-sdk/bearer-token-environment-variable/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/bearer-token-environment-variable/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/bearer-token-environment-variable/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/bytes-download/src/seed/core/jsonable_encoder.py b/seed/python-sdk/bytes-download/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/bytes-download/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/bytes-download/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/bytes-upload/src/seed/core/jsonable_encoder.py b/seed/python-sdk/bytes-upload/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/bytes-upload/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/bytes-upload/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/circular-references-advanced/no-custom-config/src/seed/core/jsonable_encoder.py b/seed/python-sdk/circular-references-advanced/no-custom-config/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/circular-references-advanced/no-custom-config/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/circular-references-advanced/no-custom-config/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/circular-references-advanced/no-inheritance-for-extended-models/src/seed/core/jsonable_encoder.py b/seed/python-sdk/circular-references-advanced/no-inheritance-for-extended-models/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/circular-references-advanced/no-inheritance-for-extended-models/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/circular-references-advanced/no-inheritance-for-extended-models/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/circular-references-extends/no-custom-config/src/seed/core/jsonable_encoder.py b/seed/python-sdk/circular-references-extends/no-custom-config/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/circular-references-extends/no-custom-config/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/circular-references-extends/no-custom-config/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/circular-references/no-custom-config/src/seed/core/jsonable_encoder.py b/seed/python-sdk/circular-references/no-custom-config/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/circular-references/no-custom-config/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/circular-references/no-custom-config/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/circular-references/no-inheritance-for-extended-models/src/seed/core/jsonable_encoder.py b/seed/python-sdk/circular-references/no-inheritance-for-extended-models/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/circular-references/no-inheritance-for-extended-models/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/circular-references/no-inheritance-for-extended-models/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/client-side-params/src/seed/core/jsonable_encoder.py b/seed/python-sdk/client-side-params/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/client-side-params/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/client-side-params/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/content-type/src/seed/core/jsonable_encoder.py b/seed/python-sdk/content-type/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/content-type/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/content-type/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/cross-package-type-names/src/seed/core/jsonable_encoder.py b/seed/python-sdk/cross-package-type-names/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/cross-package-type-names/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/cross-package-type-names/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/discriminated-union-with-nested-oneof/src/seed/core/jsonable_encoder.py b/seed/python-sdk/discriminated-union-with-nested-oneof/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/discriminated-union-with-nested-oneof/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/discriminated-union-with-nested-oneof/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/dollar-string-examples/src/seed/core/jsonable_encoder.py b/seed/python-sdk/dollar-string-examples/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/dollar-string-examples/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/dollar-string-examples/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/empty-clients/src/seed/core/jsonable_encoder.py b/seed/python-sdk/empty-clients/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/empty-clients/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/empty-clients/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/endpoint-security-auth/src/seed/core/jsonable_encoder.py b/seed/python-sdk/endpoint-security-auth/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/endpoint-security-auth/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/endpoint-security-auth/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/enum/no-custom-config/src/seed/core/jsonable_encoder.py b/seed/python-sdk/enum/no-custom-config/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/enum/no-custom-config/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/enum/no-custom-config/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/enum/real-enum-forward-compat/src/seed/core/jsonable_encoder.py b/seed/python-sdk/enum/real-enum-forward-compat/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/enum/real-enum-forward-compat/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/enum/real-enum-forward-compat/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/enum/real-enum/src/seed/core/jsonable_encoder.py b/seed/python-sdk/enum/real-enum/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/enum/real-enum/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/enum/real-enum/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/enum/strenum/src/seed/core/jsonable_encoder.py b/seed/python-sdk/enum/strenum/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/enum/strenum/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/enum/strenum/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/error-property/src/seed/core/jsonable_encoder.py b/seed/python-sdk/error-property/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/error-property/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/error-property/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/errors/src/seed/core/jsonable_encoder.py b/seed/python-sdk/errors/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/errors/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/errors/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/examples/additional_init_exports_with_duplicates/src/seed/core/jsonable_encoder.py b/seed/python-sdk/examples/additional_init_exports_with_duplicates/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/examples/additional_init_exports_with_duplicates/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/examples/additional_init_exports_with_duplicates/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/examples/client-filename/src/seed/core/jsonable_encoder.py b/seed/python-sdk/examples/client-filename/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/examples/client-filename/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/examples/client-filename/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/examples/include-platform-headers/src/seed/core/jsonable_encoder.py b/seed/python-sdk/examples/include-platform-headers/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/examples/include-platform-headers/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/examples/include-platform-headers/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/examples/legacy-wire-tests/src/seed/core/jsonable_encoder.py b/seed/python-sdk/examples/legacy-wire-tests/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/examples/legacy-wire-tests/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/examples/legacy-wire-tests/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/examples/no-custom-config/src/seed/core/jsonable_encoder.py b/seed/python-sdk/examples/no-custom-config/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/examples/no-custom-config/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/examples/no-custom-config/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/examples/omit-fern-headers/src/seed/core/jsonable_encoder.py b/seed/python-sdk/examples/omit-fern-headers/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/examples/omit-fern-headers/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/examples/omit-fern-headers/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/examples/readme/src/seed/core/jsonable_encoder.py b/seed/python-sdk/examples/readme/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/examples/readme/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/examples/readme/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/exhaustive/additional_init_exports/src/seed/core/jsonable_encoder.py b/seed/python-sdk/exhaustive/additional_init_exports/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/exhaustive/additional_init_exports/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/exhaustive/additional_init_exports/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/exhaustive/aliases_with_validation/src/seed/core/jsonable_encoder.py b/seed/python-sdk/exhaustive/aliases_with_validation/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/exhaustive/aliases_with_validation/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/exhaustive/aliases_with_validation/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/exhaustive/aliases_without_validation/src/seed/core/jsonable_encoder.py b/seed/python-sdk/exhaustive/aliases_without_validation/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/exhaustive/aliases_without_validation/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/exhaustive/aliases_without_validation/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/exhaustive/auto-generate-idempotency-key/src/seed/core/jsonable_encoder.py b/seed/python-sdk/exhaustive/auto-generate-idempotency-key/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/exhaustive/auto-generate-idempotency-key/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/exhaustive/auto-generate-idempotency-key/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/exhaustive/custom-transport/src/seed/core/jsonable_encoder.py b/seed/python-sdk/exhaustive/custom-transport/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/exhaustive/custom-transport/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/exhaustive/custom-transport/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/exhaustive/datetime-milliseconds/src/seed/core/jsonable_encoder.py b/seed/python-sdk/exhaustive/datetime-milliseconds/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/exhaustive/datetime-milliseconds/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/exhaustive/datetime-milliseconds/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/exhaustive/deps_with_min_python_version/src/seed/core/jsonable_encoder.py b/seed/python-sdk/exhaustive/deps_with_min_python_version/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/exhaustive/deps_with_min_python_version/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/exhaustive/deps_with_min_python_version/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/exhaustive/eager-imports/src/seed/core/jsonable_encoder.py b/seed/python-sdk/exhaustive/eager-imports/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/exhaustive/eager-imports/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/exhaustive/eager-imports/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/exhaustive/extra_dependencies/src/seed/core/jsonable_encoder.py b/seed/python-sdk/exhaustive/extra_dependencies/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/exhaustive/extra_dependencies/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/exhaustive/extra_dependencies/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/exhaustive/extra_dev_dependencies/src/seed/core/jsonable_encoder.py b/seed/python-sdk/exhaustive/extra_dev_dependencies/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/exhaustive/extra_dev_dependencies/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/exhaustive/extra_dev_dependencies/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/exhaustive/five-second-timeout/src/seed/core/jsonable_encoder.py b/seed/python-sdk/exhaustive/five-second-timeout/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/exhaustive/five-second-timeout/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/exhaustive/five-second-timeout/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/exhaustive/follow_redirects_by_default/src/seed/core/jsonable_encoder.py b/seed/python-sdk/exhaustive/follow_redirects_by_default/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/exhaustive/follow_redirects_by_default/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/exhaustive/follow_redirects_by_default/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/exhaustive/import-paths/src/seed/core/jsonable_encoder.py b/seed/python-sdk/exhaustive/import-paths/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/exhaustive/import-paths/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/exhaustive/import-paths/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/exhaustive/improved_imports/src/seed/core/jsonable_encoder.py b/seed/python-sdk/exhaustive/improved_imports/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/exhaustive/improved_imports/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/exhaustive/improved_imports/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/exhaustive/infinite-timeout/src/seed/core/jsonable_encoder.py b/seed/python-sdk/exhaustive/infinite-timeout/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/exhaustive/infinite-timeout/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/exhaustive/infinite-timeout/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/exhaustive/inline-path-params/src/seed/core/jsonable_encoder.py b/seed/python-sdk/exhaustive/inline-path-params/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/exhaustive/inline-path-params/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/exhaustive/inline-path-params/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/exhaustive/inline_request_params/src/seed/core/jsonable_encoder.py b/seed/python-sdk/exhaustive/inline_request_params/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/exhaustive/inline_request_params/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/exhaustive/inline_request_params/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/exhaustive/no-custom-config/src/seed/core/jsonable_encoder.py b/seed/python-sdk/exhaustive/no-custom-config/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/exhaustive/no-custom-config/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/exhaustive/no-custom-config/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/exhaustive/output-directory-project-root/src/seed/core/jsonable_encoder.py b/seed/python-sdk/exhaustive/output-directory-project-root/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/exhaustive/output-directory-project-root/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/exhaustive/output-directory-project-root/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/exhaustive/output-directory-source-root-no-package-root/sub/dir/core/jsonable_encoder.py b/seed/python-sdk/exhaustive/output-directory-source-root-no-package-root/sub/dir/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/exhaustive/output-directory-source-root-no-package-root/sub/dir/core/jsonable_encoder.py
+++ b/seed/python-sdk/exhaustive/output-directory-source-root-no-package-root/sub/dir/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/exhaustive/output-directory-source-root-with-package-path/seed/my_org/my_sdk/core/jsonable_encoder.py b/seed/python-sdk/exhaustive/output-directory-source-root-with-package-path/seed/my_org/my_sdk/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/exhaustive/output-directory-source-root-with-package-path/seed/my_org/my_sdk/core/jsonable_encoder.py
+++ b/seed/python-sdk/exhaustive/output-directory-source-root-with-package-path/seed/my_org/my_sdk/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/exhaustive/output-directory-source-root/seed/core/jsonable_encoder.py b/seed/python-sdk/exhaustive/output-directory-source-root/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/exhaustive/output-directory-source-root/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/exhaustive/output-directory-source-root/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/exhaustive/package-path/src/seed/matryoshka/doll/structure/core/jsonable_encoder.py b/seed/python-sdk/exhaustive/package-path/src/seed/matryoshka/doll/structure/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/exhaustive/package-path/src/seed/matryoshka/doll/structure/core/jsonable_encoder.py
+++ b/seed/python-sdk/exhaustive/package-path/src/seed/matryoshka/doll/structure/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/exhaustive/pydantic-extra-fields/src/seed/core/jsonable_encoder.py b/seed/python-sdk/exhaustive/pydantic-extra-fields/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/exhaustive/pydantic-extra-fields/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/exhaustive/pydantic-extra-fields/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/exhaustive/pydantic-ignore-fields/src/seed/core/jsonable_encoder.py b/seed/python-sdk/exhaustive/pydantic-ignore-fields/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/exhaustive/pydantic-ignore-fields/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/exhaustive/pydantic-ignore-fields/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/exhaustive/pydantic-v1-with-utils/src/seed/core/jsonable_encoder.py b/seed/python-sdk/exhaustive/pydantic-v1-with-utils/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/exhaustive/pydantic-v1-with-utils/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/exhaustive/pydantic-v1-with-utils/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/exhaustive/pydantic-v1-wrapped/src/seed/core/jsonable_encoder.py b/seed/python-sdk/exhaustive/pydantic-v1-wrapped/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/exhaustive/pydantic-v1-wrapped/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/exhaustive/pydantic-v1-wrapped/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/exhaustive/pydantic-v1/src/seed/core/jsonable_encoder.py b/seed/python-sdk/exhaustive/pydantic-v1/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/exhaustive/pydantic-v1/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/exhaustive/pydantic-v1/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/exhaustive/pydantic-v2-wrapped/src/seed/core/jsonable_encoder.py b/seed/python-sdk/exhaustive/pydantic-v2-wrapped/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/exhaustive/pydantic-v2-wrapped/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/exhaustive/pydantic-v2-wrapped/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/exhaustive/pyproject_extras/src/seed/core/jsonable_encoder.py b/seed/python-sdk/exhaustive/pyproject_extras/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/exhaustive/pyproject_extras/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/exhaustive/pyproject_extras/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/exhaustive/runtime-version/src/seed/core/jsonable_encoder.py b/seed/python-sdk/exhaustive/runtime-version/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/exhaustive/runtime-version/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/exhaustive/runtime-version/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/exhaustive/skip-pydantic-validation/src/seed/core/jsonable_encoder.py b/seed/python-sdk/exhaustive/skip-pydantic-validation/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/exhaustive/skip-pydantic-validation/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/exhaustive/skip-pydantic-validation/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/exhaustive/tcp-keepalive/src/seed/core/jsonable_encoder.py b/seed/python-sdk/exhaustive/tcp-keepalive/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/exhaustive/tcp-keepalive/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/exhaustive/tcp-keepalive/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/exhaustive/union-utils/src/seed/core/jsonable_encoder.py b/seed/python-sdk/exhaustive/union-utils/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/exhaustive/union-utils/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/exhaustive/union-utils/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/exhaustive/wire-tests-custom-client-name/src/seed/core/jsonable_encoder.py b/seed/python-sdk/exhaustive/wire-tests-custom-client-name/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/exhaustive/wire-tests-custom-client-name/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/exhaustive/wire-tests-custom-client-name/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/extends/src/seed/core/jsonable_encoder.py b/seed/python-sdk/extends/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/extends/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/extends/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/extra-properties/src/seed/core/jsonable_encoder.py b/seed/python-sdk/extra-properties/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/extra-properties/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/extra-properties/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/file-download/default-chunk-size/src/seed/core/jsonable_encoder.py b/seed/python-sdk/file-download/default-chunk-size/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/file-download/default-chunk-size/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/file-download/default-chunk-size/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/file-download/no-custom-config/src/seed/core/jsonable_encoder.py b/seed/python-sdk/file-download/no-custom-config/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/file-download/no-custom-config/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/file-download/no-custom-config/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/file-upload-openapi/src/seed/core/jsonable_encoder.py b/seed/python-sdk/file-upload-openapi/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/file-upload-openapi/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/file-upload-openapi/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/file-upload/exclude_types_from_init_exports/src/seed/core/jsonable_encoder.py b/seed/python-sdk/file-upload/exclude_types_from_init_exports/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/file-upload/exclude_types_from_init_exports/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/file-upload/exclude_types_from_init_exports/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/file-upload/no-custom-config/src/seed/core/jsonable_encoder.py b/seed/python-sdk/file-upload/no-custom-config/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/file-upload/no-custom-config/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/file-upload/no-custom-config/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/file-upload/use_typeddict_requests/src/seed/core/jsonable_encoder.py b/seed/python-sdk/file-upload/use_typeddict_requests/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/file-upload/use_typeddict_requests/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/file-upload/use_typeddict_requests/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/folders/src/seed/core/jsonable_encoder.py b/seed/python-sdk/folders/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/folders/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/folders/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/header-auth-environment-variable/src/seed/core/jsonable_encoder.py b/seed/python-sdk/header-auth-environment-variable/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/header-auth-environment-variable/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/header-auth-environment-variable/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/header-auth/src/seed/core/jsonable_encoder.py b/seed/python-sdk/header-auth/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/header-auth/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/header-auth/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/http-head/src/seed/core/jsonable_encoder.py b/seed/python-sdk/http-head/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/http-head/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/http-head/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/idempotency-headers/auto-generate-idempotency-key/src/seed/core/jsonable_encoder.py b/seed/python-sdk/idempotency-headers/auto-generate-idempotency-key/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/idempotency-headers/auto-generate-idempotency-key/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/idempotency-headers/auto-generate-idempotency-key/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/idempotency-headers/no-custom-config/src/seed/core/jsonable_encoder.py b/seed/python-sdk/idempotency-headers/no-custom-config/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/idempotency-headers/no-custom-config/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/idempotency-headers/no-custom-config/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/imdb/src/seed/core/jsonable_encoder.py b/seed/python-sdk/imdb/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/imdb/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/imdb/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/inferred-auth-explicit/src/seed/core/jsonable_encoder.py b/seed/python-sdk/inferred-auth-explicit/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/inferred-auth-explicit/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/inferred-auth-explicit/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/inferred-auth-implicit-api-key/src/seed/core/jsonable_encoder.py b/seed/python-sdk/inferred-auth-implicit-api-key/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/inferred-auth-implicit-api-key/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/inferred-auth-implicit-api-key/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/inferred-auth-implicit-no-expiry/src/seed/core/jsonable_encoder.py b/seed/python-sdk/inferred-auth-implicit-no-expiry/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/inferred-auth-implicit-no-expiry/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/inferred-auth-implicit-no-expiry/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/inferred-auth-implicit-reference/src/seed/core/jsonable_encoder.py b/seed/python-sdk/inferred-auth-implicit-reference/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/inferred-auth-implicit-reference/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/inferred-auth-implicit-reference/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/inferred-auth-implicit/src/seed/core/jsonable_encoder.py b/seed/python-sdk/inferred-auth-implicit/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/inferred-auth-implicit/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/inferred-auth-implicit/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/inline-enum-type-name-override/src/seed/core/jsonable_encoder.py b/seed/python-sdk/inline-enum-type-name-override/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/inline-enum-type-name-override/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/inline-enum-type-name-override/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/license/src/seed/core/jsonable_encoder.py b/seed/python-sdk/license/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/license/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/license/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/literal-user-agent/no-custom-config/src/seed/core/jsonable_encoder.py b/seed/python-sdk/literal-user-agent/no-custom-config/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/literal-user-agent/no-custom-config/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/literal-user-agent/no-custom-config/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/literal/no-custom-config/src/seed/core/jsonable_encoder.py b/seed/python-sdk/literal/no-custom-config/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/literal/no-custom-config/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/literal/no-custom-config/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/literal/use_typeddict_requests/src/seed/core/jsonable_encoder.py b/seed/python-sdk/literal/use_typeddict_requests/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/literal/use_typeddict_requests/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/literal/use_typeddict_requests/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/literal/wire-tests/src/seed/core/jsonable_encoder.py b/seed/python-sdk/literal/wire-tests/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/literal/wire-tests/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/literal/wire-tests/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/literals-unions/src/seed/core/jsonable_encoder.py b/seed/python-sdk/literals-unions/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/literals-unions/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/literals-unions/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/mixed-case/src/seed/core/jsonable_encoder.py b/seed/python-sdk/mixed-case/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/mixed-case/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/mixed-case/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/mixed-file-directory/exclude_types_from_init_exports/src/seed/core/jsonable_encoder.py b/seed/python-sdk/mixed-file-directory/exclude_types_from_init_exports/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/mixed-file-directory/exclude_types_from_init_exports/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/mixed-file-directory/exclude_types_from_init_exports/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/mixed-file-directory/no-custom-config/src/seed/core/jsonable_encoder.py b/seed/python-sdk/mixed-file-directory/no-custom-config/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/mixed-file-directory/no-custom-config/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/mixed-file-directory/no-custom-config/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/multi-content-type-examples/src/seed/core/jsonable_encoder.py b/seed/python-sdk/multi-content-type-examples/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/multi-content-type-examples/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/multi-content-type-examples/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/multi-line-docs/src/seed/core/jsonable_encoder.py b/seed/python-sdk/multi-line-docs/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/multi-line-docs/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/multi-line-docs/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/multi-url-environment-no-default/src/seed/core/jsonable_encoder.py b/seed/python-sdk/multi-url-environment-no-default/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/multi-url-environment-no-default/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/multi-url-environment-no-default/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/multi-url-environment-reference/src/seed/core/jsonable_encoder.py b/seed/python-sdk/multi-url-environment-reference/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/multi-url-environment-reference/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/multi-url-environment-reference/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/multi-url-environment/src/seed/core/jsonable_encoder.py b/seed/python-sdk/multi-url-environment/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/multi-url-environment/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/multi-url-environment/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/multiple-request-bodies/src/seed/core/jsonable_encoder.py b/seed/python-sdk/multiple-request-bodies/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/multiple-request-bodies/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/multiple-request-bodies/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/no-content-response/src/seed/core/jsonable_encoder.py b/seed/python-sdk/no-content-response/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/no-content-response/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/no-content-response/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/no-environment/src/seed/core/jsonable_encoder.py b/seed/python-sdk/no-environment/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/no-environment/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/no-environment/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/no-retries/src/seed/core/jsonable_encoder.py b/seed/python-sdk/no-retries/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/no-retries/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/no-retries/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/null-type/src/seed/core/jsonable_encoder.py b/seed/python-sdk/null-type/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/null-type/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/null-type/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/nullable-allof-extends/src/seed/core/jsonable_encoder.py b/seed/python-sdk/nullable-allof-extends/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/nullable-allof-extends/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/nullable-allof-extends/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/nullable-optional/src/seed/core/jsonable_encoder.py b/seed/python-sdk/nullable-optional/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/nullable-optional/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/nullable-optional/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/nullable-request-body/src/seed/core/jsonable_encoder.py b/seed/python-sdk/nullable-request-body/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/nullable-request-body/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/nullable-request-body/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/nullable/no-custom-config/src/seed/core/jsonable_encoder.py b/seed/python-sdk/nullable/no-custom-config/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/nullable/no-custom-config/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/nullable/no-custom-config/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/nullable/use-typeddict-requests/src/seed/core/jsonable_encoder.py b/seed/python-sdk/nullable/use-typeddict-requests/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/nullable/use-typeddict-requests/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/nullable/use-typeddict-requests/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/oauth-client-credentials-custom/src/seed/core/jsonable_encoder.py b/seed/python-sdk/oauth-client-credentials-custom/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/oauth-client-credentials-custom/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/oauth-client-credentials-custom/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/oauth-client-credentials-default/src/seed/core/jsonable_encoder.py b/seed/python-sdk/oauth-client-credentials-default/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/oauth-client-credentials-default/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/oauth-client-credentials-default/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/oauth-client-credentials-environment-variables/src/seed/core/jsonable_encoder.py b/seed/python-sdk/oauth-client-credentials-environment-variables/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/oauth-client-credentials-environment-variables/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/oauth-client-credentials-environment-variables/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/oauth-client-credentials-mandatory-auth/no-custom-config/src/seed/core/jsonable_encoder.py b/seed/python-sdk/oauth-client-credentials-mandatory-auth/no-custom-config/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/oauth-client-credentials-mandatory-auth/no-custom-config/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/oauth-client-credentials-mandatory-auth/no-custom-config/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/oauth-client-credentials-nested-root/src/seed/core/jsonable_encoder.py b/seed/python-sdk/oauth-client-credentials-nested-root/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/oauth-client-credentials-nested-root/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/oauth-client-credentials-nested-root/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/oauth-client-credentials-openapi/src/seed/core/jsonable_encoder.py b/seed/python-sdk/oauth-client-credentials-openapi/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/oauth-client-credentials-openapi/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/oauth-client-credentials-openapi/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/oauth-client-credentials-reference/src/seed/core/jsonable_encoder.py b/seed/python-sdk/oauth-client-credentials-reference/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/oauth-client-credentials-reference/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/oauth-client-credentials-reference/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/oauth-client-credentials-with-variables/src/seed/core/jsonable_encoder.py b/seed/python-sdk/oauth-client-credentials-with-variables/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/oauth-client-credentials-with-variables/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/oauth-client-credentials-with-variables/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/oauth-client-credentials/src/seed/core/jsonable_encoder.py b/seed/python-sdk/oauth-client-credentials/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/oauth-client-credentials/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/oauth-client-credentials/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/oauth-pkce/wire-tests/src/seed/core/jsonable_encoder.py b/seed/python-sdk/oauth-pkce/wire-tests/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/oauth-pkce/wire-tests/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/oauth-pkce/wire-tests/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/object/src/seed/core/jsonable_encoder.py b/seed/python-sdk/object/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/object/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/object/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/objects-with-imports/src/seed/core/jsonable_encoder.py b/seed/python-sdk/objects-with-imports/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/objects-with-imports/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/objects-with-imports/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/openapi-path-param-body-collision/src/seed/core/jsonable_encoder.py b/seed/python-sdk/openapi-path-param-body-collision/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/openapi-path-param-body-collision/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/openapi-path-param-body-collision/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/openapi-request-body-ref/src/seed/core/jsonable_encoder.py b/seed/python-sdk/openapi-request-body-ref/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/openapi-request-body-ref/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/openapi-request-body-ref/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/openapi-subtitle/src/seed/core/jsonable_encoder.py b/seed/python-sdk/openapi-subtitle/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/openapi-subtitle/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/openapi-subtitle/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/optional/src/seed/core/jsonable_encoder.py b/seed/python-sdk/optional/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/optional/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/optional/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/package-yml/src/seed/core/jsonable_encoder.py b/seed/python-sdk/package-yml/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/package-yml/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/package-yml/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/pagination-custom/src/seed/core/jsonable_encoder.py b/seed/python-sdk/pagination-custom/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/pagination-custom/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/pagination-custom/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/pagination-uri-path/src/seed/core/jsonable_encoder.py b/seed/python-sdk/pagination-uri-path/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/pagination-uri-path/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/pagination-uri-path/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/pagination/no-custom-config/src/seed/core/jsonable_encoder.py b/seed/python-sdk/pagination/no-custom-config/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/pagination/no-custom-config/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/pagination/no-custom-config/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/pagination/no-inheritance-for-extended-models/src/seed/core/jsonable_encoder.py b/seed/python-sdk/pagination/no-inheritance-for-extended-models/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/pagination/no-inheritance-for-extended-models/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/pagination/no-inheritance-for-extended-models/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/pagination/page-index-semantics/src/seed/core/jsonable_encoder.py b/seed/python-sdk/pagination/page-index-semantics/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/pagination/page-index-semantics/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/pagination/page-index-semantics/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/path-parameters/encode-path-params/.fern/metadata.json b/seed/python-sdk/path-parameters/encode-path-params/.fern/metadata.json
new file mode 100644
index 000000000000..9e57d4571337
--- /dev/null
+++ b/seed/python-sdk/path-parameters/encode-path-params/.fern/metadata.json
@@ -0,0 +1,13 @@
+{
+ "cliVersion": "DUMMY",
+ "generatorName": "fernapi/fern-python-sdk",
+ "generatorVersion": "local",
+ "generatorConfig": {
+ "encode_path_params": true
+ },
+ "originGitCommit": "DUMMY",
+ "invokedBy": "ci",
+ "requestedVersion": "0.0.1",
+ "ciProvider": "github",
+ "sdkVersion": "0.0.1"
+}
\ No newline at end of file
diff --git a/seed/python-sdk/path-parameters/.github/workflows/ci.yml b/seed/python-sdk/path-parameters/encode-path-params/.github/workflows/ci.yml
similarity index 100%
rename from seed/python-sdk/path-parameters/.github/workflows/ci.yml
rename to seed/python-sdk/path-parameters/encode-path-params/.github/workflows/ci.yml
diff --git a/seed/python-sdk/path-parameters/.gitignore b/seed/python-sdk/path-parameters/encode-path-params/.gitignore
similarity index 100%
rename from seed/python-sdk/path-parameters/.gitignore
rename to seed/python-sdk/path-parameters/encode-path-params/.gitignore
diff --git a/seed/python-sdk/path-parameters/CONTRIBUTING.md b/seed/python-sdk/path-parameters/encode-path-params/CONTRIBUTING.md
similarity index 100%
rename from seed/python-sdk/path-parameters/CONTRIBUTING.md
rename to seed/python-sdk/path-parameters/encode-path-params/CONTRIBUTING.md
diff --git a/seed/python-sdk/path-parameters/README.md b/seed/python-sdk/path-parameters/encode-path-params/README.md
similarity index 100%
rename from seed/python-sdk/path-parameters/README.md
rename to seed/python-sdk/path-parameters/encode-path-params/README.md
diff --git a/seed/python-sdk/path-parameters/poetry.lock b/seed/python-sdk/path-parameters/encode-path-params/poetry.lock
similarity index 100%
rename from seed/python-sdk/path-parameters/poetry.lock
rename to seed/python-sdk/path-parameters/encode-path-params/poetry.lock
diff --git a/seed/python-sdk/path-parameters/pyproject.toml b/seed/python-sdk/path-parameters/encode-path-params/pyproject.toml
similarity index 100%
rename from seed/python-sdk/path-parameters/pyproject.toml
rename to seed/python-sdk/path-parameters/encode-path-params/pyproject.toml
diff --git a/seed/python-sdk/path-parameters/reference.md b/seed/python-sdk/path-parameters/encode-path-params/reference.md
similarity index 100%
rename from seed/python-sdk/path-parameters/reference.md
rename to seed/python-sdk/path-parameters/encode-path-params/reference.md
diff --git a/seed/python-sdk/path-parameters/requirements.txt b/seed/python-sdk/path-parameters/encode-path-params/requirements.txt
similarity index 100%
rename from seed/python-sdk/path-parameters/requirements.txt
rename to seed/python-sdk/path-parameters/encode-path-params/requirements.txt
diff --git a/seed/python-sdk/path-parameters/snippet.json b/seed/python-sdk/path-parameters/encode-path-params/snippet.json
similarity index 100%
rename from seed/python-sdk/path-parameters/snippet.json
rename to seed/python-sdk/path-parameters/encode-path-params/snippet.json
diff --git a/seed/python-sdk/path-parameters/src/seed/__init__.py b/seed/python-sdk/path-parameters/encode-path-params/src/seed/__init__.py
similarity index 100%
rename from seed/python-sdk/path-parameters/src/seed/__init__.py
rename to seed/python-sdk/path-parameters/encode-path-params/src/seed/__init__.py
diff --git a/seed/python-sdk/path-parameters/src/seed/_default_clients.py b/seed/python-sdk/path-parameters/encode-path-params/src/seed/_default_clients.py
similarity index 100%
rename from seed/python-sdk/path-parameters/src/seed/_default_clients.py
rename to seed/python-sdk/path-parameters/encode-path-params/src/seed/_default_clients.py
diff --git a/seed/python-sdk/path-parameters/src/seed/client.py b/seed/python-sdk/path-parameters/encode-path-params/src/seed/client.py
similarity index 100%
rename from seed/python-sdk/path-parameters/src/seed/client.py
rename to seed/python-sdk/path-parameters/encode-path-params/src/seed/client.py
diff --git a/seed/python-sdk/path-parameters/src/seed/core/__init__.py b/seed/python-sdk/path-parameters/encode-path-params/src/seed/core/__init__.py
similarity index 100%
rename from seed/python-sdk/path-parameters/src/seed/core/__init__.py
rename to seed/python-sdk/path-parameters/encode-path-params/src/seed/core/__init__.py
diff --git a/seed/python-sdk/path-parameters/src/seed/core/api_error.py b/seed/python-sdk/path-parameters/encode-path-params/src/seed/core/api_error.py
similarity index 100%
rename from seed/python-sdk/path-parameters/src/seed/core/api_error.py
rename to seed/python-sdk/path-parameters/encode-path-params/src/seed/core/api_error.py
diff --git a/seed/python-sdk/path-parameters/src/seed/core/client_wrapper.py b/seed/python-sdk/path-parameters/encode-path-params/src/seed/core/client_wrapper.py
similarity index 100%
rename from seed/python-sdk/path-parameters/src/seed/core/client_wrapper.py
rename to seed/python-sdk/path-parameters/encode-path-params/src/seed/core/client_wrapper.py
diff --git a/seed/python-sdk/path-parameters/src/seed/core/datetime_utils.py b/seed/python-sdk/path-parameters/encode-path-params/src/seed/core/datetime_utils.py
similarity index 100%
rename from seed/python-sdk/path-parameters/src/seed/core/datetime_utils.py
rename to seed/python-sdk/path-parameters/encode-path-params/src/seed/core/datetime_utils.py
diff --git a/seed/python-sdk/path-parameters/src/seed/core/file.py b/seed/python-sdk/path-parameters/encode-path-params/src/seed/core/file.py
similarity index 100%
rename from seed/python-sdk/path-parameters/src/seed/core/file.py
rename to seed/python-sdk/path-parameters/encode-path-params/src/seed/core/file.py
diff --git a/seed/python-sdk/path-parameters/src/seed/core/force_multipart.py b/seed/python-sdk/path-parameters/encode-path-params/src/seed/core/force_multipart.py
similarity index 100%
rename from seed/python-sdk/path-parameters/src/seed/core/force_multipart.py
rename to seed/python-sdk/path-parameters/encode-path-params/src/seed/core/force_multipart.py
diff --git a/seed/python-sdk/path-parameters/src/seed/core/http_client.py b/seed/python-sdk/path-parameters/encode-path-params/src/seed/core/http_client.py
similarity index 100%
rename from seed/python-sdk/path-parameters/src/seed/core/http_client.py
rename to seed/python-sdk/path-parameters/encode-path-params/src/seed/core/http_client.py
diff --git a/seed/python-sdk/path-parameters/src/seed/core/http_response.py b/seed/python-sdk/path-parameters/encode-path-params/src/seed/core/http_response.py
similarity index 100%
rename from seed/python-sdk/path-parameters/src/seed/core/http_response.py
rename to seed/python-sdk/path-parameters/encode-path-params/src/seed/core/http_response.py
diff --git a/seed/python-sdk/path-parameters/src/seed/core/http_sse/__init__.py b/seed/python-sdk/path-parameters/encode-path-params/src/seed/core/http_sse/__init__.py
similarity index 100%
rename from seed/python-sdk/path-parameters/src/seed/core/http_sse/__init__.py
rename to seed/python-sdk/path-parameters/encode-path-params/src/seed/core/http_sse/__init__.py
diff --git a/seed/python-sdk/path-parameters/src/seed/core/http_sse/_api.py b/seed/python-sdk/path-parameters/encode-path-params/src/seed/core/http_sse/_api.py
similarity index 100%
rename from seed/python-sdk/path-parameters/src/seed/core/http_sse/_api.py
rename to seed/python-sdk/path-parameters/encode-path-params/src/seed/core/http_sse/_api.py
diff --git a/seed/python-sdk/path-parameters/src/seed/core/http_sse/_decoders.py b/seed/python-sdk/path-parameters/encode-path-params/src/seed/core/http_sse/_decoders.py
similarity index 100%
rename from seed/python-sdk/path-parameters/src/seed/core/http_sse/_decoders.py
rename to seed/python-sdk/path-parameters/encode-path-params/src/seed/core/http_sse/_decoders.py
diff --git a/seed/python-sdk/path-parameters/src/seed/core/http_sse/_exceptions.py b/seed/python-sdk/path-parameters/encode-path-params/src/seed/core/http_sse/_exceptions.py
similarity index 100%
rename from seed/python-sdk/path-parameters/src/seed/core/http_sse/_exceptions.py
rename to seed/python-sdk/path-parameters/encode-path-params/src/seed/core/http_sse/_exceptions.py
diff --git a/seed/python-sdk/path-parameters/src/seed/core/http_sse/_models.py b/seed/python-sdk/path-parameters/encode-path-params/src/seed/core/http_sse/_models.py
similarity index 100%
rename from seed/python-sdk/path-parameters/src/seed/core/http_sse/_models.py
rename to seed/python-sdk/path-parameters/encode-path-params/src/seed/core/http_sse/_models.py
diff --git a/seed/python-sdk/path-parameters/src/seed/core/jsonable_encoder.py b/seed/python-sdk/path-parameters/encode-path-params/src/seed/core/jsonable_encoder.py
similarity index 90%
rename from seed/python-sdk/path-parameters/src/seed/core/jsonable_encoder.py
rename to seed/python-sdk/path-parameters/encode-path-params/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/path-parameters/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/path-parameters/encode-path-params/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/path-parameters/src/seed/core/logging.py b/seed/python-sdk/path-parameters/encode-path-params/src/seed/core/logging.py
similarity index 100%
rename from seed/python-sdk/path-parameters/src/seed/core/logging.py
rename to seed/python-sdk/path-parameters/encode-path-params/src/seed/core/logging.py
diff --git a/seed/python-sdk/path-parameters/src/seed/core/parse_error.py b/seed/python-sdk/path-parameters/encode-path-params/src/seed/core/parse_error.py
similarity index 100%
rename from seed/python-sdk/path-parameters/src/seed/core/parse_error.py
rename to seed/python-sdk/path-parameters/encode-path-params/src/seed/core/parse_error.py
diff --git a/seed/python-sdk/path-parameters/src/seed/core/pydantic_utilities.py b/seed/python-sdk/path-parameters/encode-path-params/src/seed/core/pydantic_utilities.py
similarity index 100%
rename from seed/python-sdk/path-parameters/src/seed/core/pydantic_utilities.py
rename to seed/python-sdk/path-parameters/encode-path-params/src/seed/core/pydantic_utilities.py
diff --git a/seed/python-sdk/path-parameters/src/seed/core/query_encoder.py b/seed/python-sdk/path-parameters/encode-path-params/src/seed/core/query_encoder.py
similarity index 100%
rename from seed/python-sdk/path-parameters/src/seed/core/query_encoder.py
rename to seed/python-sdk/path-parameters/encode-path-params/src/seed/core/query_encoder.py
diff --git a/seed/python-sdk/path-parameters/src/seed/core/remove_none_from_dict.py b/seed/python-sdk/path-parameters/encode-path-params/src/seed/core/remove_none_from_dict.py
similarity index 100%
rename from seed/python-sdk/path-parameters/src/seed/core/remove_none_from_dict.py
rename to seed/python-sdk/path-parameters/encode-path-params/src/seed/core/remove_none_from_dict.py
diff --git a/seed/python-sdk/path-parameters/src/seed/core/request_options.py b/seed/python-sdk/path-parameters/encode-path-params/src/seed/core/request_options.py
similarity index 100%
rename from seed/python-sdk/path-parameters/src/seed/core/request_options.py
rename to seed/python-sdk/path-parameters/encode-path-params/src/seed/core/request_options.py
diff --git a/seed/python-sdk/path-parameters/src/seed/core/serialization.py b/seed/python-sdk/path-parameters/encode-path-params/src/seed/core/serialization.py
similarity index 100%
rename from seed/python-sdk/path-parameters/src/seed/core/serialization.py
rename to seed/python-sdk/path-parameters/encode-path-params/src/seed/core/serialization.py
diff --git a/seed/python-sdk/path-parameters/src/seed/organizations/__init__.py b/seed/python-sdk/path-parameters/encode-path-params/src/seed/organizations/__init__.py
similarity index 100%
rename from seed/python-sdk/path-parameters/src/seed/organizations/__init__.py
rename to seed/python-sdk/path-parameters/encode-path-params/src/seed/organizations/__init__.py
diff --git a/seed/python-sdk/path-parameters/src/seed/organizations/client.py b/seed/python-sdk/path-parameters/encode-path-params/src/seed/organizations/client.py
similarity index 100%
rename from seed/python-sdk/path-parameters/src/seed/organizations/client.py
rename to seed/python-sdk/path-parameters/encode-path-params/src/seed/organizations/client.py
diff --git a/seed/python-sdk/path-parameters/encode-path-params/src/seed/organizations/raw_client.py b/seed/python-sdk/path-parameters/encode-path-params/src/seed/organizations/raw_client.py
new file mode 100644
index 000000000000..af29884eac12
--- /dev/null
+++ b/seed/python-sdk/path-parameters/encode-path-params/src/seed/organizations/raw_client.py
@@ -0,0 +1,281 @@
+# 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 quote_path_param
+from ..core.parse_error import ParsingError
+from ..core.pydantic_utilities import parse_obj_as
+from ..core.request_options import RequestOptions
+from ..user.types.user import User
+from .types.organization import Organization
+from pydantic import ValidationError
+
+
+class RawOrganizationsClient:
+ def __init__(self, *, client_wrapper: SyncClientWrapper):
+ self._client_wrapper = client_wrapper
+
+ def get_organization(
+ self, organization_id: str, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> HttpResponse[Organization]:
+ """
+ Parameters
+ ----------
+ organization_id : str
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[Organization]
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"{quote_path_param(self._client_wrapper._tenant_id)}/organizations/{quote_path_param(organization_id)}/",
+ method="GET",
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ Organization,
+ parse_obj_as(
+ type_=Organization, # 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 get_organization_user(
+ self, organization_id: str, user_id: str, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> HttpResponse[User]:
+ """
+ Parameters
+ ----------
+ organization_id : str
+
+ user_id : str
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[User]
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"{quote_path_param(self._client_wrapper._tenant_id)}/organizations/{quote_path_param(organization_id)}/users/{quote_path_param(user_id)}",
+ method="GET",
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ User,
+ parse_obj_as(
+ type_=User, # 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 search_organizations(
+ self,
+ organization_id: str,
+ *,
+ limit: typing.Optional[int] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> HttpResponse[typing.List[Organization]]:
+ """
+ Parameters
+ ----------
+ organization_id : str
+
+ limit : typing.Optional[int]
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[typing.List[Organization]]
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"{quote_path_param(self._client_wrapper._tenant_id)}/organizations/{quote_path_param(organization_id)}/search",
+ method="GET",
+ params={
+ "limit": limit,
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ typing.List[Organization],
+ parse_obj_as(
+ type_=typing.List[Organization], # 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 AsyncRawOrganizationsClient:
+ def __init__(self, *, client_wrapper: AsyncClientWrapper):
+ self._client_wrapper = client_wrapper
+
+ async def get_organization(
+ self, organization_id: str, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> AsyncHttpResponse[Organization]:
+ """
+ Parameters
+ ----------
+ organization_id : str
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[Organization]
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"{quote_path_param(self._client_wrapper._tenant_id)}/organizations/{quote_path_param(organization_id)}/",
+ method="GET",
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ Organization,
+ parse_obj_as(
+ type_=Organization, # 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 get_organization_user(
+ self, organization_id: str, user_id: str, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> AsyncHttpResponse[User]:
+ """
+ Parameters
+ ----------
+ organization_id : str
+
+ user_id : str
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[User]
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"{quote_path_param(self._client_wrapper._tenant_id)}/organizations/{quote_path_param(organization_id)}/users/{quote_path_param(user_id)}",
+ method="GET",
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ User,
+ parse_obj_as(
+ type_=User, # 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 search_organizations(
+ self,
+ organization_id: str,
+ *,
+ limit: typing.Optional[int] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AsyncHttpResponse[typing.List[Organization]]:
+ """
+ Parameters
+ ----------
+ organization_id : str
+
+ limit : typing.Optional[int]
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[typing.List[Organization]]
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"{quote_path_param(self._client_wrapper._tenant_id)}/organizations/{quote_path_param(organization_id)}/search",
+ method="GET",
+ params={
+ "limit": limit,
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ typing.List[Organization],
+ parse_obj_as(
+ type_=typing.List[Organization], # 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/path-parameters/src/seed/organizations/types/__init__.py b/seed/python-sdk/path-parameters/encode-path-params/src/seed/organizations/types/__init__.py
similarity index 100%
rename from seed/python-sdk/path-parameters/src/seed/organizations/types/__init__.py
rename to seed/python-sdk/path-parameters/encode-path-params/src/seed/organizations/types/__init__.py
diff --git a/seed/python-sdk/path-parameters/src/seed/organizations/types/organization.py b/seed/python-sdk/path-parameters/encode-path-params/src/seed/organizations/types/organization.py
similarity index 100%
rename from seed/python-sdk/path-parameters/src/seed/organizations/types/organization.py
rename to seed/python-sdk/path-parameters/encode-path-params/src/seed/organizations/types/organization.py
diff --git a/seed/python-sdk/path-parameters/src/seed/py.typed b/seed/python-sdk/path-parameters/encode-path-params/src/seed/py.typed
similarity index 100%
rename from seed/python-sdk/path-parameters/src/seed/py.typed
rename to seed/python-sdk/path-parameters/encode-path-params/src/seed/py.typed
diff --git a/seed/python-sdk/path-parameters/src/seed/user/__init__.py b/seed/python-sdk/path-parameters/encode-path-params/src/seed/user/__init__.py
similarity index 100%
rename from seed/python-sdk/path-parameters/src/seed/user/__init__.py
rename to seed/python-sdk/path-parameters/encode-path-params/src/seed/user/__init__.py
diff --git a/seed/python-sdk/path-parameters/src/seed/user/client.py b/seed/python-sdk/path-parameters/encode-path-params/src/seed/user/client.py
similarity index 100%
rename from seed/python-sdk/path-parameters/src/seed/user/client.py
rename to seed/python-sdk/path-parameters/encode-path-params/src/seed/user/client.py
diff --git a/seed/python-sdk/path-parameters/encode-path-params/src/seed/user/raw_client.py b/seed/python-sdk/path-parameters/encode-path-params/src/seed/user/raw_client.py
new file mode 100644
index 000000000000..0b3cac2fa846
--- /dev/null
+++ b/seed/python-sdk/path-parameters/encode-path-params/src/seed/user/raw_client.py
@@ -0,0 +1,573 @@
+# 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 quote_path_param
+from ..core.parse_error import ParsingError
+from ..core.pydantic_utilities import parse_obj_as
+from ..core.request_options import RequestOptions
+from .types.user import User
+from pydantic import ValidationError
+
+# this is used as the default value for optional parameters
+OMIT = typing.cast(typing.Any, ...)
+
+
+class RawUserClient:
+ def __init__(self, *, client_wrapper: SyncClientWrapper):
+ self._client_wrapper = client_wrapper
+
+ def get_user(self, user_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> HttpResponse[User]:
+ """
+ Parameters
+ ----------
+ user_id : str
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[User]
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"{quote_path_param(self._client_wrapper._tenant_id)}/user/{quote_path_param(user_id)}",
+ method="GET",
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ User,
+ parse_obj_as(
+ type_=User, # 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 create_user(
+ self, *, name: str, tags: typing.Sequence[str], request_options: typing.Optional[RequestOptions] = None
+ ) -> HttpResponse[User]:
+ """
+ Parameters
+ ----------
+ name : str
+
+ tags : typing.Sequence[str]
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[User]
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"{quote_path_param(self._client_wrapper._tenant_id)}/user/",
+ method="POST",
+ json={
+ "name": name,
+ "tags": tags,
+ },
+ request_options=request_options,
+ omit=OMIT,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ User,
+ parse_obj_as(
+ type_=User, # 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_user(
+ self,
+ user_id: str,
+ *,
+ name: str,
+ tags: typing.Sequence[str],
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> HttpResponse[User]:
+ """
+ Parameters
+ ----------
+ user_id : str
+
+ name : str
+
+ tags : typing.Sequence[str]
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[User]
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"{quote_path_param(self._client_wrapper._tenant_id)}/user/{quote_path_param(user_id)}",
+ method="PATCH",
+ json={
+ "name": name,
+ "tags": tags,
+ },
+ request_options=request_options,
+ omit=OMIT,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ User,
+ parse_obj_as(
+ type_=User, # 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 search_users(
+ self,
+ user_id: str,
+ *,
+ limit: typing.Optional[int] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> HttpResponse[typing.List[User]]:
+ """
+ Parameters
+ ----------
+ user_id : str
+
+ limit : typing.Optional[int]
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[typing.List[User]]
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"{quote_path_param(self._client_wrapper._tenant_id)}/user/{quote_path_param(user_id)}/search",
+ method="GET",
+ params={
+ "limit": limit,
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ typing.List[User],
+ parse_obj_as(
+ type_=typing.List[User], # 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 get_user_metadata(
+ self, user_id: str, version: int, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> HttpResponse[User]:
+ """
+ Test endpoint with path parameter that has a text prefix (v{version})
+
+ Parameters
+ ----------
+ user_id : str
+
+ version : int
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[User]
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"{quote_path_param(self._client_wrapper._tenant_id)}/user/{quote_path_param(user_id)}/metadata/v{quote_path_param(version)}",
+ method="GET",
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ User,
+ parse_obj_as(
+ type_=User, # 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 get_user_specifics(
+ self, user_id: str, version: int, thought: str, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> HttpResponse[User]:
+ """
+ Test endpoint with path parameters listed in different order than found in path
+
+ Parameters
+ ----------
+ user_id : str
+
+ version : int
+
+ thought : str
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[User]
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"{quote_path_param(self._client_wrapper._tenant_id)}/user/{quote_path_param(user_id)}/specifics/{quote_path_param(version)}/{quote_path_param(thought)}",
+ method="GET",
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ User,
+ parse_obj_as(
+ type_=User, # 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 AsyncRawUserClient:
+ def __init__(self, *, client_wrapper: AsyncClientWrapper):
+ self._client_wrapper = client_wrapper
+
+ async def get_user(
+ self, user_id: str, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> AsyncHttpResponse[User]:
+ """
+ Parameters
+ ----------
+ user_id : str
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[User]
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"{quote_path_param(self._client_wrapper._tenant_id)}/user/{quote_path_param(user_id)}",
+ method="GET",
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ User,
+ parse_obj_as(
+ type_=User, # 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 create_user(
+ self, *, name: str, tags: typing.Sequence[str], request_options: typing.Optional[RequestOptions] = None
+ ) -> AsyncHttpResponse[User]:
+ """
+ Parameters
+ ----------
+ name : str
+
+ tags : typing.Sequence[str]
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[User]
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"{quote_path_param(self._client_wrapper._tenant_id)}/user/",
+ method="POST",
+ json={
+ "name": name,
+ "tags": tags,
+ },
+ request_options=request_options,
+ omit=OMIT,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ User,
+ parse_obj_as(
+ type_=User, # 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_user(
+ self,
+ user_id: str,
+ *,
+ name: str,
+ tags: typing.Sequence[str],
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AsyncHttpResponse[User]:
+ """
+ Parameters
+ ----------
+ user_id : str
+
+ name : str
+
+ tags : typing.Sequence[str]
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[User]
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"{quote_path_param(self._client_wrapper._tenant_id)}/user/{quote_path_param(user_id)}",
+ method="PATCH",
+ json={
+ "name": name,
+ "tags": tags,
+ },
+ request_options=request_options,
+ omit=OMIT,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ User,
+ parse_obj_as(
+ type_=User, # 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 search_users(
+ self,
+ user_id: str,
+ *,
+ limit: typing.Optional[int] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AsyncHttpResponse[typing.List[User]]:
+ """
+ Parameters
+ ----------
+ user_id : str
+
+ limit : typing.Optional[int]
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[typing.List[User]]
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"{quote_path_param(self._client_wrapper._tenant_id)}/user/{quote_path_param(user_id)}/search",
+ method="GET",
+ params={
+ "limit": limit,
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ typing.List[User],
+ parse_obj_as(
+ type_=typing.List[User], # 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 get_user_metadata(
+ self, user_id: str, version: int, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> AsyncHttpResponse[User]:
+ """
+ Test endpoint with path parameter that has a text prefix (v{version})
+
+ Parameters
+ ----------
+ user_id : str
+
+ version : int
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[User]
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"{quote_path_param(self._client_wrapper._tenant_id)}/user/{quote_path_param(user_id)}/metadata/v{quote_path_param(version)}",
+ method="GET",
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ User,
+ parse_obj_as(
+ type_=User, # 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 get_user_specifics(
+ self, user_id: str, version: int, thought: str, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> AsyncHttpResponse[User]:
+ """
+ Test endpoint with path parameters listed in different order than found in path
+
+ Parameters
+ ----------
+ user_id : str
+
+ version : int
+
+ thought : str
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[User]
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"{quote_path_param(self._client_wrapper._tenant_id)}/user/{quote_path_param(user_id)}/specifics/{quote_path_param(version)}/{quote_path_param(thought)}",
+ method="GET",
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ User,
+ parse_obj_as(
+ type_=User, # 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/path-parameters/src/seed/user/types/__init__.py b/seed/python-sdk/path-parameters/encode-path-params/src/seed/user/types/__init__.py
similarity index 100%
rename from seed/python-sdk/path-parameters/src/seed/user/types/__init__.py
rename to seed/python-sdk/path-parameters/encode-path-params/src/seed/user/types/__init__.py
diff --git a/seed/python-sdk/path-parameters/src/seed/user/types/user.py b/seed/python-sdk/path-parameters/encode-path-params/src/seed/user/types/user.py
similarity index 100%
rename from seed/python-sdk/path-parameters/src/seed/user/types/user.py
rename to seed/python-sdk/path-parameters/encode-path-params/src/seed/user/types/user.py
diff --git a/seed/python-sdk/path-parameters/src/seed/version.py b/seed/python-sdk/path-parameters/encode-path-params/src/seed/version.py
similarity index 100%
rename from seed/python-sdk/path-parameters/src/seed/version.py
rename to seed/python-sdk/path-parameters/encode-path-params/src/seed/version.py
diff --git a/seed/python-sdk/path-parameters/tests/conftest.py b/seed/python-sdk/path-parameters/encode-path-params/tests/conftest.py
similarity index 100%
rename from seed/python-sdk/path-parameters/tests/conftest.py
rename to seed/python-sdk/path-parameters/encode-path-params/tests/conftest.py
diff --git a/seed/python-sdk/path-parameters/tests/custom/test_client.py b/seed/python-sdk/path-parameters/encode-path-params/tests/custom/test_client.py
similarity index 100%
rename from seed/python-sdk/path-parameters/tests/custom/test_client.py
rename to seed/python-sdk/path-parameters/encode-path-params/tests/custom/test_client.py
diff --git a/seed/python-sdk/path-parameters/tests/test_aiohttp_autodetect.py b/seed/python-sdk/path-parameters/encode-path-params/tests/test_aiohttp_autodetect.py
similarity index 100%
rename from seed/python-sdk/path-parameters/tests/test_aiohttp_autodetect.py
rename to seed/python-sdk/path-parameters/encode-path-params/tests/test_aiohttp_autodetect.py
diff --git a/seed/python-sdk/path-parameters/tests/utils/__init__.py b/seed/python-sdk/path-parameters/encode-path-params/tests/utils/__init__.py
similarity index 100%
rename from seed/python-sdk/path-parameters/tests/utils/__init__.py
rename to seed/python-sdk/path-parameters/encode-path-params/tests/utils/__init__.py
diff --git a/seed/python-sdk/path-parameters/tests/utils/assets/models/__init__.py b/seed/python-sdk/path-parameters/encode-path-params/tests/utils/assets/models/__init__.py
similarity index 100%
rename from seed/python-sdk/path-parameters/tests/utils/assets/models/__init__.py
rename to seed/python-sdk/path-parameters/encode-path-params/tests/utils/assets/models/__init__.py
diff --git a/seed/python-sdk/path-parameters/tests/utils/assets/models/circle.py b/seed/python-sdk/path-parameters/encode-path-params/tests/utils/assets/models/circle.py
similarity index 100%
rename from seed/python-sdk/path-parameters/tests/utils/assets/models/circle.py
rename to seed/python-sdk/path-parameters/encode-path-params/tests/utils/assets/models/circle.py
diff --git a/seed/python-sdk/path-parameters/tests/utils/assets/models/color.py b/seed/python-sdk/path-parameters/encode-path-params/tests/utils/assets/models/color.py
similarity index 100%
rename from seed/python-sdk/path-parameters/tests/utils/assets/models/color.py
rename to seed/python-sdk/path-parameters/encode-path-params/tests/utils/assets/models/color.py
diff --git a/seed/python-sdk/path-parameters/tests/utils/assets/models/object_with_defaults.py b/seed/python-sdk/path-parameters/encode-path-params/tests/utils/assets/models/object_with_defaults.py
similarity index 100%
rename from seed/python-sdk/path-parameters/tests/utils/assets/models/object_with_defaults.py
rename to seed/python-sdk/path-parameters/encode-path-params/tests/utils/assets/models/object_with_defaults.py
diff --git a/seed/python-sdk/path-parameters/tests/utils/assets/models/object_with_optional_field.py b/seed/python-sdk/path-parameters/encode-path-params/tests/utils/assets/models/object_with_optional_field.py
similarity index 100%
rename from seed/python-sdk/path-parameters/tests/utils/assets/models/object_with_optional_field.py
rename to seed/python-sdk/path-parameters/encode-path-params/tests/utils/assets/models/object_with_optional_field.py
diff --git a/seed/python-sdk/path-parameters/tests/utils/assets/models/shape.py b/seed/python-sdk/path-parameters/encode-path-params/tests/utils/assets/models/shape.py
similarity index 100%
rename from seed/python-sdk/path-parameters/tests/utils/assets/models/shape.py
rename to seed/python-sdk/path-parameters/encode-path-params/tests/utils/assets/models/shape.py
diff --git a/seed/python-sdk/path-parameters/tests/utils/assets/models/square.py b/seed/python-sdk/path-parameters/encode-path-params/tests/utils/assets/models/square.py
similarity index 100%
rename from seed/python-sdk/path-parameters/tests/utils/assets/models/square.py
rename to seed/python-sdk/path-parameters/encode-path-params/tests/utils/assets/models/square.py
diff --git a/seed/python-sdk/path-parameters/tests/utils/assets/models/undiscriminated_shape.py b/seed/python-sdk/path-parameters/encode-path-params/tests/utils/assets/models/undiscriminated_shape.py
similarity index 100%
rename from seed/python-sdk/path-parameters/tests/utils/assets/models/undiscriminated_shape.py
rename to seed/python-sdk/path-parameters/encode-path-params/tests/utils/assets/models/undiscriminated_shape.py
diff --git a/seed/python-sdk/path-parameters/tests/utils/test_http_client.py b/seed/python-sdk/path-parameters/encode-path-params/tests/utils/test_http_client.py
similarity index 100%
rename from seed/python-sdk/path-parameters/tests/utils/test_http_client.py
rename to seed/python-sdk/path-parameters/encode-path-params/tests/utils/test_http_client.py
diff --git a/seed/python-sdk/path-parameters/tests/utils/test_query_encoding.py b/seed/python-sdk/path-parameters/encode-path-params/tests/utils/test_query_encoding.py
similarity index 100%
rename from seed/python-sdk/path-parameters/tests/utils/test_query_encoding.py
rename to seed/python-sdk/path-parameters/encode-path-params/tests/utils/test_query_encoding.py
diff --git a/seed/python-sdk/path-parameters/tests/utils/test_serialization.py b/seed/python-sdk/path-parameters/encode-path-params/tests/utils/test_serialization.py
similarity index 100%
rename from seed/python-sdk/path-parameters/tests/utils/test_serialization.py
rename to seed/python-sdk/path-parameters/encode-path-params/tests/utils/test_serialization.py
diff --git a/seed/python-sdk/path-parameters/.fern/metadata.json b/seed/python-sdk/path-parameters/no-custom-config/.fern/metadata.json
similarity index 100%
rename from seed/python-sdk/path-parameters/.fern/metadata.json
rename to seed/python-sdk/path-parameters/no-custom-config/.fern/metadata.json
diff --git a/seed/python-sdk/path-parameters/no-custom-config/.github/workflows/ci.yml b/seed/python-sdk/path-parameters/no-custom-config/.github/workflows/ci.yml
new file mode 100644
index 000000000000..4464e975b7ba
--- /dev/null
+++ b/seed/python-sdk/path-parameters/no-custom-config/.github/workflows/ci.yml
@@ -0,0 +1,47 @@
+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 .
diff --git a/seed/python-sdk/path-parameters/no-custom-config/.gitignore b/seed/python-sdk/path-parameters/no-custom-config/.gitignore
new file mode 100644
index 000000000000..d2e4ca808d21
--- /dev/null
+++ b/seed/python-sdk/path-parameters/no-custom-config/.gitignore
@@ -0,0 +1,5 @@
+.mypy_cache/
+.ruff_cache/
+__pycache__/
+dist/
+poetry.toml
diff --git a/seed/python-sdk/path-parameters/no-custom-config/CONTRIBUTING.md b/seed/python-sdk/path-parameters/no-custom-config/CONTRIBUTING.md
new file mode 100644
index 000000000000..af948ce72556
--- /dev/null
+++ b/seed/python-sdk/path-parameters/no-custom-config/CONTRIBUTING.md
@@ -0,0 +1,125 @@
+# Contributing
+
+Thanks for your interest in contributing to this SDK! This document provides guidelines for contributing to the project.
+
+## Getting Started
+
+### Prerequisites
+
+- Python 3.9+
+- pip
+- poetry
+
+### Installation
+
+Install the project dependencies:
+
+```bash
+poetry install
+```
+
+### Building
+
+Build the project:
+
+```bash
+poetry build
+```
+
+### Testing
+
+Run the test suite:
+
+```bash
+poetry run pytest
+```
+
+### Linting and Formatting
+
+Check code style:
+
+```bash
+poetry run ruff check .
+poetry run ruff format .
+```
+
+### Type Checking
+
+Run the type checker:
+
+```bash
+poetry run mypy .
+```
+
+## About Generated Code
+
+**Important**: Most files in this SDK are automatically generated by [Fern](https://buildwithfern.com) from the API definition. Direct modifications to generated files will be overwritten the next time the SDK is generated.
+
+### Generated Files
+
+The following directories contain generated code:
+- `src/` - API client classes and types
+- Most Python files in the project
+
+### How to Customize
+
+If you need to customize the SDK, you have two options:
+
+#### Option 1: Use `.fernignore`
+
+For custom code that should persist across SDK regenerations:
+
+1. Create a `.fernignore` file in the project root
+2. Add file patterns for files you want to preserve (similar to `.gitignore` syntax)
+3. Add your custom code to those files
+
+Files listed in `.fernignore` will not be overwritten when the SDK is regenerated.
+
+For more information, see the [Fern documentation on custom code](https://buildwithfern.com/learn/sdks/overview/custom-code).
+
+#### Option 2: Contribute to the Generator
+
+If you want to change how code is generated for all users of this SDK:
+
+1. The Python SDK generator lives in the [Fern repository](https://github.com/fern-api/fern)
+2. Generator code is located at `generators/python-v2/`
+3. Follow the [Fern contributing guidelines](https://github.com/fern-api/fern/blob/main/CONTRIBUTING.md)
+4. Submit a pull request with your changes to the generator
+
+This approach is best for:
+- Bug fixes in generated code
+- New features that would benefit all users
+- Improvements to code generation patterns
+
+## Making Changes
+
+### Workflow
+
+1. Create a new branch for your changes
+2. Make your modifications
+3. Run tests to ensure nothing breaks: `poetry run pytest`
+4. Run linting and formatting: `poetry run ruff check .` and `poetry run ruff format .`
+5. Run type checking: `poetry run mypy .`
+6. Build the project: `poetry build`
+7. Commit your changes with a clear commit message
+8. Push your branch and create a pull request
+
+### Commit Messages
+
+Write clear, descriptive commit messages that explain what changed and why.
+
+### Code Style
+
+This project uses automated code formatting and linting. Run `poetry run ruff format .` and `poetry run ruff check .` before committing to ensure your code meets the project's style guidelines.
+
+## Questions or Issues?
+
+If you have questions or run into issues:
+
+1. Check the [Fern documentation](https://buildwithfern.com)
+2. Search existing [GitHub issues](https://github.com/fern-api/fern/issues)
+3. Open a new issue if your question hasn't been addressed
+
+## License
+
+By contributing to this project, you agree that your contributions will be licensed under the same license as the project.
diff --git a/seed/python-sdk/path-parameters/no-custom-config/README.md b/seed/python-sdk/path-parameters/no-custom-config/README.md
new file mode 100644
index 000000000000..3ec71131bf96
--- /dev/null
+++ b/seed/python-sdk/path-parameters/no-custom-config/README.md
@@ -0,0 +1,184 @@
+# 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_path-parameters)
+
+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_path-parameters
+```
+
+## 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 SeedPathParameters
+
+client = SeedPathParameters(
+ base_url="https://yourhost.com/path/to/api",
+)
+
+client.user.create_user(
+ tenant_id="tenant_id",
+ name="name",
+ tags=[
+ "tags",
+ "tags"
+ ],
+)
+```
+
+## 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 AsyncSeedPathParameters
+
+client = AsyncSeedPathParameters(
+ base_url="https://yourhost.com/path/to/api",
+)
+
+
+async def main() -> None:
+ await client.user.create_user(
+ tenant_id="tenant_id",
+ name="name",
+ tags=[
+ "tags",
+ "tags"
+ ],
+ )
+
+
+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.user.create_user(...)
+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 SeedPathParameters
+
+client = SeedPathParameters(...)
+response = client.user.with_raw_response.create_user(...)
+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).
+
+Which status codes are retried depends on the `retryStatusCodes` generator configuration:
+
+**`legacy`** (current default): retries on
+- [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) (Timeout)
+- [409](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409) (Conflict)
+- [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#server_error_responses) (All server errors, including 500)
+
+**`recommended`**: retries on
+- [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) (Timeout)
+- [409](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409) (Conflict)
+- [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) (Too Many Requests)
+- [502](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/502) (Bad Gateway)
+- [503](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/503) (Service Unavailable)
+- [504](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504) (Gateway Timeout)
+
+Use the `max_retries` request option to configure this behavior.
+
+```python
+client.user.create_user(..., 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 SeedPathParameters
+
+client = SeedPathParameters(..., timeout=20.0)
+
+# Override timeout for a specific method
+client.user.create_user(..., request_options={
+ "timeout": 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 SeedPathParameters
+
+client = SeedPathParameters(
+ ...,
+ 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/path-parameters/no-custom-config/poetry.lock b/seed/python-sdk/path-parameters/no-custom-config/poetry.lock
new file mode 100644
index 000000000000..8c2d5982fa31
--- /dev/null
+++ b/seed/python-sdk/path-parameters/no-custom-config/poetry.lock
@@ -0,0 +1,1469 @@
+# This file is automatically @generated by Poetry 2.4.1 and should not be changed by hand.
+
+[[package]]
+name = "aiohappyeyeballs"
+version = "2.7.1"
+description = "Happy Eyeballs for asyncio"
+optional = true
+python-versions = ">=3.10"
+groups = ["main"]
+markers = "extra == \"aiohttp\""
+files = [
+ {file = "aiohappyeyeballs-2.7.1-py3-none-any.whl", hash = "sha256:9243213661e29250eb41368e5daa826fc017156c3b8a11440826b2e3ed376472"},
+ {file = "aiohappyeyeballs-2.7.1.tar.gz", hash = "sha256:065665c041c42a5938ed220bdcd7230f22527fbec085e1853d2402c8a3615d9d"},
+]
+
+[[package]]
+name = "aiohttp"
+version = "3.14.3"
+description = "Async http client/server framework (asyncio)"
+optional = true
+python-versions = ">=3.10"
+groups = ["main"]
+markers = "extra == \"aiohttp\""
+files = [
+ {file = "aiohttp-3.14.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:eb0495d778817619273c108784292be161a924b9f5ae5cbbc70a2caa6838250b"},
+ {file = "aiohttp-3.14.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c3c200cf9757edd785051dc699c7ecbec22110dbfcb3fefc7a9f9695eda8ea7a"},
+ {file = "aiohttp-3.14.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd51ebf9d3a00c074df4ede271023f4d2dba289bcc740b88191872716014e3c5"},
+ {file = "aiohttp-3.14.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:134ac5ddcf61c6fad984b9a5727d83492ada43d63471db20fb73042c13fca62f"},
+ {file = "aiohttp-3.14.3-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:70c987b27534f9ae1a723f47ae921571d616da21d3208282bf4c52af5164ac43"},
+ {file = "aiohttp-3.14.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1b59533861b70a2185c8f4f350f791f39d64358ef6944ce71c5240c9ec0982c9"},
+ {file = "aiohttp-3.14.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1c5281acc88b92396f88c7e1e2748f8466689df22b80170e4f51efa712fb47a8"},
+ {file = "aiohttp-3.14.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:48d67b87db6279c044760787eb01f6413032c2e6f3ba1cafaa492b1c8e578479"},
+ {file = "aiohttp-3.14.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f53bcd52f585e1ac3e590d61434eb61f9a88c38df041b4ea126d97144344a77b"},
+ {file = "aiohttp-3.14.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0fdea2281997af69da84c77ffa6f5938a0285f21fb3887c249d67419ca865b3d"},
+ {file = "aiohttp-3.14.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:cda5fd5c95ad7a125a2e8464acc78b98b94c475a3780d6aa0aa157c93f470f4d"},
+ {file = "aiohttp-3.14.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:6debfa7312ff9d4c124dc71d72e9a0a4b9e0879e48ba6fcb42bef5c3300289e2"},
+ {file = "aiohttp-3.14.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:f4e05329faa0ea1a404b37de4f034fd2c2defcca06a68dc6745e4e56c88e8a48"},
+ {file = "aiohttp-3.14.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:a3a8296e7ab5c295f53f1041487cb088e1480775aafbf7fe545d93b770a0f96f"},
+ {file = "aiohttp-3.14.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5373dc80ad1aa2fb9ad95c83f24eef418bbda3a61375f128e5b0192e4f3f9b32"},
+ {file = "aiohttp-3.14.3-cp310-cp310-win32.whl", hash = "sha256:a3e22975f905b89a55a488c2a08f2fdb2186175349e917d48985cc468a3d4c6e"},
+ {file = "aiohttp-3.14.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdd0e2834dce1a26c1bbe26464861e16bbe217042cbff619247c11594472518c"},
+ {file = "aiohttp-3.14.3-cp310-cp310-win_arm64.whl", hash = "sha256:eac645b09bcfdf73df7536331f0678c1086ea250981118ddb5199e17ccef72bb"},
+ {file = "aiohttp-3.14.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e568e14940c09955aa51f4e645b6daa18a581c5dcfcd73744dcc86a856e3ced3"},
+ {file = "aiohttp-3.14.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:54cfcdee2770dac994417cbb0ee1f3eb0e7cb6b30c79bf44f2c02ff79ec5124a"},
+ {file = "aiohttp-3.14.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:21c016079415ed3fd676963e9793700a566d85dbbd6bfc564b9b2d209147dcc8"},
+ {file = "aiohttp-3.14.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d6088ec9894113802bddb3c09e974929aed2c7b3a8c456219b8aab4481f1a239"},
+ {file = "aiohttp-3.14.3-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:16ea7e24c309fb7c0bbd505d149abe4fe4dccfb8db911db7dbec0921bc889a6f"},
+ {file = "aiohttp-3.14.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:56f355e79f71aef2a85c80305cc915f894b170dba76de5fe84f6351939b83c06"},
+ {file = "aiohttp-3.14.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:18c441d0a8fca6de8d1f546849b9f0ab20d435993e2c5b59562b2fae6be2f929"},
+ {file = "aiohttp-3.14.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:53e7b4ce82b54a8bcc71b3b67a5cbd177ca1d7f592cbc92cd38b7349f73482db"},
+ {file = "aiohttp-3.14.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f55119f7bf25f49ed210f6096090715da24f2943c62102448915fde3c62877ce"},
+ {file = "aiohttp-3.14.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9aa6e61fdf20105c4144e755bd586008ff450791d67b1c8146fdc15959c4d51c"},
+ {file = "aiohttp-3.14.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:ccd4893707b3e2a13e39c90d43cf80edf2e4d0457935bcc103bf2346214c3f15"},
+ {file = "aiohttp-3.14.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b2466434105a4e03113c36ec775cc2ebe6676b62eae326fa670bb607ef788c1c"},
+ {file = "aiohttp-3.14.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:ba59d59aba08ac02fc03b0c8983ccd5ee39a199d0552ce9e6d2b4845b34d59ae"},
+ {file = "aiohttp-3.14.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:ed099d105449c4f9e84f24af203cd131349d4761d8813fa7e02c32e7128cd910"},
+ {file = "aiohttp-3.14.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:152516815ef926786a0b6ae2b8f1fd2e0c71582dee0b435636865316fd4891b7"},
+ {file = "aiohttp-3.14.3-cp311-cp311-win32.whl", hash = "sha256:a4af35c443e0b1a1bd6a8af3f3485d7fda15c142751a00f3ff8090f0b93346fa"},
+ {file = "aiohttp-3.14.3-cp311-cp311-win_amd64.whl", hash = "sha256:e1e74298bab6ee0d6e749ed4fd1901c7e604bdda32c03d787a2cc71c46d0433d"},
+ {file = "aiohttp-3.14.3-cp311-cp311-win_arm64.whl", hash = "sha256:03cd2bde3d7f085b64e549c985f4bb928cad7e8ecf5323bfca320db548d81b39"},
+ {file = "aiohttp-3.14.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:39aded8c7f3b935b54aab1d8d73c70ec0ee2d3ec3b943e0e86611bc150ba47f5"},
+ {file = "aiohttp-3.14.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5bcb6ff3fdab1258a192679ff1a05d44f59626430aa05cd1a9d2447423599228"},
+ {file = "aiohttp-3.14.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:617105e2c3018ee38d0c8ce5ee3c84f621a6d8b9f723202aacaff28449ca91ee"},
+ {file = "aiohttp-3.14.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f631fe87a6f30df5fbe6d79640b25e4cffb38c31c7fb6f10871517b84b0f8c1a"},
+ {file = "aiohttp-3.14.3-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a94dbaae5ae27bd849c93570669bff91e0510f33a80805738e3de72a7be0447b"},
+ {file = "aiohttp-3.14.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8f2f1c4c032c7cedd7d8da6f54c97b70266c6570c3108d3fdffee7188bb70529"},
+ {file = "aiohttp-3.14.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ea05e1f97ceea523942d9b2a7d7c0359d781d683d6b043f5943a602b14da4787"},
+ {file = "aiohttp-3.14.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:543906c127fb1d929b95076db19b83fa2d46751006ff1e23b093aa5ac4d8db42"},
+ {file = "aiohttp-3.14.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0a5ff2dfbb9ce645fa5b8ef3e02c6c0b9cc3f6030ff863d0c51fffc50cb5541b"},
+ {file = "aiohttp-3.14.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:041badb8f84396357c4d3ad26de6afd7a32b112f43d3c63045c0c8278cfd2043"},
+ {file = "aiohttp-3.14.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:530125ee1163c4219af35dc3aa1206e541e7b31b6efc1a3f93b70a136f65d427"},
+ {file = "aiohttp-3.14.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c8653fd547c93a61aadc612007790f5555cdd18946fa48cf45e26d8ea4ea473d"},
+ {file = "aiohttp-3.14.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:89176250f686cb9853c0fb7ead90e639e915b84a6f43eedc2a4e7ec21f1037f0"},
+ {file = "aiohttp-3.14.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3a26434dafe408229ff3403458ca58de24fb51936504decac49ce6755f77e59d"},
+ {file = "aiohttp-3.14.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d1558173930a5a8d3069cee5c92fc91c87c4dbcb099debbb3622053717145a19"},
+ {file = "aiohttp-3.14.3-cp312-cp312-win32.whl", hash = "sha256:16100ad3ab8d649fdfbee87602d9d2dcdca9df0b9eda8a1b5fdc0d41f96da559"},
+ {file = "aiohttp-3.14.3-cp312-cp312-win_amd64.whl", hash = "sha256:33a2d7c28d33797a2e99923dffa63f83d908a19b6bf26cfe80fa790aa5e1a75a"},
+ {file = "aiohttp-3.14.3-cp312-cp312-win_arm64.whl", hash = "sha256:362a3fd481769cac1a824514bcd86fda51c65e8fe6e051099e008fddde6db17c"},
+ {file = "aiohttp-3.14.3-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:2e9878ae68e4a5f1c0abe4dd497dbc3d51946f5837b56759e2a02e78fa90ef86"},
+ {file = "aiohttp-3.14.3-cp313-cp313-android_21_x86_64.whl", hash = "sha256:f3d2669fe7dec7fc359ecdb5984b29b50d85d5d00f8c1cb61de4f4a24ee42627"},
+ {file = "aiohttp-3.14.3-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:cc7cb243a68167172f48c1fd43cee91ec4b1d40cefd190edd43369d1a6bc9c82"},
+ {file = "aiohttp-3.14.3-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:78253b573e6ffab5028924fc98bc281aae05445969982a10864bc360dea2016c"},
+ {file = "aiohttp-3.14.3-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7041d52c3a7fa20c9e8c182b534704abb19502c8bdcbde7ab23bfda6f642394f"},
+ {file = "aiohttp-3.14.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ac74facc01463f138b0da5580329cfcc82818dea5656e83ddcd11268fc12ff80"},
+ {file = "aiohttp-3.14.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d6218d92e450824e9b4881f44e8c09f1853b490f9a64130801024a4793b1b3b0"},
+ {file = "aiohttp-3.14.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:11fb37ef075669eee52ab1928fbf6e1741fada40409fa309ebde9607a962aebf"},
+ {file = "aiohttp-3.14.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55bdcc472aafe2de4a253045cc128007a64f1e0264fb675791e132ea5edaa3bd"},
+ {file = "aiohttp-3.14.3-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c39846c3aad97a8530c89d7a3869a8f8e9e3762c6ac0504481e5c80948f7e807"},
+ {file = "aiohttp-3.14.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5895ef58c4620afe02fa16044f023dc4dafec08158f9d08874a46a7dbc0341b8"},
+ {file = "aiohttp-3.14.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fa9467a8113aa69d3d7c55a70ef0b7c636010a40993f3df9d9d0d73b3eb7ef24"},
+ {file = "aiohttp-3.14.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d7d2deec16eeedf55f2c7cf75b521ea3856a5177e123844f8fd0f114ce252cb5"},
+ {file = "aiohttp-3.14.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:dd54d0e8717de95939766febac482ac0474d8ac3b048115f9f2b1d23a16e7db4"},
+ {file = "aiohttp-3.14.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:df82f3787c940c94986b34222d59c9e38843fba85139f36e85255a82ad5355a9"},
+ {file = "aiohttp-3.14.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:42a67efc36300d052fb4508a53e8b6901b9284b599ae63945c377569c5fcc1e1"},
+ {file = "aiohttp-3.14.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7a75aa63cbf9b21cfaf60dc2657e19df2c2867d91707d653fee171ffeedd1371"},
+ {file = "aiohttp-3.14.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:e92eb8acc45eb6a9f4935071a77edf5b85cc6f8dfad5cd99e97653c26593cdde"},
+ {file = "aiohttp-3.14.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b014a6ed7cf912e787149fdc529166d3ceabac23f26efeea3158c9aba2354e7e"},
+ {file = "aiohttp-3.14.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3d4f72af88ac2474bb5bca640030320e3d38a0163a1d7533500e87be458eef71"},
+ {file = "aiohttp-3.14.3-cp313-cp313-win32.whl", hash = "sha256:5f08ec777f35ee70720233b8b9811d3bb5d728137f30ac91b7457709c3261ac0"},
+ {file = "aiohttp-3.14.3-cp313-cp313-win_amd64.whl", hash = "sha256:dff9461ec275f22135650d5ba4b4931a11f3958df7dfbb8db630000d4dee0883"},
+ {file = "aiohttp-3.14.3-cp313-cp313-win_arm64.whl", hash = "sha256:ddcac3c6b382e81f1dd0499199d4136b877beb4cb5ef770bbbfba56c4b8f55d2"},
+ {file = "aiohttp-3.14.3-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:49f7325beb0f85ef4aef5f48f490269575f83e6e2acad00a1d80b807eb027062"},
+ {file = "aiohttp-3.14.3-cp314-cp314-android_24_x86_64.whl", hash = "sha256:e3be98a7c30b8c25d573dafba7171d66dfb05ee6a9070fc46535464ff97700a6"},
+ {file = "aiohttp-3.14.3-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:614c61d478b83953e261d02bb2df750f17227cd33ef8002945bf5aebbde21919"},
+ {file = "aiohttp-3.14.3-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:1caa7b0d05f3e3a36f87788c59e970a7ee1cefcfcbb924a9f138c4a6551c9cb7"},
+ {file = "aiohttp-3.14.3-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:dfa68deb2a443bdaa3ea5297b0699c1464f08aef3812b486d1348eee61b07dc0"},
+ {file = "aiohttp-3.14.3-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:e72ee89e28d907a18f46959b4eb0bb06701cc7f8cf4366e00029e2ccfaaf5924"},
+ {file = "aiohttp-3.14.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ad4c8b7488d745d2ca4838ebd8ae5ba9b56341d30b1da43640e4ce87f9f49646"},
+ {file = "aiohttp-3.14.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:db332af25642007330fca8be5c4d194caf2bea7a7fc84415aff3497af5dfee6b"},
+ {file = "aiohttp-3.14.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:25bd2708db6bdf6a6630dd37bdcdfcb47c4434d22ac69c64665b802910140b30"},
+ {file = "aiohttp-3.14.3-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:cef89a58e628c4efcac3275c2d68083f82426dcdc89c1492a6f654f9f7ea6ab9"},
+ {file = "aiohttp-3.14.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c23ec8ee9d5ab2f5421f9c7fffce208435607af27fd46d4a44e031954352838f"},
+ {file = "aiohttp-3.14.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e2667f0bbe7eb6c74eae5e9691441ad186e5845ca3cff63230fc09c4e7514f5d"},
+ {file = "aiohttp-3.14.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18cb43369747b2ae007bd2655fb8e63a099c2ff1d207962943636dac989b3147"},
+ {file = "aiohttp-3.14.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d77640cc618c1d99fc4f8589c0f24a730adfa54eb1e57ef7bf0c8dfb78da898c"},
+ {file = "aiohttp-3.14.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:53e5179d8abb5710f8e83ba207c41c8d1261fcffd4616500e15ca2b7a33be10a"},
+ {file = "aiohttp-3.14.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:cd817772b2fcf2b8c0905795318485f9ec16eae60b29feb7f4c77085311637f0"},
+ {file = "aiohttp-3.14.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:4e3ac92d90e92773b2362d506068e9a948192bd553e743c5b2429e28527c8661"},
+ {file = "aiohttp-3.14.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:3f42e9b78301f11c8f861746175d8b9c1ccef713fcad9eab396e2f6db8ed4a22"},
+ {file = "aiohttp-3.14.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:9d9edccfe496b476db5f398d97b865e9a6752bcf8aec4eef8390ce20fb64bb41"},
+ {file = "aiohttp-3.14.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1c5ec8fb1bcc31a8466f74aaf26c345d5c386fa4bd08a3f0eb9c7a4a3fe8b5bf"},
+ {file = "aiohttp-3.14.3-cp314-cp314-win32.whl", hash = "sha256:38901a84da3ce22249f6e860bf8f90d141bcab7da090cc398f8bb58c0e44b7da"},
+ {file = "aiohttp-3.14.3-cp314-cp314-win_amd64.whl", hash = "sha256:8b3b60de05f3dcb6f6a00f818bb2ec781cee4de0645f59ccaf99b1d1823b6100"},
+ {file = "aiohttp-3.14.3-cp314-cp314-win_arm64.whl", hash = "sha256:1576145bdceeb92382d899751e12743a3a5b8e460a841e3e50543859e54864dc"},
+ {file = "aiohttp-3.14.3-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:8800c996b01c2772a783e3e46f3e1abd5823029adca0df54231960de9bfefa5b"},
+ {file = "aiohttp-3.14.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:ebe8e504f058fe91223351cecd2d9d6946c9d241bb0250d898ffbdf584cc72b0"},
+ {file = "aiohttp-3.14.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:30402d03a7c0ff52bce290b57e564e9079fd9d0cb545c8aba73f86a103162d2e"},
+ {file = "aiohttp-3.14.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9fc7b5bfec6573f3ae844f457fdde5adeb713f8b8e4a81ad64fc207b49383716"},
+ {file = "aiohttp-3.14.3-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8a5fd34f7f7410d1730d5c2ba873cacb2eed3fede366feb268a70ba22581ed8f"},
+ {file = "aiohttp-3.14.3-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:270d3dace9ca2f10f0da5d8ebe519b7a310fc6112ed916e32df5866df0888553"},
+ {file = "aiohttp-3.14.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3ae5b3a59436d089b5395d910121a390feed4d00578eb95a0fd1a329fe963100"},
+ {file = "aiohttp-3.14.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2498f0fe69ead802f9675beca44a7c21c62fdaa4ec5145ea1c3ad6edbee29f85"},
+ {file = "aiohttp-3.14.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a0dc483c00da8b673abbb367eb6f8d8f4bcec30eb58529ea13cb42e7fd2dfa33"},
+ {file = "aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c7d3a97c678d34fc5b59da671ee9cd630096ddc643e7b5a30d54a2a6f3574d3f"},
+ {file = "aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:f8fb78a83c9e5f741ca3a68cfb455c1f5bb83b4e7249a3848b3cd78d0a8563b0"},
+ {file = "aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:74ab5b6a9fb13e873e5a90946588baecaf488745e1db1a4a5c433f971f035098"},
+ {file = "aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:bd52f811e65f6fb634b1047159657c98f52b407f8efec907bcfc09da9a4c0a25"},
+ {file = "aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:f0f177d1b195b9e06376cfd7d308d8a1b920909a609d03ac82a8c73bbb16d3b9"},
+ {file = "aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:498c6c623134f8e09a3c4e60bcd607a0b4590dd7dbf08dd40851b27cbb520ccb"},
+ {file = "aiohttp-3.14.3-cp314-cp314t-win32.whl", hash = "sha256:b304db572b4368edd8dda8a2274f73156fe15558fca4a917cb8a09fc47af5963"},
+ {file = "aiohttp-3.14.3-cp314-cp314t-win_amd64.whl", hash = "sha256:b20032766aedf6261c7a566585a40867d092ac03a0d81592d5370ef9b054f99b"},
+ {file = "aiohttp-3.14.3-cp314-cp314t-win_arm64.whl", hash = "sha256:2e1161602f45a54de2ce0905243a95f58cb42dcd378402f3697f5e0b21e9d2e7"},
+ {file = "aiohttp-3.14.3.tar.gz", hash = "sha256:9491196535a88924a60afd5b5f434b5b203b6cc616250878dbdb223a8f7844bc"},
+]
+
+[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"
+typing_extensions = {version = ">=4.4", markers = "python_version < \"3.13\""}
+yarl = ">=1.17.0,<2.0"
+
+[package.extras]
+speedups = ["Brotli (>=1.2) ; platform_python_implementation == \"CPython\" and sys_platform != \"android\" and sys_platform != \"ios\"", "aiodns (>=3.3.0) ; sys_platform != \"android\" and sys_platform != \"ios\"", "backports.zstd ; platform_python_implementation == \"CPython\" and python_version < \"3.14\" and sys_platform != \"android\" and sys_platform != \"ios\"", "brotlicffi (>=1.2) ; platform_python_implementation != \"CPython\""]
+
+[[package]]
+name = "aiosignal"
+version = "1.4.0"
+description = "aiosignal: a list of registered asynchronous callbacks"
+optional = true
+python-versions = ">=3.9"
+groups = ["main"]
+markers = "extra == \"aiohttp\""
+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.8.0"
+description = "Reusable constraint types to use with typing.Annotated"
+optional = false
+python-versions = ">=3.10"
+groups = ["main"]
+files = [
+ {file = "annotated_types-0.8.0-py3-none-any.whl", hash = "sha256:f072f4d804ea359e4eaf198b1af7a8b0943881a87f31bb764f8bf219bb9419e0"},
+ {file = "annotated_types-0.8.0.tar.gz", hash = "sha256:13b2beaad985e05e2d6407ee4c4f35590b11f8d693a258a561055cac8f64cab7"},
+]
+
+[[package]]
+name = "anyio"
+version = "4.14.2"
+description = "High-level concurrency and networking framework on top of asyncio or Trio"
+optional = false
+python-versions = ">=3.10"
+groups = ["main"]
+files = [
+ {file = "anyio-4.14.2-py3-none-any.whl", hash = "sha256:9f505dda5ac9f0c8309b5e8bd445a8c2bf7246f3ce950121e45ea15bc41d1494"},
+ {file = "anyio-4.14.2.tar.gz", hash = "sha256:cfa139f3ed1a23ee8f88a145ddb5ac7605b8bbfd8592baacd7ce3d8bb4313c7f"},
+]
+
+[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"
+groups = ["main"]
+markers = "python_version == \"3.10\" and extra == \"aiohttp\""
+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"
+groups = ["main"]
+markers = "extra == \"aiohttp\""
+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"
+groups = ["dev"]
+markers = "python_version == \"3.10\""
+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.7.22"
+description = "Python package for providing Mozilla's CA Bundle."
+optional = false
+python-versions = ">=3.7"
+groups = ["main"]
+files = [
+ {file = "certifi-2026.7.22-py3-none-any.whl", hash = "sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775"},
+ {file = "certifi-2026.7.22.tar.gz", hash = "sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55"},
+]
+
+[[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"
+groups = ["dev"]
+markers = "sys_platform == \"win32\""
+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"
+groups = ["main", "dev"]
+markers = "python_version == \"3.10\""
+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"
+groups = ["dev"]
+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"
+groups = ["main"]
+markers = "extra == \"aiohttp\""
+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"
+groups = ["main"]
+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"
+groups = ["main"]
+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"
+groups = ["main"]
+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 ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""]
+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.12"
+description = "Aiohttp transport for HTTPX"
+optional = true
+python-versions = ">=3.8"
+groups = ["main"]
+markers = "extra == \"aiohttp\""
+files = [
+ {file = "httpx_aiohttp-0.1.12-py3-none-any.whl", hash = "sha256:5b0eac39a7f360fa7867a60bcb46bb1024eada9c01cbfecdb54dc1edb3fb7141"},
+ {file = "httpx_aiohttp-0.1.12.tar.gz", hash = "sha256:81feec51fd82c0ecfa0e9aaf1b1a6c2591260d5e2bcbeb7eb0277a78e610df2c"},
+]
+
+[package.dependencies]
+aiohttp = ">=3.10.0,<4"
+httpx = ">=0.27.0"
+
+[[package]]
+name = "idna"
+version = "3.18"
+description = "Internationalized Domain Names in Applications (IDNA)"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+files = [
+ {file = "idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2"},
+ {file = "idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848"},
+]
+
+[package.extras]
+all = ["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"
+groups = ["dev"]
+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"
+groups = ["main"]
+markers = "extra == \"aiohttp\""
+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"
+groups = ["dev"]
+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"
+groups = ["dev"]
+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.2"
+description = "Core utilities for Python packages"
+optional = false
+python-versions = ">=3.8"
+groups = ["dev"]
+files = [
+ {file = "packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e"},
+ {file = "packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661"},
+]
+
+[[package]]
+name = "pluggy"
+version = "1.6.0"
+description = "plugin and hook calling mechanisms for python"
+optional = false
+python-versions = ">=3.9"
+groups = ["dev"]
+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.5.2"
+description = "Accelerated property cache"
+optional = true
+python-versions = ">=3.10"
+groups = ["main"]
+markers = "extra == \"aiohttp\""
+files = [
+ {file = "propcache-0.5.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d5a81be28596d6559f6131ef33e10200de6e17643b3c74ce03f9eb103be6ae8b"},
+ {file = "propcache-0.5.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29cbaac5ea0212663e6845e04b5e188d5a6ae6dd919810ac835bf1d3b42c3f4c"},
+ {file = "propcache-0.5.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6bf3be92233808fcd338eba0fb4d0b59ec5772af4f4ecfcec450d1bfc0f8b5eb"},
+ {file = "propcache-0.5.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2f8ea531c794b9d6274acd4e8d2c2ebcac590a4361d27482edd3010b79f1325e"},
+ {file = "propcache-0.5.2-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:decfca4c79dd53ebab484b00cc4b6717d8c369f86e74aa4ca395a64ac651495e"},
+ {file = "propcache-0.5.2-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4621064bbf28fa77ff64dd5d94367c04684c67d3a5bf1dff25f0cd0d98a38f3b"},
+ {file = "propcache-0.5.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b96db7141a592cbc968daf1feea83a118e6ab378af4abbc72b248c895414c22d"},
+ {file = "propcache-0.5.2-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1ca071adabaab6e9219924bbe00af821f1ee7de113a9eca1cdc292de3d120f4d"},
+ {file = "propcache-0.5.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e4294d04a94dcab1b3bccd8b66d962dcad411a1d19414b2a41d1445f1de32ad0"},
+ {file = "propcache-0.5.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:a0e399a2eccb91ed18721f86aa85757727400b6865c89e88934781deb9c8498b"},
+ {file = "propcache-0.5.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:823581fd5cb08b12a48bfa11fe962a7916766b6170c17b028fbdf762b85eb9bf"},
+ {file = "propcache-0.5.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:949c91d1a990cf3b2e8188dfcfb25005e0b834a06c63fa4ef9f360878ce21ecf"},
+ {file = "propcache-0.5.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:cc1177027eda740fdb152706bd215a3f124e3eea15afc39f2cb9fe351b50619e"},
+ {file = "propcache-0.5.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b05d643f944a8c3c4bd86d65ffd87bf3264b617f87791940302bc474d2ff5274"},
+ {file = "propcache-0.5.2-cp310-cp310-win32.whl", hash = "sha256:8114f28879e0904748e831c3a7774261bd9e75f49be089f389a76f959dcd13fe"},
+ {file = "propcache-0.5.2-cp310-cp310-win_amd64.whl", hash = "sha256:5fcb98e7598b1ee0addab320d90f65b530297a867dbfe9de52ea838077e16e3d"},
+ {file = "propcache-0.5.2-cp310-cp310-win_arm64.whl", hash = "sha256:04dc2390d9edbbaef7461f33322555976ffddf0b650a038649d026358714e6c5"},
+ {file = "propcache-0.5.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:74b70780220e2dd89175ca24b81b68b67c83db499ae611e7f2313cb329801c78"},
+ {file = "propcache-0.5.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a4840ab0ae0216d952f4b53dc6d0b992bfc2bedbfe360bdd9b548bc184c08959"},
+ {file = "propcache-0.5.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c6844ba6364fb12f403928a82cfd295ab103a2b315c77c747b2dbe4a41894ea7"},
+ {file = "propcache-0.5.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2293949b855ce597f2826452d17c2d545fb5622379c4ea6fdf525e9b8e8a2511"},
+ {file = "propcache-0.5.2-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0fd59b5af35f74da48d905dcbad55449ba13be91823cb05a9bd590bbf5b61660"},
+ {file = "propcache-0.5.2-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29f9309a2e42b0d273be006fdb4be2d6c39a47f6f57d8fb1cf9f81481df81b66"},
+ {file = "propcache-0.5.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5aaa2b923c1944ac8febd6609cb373540a5563e7cbcb0fd770f75dace2eb817b"},
+ {file = "propcache-0.5.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:66ea454f095ddf5b6b14f56c064c0941c4788be11e18d2464cf643bf7203ff67"},
+ {file = "propcache-0.5.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:95f1e3f4760d404b13c9976c0229b2b49a3c8e2c62a9ce92efdd2b11ada75e3f"},
+ {file = "propcache-0.5.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:85341b12b9d55bad0bded24cac341bb34289469e03a11f3f583ea1cc1db0326c"},
+ {file = "propcache-0.5.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:26a4dca084132874e639895c3135dfad5eb20bae209f62d1aeb31b03e601c3c0"},
+ {file = "propcache-0.5.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:3b199b9b2b3d6a7edf3183ba8a9a137a22b97f7df525feb5ae1eccf026d2a9c6"},
+ {file = "propcache-0.5.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e59bc9e66329185b93dab73f210f1a37f81cb40f321501db8017c9aea15dba27"},
+ {file = "propcache-0.5.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:552ffadf6ad409844bc5919c42a0a83d88314cedddaea0e41e80a8b8fffe881f"},
+ {file = "propcache-0.5.2-cp311-cp311-win32.whl", hash = "sha256:cd416c1de191973c52ff1a12a57446bfc7642797b282d7caf2162d7d1b8aa9a0"},
+ {file = "propcache-0.5.2-cp311-cp311-win_amd64.whl", hash = "sha256:44e488ef40dbb452700b2b1f8188934121f6648f52c295055662d2191959ff82"},
+ {file = "propcache-0.5.2-cp311-cp311-win_arm64.whl", hash = "sha256:54adaa85a22078d1e306304a40984dc5be99d599bf3dc0a24dc98f7daeab89ab"},
+ {file = "propcache-0.5.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:806719138ecd720339a12410fb9614ac9b2b2d3a5fdf8235d56981c36f4039ba"},
+ {file = "propcache-0.5.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:db2b80ea58eab4f86b2beec3cc8b39e8ff9276ac20e96b7cce43c8ae84cd6b5a"},
+ {file = "propcache-0.5.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e5cbfac9f61484f7e9f3597775500cd3ebe8274e9b050c38f9525c77c97520bf"},
+ {file = "propcache-0.5.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5dbc581d2814337da56222fab8dc5f161cd798a434e49bac27930aaef798e144"},
+ {file = "propcache-0.5.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:857187f381f88c8e2fa2fe56ab94879d011b883d5a2ee5a1b60a8cd2a06846d9"},
+ {file = "propcache-0.5.2-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:178b4a2cdaac1818e2bf1c5a99b94383fa73ea5382e032a48dec07dc5668dc42"},
+ {file = "propcache-0.5.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f328175a2cde1f0ff2c4ed8ce968b9dcfb55f3a7153f39e2957ed994da13476"},
+ {file = "propcache-0.5.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5671d09a36b06d0fd4a3da0fccbcae360e9b1570924171a15e9e0997f0249fba"},
+ {file = "propcache-0.5.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:80168e2ebe4d3ec6599d10ad8f520304ae1cad9b6c5a95372aef1b66b7bfb53a"},
+ {file = "propcache-0.5.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:45f11346f884bc47444f6e6647131055844134c3175b629f84952e2b5cd62b64"},
+ {file = "propcache-0.5.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8e778ebd44ef4f66ed60a0416b06b489687db264a9c0b3620362f26489492913"},
+ {file = "propcache-0.5.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:c0cb9ed24c8964e172768d455a38254c2dd8a552905729ce006cad3d3dda59b1"},
+ {file = "propcache-0.5.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1d1ad32d9d4355e2be65574fd0bfd3677e7066b009cd5b9b2dee8aa6a6393b33"},
+ {file = "propcache-0.5.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c80f4ba3e8f00189165999a742ee526ebeccedf6c3f7beb0c7df821e9772435a"},
+ {file = "propcache-0.5.2-cp312-cp312-win32.whl", hash = "sha256:8c7972d8f193740d9175f0998ab38717e6cd322d5935c5b0fef8c0d323fd9031"},
+ {file = "propcache-0.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:d9ee8826a7d47863a08ac44e1a5f611a462eefc3a194b492da242128bec75b42"},
+ {file = "propcache-0.5.2-cp312-cp312-win_arm64.whl", hash = "sha256:2800a4a8ead6b28cccd1ec54b59346f0def7922ee1c7598e8499c733cfbb7c84"},
+ {file = "propcache-0.5.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:099aaf4b4d1a02265b92a977edf00b5c4f63b3b17ac6de39b0d637c9cac0188a"},
+ {file = "propcache-0.5.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:68ce1c44c7a813a7f71ea04315a8c7b330b63db99d059a797a4651bb6f69f117"},
+ {file = "propcache-0.5.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fc299c129490f55f254cd90be0deca4764e36e9a7c08b4aa588479a3bbed3098"},
+ {file = "propcache-0.5.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a6ae2198be502c10f09b2516e7b5d019816924bc3183a43ce792a7bd6625e6f4"},
+ {file = "propcache-0.5.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6041d31504dc1779d700e1edcfb08eea334b357620b06681a4eabb57a74e574e"},
+ {file = "propcache-0.5.2-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f7eabc04151c78a9f4d5bbb5f1faf571e4defeb4b585e0fe95b60ff2dbe4d3d7"},
+ {file = "propcache-0.5.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4db0ba63d693afd40d249bd93f842b5f144f8fcbb83de05660373bcf30517b1d"},
+ {file = "propcache-0.5.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1dbcf7675229b35d31abb6547d8ebc8c27a830ac3f9a794edff6254873ec7c0a"},
+ {file = "propcache-0.5.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d310c013aad2c72f1c3f2f8dd3279d460a858c551f97aeb8c63e4693cca7b4d2"},
+ {file = "propcache-0.5.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:06187263ddad280d05b4d8a8b3bb7d164cbebd469236544a42e6d9b28ac6a4fa"},
+ {file = "propcache-0.5.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3115559b8effafd63b142ea5ed53d63a16ea6469cbc63dce4ee194b42db5d853"},
+ {file = "propcache-0.5.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c60462af8e6dc30c35407c7237ea908d777b22862bbee27bc4699c0d8bcdc45a"},
+ {file = "propcache-0.5.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:40314bca9ac559716fe374094fc81c11dcc34b64fd6c585360f5775690505704"},
+ {file = "propcache-0.5.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:cfa21e036ce1e1db2be04ba3b85d2df1bb1702fa01932d984c5464c665228ff4"},
+ {file = "propcache-0.5.2-cp313-cp313-win32.whl", hash = "sha256:f156a3529f38063b6dbaf356e15602a7f95f8055b1295a438433a6386f10463d"},
+ {file = "propcache-0.5.2-cp313-cp313-win_amd64.whl", hash = "sha256:dfed59d0a5aeb01e242e66ff0300bc4a265a7c05f612d30016f0b60b1017d757"},
+ {file = "propcache-0.5.2-cp313-cp313-win_arm64.whl", hash = "sha256:ba338430e87ceb9c8f0cf754de38a9860560261e56c00376debd628698a7364f"},
+ {file = "propcache-0.5.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a592f5f3da71c8691c788c13cb6734b6d17663d2e1cb8caddf0673d01ef8847d"},
+ {file = "propcache-0.5.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6a997d0489e9668a384fcfd5061b857aa5361de73191cac204d04b889cfbbafa"},
+ {file = "propcache-0.5.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:10734b5484ea113152ee25a91dccedf81631791805d2c9ccb054958e51842c94"},
+ {file = "propcache-0.5.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cafca7e56c12bb02ae16d283742bef25a61122e9dab2b5b3f2ccbe589ce32164"},
+ {file = "propcache-0.5.2-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f064f8d2b59177878b7615df1735cd8fe3462ed6be8c7b217d17a276489c2b7f"},
+ {file = "propcache-0.5.2-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f78abfa8dfc32376fd1aacf597b2f2fbbe0ea751419aee718af5d4f82537ef8c"},
+ {file = "propcache-0.5.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f7467da8a9822bf1a55336f877340c5bcbd3c482afc43a99771169f74a26dedc"},
+ {file = "propcache-0.5.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a6ddc6ac9e25de626c1f129c1b467d7ecd33ce2237d3fd0c4e429feef0a7ee1f"},
+ {file = "propcache-0.5.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2f22cbbac9e26a8e864c0985ff1268d5d939d53d9d9411a9824279097e03a2cb"},
+ {file = "propcache-0.5.2-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:fc76378c62a0f04d0cd82fbb1a2cd2d7e28fcb40d5873f28a6c44e388aaa2751"},
+ {file = "propcache-0.5.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:acd2c8edba48e31e58a363b8cf4e5c7db3b04b3f9e371f601df30d9b0d244836"},
+ {file = "propcache-0.5.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:452b5065457eb9991ec5eb38ff41d6cd4c991c9ac7c531c4d5849ae473a9a13f"},
+ {file = "propcache-0.5.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:3430bb2bfe1331885c427745a751e774ee679fd4344f80b97bf879815fe8fa55"},
+ {file = "propcache-0.5.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:cef6cea3922890dd6c9654971001fa797b526c16ab5e1e46c05fd6f877be7568"},
+ {file = "propcache-0.5.2-cp313-cp313t-win32.whl", hash = "sha256:72d61e16dd78228b58c5d47be830ff3da7e5f139abdf0aef9d86cde1c5cf2191"},
+ {file = "propcache-0.5.2-cp313-cp313t-win_amd64.whl", hash = "sha256:0958834041a0166d343b8d2cedcd8bcbaeb4fdbe0cf08320c5379f143c3be6e7"},
+ {file = "propcache-0.5.2-cp313-cp313t-win_arm64.whl", hash = "sha256:6de8bd93ddde9b992cf2b2e0d796d501a19026b5b9fd87356d7d0779531a8d96"},
+ {file = "propcache-0.5.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:46088abff4cba581dea21ae0467a480526cb25aa5f3c269e909f800328bc3999"},
+ {file = "propcache-0.5.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fc88b26f08d634f7bc819a7852e5214f5802641ab8d9fd5326892292eee1993e"},
+ {file = "propcache-0.5.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:97797ebb098e670a2f92dd66f32897e30d7615b14e7f59711de23e30a9072539"},
+ {file = "propcache-0.5.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba57fffe4ac99c5d30076161b5866336d97600769bad35cc68f7774b15298a4e"},
+ {file = "propcache-0.5.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:583c19759d9eec1e5b69e2fbef36a7d9c326041be9746cb822d335c8cedc2979"},
+ {file = "propcache-0.5.2-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d0326e2e5e1f3163fa306c834e48e8d490e5fae607a097a40c0648109b47ba80"},
+ {file = "propcache-0.5.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e00820e192c8dbebcafb383ebbf99030895f09905e7a0eb2e0340a0bcc2bc825"},
+ {file = "propcache-0.5.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c66afea89b1e43725731d2004732a046fe6fe955d51f952c3e95a7314a284a39"},
+ {file = "propcache-0.5.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d4dc37dec6c6cdad0b57881a5658fd14fbf53e333b1a86cf86559f190e1d9ec4"},
+ {file = "propcache-0.5.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:5570dbcc97571c15f68068e529c92715a12f8d54030e272d264b377e22bd17a5"},
+ {file = "propcache-0.5.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f814362777a9f841adddb200ecdf8f5cb1e5a3c4b7a86378edbd6ccb26edd702"},
+ {file = "propcache-0.5.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:196913dea116aeb5a2ba95af4ddcb7ea85559ae07d8eee8751688310d09168c3"},
+ {file = "propcache-0.5.2-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:6e7b8719005dd1175be4ab1cd25e9b98659a5e0347331506ec6760d2773a7fb5"},
+ {file = "propcache-0.5.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:51f96d685ab16e88cab128cd37a52c5da540809c8b879fa047731bfcb4ad35a4"},
+ {file = "propcache-0.5.2-cp314-cp314-win32.whl", hash = "sha256:cc6fc3cc62e8501d3ed62894425040d2728ecddb1ed072737a5c70bd537aa9f0"},
+ {file = "propcache-0.5.2-cp314-cp314-win_amd64.whl", hash = "sha256:81e3a30b0bb60caa22033dd0f8a3618d1d67356212514f62c57db75cb0ef410c"},
+ {file = "propcache-0.5.2-cp314-cp314-win_arm64.whl", hash = "sha256:0d2c9bf8528f135dbb805ce027567e09164f7efa51a2be07458a2c0420f292d0"},
+ {file = "propcache-0.5.2-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:4bc8ff1feffc6a61c7002ffe84634c41b822e104990ae009f44a0834430070bb"},
+ {file = "propcache-0.5.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:79aa3ff0a9b566633b642fa9caf7e21ed1c13d6feca718187873f199e1514078"},
+ {file = "propcache-0.5.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1b31822f4474c4036bae62de9402710051d431a606d6a0f907fec79935a071aa"},
+ {file = "propcache-0.5.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13fef48778b5a2a756523fdb781326b028ca75e32858b04f2cdd19f394564917"},
+ {file = "propcache-0.5.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8b73ab70f1a3351fbc71f663b3e645af6dd0329100c353081cf69c37433fc6fe"},
+ {file = "propcache-0.5.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5538d2c13d93e4698af7e092b57bc7298fd35d1d58e656ae18f23ee0d0378e03"},
+ {file = "propcache-0.5.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd645f03898405cabe694fb8bc35241e3a9c332ec85627584fe3de201452b335"},
+ {file = "propcache-0.5.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a473b3440261e0c60706e732b2ed2f517857344fc21bf48fdfe211e2d98eb285"},
+ {file = "propcache-0.5.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7afa37062e6650640e932e4cc9297d81f9f42d9944029cc386b8247dea4da837"},
+ {file = "propcache-0.5.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:8a90efd5777e996e42d568db9ac740b944d691e565cbfd31b2f7832f9184b2b8"},
+ {file = "propcache-0.5.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:f19bb891234d72535764d703bfed1153cc34f4214d5bd7150aee1eec9e8f4366"},
+ {file = "propcache-0.5.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:32775082acd2d807ee3db715c7770d38767b817870acfa08c29e057f3c4d5b56"},
+ {file = "propcache-0.5.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9282fb1a3bccd038da9f768b927b24a0c753e466c086b7c4f3c6982851eefb2d"},
+ {file = "propcache-0.5.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cc49723e2f60d6b32a0f0b08a3fd6d13203c07f1cd9566cfce0f12a917c967a2"},
+ {file = "propcache-0.5.2-cp314-cp314t-win32.whl", hash = "sha256:2d7aa89ebca5acc98cba9d1472d976e394782f587bad6661003602a619fd1821"},
+ {file = "propcache-0.5.2-cp314-cp314t-win_amd64.whl", hash = "sha256:d447bb0b3054be5818458fbb171208b1d9ff11eba14e18ca18b90cbb45767370"},
+ {file = "propcache-0.5.2-cp314-cp314t-win_arm64.whl", hash = "sha256:fe67a3d11cd9b4efabfa45c3d00ffba2b26811442a73a581a94b67c2b5faccf6"},
+ {file = "propcache-0.5.2-py3-none-any.whl", hash = "sha256:be1ddfcbb376e3de5d2e2db1d58d6d67463e6b4f9f040c000de8e300295465fe"},
+ {file = "propcache-0.5.2.tar.gz", hash = "sha256:01c4fc7480cd0598bb4b57022df55b9ca296da7fc5a8760bd8451a7e63a7d427"},
+]
+
+[[package]]
+name = "pydantic"
+version = "2.13.4"
+description = "Data validation using Python type hints"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+files = [
+ {file = "pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba"},
+ {file = "pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6"},
+]
+
+[package.dependencies]
+annotated-types = ">=0.6.0"
+pydantic-core = "2.46.4"
+typing-extensions = ">=4.14.1"
+typing-inspection = ">=0.4.2"
+
+[package.extras]
+email = ["email-validator (>=2.0.0)"]
+timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""]
+
+[[package]]
+name = "pydantic-core"
+version = "2.46.4"
+description = "Core functionality for Pydantic validation and serialization"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+files = [
+ {file = "pydantic_core-2.46.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:a396dcc17e5a0b164dbe026896245a4fa9ff402edca1dff0be3d53a517f74de4"},
+ {file = "pydantic_core-2.46.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:da4b951fe36dc7c3a1ccb4e3cd1747c3542b8c9ceede8fc86cae054e764485f5"},
+ {file = "pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb63e0198ca18aad131c089b9204c23079c3afa95487e561f4c522d519e55aba"},
+ {file = "pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f47286a97f0bc9b8859519809077b91b2cefe4ae47fcbf5e466a009c1c5d742b"},
+ {file = "pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:905a0ed8ea6f2d61c1738835f99b699348d7857379083e5fc497fa0c967a407c"},
+ {file = "pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea793e075b70290d89d8142074262885d3f7da19634845135751bd6344f73b50"},
+ {file = "pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:395aebd9183f9d112f569aeb5b2214d1a10a33bec8456447f7fbdfa51d38d4cd"},
+ {file = "pydantic_core-2.46.4-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:b078afbc25f3a1436c7a1d2cd3e322497ee99615ba97c563566fdf46aff1ee01"},
+ {file = "pydantic_core-2.46.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f747929cf940cddb5b3668a390056ddd5ba2e5010615ea2dcf4f9c4f3ab8791d"},
+ {file = "pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:daa27d92c36f24388fe3ad306b174781c747627f134452e4f128ea00ce1fe8c4"},
+ {file = "pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:19e51f073cd3df251856a8a4189fbdf1de4012c3ebacfb1884f94f1eb406079f"},
+ {file = "pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1747f85cee84c26985853c6f3d9bd3e75da5212912443fa111c113b9c246f39"},
+ {file = "pydantic_core-2.46.4-cp310-cp310-win32.whl", hash = "sha256:2f84c03c8607173d16b5a854ec68a2f9079ae03237a54fb506d13af47e1d018d"},
+ {file = "pydantic_core-2.46.4-cp310-cp310-win_amd64.whl", hash = "sha256:8358a950c8909158e3df31538a7e4edc2d7265a7c54b47f0864d9e5bae9dcebf"},
+ {file = "pydantic_core-2.46.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0e96592440881c74a213e5ad528e2b24d3d4f940de2766bed9010ab1d9e51594"},
+ {file = "pydantic_core-2.46.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0d65b8c354be7fb5f720c3caa8bc940bc2d20ce749c8e06135f07f8ed95dd7c"},
+ {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bfb192b3f4b9e8a89b6277b6ce787564f62cfd272055f6e685726b111dc7826"},
+ {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9037063db01f09b09e237c282b6792bd4da634b5402c4e7f0c61effed7701a04"},
+ {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc010ab034c8c7452522748bf937df58020d256ccae0874463d1f4d01758af8e"},
+ {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5dac79fa1614d1e06ca695109c6105923bd9c7d1d6c918d4e637b7e6b32fd3"},
+ {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9fa868638bf362d3d138ea55829cefb3d5f4b0d7f142234382a15e2485dbec4"},
+ {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:17299feefe090f2caa5b8e37222bb5f663e4935a8bfa6931d4102e5df1a9f398"},
+ {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4c63ebc82684aa89d9a3bcbd13d515b3be44250dc68dd3bd81526c1cb31286c3"},
+ {file = "pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:aaa2a54443eff1950ba5ddc6b6ccda0d9c84a364276a62f969bdf2a390650848"},
+ {file = "pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:18e5ceec2ab67e6d5f1a9085e5a24c9c4e2ac4545730bfe668680bca05e555f3"},
+ {file = "pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a0f62d0a58f4e7da165457e995725421e0064f2255d8eccebc49f41bbc23b109"},
+ {file = "pydantic_core-2.46.4-cp311-cp311-win32.whl", hash = "sha256:041bde0a48fd37cf71cab1c9d56d3e8625a3793fef1f7dd232b3ff37e978ecda"},
+ {file = "pydantic_core-2.46.4-cp311-cp311-win_amd64.whl", hash = "sha256:6f2eeda33a839975441c86a4119e1383c50b47faf0cbb5176985565c6bb02c33"},
+ {file = "pydantic_core-2.46.4-cp311-cp311-win_arm64.whl", hash = "sha256:14f4c5d6db102bd796a627bbb3a17b4cf4574b9ae861d8b7c9a9661c6dd3362d"},
+ {file = "pydantic_core-2.46.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2"},
+ {file = "pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f"},
+ {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7"},
+ {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a233125ac121aa3ffba9a2b59edfc4a985a76092dc8279586ab4b71390875e7"},
+ {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b712b53160b79a5850310b912a5ef8e57e56947c8ad690c227f5c9d7e561712"},
+ {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9401557acd873c3a7f3eb9383edef8ac4968f9510e340f4808d427e75667e7b4"},
+ {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:926c9541b14b12b1681dca8a0b75feb510b06c6341b70a8e500c2fdcff837cce"},
+ {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:56cb4851bcaf3d117eddcef4fe66afd750a50274b0da8e22be256d10e5611987"},
+ {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c68fcd102d71ea85c5b2dfac3f4f8476eff42a9e078fd5faefff6d145063536b"},
+ {file = "pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b2f69dec1725e79a012d920df1707de5caf7ed5e08f3be4435e25803efc47458"},
+ {file = "pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8d0820e8192167f80d88d64038e609c31452eeca865b4e1d9950a27a4609b00b"},
+ {file = "pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fbdb89b3e1c94a30cc5edfce477c6e6a5dc4d8f84665b455c27582f211a1c72c"},
+ {file = "pydantic_core-2.46.4-cp312-cp312-win32.whl", hash = "sha256:9aa768456404a8bf48a4406685ac2bec8e72b62c69313734fa3b73cf33b3a894"},
+ {file = "pydantic_core-2.46.4-cp312-cp312-win_amd64.whl", hash = "sha256:e9c26f834c65f5752f3f06cb08cb86a913ceb7274d0db6e267808a708b46bc89"},
+ {file = "pydantic_core-2.46.4-cp312-cp312-win_arm64.whl", hash = "sha256:4fc73cb559bdb54b1134a706a2802a4cddd27a0633f5abb7e53056268751ac6a"},
+ {file = "pydantic_core-2.46.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008"},
+ {file = "pydantic_core-2.46.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4"},
+ {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76"},
+ {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3"},
+ {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76"},
+ {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4"},
+ {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a"},
+ {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262"},
+ {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e"},
+ {file = "pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd"},
+ {file = "pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be"},
+ {file = "pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d"},
+ {file = "pydantic_core-2.46.4-cp313-cp313-win32.whl", hash = "sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb"},
+ {file = "pydantic_core-2.46.4-cp313-cp313-win_amd64.whl", hash = "sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292"},
+ {file = "pydantic_core-2.46.4-cp313-cp313-win_arm64.whl", hash = "sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d"},
+ {file = "pydantic_core-2.46.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:428e04521a40150c85216fc8b85e8d39fece235a9cf5e383761238c7fa9b96fb"},
+ {file = "pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23ace664830ee0bfe014a0c7bc248b1f7f25ed7ad103852c317624a1083af462"},
+ {file = "pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce5c1d2a8b27468f433ca974829c44060b8097eedc39933e3c206a90ee49c4a9"},
+ {file = "pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7283d57845ecf5a163403eb0702dfc220cc4fbdd18919cb5ccea4f95ee1cdab4"},
+ {file = "pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8daafc69c93ee8a0204506a3b6b30f586ef54028f52aeeeb5c4cfc5184fd5914"},
+ {file = "pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2213145bcc2ba85884d0ac63d222fece9209678f77b9b4d76f054c561adb28"},
+ {file = "pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b"},
+ {file = "pydantic_core-2.46.4-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:c1b3f518abeca3aa13c712fd202306e145abf59a18b094a6bafb2d2bbf59192c"},
+ {file = "pydantic_core-2.46.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a7dd0b3ee80d90150e3495a3a13ac34dbcbfd4f012996a6a1d8900e91b5c0fb"},
+ {file = "pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:3fb702cd90b0446a3a1c5e470bfa0dd23c0233b676a9099ddcc964fa6ca13898"},
+ {file = "pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b8458003118a712e66286df6a707db01c52c0f52f7db8e4a38f0da1d3b94fc4e"},
+ {file = "pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:372429a130e469c9cd698925ce5fc50940b7a1336b0d82038e63d5bbc4edc519"},
+ {file = "pydantic_core-2.46.4-cp314-cp314-win32.whl", hash = "sha256:85bb3611ff1802f3ee7fdd7dbff26b56f343fb432d57a4728fdd49b6ef35e2f4"},
+ {file = "pydantic_core-2.46.4-cp314-cp314-win_amd64.whl", hash = "sha256:811ff8e9c313ab425368bcbb36e5c4ebd7108c2bbf4e4089cfbb0b01eff63fac"},
+ {file = "pydantic_core-2.46.4-cp314-cp314-win_arm64.whl", hash = "sha256:bfec22eab3c8cc2ceec0248aec886624116dc079afa027ecc8ad4a7e62010f8a"},
+ {file = "pydantic_core-2.46.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:af8244b2bef6aaad6d92cda81372de7f8c8d36c9f0c3ea36e827c60e7d9467a0"},
+ {file = "pydantic_core-2.46.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a4330cdbc57162e4b3aa303f588ba752257694c9c9be3e7ebb11b4aca659b5d"},
+ {file = "pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c61fc04a3d840155ff08e475a04809278972fe6aef51e2720554e96367e34b"},
+ {file = "pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c50f2528cf200c5eed56faf3f4e22fcd5f38c157a8b78576e6ba3168ec35f000"},
+ {file = "pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cbe8b01f948de4286c74cdd6c667aceb38f5c1e26f0693b3983d9d74887c65e"},
+ {file = "pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:617d7e2ca7dcb8c5cf6bcb8c59b8832c94b36196bbf1cbd1bfb56ed341905edd"},
+ {file = "pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7027560ee92211647d0d34e3f7cd6f50da56399d26a9c8ad0da286d3869a53f3"},
+ {file = "pydantic_core-2.46.4-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:f99626688942fb746e545232e7726926f3be91b5975f8b55327665fafda991c7"},
+ {file = "pydantic_core-2.46.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff"},
+ {file = "pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:97e7cf2be5c77b7d1a9713a05605d49460d02c6078d38d8bef3cbe323c548424"},
+ {file = "pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:3bf92c5d0e00fefaab325a4d27828fe6b6e2a21848686b5b60d2d9eeb09d76c6"},
+ {file = "pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:3ecbc122d18468d06ca279dc26a8c2e2d5acb10943bb35e36ae92096dc3b5565"},
+ {file = "pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02"},
+ {file = "pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5"},
+ {file = "pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596"},
+ {file = "pydantic_core-2.46.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:fd8b3d9fd264be37976686c7f65cd52a83f5e84f4bfd2adf9c1d469676bbb6ae"},
+ {file = "pydantic_core-2.46.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9f444c499b3eefd3a92e348059471ea0c3a6e303d9c1cec09fa748fd9f895201"},
+ {file = "pydantic_core-2.46.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3447661d99f75a3683a4cf5c87da72f2161964611864dbbeac7fbb118bb4bfc0"},
+ {file = "pydantic_core-2.46.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8b9bab013d1c7a79d3501ff86d0bc9c31bf587db4551677b96bec07df78c6b15"},
+ {file = "pydantic_core-2.46.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d995260fdf4e1db774581b4900e0f832abe3c7c84996726bbc161b19c8f29e76"},
+ {file = "pydantic_core-2.46.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f13a646d65d09fbf1bc6b3a9635d30095c8e7e5cc419ff35ecc563c5fd04cd49"},
+ {file = "pydantic_core-2.46.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432c179df7874eeb73307aad2df0755e1ae0efa61ff0ea89b93e194411ae3928"},
+ {file = "pydantic_core-2.46.4-cp39-cp39-manylinux_2_31_riscv64.whl", hash = "sha256:e68b7a074f65a2fd746c52a7ce6142ab7006074ac269ace0c25cd8ba171f8066"},
+ {file = "pydantic_core-2.46.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4a05d69cba51d852c5c3e92758653245a50c0b646ced0cf05bd793ed592839d6"},
+ {file = "pydantic_core-2.46.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:228ee9bae8bef5b1e97ec58302f80357c37199e0d0a99174e138d28e6957b9d9"},
+ {file = "pydantic_core-2.46.4-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:10e17cbb10a330363733efc4d7c4d0dd827ac0909b8f6a6542298fed1ea62f29"},
+ {file = "pydantic_core-2.46.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:91a06d2e259ecfbd8c901d70c3c507900458498142b3026a296b7de4d1322cc9"},
+ {file = "pydantic_core-2.46.4-cp39-cp39-win32.whl", hash = "sha256:d80ee3d731373b24cebbc10d689ca4ee1875caf0d5703a245db18efd4dd37fc1"},
+ {file = "pydantic_core-2.46.4-cp39-cp39-win_amd64.whl", hash = "sha256:3be77f45df024d789a672ae34f8b06fb346c4f9f46ea714956660ea4862e89ac"},
+ {file = "pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:14d4edf427bdcf950a8a02d7cb44a08614388dd6e1bdcbf4f67504fa7887da9c"},
+ {file = "pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:0ce40cd7b21210e99342afafbd4d0f76d784eb5b1d60f3bdc566be4983c6c73b"},
+ {file = "pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90884113d8b48f760e9587002789ddd741e76ab9f89518cd1e43b1f1a52ec44b"},
+ {file = "pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66ce7632c22d837c95301830e111ad0128a32b8207533b60896a96c4915192ea"},
+ {file = "pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7"},
+ {file = "pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df"},
+ {file = "pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526"},
+ {file = "pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0"},
+ {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0c563b08bca408dc7f65f700633d8442fffb2421fc47b8101377e9fd65051ff0"},
+ {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:db06ffe51636ffe9ca531fe9023dd64bdd794be8754cb5df57c5498ae5b518a7"},
+ {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:133878133d271ade3d41d1bfb2a45ec38dbdbda40bc065921c6b04e4630127e2"},
+ {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9bc519fbf2b7578398853d815009ae5e4d4603d12f4e3f91da8c06852d3da3e9"},
+ {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c7a7bd4e39e8e4c12c39cd480356842b6a8a06e41b23a55a5e3e191718838ddf"},
+ {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:d396ec2b979760aaf3218e76c24e65bd0aca24983298653b3a9d7a45f9e47b30"},
+ {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:86e1a4418c6cd97d60c95c71164158eaf7324fae7b0923264016baa993eba6fc"},
+ {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:d51026d73fcfd93610abc7b27789c26b313920fcfb20e27462d74a7f8b06e983"},
+ {file = "pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1"},
+]
+
+[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"
+groups = ["dev"]
+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 = "9.1.1"
+description = "pytest: simple powerful testing with Python"
+optional = false
+python-versions = ">=3.10"
+groups = ["dev"]
+files = [
+ {file = "pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c"},
+ {file = "pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313"},
+]
+
+[package.dependencies]
+colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""}
+exceptiongroup = {version = ">=1", markers = "python_version < \"3.11\""}
+iniconfig = ">=1.0.1"
+packaging = ">=22"
+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.4.0"
+description = "Pytest support for asyncio"
+optional = false
+python-versions = ">=3.10"
+groups = ["dev"]
+files = [
+ {file = "pytest_asyncio-1.4.0-py3-none-any.whl", hash = "sha256:933ca923a23075a87fb7070c0ec272a6848489824d887c85c812670932835aa1"},
+ {file = "pytest_asyncio-1.4.0.tar.gz", hash = "sha256:c6c0d2259945122819f171a32ecea2c349ead889ee28176caaf492143424be42"},
+]
+
+[package.dependencies]
+backports-asyncio-runner = {version = ">=1.1,<2", markers = "python_version < \"3.11\""}
+pytest = ">=8.4,<10"
+typing-extensions = {version = ">=4.12", markers = "python_version < \"3.13\""}
+
+[package.extras]
+docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1)", "sphinx-tabs (>=3.5)"]
+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"
+groups = ["dev"]
+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"
+groups = ["dev"]
+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 = "ruff"
+version = "0.11.5"
+description = "An extremely fast Python linter and code formatter, written in Rust."
+optional = false
+python-versions = ">=3.7"
+groups = ["dev"]
+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"
+groups = ["dev"]
+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"
+groups = ["dev"]
+markers = "python_version == \"3.10\""
+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.20260716"
+description = "Typing stubs for python-dateutil"
+optional = false
+python-versions = ">=3.10"
+groups = ["dev"]
+files = [
+ {file = "types_python_dateutil-2.9.0.20260716-py3-none-any.whl", hash = "sha256:1ae41d51a5c5f6bbeeb7f1f34df7086d55ff1605cf88d2ed71a7a276e1a7794e"},
+ {file = "types_python_dateutil-2.9.0.20260716.tar.gz", hash = "sha256:1d55d1c3024bdb4861bb6a6622c9ec800c433d87bdc5b16fb84cdd0eed4ef2cb"},
+]
+
+[[package]]
+name = "typing-extensions"
+version = "4.16.0"
+description = "Backported and Experimental Type Hints for Python 3.9+"
+optional = false
+python-versions = ">=3.9"
+groups = ["main", "dev"]
+files = [
+ {file = "typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8"},
+ {file = "typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5"},
+]
+
+[[package]]
+name = "typing-inspection"
+version = "0.4.2"
+description = "Runtime typing introspection tools"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+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.7.0"
+description = "HTTP library with thread-safe connection pooling, file post, and more."
+optional = false
+python-versions = ">=3.10"
+groups = ["dev"]
+files = [
+ {file = "urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897"},
+ {file = "urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c"},
+]
+
+[package.extras]
+brotli = ["brotli (>=1.2.0) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=1.2.0.0) ; platform_python_implementation != \"CPython\""]
+h2 = ["h2 (>=4,<5)"]
+socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
+zstd = ["backports-zstd (>=1.0.0) ; python_version < \"3.14\""]
+
+[[package]]
+name = "yarl"
+version = "1.24.5"
+description = "Yet another URL library"
+optional = true
+python-versions = ">=3.10"
+groups = ["main"]
+markers = "extra == \"aiohttp\""
+files = [
+ {file = "yarl-1.24.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88f50c94e21a0a7f14042c015b0eba1881af78562e7bf007e0033e624da59750"},
+ {file = "yarl-1.24.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6efbccc3d7f75d5b03105172a8dc86d82ba4da86817952529dd93185f4a88be2"},
+ {file = "yarl-1.24.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0ebfaffe1a16cb72141c8e09f18cc76856dbe58639f393a4f2b26e474b96b871"},
+ {file = "yarl-1.24.5-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8ac73abdc7ab75610f95a8fd994c6457e87752b02a63987e188f937a1fc180f0"},
+ {file = "yarl-1.24.5-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4d97a951a81039050e45f04e96689b58b8243fa5e62aa14fe67cb6075300885e"},
+ {file = "yarl-1.24.5-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fe7b7bb170daccbba19ad33012d2b15f1e7942296fd4d45fc1b79013da8cc0f2"},
+ {file = "yarl-1.24.5-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:89a1bbb58e0e3f7a283653d854b1e95d65e5cfd4af224dac5f02629ec1a3e621"},
+ {file = "yarl-1.24.5-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7fa5e51397466ea7e98de493fa2ff1b8193cfef8a7b0f9b4842f92d342df0dba"},
+ {file = "yarl-1.24.5-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4103b77b8a8225e413107d2349b65eb3c1c52627b5cc5c3c4c1c6a798b218950"},
+ {file = "yarl-1.24.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f9f3e9c8a9ecffa57bef8fb4fa19e5fa4d2d8307cf6bac5b1fca5e5860f4ba00"},
+ {file = "yarl-1.24.5-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c0ebc836c47a6477e182169c6a476fc691d12b518894bf7dd2572f0d59f1c7ed"},
+ {file = "yarl-1.24.5-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:96d30286dd02679e32a39aa8f0b7498fc847fcda46cfc09df5513e82ce252440"},
+ {file = "yarl-1.24.5-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:fd8c81f346b58f45818d09ea11db69a8d5fd34a224b79871f6d44f12cd7977b1"},
+ {file = "yarl-1.24.5-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:5c55256dee8f4b27bfbf636c8363383c7c8db7890c7cba5217d7bd5f5f21dab6"},
+ {file = "yarl-1.24.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9f4d8cf085a4c6a40fb97ea0f46938a8df43c85d31f9d45e2a8867ea9293790d"},
+ {file = "yarl-1.24.5-cp310-cp310-win_amd64.whl", hash = "sha256:240cbec09667c1fed4c6cd0060b9ec57332427d7441289a2ed8875dc9fb2b224"},
+ {file = "yarl-1.24.5-cp310-cp310-win_arm64.whl", hash = "sha256:8a6987eaad834cb32dd57d9d582225f0054a5d1af706ccfbbdba735af4927e13"},
+ {file = "yarl-1.24.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2c1fe720934a16ea8e7146175cba2126f87f54912c8c5435e7f7c7a51ef808d3"},
+ {file = "yarl-1.24.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c687ed078e145f5fd53a14854beff320e1d2ab76df03e2009c98f39a0f68f39a"},
+ {file = "yarl-1.24.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:709f1efed56c4a145793c046cd4939f9959bcd818979a787b77d8e09c57a0840"},
+ {file = "yarl-1.24.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:874019bd513008b009f58657134e5d0c5e030b3559bd0553976837adf52fe966"},
+ {file = "yarl-1.24.5-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a4582acf7ef76482f6f511ebaf1946dae7f2e85ec4728b81a678c01df63bd723"},
+ {file = "yarl-1.24.5-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2cabe6546e41dabe439999a23fcb5246e0c3b595b4315b96ef755252be90caeb"},
+ {file = "yarl-1.24.5-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:17f57620f5475b3c69109376cc87e42a7af5db13c9398e4292772a706ff10780"},
+ {file = "yarl-1.24.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:570fec8fbd22b032733625f03f10b7ff023bc399213db15e72a7acaef28c2f4e"},
+ {file = "yarl-1.24.5-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5fede79c6f73ff2c3ef822864cb1ada23196e62756df53bc6231d351a49516a2"},
+ {file = "yarl-1.24.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8ccf9aca873b767977c73df497a85dbedee4ee086ae9ae49dc461333b9b79f58"},
+ {file = "yarl-1.24.5-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:ad5d8201d310b031e6cd839d9bac2d4e5a01533ce5d3d5b50b7de1ef3af1de61"},
+ {file = "yarl-1.24.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:841f0852f48fefea3b12c9dfec00704dfa3aef5215d0e3ce564bb3d7cd8d57c6"},
+ {file = "yarl-1.24.5-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:9baafc71b04f8f4bb0703b21d6fc9f0c30b346c636a532ff16ec8491a5ea4b1f"},
+ {file = "yarl-1.24.5-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d897129df1a22b12aeed2c2c98df0785a2e8e6e0bde87b389491d0025c187077"},
+ {file = "yarl-1.24.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dd625535328fd9882374356269227670189adfcc6a2d90284f323c05862eecbd"},
+ {file = "yarl-1.24.5-cp311-cp311-win_amd64.whl", hash = "sha256:f4239bbec5a3577ddb49e4b50aeb32d8e5792098262ae2f63723f916a29b1a25"},
+ {file = "yarl-1.24.5-cp311-cp311-win_arm64.whl", hash = "sha256:3ac6aff147deb9c09461b2d4bbdf6256831198f5d8a23f5d37138213090b6d8a"},
+ {file = "yarl-1.24.5-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d693396e5aea78db03decd60aec9ece16c9b40ba00a587f089615ff4e718a81d"},
+ {file = "yarl-1.24.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3363fcc96e665878946ad7a106b9a13eac0541766a690ef287c0232ac768b6ec"},
+ {file = "yarl-1.24.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9d399bdcfb4a0f659b9b3788bbc89babe63d9a6a65aacdf4d4e7065ff2e6316c"},
+ {file = "yarl-1.24.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90333fd89b43c0d08ac85f3f1447593fc2c66de18c3d6378d7125ea118dc7a54"},
+ {file = "yarl-1.24.5-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:665b0a2c463cc9423dd647e0bfd9f4ccc9b50f768c55304d5e9f80b177c1de12"},
+ {file = "yarl-1.24.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e006d3a974c4ee19512e5f058abedb6eef36a5e553c14812bdeba1758d812e6d"},
+ {file = "yarl-1.24.5-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e7d42c531243450ef0d4d9c172e7ed6ef052640f195629065041b5add4e058d1"},
+ {file = "yarl-1.24.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f08c7513ecef5aad65687bfdf6bc601ae9fccd04a42904501f8f7141abad9eb9"},
+ {file = "yarl-1.24.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6c95b17fe34ed802f17e205112e6e10db92275c34fee290aa9bdc55a9c724027"},
+ {file = "yarl-1.24.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:56b149b22de33b23b0c6077ab9518c6dcb538ad462e1830e68d06591ccf6e38b"},
+ {file = "yarl-1.24.5-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:a8fe66b8f300da93798025a785a5b90b42f3810dc2b72283ff84a41aaaebc293"},
+ {file = "yarl-1.24.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:377fe3732edbaf78ee74efdf2c9f49f6e99f20e7f9d2649fda3eb4badd77d76e"},
+ {file = "yarl-1.24.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:e8ffa78582120024f476a611d7befc123cee59e47e8309d470cf667d806e613b"},
+ {file = "yarl-1.24.5-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:daba5e594f06114e37db186efd2dd916609071e59daca901a0a2e71f02b142ce"},
+ {file = "yarl-1.24.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:65be18ec59496c13908f02a2472751d9ef840b4f3fb5726f129306bf6a2a7bba"},
+ {file = "yarl-1.24.5-cp312-cp312-win_amd64.whl", hash = "sha256:a929d878fec099030c292803b31e5d5540a7b6a31e6a3cc76cb4685fc2a2f51b"},
+ {file = "yarl-1.24.5-cp312-cp312-win_arm64.whl", hash = "sha256:7ce27823052e2013b597e0c738b13e7e36b8ccb9400df8959417b052ab0fd92c"},
+ {file = "yarl-1.24.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:79af890482fc94648e8cde4c68620378f7fef60932710fa17a66abc039244da2"},
+ {file = "yarl-1.24.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:46c2f213e23a04b93a392942d782eb9e413e6ef6bf7c8c53884e599a5c174dcb"},
+ {file = "yarl-1.24.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92ab3e11448f2ff7bf53c5a26eff0edc086898ec8b21fb154b85839ce1d88075"},
+ {file = "yarl-1.24.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ebb0ec7f17803063d5aeb982f3b1bd2b2f4e4fae6751226cbd6ba1fcfe9e63ff"},
+ {file = "yarl-1.24.5-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:82632daed195dcc8ea664e8556dc9bdbd671960fb3776bd92806ce05792c2448"},
+ {file = "yarl-1.24.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:53e549287ef628fecba270045c9701b0c564563a9b0577d24a4ec75b8ab8040f"},
+ {file = "yarl-1.24.5-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fcd3b77e2f17bbe4ca56ec7bcb07992647d19d0b9c05d84886dcd6f9eb810afd"},
+ {file = "yarl-1.24.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d46b86567dd4e248c6c159fcbcdcce01e0a5c8a7cd2334a0fff759d0fa075b16"},
+ {file = "yarl-1.24.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7f72c74aa99359e27a2ee8d6613fefa28b5f76a983c083074dfc2aaa4ab46213"},
+ {file = "yarl-1.24.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3f45789ce415a7ec0820dc4f82925f9b5f7732070be1dec1f5f23ec381435a24"},
+ {file = "yarl-1.24.5-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:6e73e7fe93f17a7b191f52ec9da9dd8c06a8fe735a1ecbd13b97d1c723bff385"},
+ {file = "yarl-1.24.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4a36f9becdd4c5c52a20c3e9484128b070b1dcfc8944c006f3a528295a359a9c"},
+ {file = "yarl-1.24.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:7bcbe0fcf850eae67b6b01749815a4f7161c560a844c769ad7b48fcd99f791c4"},
+ {file = "yarl-1.24.5-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:24e861e9630e0daddcb9191fb187f60f034e17a4426f8101279f0c475cd74144"},
+ {file = "yarl-1.24.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9335a099ad87287c37fe5d1a982ff392fa5efe5d14b40a730b1ec1d6a41382b4"},
+ {file = "yarl-1.24.5-cp313-cp313-win_amd64.whl", hash = "sha256:2dbe06fc16bc91502bca713704022182e5729861ae00277c3a23354b40929740"},
+ {file = "yarl-1.24.5-cp313-cp313-win_arm64.whl", hash = "sha256:6b8536851f9f65e7f00c7a1d49ba7f2be0ffe2c11555367fc9f50d9f842410a1"},
+ {file = "yarl-1.24.5-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:2729fcfc4f6a596fb0c50f32090400aa9367774ac296a00387e65098c0befa76"},
+ {file = "yarl-1.24.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ff330d3c30db4eb6b01d79e29d2d0b407a7ecad39cfd9ec993ece57396a2ec0d"},
+ {file = "yarl-1.24.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e42d75862735da90e7fc5a7b23db0c976f737113a54b3c9777a9b665e9cbff75"},
+ {file = "yarl-1.24.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a3732e66413163e72508da9eff9ce9d2846fde51fae45d3605393d3e6cd303e9"},
+ {file = "yarl-1.24.5-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5b8ee53be440a0cffc991a27be3057e0530122548dbe7c0892df08822fce5ede"},
+ {file = "yarl-1.24.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:af3aefa655adb5869491fa907e652290386800ae99cc50095cba71e2c6aefdca"},
+ {file = "yarl-1.24.5-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2120b96872df4a117cde97d270bac96aea7cc52205d305cf4611df694a487027"},
+ {file = "yarl-1.24.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:66410eb6345d467151934b49bfa70fb32f5b35a6140baa40ad97d6436abea2e9"},
+ {file = "yarl-1.24.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4af7b7e1be0a69bee8210735fe6dcfc38879adfac6d62e789d53ba432d1ffa41"},
+ {file = "yarl-1.24.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa139875ff98ab97da323cfadfaff08900d1ad42f1b5087b0b812a55c5a06373"},
+ {file = "yarl-1.24.5-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:0055afc45e864b92729ac7600e2d102c17bef060647e74bca75fa84d66b9ff36"},
+ {file = "yarl-1.24.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f0e466ed7511fe9d459a819edbc6c2585c0b6eabde9fa8a8947552468a7a6ef0"},
+ {file = "yarl-1.24.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:f141474e85b7e54998ec5180530a7cda99ab29e282fa50e0756d89981a9b43c5"},
+ {file = "yarl-1.24.5-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:e2935f8c39e3b03e83519292d78f075189978f3f4adc15a78144c7c8e2a1cba5"},
+ {file = "yarl-1.24.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9d1216a7f6f77836617dba35687c5b78a4170afc3c3f18fc788f785ba26565c4"},
+ {file = "yarl-1.24.5-cp314-cp314-win_amd64.whl", hash = "sha256:5ba4f78df2bcc19f764a4b26a8a4f5049c110090ad5825993aacb052bf8003ad"},
+ {file = "yarl-1.24.5-cp314-cp314-win_arm64.whl", hash = "sha256:9e4e16c73d717c5cf27626c524d0a2e261ad20e46932b2670f64ad5dde23e26f"},
+ {file = "yarl-1.24.5-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:e1ae548a9d901adca07899a4147a7c826bbcc06239d3ce9a59f57886a28a4c88"},
+ {file = "yarl-1.24.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:ff405d91509d88e8d44129cd87b18d70acd1f0c1aeabd7bc3c46792b1fe2acba"},
+ {file = "yarl-1.24.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:47e98aab9d8d82ff682e7b0b5dded33bf138a32b817fcf7fa3b27b2d7c412928"},
+ {file = "yarl-1.24.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f0a658a6d3fafee5c6f63c58f3e785c8c43c93fbc02bf9f2b6663f8185e0971f"},
+ {file = "yarl-1.24.5-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4377407001ca3c057773f44d8ddd6358fa5f691407c1ba92210bd3cf8d9e4c95"},
+ {file = "yarl-1.24.5-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7c0494a31a1ac5461a226e7947a9c9b78c44e1dc7185164fa7e9651557a5d9bc"},
+ {file = "yarl-1.24.5-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a7cff474ab7cd149765bb784cf6d78b32e18e20473fb7bda860bce98ab58e9da"},
+ {file = "yarl-1.24.5-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cbb833ccacdb5519eff9b8b71ee618cc2801c878e77e288775d77c3a2ced858a"},
+ {file = "yarl-1.24.5-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:82f75e05912e84b7a0fe57075d9c59de3cb352b928330f2eb69b2e1f54c3e1f0"},
+ {file = "yarl-1.24.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:16a2f5010280020e90f5330257e6944bc33e73593b136cc5a241e6c1dc292498"},
+ {file = "yarl-1.24.5-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:ffcd54362564dc1a30fb74d8b8a6e5a6b11ebd5e27266adc3b7427a21a6c9104"},
+ {file = "yarl-1.24.5-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0465ec8cedc2349b97a6b595ace64084a50c6e839eca40aa0626f38b8350e331"},
+ {file = "yarl-1.24.5-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:4db9aecb141cb7a5447171b57aa1ed3a8fee06af40b992ffc31206c0b0121550"},
+ {file = "yarl-1.24.5-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:f540c013589084679a6c7fac07096b10159737918174f5dfc5e11bf5bca4dfe6"},
+ {file = "yarl-1.24.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a61834fb15d81322d872eaafd333838ae7c9cea84067f232656f75965933d047"},
+ {file = "yarl-1.24.5-cp314-cp314t-win_amd64.whl", hash = "sha256:5c88e5815a49d289e599f3513aa7fde0bc2092ff188f99c940f007f90f53d104"},
+ {file = "yarl-1.24.5-cp314-cp314t-win_arm64.whl", hash = "sha256:cf139c02f5f23ef6532040a30ff662c00a318c952334f211046b8e60b7f17688"},
+ {file = "yarl-1.24.5-py3-none-any.whl", hash = "sha256:a33700d13d9b7d84fd10947b09ff69fb9a792e519c8cb9764a3ca70baa6c23a7"},
+ {file = "yarl-1.24.5.tar.gz", hash = "sha256:e81b83143bee16329c23db3c1b2d82b29892fcbcb849186d2f6e98a5abe9a57f"},
+]
+
+[package.dependencies]
+idna = ">=2.0"
+multidict = ">=4.0"
+propcache = ">=0.2.1"
+
+[extras]
+aiohttp = ["aiohttp", "httpx-aiohttp"]
+
+[metadata]
+lock-version = "2.1"
+python-versions = "^3.10"
+content-hash = "4eaeff06fb8e558535aa3729ed6f99c2398eaaba665bcdf9c096bb08d9cb5fc6"
diff --git a/seed/python-sdk/path-parameters/no-custom-config/pyproject.toml b/seed/python-sdk/path-parameters/no-custom-config/pyproject.toml
new file mode 100644
index 000000000000..095249318c30
--- /dev/null
+++ b/seed/python-sdk/path-parameters/no-custom-config/pyproject.toml
@@ -0,0 +1,102 @@
+[project]
+name = "fern_path-parameters"
+dynamic = ["version"]
+
+[tool.poetry]
+name = "fern_path-parameters"
+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/path-parameters/fern'
+
+[tool.poetry.dependencies]
+python = "^3.10"
+aiohttp = { version = ">=3.14.1,<4", optional = true, python = ">=3.10"}
+httpx = ">=0.21.2"
+httpx-aiohttp = { version = "^0.1.8", optional = true, python = ">=3.10"}
+pydantic = ">= 1.9.2"
+pydantic-core = ">=2.18.2,<3.0.0"
+typing_extensions = ">= 4.0.0"
+
+[tool.poetry.group.dev.dependencies]
+mypy = "==1.13.0"
+pytest = "^9.0.3"
+pytest-asyncio = "^1.0.0"
+pytest-xdist = "^3.6.1"
+python-dateutil = "^2.9.0"
+types-python-dateutil = "^2.9.0.20240316"
+urllib3 = ">=2.6.3,<3.0.0"
+ruff = "==0.11.5"
+
+[tool.pytest.ini_options]
+testpaths = [ "tests" ]
+asyncio_mode = "auto"
+norecursedirs = [ "src" ]
+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/path-parameters/no-custom-config/reference.md b/seed/python-sdk/path-parameters/no-custom-config/reference.md
new file mode 100644
index 000000000000..39537e6d0b63
--- /dev/null
+++ b/seed/python-sdk/path-parameters/no-custom-config/reference.md
@@ -0,0 +1,696 @@
+# Reference
+## Organizations
+client.organizations.get_organization(...) -> Organization
+
+-
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from seed import SeedPathParameters
+
+client = SeedPathParameters(
+ base_url="https://yourhost.com/path/to/api",
+)
+
+client.organizations.get_organization(
+ tenant_id="tenant_id",
+ organization_id="organization_id",
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**tenant_id:** `str`
+
+
+
+
+
+-
+
+**organization_id:** `str`
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.organizations.get_organization_user(...) -> User
+
+-
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from seed import SeedPathParameters
+
+client = SeedPathParameters(
+ base_url="https://yourhost.com/path/to/api",
+)
+
+client.organizations.get_organization_user(
+ tenant_id="tenant_id",
+ organization_id="organization_id",
+ user_id="user_id",
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**tenant_id:** `str`
+
+
+
+
+
+-
+
+**organization_id:** `str`
+
+
+
+
+
+-
+
+**user_id:** `str`
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.organizations.search_organizations(...) -> typing.List[Organization]
+
+-
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from seed import SeedPathParameters
+
+client = SeedPathParameters(
+ base_url="https://yourhost.com/path/to/api",
+)
+
+client.organizations.search_organizations(
+ tenant_id="tenant_id",
+ organization_id="organization_id",
+ limit=1,
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**tenant_id:** `str`
+
+
+
+
+
+-
+
+**organization_id:** `str`
+
+
+
+
+
+-
+
+**limit:** `typing.Optional[int]`
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+## User
+client.user.get_user(...) -> User
+
+-
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from seed import SeedPathParameters
+
+client = SeedPathParameters(
+ base_url="https://yourhost.com/path/to/api",
+)
+
+client.user.get_user(
+ tenant_id="tenant_id",
+ user_id="user_id",
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**tenant_id:** `str`
+
+
+
+
+
+-
+
+**user_id:** `str`
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.user.create_user(...) -> User
+
+-
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from seed import SeedPathParameters
+
+client = SeedPathParameters(
+ base_url="https://yourhost.com/path/to/api",
+)
+
+client.user.create_user(
+ tenant_id="tenant_id",
+ name="name",
+ tags=[
+ "tags",
+ "tags"
+ ],
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**tenant_id:** `str`
+
+
+
+
+
+-
+
+**request:** `User`
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.user.update_user(...) -> User
+
+-
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from seed import SeedPathParameters
+
+client = SeedPathParameters(
+ base_url="https://yourhost.com/path/to/api",
+)
+
+client.user.update_user(
+ tenant_id="tenant_id",
+ user_id="user_id",
+ name="name",
+ tags=[
+ "tags",
+ "tags"
+ ],
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**tenant_id:** `str`
+
+
+
+
+
+-
+
+**user_id:** `str`
+
+
+
+
+
+-
+
+**request:** `User`
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.user.search_users(...) -> typing.List[User]
+
+-
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from seed import SeedPathParameters
+
+client = SeedPathParameters(
+ base_url="https://yourhost.com/path/to/api",
+)
+
+client.user.search_users(
+ tenant_id="tenant_id",
+ user_id="user_id",
+ limit=1,
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**tenant_id:** `str`
+
+
+
+
+
+-
+
+**user_id:** `str`
+
+
+
+
+
+-
+
+**limit:** `typing.Optional[int]`
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.user.get_user_metadata(...) -> User
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Test endpoint with path parameter that has a text prefix (v{version})
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from seed import SeedPathParameters
+
+client = SeedPathParameters(
+ base_url="https://yourhost.com/path/to/api",
+)
+
+client.user.get_user_metadata(
+ tenant_id="tenant_id",
+ user_id="user_id",
+ version=1,
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**tenant_id:** `str`
+
+
+
+
+
+-
+
+**user_id:** `str`
+
+
+
+
+
+-
+
+**version:** `int`
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.user.get_user_specifics(...) -> User
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Test endpoint with path parameters listed in different order than found in path
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from seed import SeedPathParameters
+
+client = SeedPathParameters(
+ base_url="https://yourhost.com/path/to/api",
+)
+
+client.user.get_user_specifics(
+ tenant_id="tenant_id",
+ user_id="user_id",
+ version=1,
+ thought="thought",
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**tenant_id:** `str`
+
+
+
+
+
+-
+
+**user_id:** `str`
+
+
+
+
+
+-
+
+**version:** `int`
+
+
+
+
+
+-
+
+**thought:** `str`
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
diff --git a/seed/python-sdk/path-parameters/no-custom-config/requirements.txt b/seed/python-sdk/path-parameters/no-custom-config/requirements.txt
new file mode 100644
index 000000000000..443b1af40e27
--- /dev/null
+++ b/seed/python-sdk/path-parameters/no-custom-config/requirements.txt
@@ -0,0 +1,4 @@
+httpx>=0.21.2
+pydantic>= 1.9.2
+pydantic-core>=2.18.2,<3.0.0
+typing_extensions>= 4.0.0
diff --git a/seed/python-sdk/path-parameters/no-custom-config/snippet.json b/seed/python-sdk/path-parameters/no-custom-config/snippet.json
new file mode 100644
index 000000000000..c33c5d96b140
--- /dev/null
+++ b/seed/python-sdk/path-parameters/no-custom-config/snippet.json
@@ -0,0 +1,122 @@
+{
+ "types": {},
+ "endpoints": [
+ {
+ "example_identifier": "default",
+ "id": {
+ "path": "/{tenant_id}/organizations/{organization_id}/",
+ "method": "GET",
+ "identifier_override": "endpoint_organizations.getOrganization"
+ },
+ "snippet": {
+ "sync_client": "from seed import SeedPathParameters\n\nclient = SeedPathParameters(\n tenant_id=\"YOUR_TENANT_ID\",\n base_url=\"https://yourhost.com/path/to/api\",\n)\nclient.organizations.get_organization(\n organization_id=\"organization_id\",\n)\n",
+ "async_client": "import asyncio\n\nfrom seed import AsyncSeedPathParameters\n\nclient = AsyncSeedPathParameters(\n tenant_id=\"YOUR_TENANT_ID\",\n base_url=\"https://yourhost.com/path/to/api\",\n)\n\n\nasync def main() -> None:\n await client.organizations.get_organization(\n organization_id=\"organization_id\",\n )\n\n\nasyncio.run(main())\n",
+ "type": "python"
+ }
+ },
+ {
+ "example_identifier": "default",
+ "id": {
+ "path": "/{tenant_id}/organizations/{organization_id}/users/{user_id}",
+ "method": "GET",
+ "identifier_override": "endpoint_organizations.getOrganizationUser"
+ },
+ "snippet": {
+ "sync_client": "from seed import SeedPathParameters\n\nclient = SeedPathParameters(\n tenant_id=\"YOUR_TENANT_ID\",\n base_url=\"https://yourhost.com/path/to/api\",\n)\nclient.organizations.get_organization_user(\n organization_id=\"organization_id\",\n user_id=\"user_id\",\n)\n",
+ "async_client": "import asyncio\n\nfrom seed import AsyncSeedPathParameters\n\nclient = AsyncSeedPathParameters(\n tenant_id=\"YOUR_TENANT_ID\",\n base_url=\"https://yourhost.com/path/to/api\",\n)\n\n\nasync def main() -> None:\n await client.organizations.get_organization_user(\n organization_id=\"organization_id\",\n user_id=\"user_id\",\n )\n\n\nasyncio.run(main())\n",
+ "type": "python"
+ }
+ },
+ {
+ "example_identifier": "default",
+ "id": {
+ "path": "/{tenant_id}/organizations/{organization_id}/search",
+ "method": "GET",
+ "identifier_override": "endpoint_organizations.searchOrganizations"
+ },
+ "snippet": {
+ "sync_client": "from seed import SeedPathParameters\n\nclient = SeedPathParameters(\n tenant_id=\"YOUR_TENANT_ID\",\n base_url=\"https://yourhost.com/path/to/api\",\n)\nclient.organizations.search_organizations(\n organization_id=\"organization_id\",\n limit=1,\n)\n",
+ "async_client": "import asyncio\n\nfrom seed import AsyncSeedPathParameters\n\nclient = AsyncSeedPathParameters(\n tenant_id=\"YOUR_TENANT_ID\",\n base_url=\"https://yourhost.com/path/to/api\",\n)\n\n\nasync def main() -> None:\n await client.organizations.search_organizations(\n organization_id=\"organization_id\",\n limit=1,\n )\n\n\nasyncio.run(main())\n",
+ "type": "python"
+ }
+ },
+ {
+ "example_identifier": "default",
+ "id": {
+ "path": "/{tenant_id}/user/{user_id}",
+ "method": "GET",
+ "identifier_override": "endpoint_user.getUser"
+ },
+ "snippet": {
+ "sync_client": "from seed import SeedPathParameters\n\nclient = SeedPathParameters(\n tenant_id=\"YOUR_TENANT_ID\",\n base_url=\"https://yourhost.com/path/to/api\",\n)\nclient.user.get_user(\n user_id=\"user_id\",\n)\n",
+ "async_client": "import asyncio\n\nfrom seed import AsyncSeedPathParameters\n\nclient = AsyncSeedPathParameters(\n tenant_id=\"YOUR_TENANT_ID\",\n base_url=\"https://yourhost.com/path/to/api\",\n)\n\n\nasync def main() -> None:\n await client.user.get_user(\n user_id=\"user_id\",\n )\n\n\nasyncio.run(main())\n",
+ "type": "python"
+ }
+ },
+ {
+ "example_identifier": "default",
+ "id": {
+ "path": "/{tenant_id}/user/",
+ "method": "POST",
+ "identifier_override": "endpoint_user.createUser"
+ },
+ "snippet": {
+ "sync_client": "from seed import SeedPathParameters\n\nclient = SeedPathParameters(\n tenant_id=\"YOUR_TENANT_ID\",\n base_url=\"https://yourhost.com/path/to/api\",\n)\nclient.user.create_user(\n name=\"name\",\n tags=[\"tags\", \"tags\"],\n)\n",
+ "async_client": "import asyncio\n\nfrom seed import AsyncSeedPathParameters\n\nclient = AsyncSeedPathParameters(\n tenant_id=\"YOUR_TENANT_ID\",\n base_url=\"https://yourhost.com/path/to/api\",\n)\n\n\nasync def main() -> None:\n await client.user.create_user(\n name=\"name\",\n tags=[\"tags\", \"tags\"],\n )\n\n\nasyncio.run(main())\n",
+ "type": "python"
+ }
+ },
+ {
+ "example_identifier": "default",
+ "id": {
+ "path": "/{tenant_id}/user/{user_id}",
+ "method": "PATCH",
+ "identifier_override": "endpoint_user.updateUser"
+ },
+ "snippet": {
+ "sync_client": "from seed import SeedPathParameters\n\nclient = SeedPathParameters(\n tenant_id=\"YOUR_TENANT_ID\",\n base_url=\"https://yourhost.com/path/to/api\",\n)\nclient.user.update_user(\n user_id=\"user_id\",\n name=\"name\",\n tags=[\"tags\", \"tags\"],\n)\n",
+ "async_client": "import asyncio\n\nfrom seed import AsyncSeedPathParameters\n\nclient = AsyncSeedPathParameters(\n tenant_id=\"YOUR_TENANT_ID\",\n base_url=\"https://yourhost.com/path/to/api\",\n)\n\n\nasync def main() -> None:\n await client.user.update_user(\n user_id=\"user_id\",\n name=\"name\",\n tags=[\"tags\", \"tags\"],\n )\n\n\nasyncio.run(main())\n",
+ "type": "python"
+ }
+ },
+ {
+ "example_identifier": "default",
+ "id": {
+ "path": "/{tenant_id}/user/{user_id}/search",
+ "method": "GET",
+ "identifier_override": "endpoint_user.searchUsers"
+ },
+ "snippet": {
+ "sync_client": "from seed import SeedPathParameters\n\nclient = SeedPathParameters(\n tenant_id=\"YOUR_TENANT_ID\",\n base_url=\"https://yourhost.com/path/to/api\",\n)\nclient.user.search_users(\n user_id=\"user_id\",\n limit=1,\n)\n",
+ "async_client": "import asyncio\n\nfrom seed import AsyncSeedPathParameters\n\nclient = AsyncSeedPathParameters(\n tenant_id=\"YOUR_TENANT_ID\",\n base_url=\"https://yourhost.com/path/to/api\",\n)\n\n\nasync def main() -> None:\n await client.user.search_users(\n user_id=\"user_id\",\n limit=1,\n )\n\n\nasyncio.run(main())\n",
+ "type": "python"
+ }
+ },
+ {
+ "example_identifier": "default",
+ "id": {
+ "path": "/{tenant_id}/user/{user_id}/metadata/v{version}",
+ "method": "GET",
+ "identifier_override": "endpoint_user.getUserMetadata"
+ },
+ "snippet": {
+ "sync_client": "from seed import SeedPathParameters\n\nclient = SeedPathParameters(\n tenant_id=\"YOUR_TENANT_ID\",\n base_url=\"https://yourhost.com/path/to/api\",\n)\nclient.user.get_user_metadata(\n user_id=\"user_id\",\n version=1,\n)\n",
+ "async_client": "import asyncio\n\nfrom seed import AsyncSeedPathParameters\n\nclient = AsyncSeedPathParameters(\n tenant_id=\"YOUR_TENANT_ID\",\n base_url=\"https://yourhost.com/path/to/api\",\n)\n\n\nasync def main() -> None:\n await client.user.get_user_metadata(\n user_id=\"user_id\",\n version=1,\n )\n\n\nasyncio.run(main())\n",
+ "type": "python"
+ }
+ },
+ {
+ "example_identifier": "default",
+ "id": {
+ "path": "/{tenant_id}/user/{user_id}/specifics/{version}/{thought}",
+ "method": "GET",
+ "identifier_override": "endpoint_user.getUserSpecifics"
+ },
+ "snippet": {
+ "sync_client": "from seed import SeedPathParameters\n\nclient = SeedPathParameters(\n tenant_id=\"YOUR_TENANT_ID\",\n base_url=\"https://yourhost.com/path/to/api\",\n)\nclient.user.get_user_specifics(\n user_id=\"user_id\",\n version=1,\n thought=\"thought\",\n)\n",
+ "async_client": "import asyncio\n\nfrom seed import AsyncSeedPathParameters\n\nclient = AsyncSeedPathParameters(\n tenant_id=\"YOUR_TENANT_ID\",\n base_url=\"https://yourhost.com/path/to/api\",\n)\n\n\nasync def main() -> None:\n await client.user.get_user_specifics(\n user_id=\"user_id\",\n version=1,\n thought=\"thought\",\n )\n\n\nasyncio.run(main())\n",
+ "type": "python"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/seed/python-sdk/path-parameters/no-custom-config/src/seed/__init__.py b/seed/python-sdk/path-parameters/no-custom-config/src/seed/__init__.py
new file mode 100644
index 000000000000..540aa71bdd60
--- /dev/null
+++ b/seed/python-sdk/path-parameters/no-custom-config/src/seed/__init__.py
@@ -0,0 +1,59 @@
+# This file was auto-generated by Fern from our API Definition.
+
+# isort: skip_file
+
+import typing
+from importlib import import_module
+
+if typing.TYPE_CHECKING:
+ from . import organizations, user
+ from ._default_clients import DefaultAioHttpClient, DefaultAsyncHttpxClient
+ from .client import AsyncSeedPathParameters, SeedPathParameters
+ from .organizations import Organization
+ from .user import User
+ from .version import __version__
+_dynamic_imports: typing.Dict[str, str] = {
+ "AsyncSeedPathParameters": ".client",
+ "DefaultAioHttpClient": "._default_clients",
+ "DefaultAsyncHttpxClient": "._default_clients",
+ "Organization": ".organizations",
+ "SeedPathParameters": ".client",
+ "User": ".user",
+ "__version__": ".version",
+ "organizations": ".organizations",
+ "user": ".user",
+}
+
+
+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__ = [
+ "AsyncSeedPathParameters",
+ "DefaultAioHttpClient",
+ "DefaultAsyncHttpxClient",
+ "Organization",
+ "SeedPathParameters",
+ "User",
+ "__version__",
+ "organizations",
+ "user",
+]
diff --git a/seed/python-sdk/path-parameters/no-custom-config/src/seed/_default_clients.py b/seed/python-sdk/path-parameters/no-custom-config/src/seed/_default_clients.py
new file mode 100644
index 000000000000..46c945b83183
--- /dev/null
+++ b/seed/python-sdk/path-parameters/no-custom-config/src/seed/_default_clients.py
@@ -0,0 +1,32 @@
+# 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_path-parameters[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/path-parameters/no-custom-config/src/seed/client.py b/seed/python-sdk/path-parameters/no-custom-config/src/seed/client.py
new file mode 100644
index 000000000000..db50211b5008
--- /dev/null
+++ b/seed/python-sdk/path-parameters/no-custom-config/src/seed/client.py
@@ -0,0 +1,219 @@
+# 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 .organizations.client import AsyncOrganizationsClient, OrganizationsClient
+ from .user.client import AsyncUserClient, UserClient
+
+
+class SeedPathParameters:
+ """
+ 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.
+
+ tenant_id : str
+ 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.
+
+ max_retries : typing.Optional[int]
+ The default maximum number of retries for failed requests. Defaults to 2. Per-request `max_retries` in `request_options` takes precedence over this value.
+
+ stream_reconnection_enabled : typing.Optional[bool]
+ Whether to automatically reconnect on stream disconnection for resumable streaming endpoints. Defaults to True. Per-request `stream_reconnection_enabled` in `request_options` takes precedence over this value.
+
+ max_stream_reconnection_attempts : typing.Optional[int]
+ The maximum number of reconnection attempts for resumable streaming endpoints. Defaults to no limit. Per-request `max_stream_reconnection_attempts` in `request_options` takes precedence over this value.
+
+ 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 SeedPathParameters
+
+ client = SeedPathParameters(
+ tenant_id="YOUR_TENANT_ID",
+ base_url="https://yourhost.com/path/to/api",
+ )
+ """
+
+ def __init__(
+ self,
+ *,
+ base_url: str,
+ tenant_id: str,
+ headers: typing.Optional[typing.Dict[str, str]] = None,
+ timeout: typing.Optional[float] = None,
+ max_retries: typing.Optional[int] = None,
+ stream_reconnection_enabled: typing.Optional[bool] = None,
+ max_stream_reconnection_attempts: typing.Optional[int] = 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 None
+ _defaulted_max_retries = max_retries if max_retries is not None else 2
+ self._client_wrapper = SyncClientWrapper(
+ base_url=base_url,
+ tenant_id=tenant_id,
+ 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,
+ max_retries=_defaulted_max_retries,
+ stream_reconnection_enabled=stream_reconnection_enabled,
+ max_stream_reconnection_attempts=max_stream_reconnection_attempts,
+ logging=logging,
+ )
+ self._organizations: typing.Optional[OrganizationsClient] = None
+ self._user: typing.Optional[UserClient] = None
+
+ @property
+ def organizations(self):
+ if self._organizations is None:
+ from .organizations.client import OrganizationsClient # noqa: E402
+
+ self._organizations = OrganizationsClient(client_wrapper=self._client_wrapper)
+ return self._organizations
+
+ @property
+ def user(self):
+ if self._user is None:
+ from .user.client import UserClient # noqa: E402
+
+ self._user = UserClient(client_wrapper=self._client_wrapper)
+ return self._user
+
+
+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 AsyncSeedPathParameters:
+ """
+ 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.
+
+ tenant_id : str
+ 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.
+
+ max_retries : typing.Optional[int]
+ The default maximum number of retries for failed requests. Defaults to 2. Per-request `max_retries` in `request_options` takes precedence over this value.
+
+ stream_reconnection_enabled : typing.Optional[bool]
+ Whether to automatically reconnect on stream disconnection for resumable streaming endpoints. Defaults to True. Per-request `stream_reconnection_enabled` in `request_options` takes precedence over this value.
+
+ max_stream_reconnection_attempts : typing.Optional[int]
+ The maximum number of reconnection attempts for resumable streaming endpoints. Defaults to no limit. Per-request `max_stream_reconnection_attempts` in `request_options` takes precedence over this value.
+
+ 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 AsyncSeedPathParameters
+
+ client = AsyncSeedPathParameters(
+ tenant_id="YOUR_TENANT_ID",
+ base_url="https://yourhost.com/path/to/api",
+ )
+ """
+
+ def __init__(
+ self,
+ *,
+ base_url: str,
+ tenant_id: str,
+ headers: typing.Optional[typing.Dict[str, str]] = None,
+ timeout: typing.Optional[float] = None,
+ max_retries: typing.Optional[int] = None,
+ stream_reconnection_enabled: typing.Optional[bool] = None,
+ max_stream_reconnection_attempts: typing.Optional[int] = 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 None
+ _defaulted_max_retries = max_retries if max_retries is not None else 2
+ self._client_wrapper = AsyncClientWrapper(
+ base_url=base_url,
+ tenant_id=tenant_id,
+ 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,
+ max_retries=_defaulted_max_retries,
+ stream_reconnection_enabled=stream_reconnection_enabled,
+ max_stream_reconnection_attempts=max_stream_reconnection_attempts,
+ logging=logging,
+ )
+ self._organizations: typing.Optional[AsyncOrganizationsClient] = None
+ self._user: typing.Optional[AsyncUserClient] = None
+
+ @property
+ def organizations(self):
+ if self._organizations is None:
+ from .organizations.client import AsyncOrganizationsClient # noqa: E402
+
+ self._organizations = AsyncOrganizationsClient(client_wrapper=self._client_wrapper)
+ return self._organizations
+
+ @property
+ def user(self):
+ if self._user is None:
+ from .user.client import AsyncUserClient # noqa: E402
+
+ self._user = AsyncUserClient(client_wrapper=self._client_wrapper)
+ return self._user
diff --git a/seed/python-sdk/path-parameters/no-custom-config/src/seed/core/__init__.py b/seed/python-sdk/path-parameters/no-custom-config/src/seed/core/__init__.py
new file mode 100644
index 000000000000..5bc159a110f2
--- /dev/null
+++ b/seed/python-sdk/path-parameters/no-custom-config/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/path-parameters/no-custom-config/src/seed/core/api_error.py b/seed/python-sdk/path-parameters/no-custom-config/src/seed/core/api_error.py
new file mode 100644
index 000000000000..6f850a60cba3
--- /dev/null
+++ b/seed/python-sdk/path-parameters/no-custom-config/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/path-parameters/no-custom-config/src/seed/core/client_wrapper.py b/seed/python-sdk/path-parameters/no-custom-config/src/seed/core/client_wrapper.py
new file mode 100644
index 000000000000..36c4267995d7
--- /dev/null
+++ b/seed/python-sdk/path-parameters/no-custom-config/src/seed/core/client_wrapper.py
@@ -0,0 +1,140 @@
+# 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,
+ *,
+ tenant_id: str,
+ headers: typing.Optional[typing.Dict[str, str]] = None,
+ base_url: str,
+ timeout: typing.Optional[float] = None,
+ max_retries: int = 2,
+ stream_reconnection_enabled: typing.Optional[bool] = None,
+ max_stream_reconnection_attempts: typing.Optional[int] = None,
+ logging: typing.Optional[typing.Union[LogConfig, Logger]] = None,
+ ):
+ self._tenant_id = tenant_id
+ self._headers = headers
+ self._base_url = base_url
+ self._timeout = timeout
+ self._max_retries = max_retries
+ self._stream_reconnection_enabled = stream_reconnection_enabled
+ self._max_stream_reconnection_attempts = max_stream_reconnection_attempts
+ self._logging = logging
+
+ def get_headers(self) -> typing.Dict[str, str]:
+ import platform
+
+ headers: typing.Dict[str, str] = {
+ "User-Agent": "fern_path-parameters/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_path-parameters",
+ "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
+
+ def get_max_retries(self) -> int:
+ return self._max_retries
+
+ def get_stream_reconnection_enabled(self) -> bool:
+ return self._stream_reconnection_enabled if self._stream_reconnection_enabled is not None else True
+
+ def get_max_stream_reconnection_attempts(self) -> typing.Optional[int]:
+ return self._max_stream_reconnection_attempts
+
+
+class SyncClientWrapper(BaseClientWrapper):
+ def __init__(
+ self,
+ *,
+ tenant_id: str,
+ headers: typing.Optional[typing.Dict[str, str]] = None,
+ base_url: str,
+ timeout: typing.Optional[float] = None,
+ max_retries: int = 2,
+ stream_reconnection_enabled: typing.Optional[bool] = None,
+ max_stream_reconnection_attempts: typing.Optional[int] = None,
+ logging: typing.Optional[typing.Union[LogConfig, Logger]] = None,
+ httpx_client: httpx.Client,
+ ):
+ super().__init__(
+ tenant_id=tenant_id,
+ headers=headers,
+ base_url=base_url,
+ timeout=timeout,
+ max_retries=max_retries,
+ stream_reconnection_enabled=stream_reconnection_enabled,
+ max_stream_reconnection_attempts=max_stream_reconnection_attempts,
+ 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,
+ base_max_retries=self.get_max_retries(),
+ logging_config=self._logging,
+ )
+
+
+class AsyncClientWrapper(BaseClientWrapper):
+ def __init__(
+ self,
+ *,
+ tenant_id: str,
+ headers: typing.Optional[typing.Dict[str, str]] = None,
+ base_url: str,
+ timeout: typing.Optional[float] = None,
+ max_retries: int = 2,
+ stream_reconnection_enabled: typing.Optional[bool] = None,
+ max_stream_reconnection_attempts: typing.Optional[int] = 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__(
+ tenant_id=tenant_id,
+ headers=headers,
+ base_url=base_url,
+ timeout=timeout,
+ max_retries=max_retries,
+ stream_reconnection_enabled=stream_reconnection_enabled,
+ max_stream_reconnection_attempts=max_stream_reconnection_attempts,
+ 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,
+ base_max_retries=self.get_max_retries(),
+ 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/path-parameters/no-custom-config/src/seed/core/datetime_utils.py b/seed/python-sdk/path-parameters/no-custom-config/src/seed/core/datetime_utils.py
new file mode 100644
index 000000000000..a12b2ad03c53
--- /dev/null
+++ b/seed/python-sdk/path-parameters/no-custom-config/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/path-parameters/no-custom-config/src/seed/core/file.py b/seed/python-sdk/path-parameters/no-custom-config/src/seed/core/file.py
new file mode 100644
index 000000000000..44b0d27c0895
--- /dev/null
+++ b/seed/python-sdk/path-parameters/no-custom-config/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/path-parameters/no-custom-config/src/seed/core/force_multipart.py b/seed/python-sdk/path-parameters/no-custom-config/src/seed/core/force_multipart.py
new file mode 100644
index 000000000000..5440913fd4bc
--- /dev/null
+++ b/seed/python-sdk/path-parameters/no-custom-config/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/path-parameters/no-custom-config/src/seed/core/http_client.py b/seed/python-sdk/path-parameters/no-custom-config/src/seed/core/http_client.py
new file mode 100644
index 000000000000..409d2670933f
--- /dev/null
+++ b/seed/python-sdk/path-parameters/no-custom-config/src/seed/core/http_client.py
@@ -0,0 +1,885 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import asyncio
+import email.utils
+import re
+import socket
+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 get_keepalive_socket_options(
+ idle: int = 60,
+ intvl: int = 30,
+ cnt: int = 5,
+) -> typing.List[typing.Tuple[int, int, int]]:
+ """
+ Build TCP keepalive socket options for the current platform.
+
+ Keepalive probes keep otherwise-idle connections alive so that long,
+ non-streaming requests survive idle-connection reaping by a firewall,
+ load balancer, or NAT. The available socket constants are OS-dependent,
+ so each option is guarded and only emitted when the platform defines it:
+
+ - ``SO_KEEPALIVE`` is portable (Linux/macOS/Windows).
+ - The idle-before-first-probe knob is ``TCP_KEEPIDLE`` on Linux and modern
+ Windows, but ``TCP_KEEPALIVE`` on macOS.
+ - ``TCP_KEEPINTVL`` / ``TCP_KEEPCNT`` exist on Linux/macOS/modern Windows.
+
+ Passing these tuples to ``httpx.HTTPTransport(socket_options=...)`` /
+ ``httpx.AsyncHTTPTransport(socket_options=...)`` applies them to every
+ connection the transport opens.
+ """
+ opts: typing.List[typing.Tuple[int, int, int]] = [(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)]
+ idle_const = getattr(socket, "TCP_KEEPIDLE", None) or getattr(socket, "TCP_KEEPALIVE", None)
+ if idle_const:
+ opts.append((socket.IPPROTO_TCP, idle_const, idle))
+ if hasattr(socket, "TCP_KEEPINTVL"):
+ opts.append((socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, intvl))
+ if hasattr(socket, "TCP_KEEPCNT"):
+ opts.append((socket.IPPROTO_TCP, socket.TCP_KEEPCNT, cnt))
+ return opts
+
+
+def _parse_retry_after(response_headers: httpx.Headers) -> typing.Optional[float]:
+ """
+ This function parses the `Retry-After` header in a HTTP response and returns the number of seconds to wait.
+
+ 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:
+ return response.status_code >= 500 or response.status_code in [429, 408, 409]
+
+
+_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")
+ if request_options is not None and request_options.get("timeout") is not None
+ else 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()
+ )
+ timeout = _timeout if _timeout is not None else httpx.USE_CLIENT_DEFAULT
+
+ json_body, data_body = get_request_body(json=json, data=data, request_options=request_options, omit=omit)
+
+ 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")
+ if request_options is not None and request_options.get("timeout") is not None
+ else 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()
+ )
+ timeout = _timeout if _timeout is not None else httpx.USE_CLIENT_DEFAULT
+
+ 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")
+ if request_options is not None and request_options.get("timeout") is not None
+ else 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()
+ )
+ timeout = _timeout if _timeout is not None else httpx.USE_CLIENT_DEFAULT
+
+ 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")
+ if request_options is not None and request_options.get("timeout") is not None
+ else 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()
+ )
+ timeout = _timeout if _timeout is not None else httpx.USE_CLIENT_DEFAULT
+
+ 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/path-parameters/no-custom-config/src/seed/core/http_response.py b/seed/python-sdk/path-parameters/no-custom-config/src/seed/core/http_response.py
new file mode 100644
index 000000000000..00bb1096d2d0
--- /dev/null
+++ b/seed/python-sdk/path-parameters/no-custom-config/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/path-parameters/no-custom-config/src/seed/core/http_sse/__init__.py b/seed/python-sdk/path-parameters/no-custom-config/src/seed/core/http_sse/__init__.py
new file mode 100644
index 000000000000..730e5a3382eb
--- /dev/null
+++ b/seed/python-sdk/path-parameters/no-custom-config/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/path-parameters/no-custom-config/src/seed/core/http_sse/_api.py b/seed/python-sdk/path-parameters/no-custom-config/src/seed/core/http_sse/_api.py
new file mode 100644
index 000000000000..9ca5602c2a74
--- /dev/null
+++ b/seed/python-sdk/path-parameters/no-custom-config/src/seed/core/http_sse/_api.py
@@ -0,0 +1,455 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import codecs
+import re
+import time
+from contextlib import asynccontextmanager, contextmanager
+from typing import (
+ Any,
+ AsyncContextManager,
+ AsyncGenerator,
+ AsyncIterator,
+ Callable,
+ ContextManager,
+ Iterator,
+ Optional,
+)
+
+import anyio
+import httpx
+from ._decoders import SSEDecoder
+from ._exceptions import SSEError
+from ._models import ServerSentEvent
+
+MAX_LINE_SIZE: int = 1_048_576 # 1 MiB
+
+# Reconnection defaults, mirroring the TypeScript SDK's Stream implementation.
+DEFAULT_MAX_RECONNECTION_ATTEMPTS: int = 5
+DEFAULT_RECONNECT_DELAY_MS: int = 1_000
+MAX_RECONNECT_DELAY_MS: int = 30_000
+
+
+# A reconnect callback re-issues the original request (with a ``Last-Event-ID``
+# header set to the supplied event id) and returns a *context manager* yielding
+# a fresh streaming ``httpx.Response``. Sync clients supply a sync context
+# manager; async clients supply an async one.
+class EventSource:
+ def __init__(
+ self,
+ response: httpx.Response,
+ *,
+ resumable: bool = False,
+ stream_reconnection_enabled: bool = True,
+ max_stream_reconnection_attempts: Optional[int] = None,
+ stream_terminator: Optional[str] = None,
+ reconnect: Optional[Callable[[str], Any]] = None,
+ ) -> None:
+ self._response = response
+ self._resumable = resumable
+ self._stream_reconnection_enabled = stream_reconnection_enabled
+ self._max_stream_reconnection_attempts = max_stream_reconnection_attempts
+ self._stream_terminator = stream_terminator
+ self._reconnect = reconnect
+
+ @staticmethod
+ def _is_event_stream(response: httpx.Response) -> bool:
+ content_type = response.headers.get("content-type", "").partition(";")[0]
+ return "text/event-stream" in content_type
+
+ def _check_content_type(self) -> None:
+ if not self._is_event_stream(self._response):
+ content_type = self._response.headers.get("content-type", "").partition(";")[0]
+ raise SSEError(
+ f"Expected response header Content-Type to contain 'text/event-stream', got {content_type!r}"
+ )
+
+ def _is_reconnect_response_usable(self, response: httpx.Response) -> bool:
+ """Whether a reconnected response can be resumed as an SSE stream.
+
+ ``httpx.stream`` does not raise on non-success status, so a resume that
+ returns an error page (e.g. ``200 text/html`` or a ``500`` body) would
+ otherwise be parsed as SSE and yield garbage/zero events. Such a
+ response is treated as a failed attempt (back off and retry) instead.
+ """
+ return response.status_code < 400 and self._is_event_stream(response)
+
+ def _get_charset(self, response: Optional[httpx.Response] = None) -> str:
+ """Extract charset from Content-Type header, fallback to UTF-8."""
+ resolved = response if response is not None else self._response
+ content_type = resolved.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
+
+ @staticmethod
+ def _normalize_sse_line_endings(buf: str) -> str:
+ """Normalize line endings per the SSE spec (\\r\\n → \\n, bare \\r → \\n).
+
+ A trailing \\r is preserved because it may pair with a leading \\n in
+ the next chunk to form a single \\r\\n terminator.
+ """
+ buf = buf.replace("\r\n", "\n")
+ if buf.endswith("\r"):
+ return buf[:-1].replace("\r", "\n") + "\r"
+ return buf.replace("\r", "\n")
+
+ def _new_text_decoder(self, response: Optional[httpx.Response] = None) -> "codecs.IncrementalDecoder":
+ return codecs.getincrementaldecoder(self._get_charset(response))(errors="replace")
+
+ def _reconnect_applicable(self) -> bool:
+ """Whether reconnection is configured for this stream at all.
+
+ This is the terminator-gating half of the reconnect decision, kept
+ separate from :meth:`_should_reconnect` (which additionally requires a
+ last *dispatched* id and an unexhausted attempt budget). The split lets
+ a mid-stream transport error terminate consistently:
+ - a stream that can never reconnect (non-resumable, no terminator,
+ disabled, or no callback) must re-raise the error to the caller, so a
+ truncated stream is not mistaken for a clean completion;
+ - a resumable stream that has merely run out of attempts (or has no id
+ to resume from) ends cleanly — the same way an exhausted empty/error
+ -body resume already does, matching the TypeScript ``return``.
+ """
+ return (
+ self._resumable
+ and self._stream_terminator is not None
+ and self._stream_reconnection_enabled
+ and self._reconnect is not None
+ )
+
+ def _should_reconnect(self, last_dispatched_id: Optional[str], reconnect_attempts: int) -> bool:
+ """Decide whether a prematurely-ended stream should be reconnected.
+
+ Mirrors the TypeScript ``shouldReconnect`` gating:
+ - only resumable SSE endpoints with a configured terminator, reconnect
+ enabled, and a reconnect callback are eligible (see
+ :meth:`_reconnect_applicable`);
+ - a last *dispatched* event id must exist to resume from;
+ - the consecutive-failed-attempt cap must not be exceeded.
+ """
+ if not self._reconnect_applicable():
+ return False
+ if not last_dispatched_id:
+ return False
+ max_attempts = (
+ self._max_stream_reconnection_attempts
+ if self._max_stream_reconnection_attempts is not None
+ else DEFAULT_MAX_RECONNECTION_ATTEMPTS
+ )
+ if reconnect_attempts >= max_attempts:
+ return False
+ return True
+
+ def _reconnect_delay_seconds(self, last_retry: Optional[int]) -> float:
+ """Backoff before a reconnect.
+
+ Uses the server's most recent ``retry:`` directive (milliseconds) when
+ present, otherwise a default of ``DEFAULT_RECONNECT_DELAY_MS``, clamped
+ to ``MAX_RECONNECT_DELAY_MS``.
+ """
+ base_ms = last_retry if (last_retry is not None and last_retry > 0) else DEFAULT_RECONNECT_DELAY_MS
+ return min(base_ms, MAX_RECONNECT_DELAY_MS) / 1000.0
+
+ def _sleep_before_reconnect(self, last_retry: Optional[int]) -> None:
+ # ``time.sleep`` blocks the calling thread but remains interruptible by
+ # signals (e.g. ``KeyboardInterrupt``), which propagate out and abort
+ # the reconnect without issuing another request.
+ time.sleep(self._reconnect_delay_seconds(last_retry))
+
+ async def _asleep_before_reconnect(self, last_retry: Optional[int]) -> None:
+ # ``anyio.sleep`` is cancellation-aware: if the consumer cancels the task
+ # or closes the async generator mid-delay, this raises (and no further
+ # request is issued) instead of blocking for the whole interval.
+ await anyio.sleep(self._reconnect_delay_seconds(last_retry))
+
+ def _decode_response(
+ self,
+ response: httpx.Response,
+ decoder: SSEDecoder,
+ text_decoder: "codecs.IncrementalDecoder",
+ ) -> Iterator[ServerSentEvent]:
+ buf = ""
+ for chunk in response.iter_bytes():
+ buf += text_decoder.decode(chunk)
+ buf = self._normalize_sse_line_endings(buf)
+
+ while "\n" in buf:
+ line, buf = buf.split("\n", 1)
+ sse = decoder.decode(line)
+ if sse is not None:
+ yield sse
+
+ if len(buf) > MAX_LINE_SIZE:
+ raise SSEError(
+ f"SSE line exceeded maximum size of {MAX_LINE_SIZE} characters without encountering a newline"
+ )
+
+ yield from self._flush_decoder(buf, decoder, text_decoder)
+
+ async def _adecode_response(
+ self,
+ response: httpx.Response,
+ decoder: SSEDecoder,
+ text_decoder: "codecs.IncrementalDecoder",
+ ) -> AsyncGenerator[ServerSentEvent, None]:
+ buf = ""
+ async for chunk in response.aiter_bytes():
+ buf += text_decoder.decode(chunk)
+ buf = self._normalize_sse_line_endings(buf)
+
+ while "\n" in buf:
+ line, buf = buf.split("\n", 1)
+ sse = decoder.decode(line)
+ if sse is not None:
+ yield sse
+
+ if len(buf) > MAX_LINE_SIZE:
+ raise SSEError(
+ f"SSE line exceeded maximum size of {MAX_LINE_SIZE} characters without encountering a newline"
+ )
+
+ for sse in self._flush_decoder(buf, decoder, text_decoder):
+ yield sse
+
+ def _flush_decoder(
+ self,
+ buf: str,
+ decoder: SSEDecoder,
+ text_decoder: "codecs.IncrementalDecoder",
+ ) -> Iterator[ServerSentEvent]:
+ # Flush any remaining bytes from the incremental decoder
+ buf += text_decoder.decode(b"", final=True)
+ buf = buf.replace("\r\n", "\n").replace("\r", "\n")
+
+ if len(buf) > MAX_LINE_SIZE:
+ raise SSEError(
+ f"SSE line exceeded maximum size of {MAX_LINE_SIZE} characters without encountering a newline"
+ )
+
+ while "\n" in buf:
+ line, buf = buf.split("\n", 1)
+ sse = decoder.decode(line)
+ if sse is not None:
+ yield sse
+
+ if buf.strip():
+ sse = decoder.decode(buf)
+ if sse is not None:
+ yield sse
+
+ def iter_sse(self) -> Iterator[ServerSentEvent]:
+ self._check_content_type()
+ decoder = SSEDecoder()
+ text_decoder = self._new_text_decoder()
+
+ last_dispatched_id: Optional[str] = None
+ last_retry: Optional[int] = None
+ # Consecutive failed reconnection attempts. Reset to 0 whenever an event
+ # is successfully dispatched (reset-on-progress) — matching browser
+ # `EventSource` semantics: a server that emits >=1 event then drops on
+ # every connection can reconnect indefinitely.
+ reconnect_attempts = 0
+
+ # ``None`` means there is no live stream to read this iteration (e.g. a
+ # failed reconnect); the loop then re-evaluates the reconnect decision
+ # without re-reading an exhausted response.
+ response: Optional[httpx.Response] = self._response
+ # Context manager for a response we opened ourselves and must close.
+ # The initial response is owned by the caller, so it starts as None.
+ owned_cm: Optional[ContextManager[httpx.Response]] = None
+ try:
+ while True:
+ if response is not None:
+ events = self._decode_response(response, decoder, text_decoder)
+ while True:
+ try:
+ sse = next(events)
+ except StopIteration:
+ break
+ except SSEError:
+ # A protocol violation (e.g. an oversized line) is a
+ # genuine error, not a dropped connection; propagate it.
+ # Listed first because ``SSEError`` subclasses
+ # ``httpx.TransportError``.
+ raise
+ except httpx.TransportError:
+ # A transport error mid-stream (e.g. the server dropped
+ # the connection: ``ReadError``/``RemoteProtocolError``)
+ # is a premature end. Only swallow it when reconnection
+ # is configured for this stream; otherwise re-raise so a
+ # non-resumable stream still surfaces the error to the
+ # caller instead of looking like a clean completion.
+ # When reconnection is applicable but the attempt budget
+ # is exhausted, we ``break`` and end cleanly below — the
+ # same way an exhausted empty/error-body resume does, so
+ # give-up is consistent regardless of failure shape.
+ # ``next`` is used rather than ``for`` so this cannot
+ # swallow a ``GeneratorExit`` raised at a ``yield``.
+ if not self._reconnect_applicable():
+ raise
+ break
+ yield sse
+ if sse.id:
+ last_dispatched_id = sse.id
+ if sse.retry is not None:
+ last_retry = sse.retry
+ reconnect_attempts = 0
+
+ if not self._should_reconnect(last_dispatched_id, reconnect_attempts):
+ return
+ reconnect_attempts += 1
+
+ self._sleep_before_reconnect(last_retry)
+
+ # Close the previously-opened reconnect response before opening
+ # a new one so we never hold more than one extra connection.
+ if owned_cm is not None:
+ owned_cm.__exit__(None, None, None)
+ owned_cm = None
+
+ assert self._reconnect is not None # guaranteed by _should_reconnect
+ try:
+ cm: ContextManager[httpx.Response] = self._reconnect(last_dispatched_id or "")
+ new_response = cm.__enter__()
+ except Exception:
+ # A failed reconnect consumes an attempt; back off and retry.
+ response = None
+ continue
+ owned_cm = cm
+ if new_response is None or not self._is_reconnect_response_usable(new_response):
+ # Null/empty body or a non-SSE/error response (e.g. 204/304,
+ # a 500, or an HTML error page): treat as a failed attempt.
+ response = None
+ continue
+
+ response = new_response
+ # Drop any partial event left over from the dropped stream, but
+ # keep the last event id (per the SSE spec) and start a fresh
+ # incremental text decoder for the new connection.
+ decoder.reset_in_progress_event()
+ text_decoder = self._new_text_decoder(new_response)
+ finally:
+ if owned_cm is not None:
+ owned_cm.__exit__(None, None, None)
+
+ async def aiter_sse(self) -> AsyncGenerator[ServerSentEvent, None]:
+ self._check_content_type()
+ decoder = SSEDecoder()
+ text_decoder = self._new_text_decoder()
+
+ last_dispatched_id: Optional[str] = None
+ last_retry: Optional[int] = None
+ reconnect_attempts = 0
+
+ response: Optional[httpx.Response] = self._response
+ owned_cm: Optional[AsyncContextManager[httpx.Response]] = None
+ try:
+ while True:
+ if response is not None:
+ events = self._adecode_response(response, decoder, text_decoder)
+ while True:
+ try:
+ sse = await events.__anext__()
+ except StopAsyncIteration:
+ break
+ except SSEError:
+ # A protocol violation (e.g. an oversized line) is a
+ # genuine error, not a dropped connection; propagate it.
+ # Listed first because ``SSEError`` subclasses
+ # ``httpx.TransportError``.
+ raise
+ except httpx.TransportError:
+ # A transport error mid-stream (e.g. the server dropped
+ # the connection: ``ReadError``/``RemoteProtocolError``)
+ # is a premature end. Only swallow it when reconnection
+ # is configured for this stream; otherwise re-raise so a
+ # non-resumable stream still surfaces the error to the
+ # caller instead of looking like a clean completion.
+ # When reconnection is applicable but the attempt budget
+ # is exhausted, we ``break`` and end cleanly below — the
+ # same way an exhausted empty/error-body resume does, so
+ # give-up is consistent regardless of failure shape.
+ if not self._reconnect_applicable():
+ raise
+ break
+ yield sse
+ if sse.id:
+ last_dispatched_id = sse.id
+ if sse.retry is not None:
+ last_retry = sse.retry
+ reconnect_attempts = 0
+
+ if not self._should_reconnect(last_dispatched_id, reconnect_attempts):
+ return
+ reconnect_attempts += 1
+
+ await self._asleep_before_reconnect(last_retry)
+
+ if owned_cm is not None:
+ await owned_cm.__aexit__(None, None, None)
+ owned_cm = None
+
+ assert self._reconnect is not None # guaranteed by _should_reconnect
+ try:
+ cm: AsyncContextManager[httpx.Response] = self._reconnect(last_dispatched_id or "")
+ new_response = await cm.__aenter__()
+ except Exception:
+ response = None
+ continue
+ owned_cm = cm
+ if new_response is None or not self._is_reconnect_response_usable(new_response):
+ response = None
+ continue
+
+ response = new_response
+ decoder.reset_in_progress_event()
+ text_decoder = self._new_text_decoder(new_response)
+ finally:
+ if owned_cm is not None:
+ # Shield the close so a cancellation delivered while reading a
+ # reconnected response still fully tears the connection down
+ # instead of leaking it until the client is closed.
+ with anyio.CancelScope(shield=True):
+ await owned_cm.__aexit__(None, None, None)
+
+
+@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/path-parameters/no-custom-config/src/seed/core/http_sse/_decoders.py b/seed/python-sdk/path-parameters/no-custom-config/src/seed/core/http_sse/_decoders.py
new file mode 100644
index 000000000000..1f6b35ec0bdf
--- /dev/null
+++ b/seed/python-sdk/path-parameters/no-custom-config/src/seed/core/http_sse/_decoders.py
@@ -0,0 +1,74 @@
+# 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 reset_in_progress_event(self) -> None:
+ """Discard any partially-parsed (undispatched) event.
+
+ Used when a stream ends mid-event before reconnecting: the buffered
+ ``event``/``data``/``retry`` fields of the never-dispatched event must
+ be dropped so they do not corrupt the first event of the reconnected
+ stream. Per the SSE spec the last event id is *not* reset here — it
+ persists across connections.
+ """
+ self._event = ""
+ self._data = []
+ self._retry = 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/path-parameters/no-custom-config/src/seed/core/http_sse/_exceptions.py b/seed/python-sdk/path-parameters/no-custom-config/src/seed/core/http_sse/_exceptions.py
new file mode 100644
index 000000000000..81605a8a65ed
--- /dev/null
+++ b/seed/python-sdk/path-parameters/no-custom-config/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/path-parameters/no-custom-config/src/seed/core/http_sse/_models.py b/seed/python-sdk/path-parameters/no-custom-config/src/seed/core/http_sse/_models.py
new file mode 100644
index 000000000000..1af57f8fd0d2
--- /dev/null
+++ b/seed/python-sdk/path-parameters/no-custom-config/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/path-parameters/no-custom-config/src/seed/core/jsonable_encoder.py b/seed/python-sdk/path-parameters/no-custom-config/src/seed/core/jsonable_encoder.py
new file mode 100644
index 000000000000..f638cc9a4252
--- /dev/null
+++ b/seed/python-sdk/path-parameters/no-custom-config/src/seed/core/jsonable_encoder.py
@@ -0,0 +1,133 @@
+# 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
+from urllib.parse import quote
+
+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))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/path-parameters/no-custom-config/src/seed/core/logging.py b/seed/python-sdk/path-parameters/no-custom-config/src/seed/core/logging.py
new file mode 100644
index 000000000000..e5e572458bc8
--- /dev/null
+++ b/seed/python-sdk/path-parameters/no-custom-config/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/path-parameters/no-custom-config/src/seed/core/parse_error.py b/seed/python-sdk/path-parameters/no-custom-config/src/seed/core/parse_error.py
new file mode 100644
index 000000000000..4527c6a8adec
--- /dev/null
+++ b/seed/python-sdk/path-parameters/no-custom-config/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/path-parameters/no-custom-config/src/seed/core/pydantic_utilities.py b/seed/python-sdk/path-parameters/no-custom-config/src/seed/core/pydantic_utilities.py
new file mode 100644
index 000000000000..6587f5e1820f
--- /dev/null
+++ b/seed/python-sdk/path-parameters/no-custom-config/src/seed/core/pydantic_utilities.py
@@ -0,0 +1,508 @@
+# 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 parse_sse_obj(sse: "ServerSentEvent", type_: Type[T]) -> T:
+ """
+ Parse a ServerSentEvent into the appropriate type.
+
+ This function handles data-level discrimination where the discriminator
+ (e.g., 'type') is inside the 'data' payload. It parses the SSE data field
+ as JSON and deserializes it into the target type.
+
+ Note: Protocol-level discrimination (where the discriminator comes from
+ the SSE event: field) is handled at code-generation time and does not
+ use this function.
+
+ Args:
+ sse: The ServerSentEvent object to parse
+ type_: The target type to deserialize into
+
+ 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)
+ 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)
+
+
+_type_adapter_cache: Dict[int, Any] = {}
+
+
+def _get_type_adapter(type_: Type[Any]) -> Any:
+ key = id(type_)
+ adapter = _type_adapter_cache.get(key)
+ if adapter is None:
+ adapter = pydantic.TypeAdapter(type_) # type: ignore[attr-defined]
+ _type_adapter_cache[key] = adapter
+ return adapter
+
+
+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 = _get_type_adapter(type_)
+ return adapter.validate_python(dealiased_object) # type: ignore[no-any-return]
+ 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/path-parameters/no-custom-config/src/seed/core/query_encoder.py b/seed/python-sdk/path-parameters/no-custom-config/src/seed/core/query_encoder.py
new file mode 100644
index 000000000000..3183001d4046
--- /dev/null
+++ b/seed/python-sdk/path-parameters/no-custom-config/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/path-parameters/no-custom-config/src/seed/core/remove_none_from_dict.py b/seed/python-sdk/path-parameters/no-custom-config/src/seed/core/remove_none_from_dict.py
new file mode 100644
index 000000000000..c2298143f14a
--- /dev/null
+++ b/seed/python-sdk/path-parameters/no-custom-config/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/path-parameters/no-custom-config/src/seed/core/request_options.py b/seed/python-sdk/path-parameters/no-custom-config/src/seed/core/request_options.py
new file mode 100644
index 000000000000..caa6f669b3e8
--- /dev/null
+++ b/seed/python-sdk/path-parameters/no-custom-config/src/seed/core/request_options.py
@@ -0,0 +1,40 @@
+# 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: float. The number of seconds to await an API call before timing out.
+
+ - timeout_in_seconds: int. Deprecated alias for `timeout`; both are in seconds. Prefer `timeout`.
+
+ - 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: NotRequired[float]
+ 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]
+ stream_reconnection_enabled: NotRequired[bool]
+ max_stream_reconnection_attempts: NotRequired[int]
diff --git a/seed/python-sdk/path-parameters/no-custom-config/src/seed/core/serialization.py b/seed/python-sdk/path-parameters/no-custom-config/src/seed/core/serialization.py
new file mode 100644
index 000000000000..1d753e26f739
--- /dev/null
+++ b/seed/python-sdk/path-parameters/no-custom-config/src/seed/core/serialization.py
@@ -0,0 +1,347 @@
+# 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
+
+
+# Resolving type hints (typing.get_type_hints) is expensive because it eval/compiles
+# forward-reference annotations. The result is constant for a given type, so we cache it.
+# This is critical for hot paths like SSE event parsing, where the same (often large
+# discriminated-union) type is converted on every single event.
+_type_hints_cache: typing.Dict[typing.Any, typing.Dict[str, typing.Any]] = {}
+
+
+def _get_cached_type_hints(expected_type: typing.Any) -> typing.Dict[str, typing.Any]:
+ try:
+ cached = _type_hints_cache.get(expected_type)
+ except TypeError:
+ # Unhashable type; resolve without caching.
+ return _resolve_type_hints(expected_type)
+ if cached is None:
+ cached = _resolve_type_hints(expected_type)
+ _type_hints_cache[expected_type] = cached
+ return cached
+
+
+def _resolve_type_hints(expected_type: typing.Any) -> typing.Dict[str, typing.Any]:
+ try:
+ return typing_extensions.get_type_hints(expected_type, include_extras=True)
+ except NameError:
+ # The type contains a circular reference, so we use the __annotations__ attribute directly.
+ return getattr(expected_type, "__annotations__", {})
+
+
+# Whether convert_and_respect_annotation_metadata can possibly rewrite anything for a given
+# annotation, i.e. whether any reachable model/TypedDict field carries a FieldMetadata alias.
+# This is constant per type, so we cache it and use it to short-circuit the recursive walk.
+_requires_conversion_cache: typing.Dict[typing.Any, bool] = {}
+
+
+def _requires_conversion(type_: typing.Any) -> bool:
+ try:
+ cached = _requires_conversion_cache.get(type_)
+ except TypeError:
+ # Unhashable annotation; compute without caching.
+ return _compute_requires_conversion(type_, set())
+ if cached is None:
+ cached = _compute_requires_conversion(type_, set())
+ _requires_conversion_cache[type_] = cached
+ return cached
+
+
+def _compute_requires_conversion(type_: typing.Any, seen: typing.Set[typing.Any]) -> bool:
+ clean_type = _remove_annotations(type_)
+
+ try:
+ if clean_type in seen:
+ return False
+ seen = seen | {clean_type}
+ except TypeError:
+ # Unhashable type; skip cycle tracking (the type graph is finite in practice).
+ pass
+
+ # Models / TypedDicts: a field alias here means we must dealias; otherwise recurse into fields.
+ if (inspect.isclass(clean_type) and issubclass(clean_type, pydantic.BaseModel)) or typing_extensions.is_typeddict(
+ clean_type
+ ):
+ annotations = _get_cached_type_hints(clean_type)
+ if _get_alias_to_field_name(annotations):
+ return True
+ return any(_compute_requires_conversion(hint, seen) for hint in annotations.values())
+
+ # Containers / unions: recurse into the type arguments (List/Set/Sequence/Dict/Union/etc.).
+ return any(_compute_requires_conversion(arg, seen) for arg in typing_extensions.get_args(clean_type))
+
+
+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
+ # The only thing this function ever rewrites is keys that carry a FieldMetadata
+ # alias. If nothing in the (cached) type graph has such an alias, the conversion is
+ # a content-identity transform, so we can skip the entire recursive walk. This is
+ # the hot path for SSE streaming, where a large discriminated union would otherwise
+ # be traversed on every single event.
+ if not _requires_conversion(annotation):
+ return object_
+
+ 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] = {}
+ annotations = _get_cached_type_hints(expected_type)
+ 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 = _get_cached_type_hints(type_)
+ return _get_alias_to_field_name(annotations)
+
+
+def get_field_to_alias_mapping(type_: typing.Any) -> typing.Dict[str, str]:
+ annotations = _get_cached_type_hints(type_)
+ 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/path-parameters/no-custom-config/src/seed/organizations/__init__.py b/seed/python-sdk/path-parameters/no-custom-config/src/seed/organizations/__init__.py
new file mode 100644
index 000000000000..fa16cbe223a2
--- /dev/null
+++ b/seed/python-sdk/path-parameters/no-custom-config/src/seed/organizations/__init__.py
@@ -0,0 +1,34 @@
+# 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 Organization
+_dynamic_imports: typing.Dict[str, str] = {"Organization": ".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__ = ["Organization"]
diff --git a/seed/python-sdk/path-parameters/no-custom-config/src/seed/organizations/client.py b/seed/python-sdk/path-parameters/no-custom-config/src/seed/organizations/client.py
new file mode 100644
index 000000000000..18724a2a5e19
--- /dev/null
+++ b/seed/python-sdk/path-parameters/no-custom-config/src/seed/organizations/client.py
@@ -0,0 +1,269 @@
+# 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 ..user.types.user import User
+from .raw_client import AsyncRawOrganizationsClient, RawOrganizationsClient
+from .types.organization import Organization
+
+
+class OrganizationsClient:
+ def __init__(self, *, client_wrapper: SyncClientWrapper):
+ self._raw_client = RawOrganizationsClient(client_wrapper=client_wrapper)
+
+ @property
+ def with_raw_response(self) -> RawOrganizationsClient:
+ """
+ Retrieves a raw implementation of this client that returns raw responses.
+
+ Returns
+ -------
+ RawOrganizationsClient
+ """
+ return self._raw_client
+
+ def get_organization(
+ self, organization_id: str, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> Organization:
+ """
+ Parameters
+ ----------
+ organization_id : str
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ Organization
+
+ Examples
+ --------
+ from seed import SeedPathParameters
+
+ client = SeedPathParameters(
+ tenant_id="YOUR_TENANT_ID",
+ base_url="https://yourhost.com/path/to/api",
+ )
+ client.organizations.get_organization(
+ organization_id="organization_id",
+ )
+ """
+ _response = self._raw_client.get_organization(organization_id, request_options=request_options)
+ return _response.data
+
+ def get_organization_user(
+ self, organization_id: str, user_id: str, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> User:
+ """
+ Parameters
+ ----------
+ organization_id : str
+
+ user_id : str
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ User
+
+ Examples
+ --------
+ from seed import SeedPathParameters
+
+ client = SeedPathParameters(
+ tenant_id="YOUR_TENANT_ID",
+ base_url="https://yourhost.com/path/to/api",
+ )
+ client.organizations.get_organization_user(
+ organization_id="organization_id",
+ user_id="user_id",
+ )
+ """
+ _response = self._raw_client.get_organization_user(organization_id, user_id, request_options=request_options)
+ return _response.data
+
+ def search_organizations(
+ self,
+ organization_id: str,
+ *,
+ limit: typing.Optional[int] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> typing.List[Organization]:
+ """
+ Parameters
+ ----------
+ organization_id : str
+
+ limit : typing.Optional[int]
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ typing.List[Organization]
+
+ Examples
+ --------
+ from seed import SeedPathParameters
+
+ client = SeedPathParameters(
+ tenant_id="YOUR_TENANT_ID",
+ base_url="https://yourhost.com/path/to/api",
+ )
+ client.organizations.search_organizations(
+ organization_id="organization_id",
+ limit=1,
+ )
+ """
+ _response = self._raw_client.search_organizations(organization_id, limit=limit, request_options=request_options)
+ return _response.data
+
+
+class AsyncOrganizationsClient:
+ def __init__(self, *, client_wrapper: AsyncClientWrapper):
+ self._raw_client = AsyncRawOrganizationsClient(client_wrapper=client_wrapper)
+
+ @property
+ def with_raw_response(self) -> AsyncRawOrganizationsClient:
+ """
+ Retrieves a raw implementation of this client that returns raw responses.
+
+ Returns
+ -------
+ AsyncRawOrganizationsClient
+ """
+ return self._raw_client
+
+ async def get_organization(
+ self, organization_id: str, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> Organization:
+ """
+ Parameters
+ ----------
+ organization_id : str
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ Organization
+
+ Examples
+ --------
+ import asyncio
+
+ from seed import AsyncSeedPathParameters
+
+ client = AsyncSeedPathParameters(
+ tenant_id="YOUR_TENANT_ID",
+ base_url="https://yourhost.com/path/to/api",
+ )
+
+
+ async def main() -> None:
+ await client.organizations.get_organization(
+ organization_id="organization_id",
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.get_organization(organization_id, request_options=request_options)
+ return _response.data
+
+ async def get_organization_user(
+ self, organization_id: str, user_id: str, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> User:
+ """
+ Parameters
+ ----------
+ organization_id : str
+
+ user_id : str
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ User
+
+ Examples
+ --------
+ import asyncio
+
+ from seed import AsyncSeedPathParameters
+
+ client = AsyncSeedPathParameters(
+ tenant_id="YOUR_TENANT_ID",
+ base_url="https://yourhost.com/path/to/api",
+ )
+
+
+ async def main() -> None:
+ await client.organizations.get_organization_user(
+ organization_id="organization_id",
+ user_id="user_id",
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.get_organization_user(
+ organization_id, user_id, request_options=request_options
+ )
+ return _response.data
+
+ async def search_organizations(
+ self,
+ organization_id: str,
+ *,
+ limit: typing.Optional[int] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> typing.List[Organization]:
+ """
+ Parameters
+ ----------
+ organization_id : str
+
+ limit : typing.Optional[int]
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ typing.List[Organization]
+
+ Examples
+ --------
+ import asyncio
+
+ from seed import AsyncSeedPathParameters
+
+ client = AsyncSeedPathParameters(
+ tenant_id="YOUR_TENANT_ID",
+ base_url="https://yourhost.com/path/to/api",
+ )
+
+
+ async def main() -> None:
+ await client.organizations.search_organizations(
+ organization_id="organization_id",
+ limit=1,
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.search_organizations(
+ organization_id, limit=limit, request_options=request_options
+ )
+ return _response.data
diff --git a/seed/python-sdk/path-parameters/src/seed/organizations/raw_client.py b/seed/python-sdk/path-parameters/no-custom-config/src/seed/organizations/raw_client.py
similarity index 100%
rename from seed/python-sdk/path-parameters/src/seed/organizations/raw_client.py
rename to seed/python-sdk/path-parameters/no-custom-config/src/seed/organizations/raw_client.py
diff --git a/seed/python-sdk/path-parameters/no-custom-config/src/seed/organizations/types/__init__.py b/seed/python-sdk/path-parameters/no-custom-config/src/seed/organizations/types/__init__.py
new file mode 100644
index 000000000000..dff8e04c10ce
--- /dev/null
+++ b/seed/python-sdk/path-parameters/no-custom-config/src/seed/organizations/types/__init__.py
@@ -0,0 +1,34 @@
+# 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 .organization import Organization
+_dynamic_imports: typing.Dict[str, str] = {"Organization": ".organization"}
+
+
+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__ = ["Organization"]
diff --git a/seed/python-sdk/path-parameters/no-custom-config/src/seed/organizations/types/organization.py b/seed/python-sdk/path-parameters/no-custom-config/src/seed/organizations/types/organization.py
new file mode 100644
index 000000000000..57d98480bb13
--- /dev/null
+++ b/seed/python-sdk/path-parameters/no-custom-config/src/seed/organizations/types/organization.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 Organization(UniversalBaseModel):
+ name: str
+ tags: typing.List[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/path-parameters/no-custom-config/src/seed/py.typed b/seed/python-sdk/path-parameters/no-custom-config/src/seed/py.typed
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/seed/python-sdk/path-parameters/no-custom-config/src/seed/user/__init__.py b/seed/python-sdk/path-parameters/no-custom-config/src/seed/user/__init__.py
new file mode 100644
index 000000000000..5f841626b151
--- /dev/null
+++ b/seed/python-sdk/path-parameters/no-custom-config/src/seed/user/__init__.py
@@ -0,0 +1,34 @@
+# 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 User
+_dynamic_imports: typing.Dict[str, str] = {"User": ".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__ = ["User"]
diff --git a/seed/python-sdk/path-parameters/no-custom-config/src/seed/user/client.py b/seed/python-sdk/path-parameters/no-custom-config/src/seed/user/client.py
new file mode 100644
index 000000000000..122e175c6324
--- /dev/null
+++ b/seed/python-sdk/path-parameters/no-custom-config/src/seed/user/client.py
@@ -0,0 +1,517 @@
+# 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 AsyncRawUserClient, RawUserClient
+from .types.user import User
+
+# this is used as the default value for optional parameters
+OMIT = typing.cast(typing.Any, ...)
+
+
+class UserClient:
+ def __init__(self, *, client_wrapper: SyncClientWrapper):
+ self._raw_client = RawUserClient(client_wrapper=client_wrapper)
+
+ @property
+ def with_raw_response(self) -> RawUserClient:
+ """
+ Retrieves a raw implementation of this client that returns raw responses.
+
+ Returns
+ -------
+ RawUserClient
+ """
+ return self._raw_client
+
+ def get_user(self, user_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> User:
+ """
+ Parameters
+ ----------
+ user_id : str
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ User
+
+ Examples
+ --------
+ from seed import SeedPathParameters
+
+ client = SeedPathParameters(
+ tenant_id="YOUR_TENANT_ID",
+ base_url="https://yourhost.com/path/to/api",
+ )
+ client.user.get_user(
+ user_id="user_id",
+ )
+ """
+ _response = self._raw_client.get_user(user_id, request_options=request_options)
+ return _response.data
+
+ def create_user(
+ self, *, name: str, tags: typing.Sequence[str], request_options: typing.Optional[RequestOptions] = None
+ ) -> User:
+ """
+ Parameters
+ ----------
+ name : str
+
+ tags : typing.Sequence[str]
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ User
+
+ Examples
+ --------
+ from seed import SeedPathParameters
+
+ client = SeedPathParameters(
+ tenant_id="YOUR_TENANT_ID",
+ base_url="https://yourhost.com/path/to/api",
+ )
+ client.user.create_user(
+ name="name",
+ tags=["tags", "tags"],
+ )
+ """
+ _response = self._raw_client.create_user(name=name, tags=tags, request_options=request_options)
+ return _response.data
+
+ def update_user(
+ self,
+ user_id: str,
+ *,
+ name: str,
+ tags: typing.Sequence[str],
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> User:
+ """
+ Parameters
+ ----------
+ user_id : str
+
+ name : str
+
+ tags : typing.Sequence[str]
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ User
+
+ Examples
+ --------
+ from seed import SeedPathParameters
+
+ client = SeedPathParameters(
+ tenant_id="YOUR_TENANT_ID",
+ base_url="https://yourhost.com/path/to/api",
+ )
+ client.user.update_user(
+ user_id="user_id",
+ name="name",
+ tags=["tags", "tags"],
+ )
+ """
+ _response = self._raw_client.update_user(user_id, name=name, tags=tags, request_options=request_options)
+ return _response.data
+
+ def search_users(
+ self,
+ user_id: str,
+ *,
+ limit: typing.Optional[int] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> typing.List[User]:
+ """
+ Parameters
+ ----------
+ user_id : str
+
+ limit : typing.Optional[int]
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ typing.List[User]
+
+ Examples
+ --------
+ from seed import SeedPathParameters
+
+ client = SeedPathParameters(
+ tenant_id="YOUR_TENANT_ID",
+ base_url="https://yourhost.com/path/to/api",
+ )
+ client.user.search_users(
+ user_id="user_id",
+ limit=1,
+ )
+ """
+ _response = self._raw_client.search_users(user_id, limit=limit, request_options=request_options)
+ return _response.data
+
+ def get_user_metadata(
+ self, user_id: str, version: int, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> User:
+ """
+ Test endpoint with path parameter that has a text prefix (v{version})
+
+ Parameters
+ ----------
+ user_id : str
+
+ version : int
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ User
+
+ Examples
+ --------
+ from seed import SeedPathParameters
+
+ client = SeedPathParameters(
+ tenant_id="YOUR_TENANT_ID",
+ base_url="https://yourhost.com/path/to/api",
+ )
+ client.user.get_user_metadata(
+ user_id="user_id",
+ version=1,
+ )
+ """
+ _response = self._raw_client.get_user_metadata(user_id, version, request_options=request_options)
+ return _response.data
+
+ def get_user_specifics(
+ self, user_id: str, version: int, thought: str, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> User:
+ """
+ Test endpoint with path parameters listed in different order than found in path
+
+ Parameters
+ ----------
+ user_id : str
+
+ version : int
+
+ thought : str
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ User
+
+ Examples
+ --------
+ from seed import SeedPathParameters
+
+ client = SeedPathParameters(
+ tenant_id="YOUR_TENANT_ID",
+ base_url="https://yourhost.com/path/to/api",
+ )
+ client.user.get_user_specifics(
+ user_id="user_id",
+ version=1,
+ thought="thought",
+ )
+ """
+ _response = self._raw_client.get_user_specifics(user_id, version, thought, request_options=request_options)
+ return _response.data
+
+
+class AsyncUserClient:
+ def __init__(self, *, client_wrapper: AsyncClientWrapper):
+ self._raw_client = AsyncRawUserClient(client_wrapper=client_wrapper)
+
+ @property
+ def with_raw_response(self) -> AsyncRawUserClient:
+ """
+ Retrieves a raw implementation of this client that returns raw responses.
+
+ Returns
+ -------
+ AsyncRawUserClient
+ """
+ return self._raw_client
+
+ async def get_user(self, user_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> User:
+ """
+ Parameters
+ ----------
+ user_id : str
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ User
+
+ Examples
+ --------
+ import asyncio
+
+ from seed import AsyncSeedPathParameters
+
+ client = AsyncSeedPathParameters(
+ tenant_id="YOUR_TENANT_ID",
+ base_url="https://yourhost.com/path/to/api",
+ )
+
+
+ async def main() -> None:
+ await client.user.get_user(
+ user_id="user_id",
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.get_user(user_id, request_options=request_options)
+ return _response.data
+
+ async def create_user(
+ self, *, name: str, tags: typing.Sequence[str], request_options: typing.Optional[RequestOptions] = None
+ ) -> User:
+ """
+ Parameters
+ ----------
+ name : str
+
+ tags : typing.Sequence[str]
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ User
+
+ Examples
+ --------
+ import asyncio
+
+ from seed import AsyncSeedPathParameters
+
+ client = AsyncSeedPathParameters(
+ tenant_id="YOUR_TENANT_ID",
+ base_url="https://yourhost.com/path/to/api",
+ )
+
+
+ async def main() -> None:
+ await client.user.create_user(
+ name="name",
+ tags=["tags", "tags"],
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.create_user(name=name, tags=tags, request_options=request_options)
+ return _response.data
+
+ async def update_user(
+ self,
+ user_id: str,
+ *,
+ name: str,
+ tags: typing.Sequence[str],
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> User:
+ """
+ Parameters
+ ----------
+ user_id : str
+
+ name : str
+
+ tags : typing.Sequence[str]
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ User
+
+ Examples
+ --------
+ import asyncio
+
+ from seed import AsyncSeedPathParameters
+
+ client = AsyncSeedPathParameters(
+ tenant_id="YOUR_TENANT_ID",
+ base_url="https://yourhost.com/path/to/api",
+ )
+
+
+ async def main() -> None:
+ await client.user.update_user(
+ user_id="user_id",
+ name="name",
+ tags=["tags", "tags"],
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.update_user(user_id, name=name, tags=tags, request_options=request_options)
+ return _response.data
+
+ async def search_users(
+ self,
+ user_id: str,
+ *,
+ limit: typing.Optional[int] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> typing.List[User]:
+ """
+ Parameters
+ ----------
+ user_id : str
+
+ limit : typing.Optional[int]
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ typing.List[User]
+
+ Examples
+ --------
+ import asyncio
+
+ from seed import AsyncSeedPathParameters
+
+ client = AsyncSeedPathParameters(
+ tenant_id="YOUR_TENANT_ID",
+ base_url="https://yourhost.com/path/to/api",
+ )
+
+
+ async def main() -> None:
+ await client.user.search_users(
+ user_id="user_id",
+ limit=1,
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.search_users(user_id, limit=limit, request_options=request_options)
+ return _response.data
+
+ async def get_user_metadata(
+ self, user_id: str, version: int, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> User:
+ """
+ Test endpoint with path parameter that has a text prefix (v{version})
+
+ Parameters
+ ----------
+ user_id : str
+
+ version : int
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ User
+
+ Examples
+ --------
+ import asyncio
+
+ from seed import AsyncSeedPathParameters
+
+ client = AsyncSeedPathParameters(
+ tenant_id="YOUR_TENANT_ID",
+ base_url="https://yourhost.com/path/to/api",
+ )
+
+
+ async def main() -> None:
+ await client.user.get_user_metadata(
+ user_id="user_id",
+ version=1,
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.get_user_metadata(user_id, version, request_options=request_options)
+ return _response.data
+
+ async def get_user_specifics(
+ self, user_id: str, version: int, thought: str, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> User:
+ """
+ Test endpoint with path parameters listed in different order than found in path
+
+ Parameters
+ ----------
+ user_id : str
+
+ version : int
+
+ thought : str
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ User
+
+ Examples
+ --------
+ import asyncio
+
+ from seed import AsyncSeedPathParameters
+
+ client = AsyncSeedPathParameters(
+ tenant_id="YOUR_TENANT_ID",
+ base_url="https://yourhost.com/path/to/api",
+ )
+
+
+ async def main() -> None:
+ await client.user.get_user_specifics(
+ user_id="user_id",
+ version=1,
+ thought="thought",
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.get_user_specifics(
+ user_id, version, thought, request_options=request_options
+ )
+ return _response.data
diff --git a/seed/python-sdk/path-parameters/src/seed/user/raw_client.py b/seed/python-sdk/path-parameters/no-custom-config/src/seed/user/raw_client.py
similarity index 100%
rename from seed/python-sdk/path-parameters/src/seed/user/raw_client.py
rename to seed/python-sdk/path-parameters/no-custom-config/src/seed/user/raw_client.py
diff --git a/seed/python-sdk/path-parameters/no-custom-config/src/seed/user/types/__init__.py b/seed/python-sdk/path-parameters/no-custom-config/src/seed/user/types/__init__.py
new file mode 100644
index 000000000000..9a15de15fc1f
--- /dev/null
+++ b/seed/python-sdk/path-parameters/no-custom-config/src/seed/user/types/__init__.py
@@ -0,0 +1,34 @@
+# 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 .user import User
+_dynamic_imports: typing.Dict[str, str] = {"User": ".user"}
+
+
+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__ = ["User"]
diff --git a/seed/python-sdk/path-parameters/no-custom-config/src/seed/user/types/user.py b/seed/python-sdk/path-parameters/no-custom-config/src/seed/user/types/user.py
new file mode 100644
index 000000000000..344d574ea329
--- /dev/null
+++ b/seed/python-sdk/path-parameters/no-custom-config/src/seed/user/types/user.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 User(UniversalBaseModel):
+ name: str
+ tags: typing.List[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/path-parameters/no-custom-config/src/seed/version.py b/seed/python-sdk/path-parameters/no-custom-config/src/seed/version.py
new file mode 100644
index 000000000000..e59e6530060f
--- /dev/null
+++ b/seed/python-sdk/path-parameters/no-custom-config/src/seed/version.py
@@ -0,0 +1,3 @@
+from importlib import metadata
+
+__version__ = metadata.version("fern_path-parameters")
diff --git a/seed/python-sdk/path-parameters/no-custom-config/tests/conftest.py b/seed/python-sdk/path-parameters/no-custom-config/tests/conftest.py
new file mode 100644
index 000000000000..25710dbe6278
--- /dev/null
+++ b/seed/python-sdk/path-parameters/no-custom-config/tests/conftest.py
@@ -0,0 +1,21 @@
+import pytest
+
+
+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)
diff --git a/seed/python-sdk/path-parameters/no-custom-config/tests/custom/test_client.py b/seed/python-sdk/path-parameters/no-custom-config/tests/custom/test_client.py
new file mode 100644
index 000000000000..ab04ce6393ef
--- /dev/null
+++ b/seed/python-sdk/path-parameters/no-custom-config/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/path-parameters/no-custom-config/tests/test_aiohttp_autodetect.py b/seed/python-sdk/path-parameters/no-custom-config/tests/test_aiohttp_autodetect.py
new file mode 100644
index 000000000000..671c754de059
--- /dev/null
+++ b/seed/python-sdk/path-parameters/no-custom-config/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_path-parameters[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/path-parameters/no-custom-config/tests/utils/__init__.py b/seed/python-sdk/path-parameters/no-custom-config/tests/utils/__init__.py
new file mode 100644
index 000000000000..f3ea2659bb1c
--- /dev/null
+++ b/seed/python-sdk/path-parameters/no-custom-config/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/path-parameters/no-custom-config/tests/utils/assets/models/__init__.py b/seed/python-sdk/path-parameters/no-custom-config/tests/utils/assets/models/__init__.py
new file mode 100644
index 000000000000..2cf01263529d
--- /dev/null
+++ b/seed/python-sdk/path-parameters/no-custom-config/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/path-parameters/no-custom-config/tests/utils/assets/models/circle.py b/seed/python-sdk/path-parameters/no-custom-config/tests/utils/assets/models/circle.py
new file mode 100644
index 000000000000..74ecf38c308b
--- /dev/null
+++ b/seed/python-sdk/path-parameters/no-custom-config/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/path-parameters/no-custom-config/tests/utils/assets/models/color.py b/seed/python-sdk/path-parameters/no-custom-config/tests/utils/assets/models/color.py
new file mode 100644
index 000000000000..2aa2c4c52f0c
--- /dev/null
+++ b/seed/python-sdk/path-parameters/no-custom-config/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/path-parameters/no-custom-config/tests/utils/assets/models/object_with_defaults.py b/seed/python-sdk/path-parameters/no-custom-config/tests/utils/assets/models/object_with_defaults.py
new file mode 100644
index 000000000000..a977b1d2aa1c
--- /dev/null
+++ b/seed/python-sdk/path-parameters/no-custom-config/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/path-parameters/no-custom-config/tests/utils/assets/models/object_with_optional_field.py b/seed/python-sdk/path-parameters/no-custom-config/tests/utils/assets/models/object_with_optional_field.py
new file mode 100644
index 000000000000..6b5608bc05b6
--- /dev/null
+++ b/seed/python-sdk/path-parameters/no-custom-config/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/path-parameters/no-custom-config/tests/utils/assets/models/shape.py b/seed/python-sdk/path-parameters/no-custom-config/tests/utils/assets/models/shape.py
new file mode 100644
index 000000000000..7e70010a251f
--- /dev/null
+++ b/seed/python-sdk/path-parameters/no-custom-config/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/path-parameters/no-custom-config/tests/utils/assets/models/square.py b/seed/python-sdk/path-parameters/no-custom-config/tests/utils/assets/models/square.py
new file mode 100644
index 000000000000..71c7d25fd4ad
--- /dev/null
+++ b/seed/python-sdk/path-parameters/no-custom-config/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/path-parameters/no-custom-config/tests/utils/assets/models/undiscriminated_shape.py b/seed/python-sdk/path-parameters/no-custom-config/tests/utils/assets/models/undiscriminated_shape.py
new file mode 100644
index 000000000000..99f12b300d1d
--- /dev/null
+++ b/seed/python-sdk/path-parameters/no-custom-config/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/path-parameters/no-custom-config/tests/utils/test_http_client.py b/seed/python-sdk/path-parameters/no-custom-config/tests/utils/test_http_client.py
new file mode 100644
index 000000000000..d17928fb3019
--- /dev/null
+++ b/seed/python-sdk/path-parameters/no-custom-config/tests/utils/test_http_client.py
@@ -0,0 +1,761 @@
+# This file was auto-generated by Fern from our API Definition.
+
+from typing import Any, Dict, Tuple, cast
+from unittest.mock import AsyncMock, MagicMock, patch
+
+import httpx
+import pytest
+
+from seed.core.http_client import (
+ AsyncHttpClient,
+ HttpClient,
+ _build_url,
+ _should_retry,
+ 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
+
+
+# ---------------------------------------------------------------------------
+# _should_retry unit tests
+# ---------------------------------------------------------------------------
+
+
+def _make_response(status_code: int) -> httpx.Response:
+ return httpx.Response(status_code=status_code, content=b"")
+
+
+@pytest.mark.parametrize(
+ "status_code",
+ [408, 409, 429, 500, 501, 502, 503, 504, 599],
+)
+def test_should_retry_retryable_status_codes(status_code: int) -> None:
+ """Legacy mode: retries on 408, 409, 429, and all >= 500."""
+ assert _should_retry(_make_response(status_code)) is True
+
+
+@pytest.mark.parametrize(
+ "status_code",
+ [200, 201, 301, 400, 401, 403, 404],
+)
+def test_should_not_retry_non_retryable_status_codes(status_code: int) -> None:
+ assert _should_retry(_make_response(status_code)) is False
+
+
+def test_should_retry_599_upper_boundary() -> None:
+ """Legacy mode retries on >= 500, which includes 599."""
+ assert _should_retry(_make_response(599)) is True
+
+
+# ---------------------------------------------------------------------------
+# RequestOptions timeout resolution tests (timeout / deprecated timeout_in_seconds)
+# ---------------------------------------------------------------------------
+
+
+def _sync_client_with_base_timeout(base_timeout: Any) -> Tuple[HttpClient, _DummySyncClient]:
+ dummy_client = _DummySyncClient()
+ http_client = HttpClient(
+ httpx_client=dummy_client, # type: ignore[arg-type]
+ base_timeout=lambda: base_timeout,
+ base_headers=lambda: {},
+ base_url=lambda: "https://example.com",
+ )
+ return http_client, dummy_client
+
+
+def test_sync_request_options_timeout_used() -> None:
+ """The new `timeout` request option is passed through to httpx (in seconds)."""
+ http_client, dummy_client = _sync_client_with_base_timeout(60)
+ http_client.request(path="/test", method="GET", request_options={"timeout": 30})
+ assert dummy_client.last_request_kwargs["timeout"] == 30
+
+
+def test_sync_request_options_timeout_in_seconds_still_works() -> None:
+ """The deprecated `timeout_in_seconds` request option remains backwards compatible."""
+ http_client, dummy_client = _sync_client_with_base_timeout(60)
+ http_client.request(path="/test", method="GET", request_options={"timeout_in_seconds": 45})
+ assert dummy_client.last_request_kwargs["timeout"] == 45
+
+
+def test_sync_request_options_timeout_takes_precedence() -> None:
+ """When both are set, `timeout` wins over `timeout_in_seconds` (same seconds unit)."""
+ http_client, dummy_client = _sync_client_with_base_timeout(60)
+ http_client.request(path="/test", method="GET", request_options={"timeout": 30, "timeout_in_seconds": 45})
+ assert dummy_client.last_request_kwargs["timeout"] == 30
+
+
+def test_sync_request_options_timeout_falls_back_to_base() -> None:
+ """When neither key is set, the client-level base timeout is used."""
+ http_client, dummy_client = _sync_client_with_base_timeout(60)
+ http_client.request(path="/test", method="GET", request_options=None)
+ assert dummy_client.last_request_kwargs["timeout"] == 60
+
+
+def test_sync_request_options_timeout_none_falls_back_to_deprecated() -> None:
+ """An explicit `timeout=None` (dynamic caller) falls back to the deprecated `timeout_in_seconds`."""
+ http_client, dummy_client = _sync_client_with_base_timeout(60)
+ request_options = cast(RequestOptions, {"timeout": None, "timeout_in_seconds": 45})
+ http_client.request(path="/test", method="GET", request_options=request_options)
+ assert dummy_client.last_request_kwargs["timeout"] == 45
+
+
+@pytest.mark.asyncio
+async def test_async_request_options_timeout_takes_precedence() -> None:
+ """Async: `timeout` wins over the deprecated `timeout_in_seconds`."""
+ dummy_client = _DummyAsyncClient()
+ http_client = AsyncHttpClient(
+ httpx_client=dummy_client, # type: ignore[arg-type]
+ base_timeout=lambda: 60,
+ base_headers=lambda: {},
+ base_url=lambda: "https://example.com",
+ async_base_headers=None,
+ )
+ await http_client.request(path="/test", method="GET", request_options={"timeout": 30, "timeout_in_seconds": 45})
+ assert dummy_client.last_request_kwargs["timeout"] == 30
diff --git a/seed/python-sdk/path-parameters/no-custom-config/tests/utils/test_query_encoding.py b/seed/python-sdk/path-parameters/no-custom-config/tests/utils/test_query_encoding.py
new file mode 100644
index 000000000000..ef5fd7094f9b
--- /dev/null
+++ b/seed/python-sdk/path-parameters/no-custom-config/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/path-parameters/no-custom-config/tests/utils/test_serialization.py b/seed/python-sdk/path-parameters/no-custom-config/tests/utils/test_serialization.py
new file mode 100644
index 000000000000..b298db89c4bd
--- /dev/null
+++ b/seed/python-sdk/path-parameters/no-custom-config/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/plain-text/src/seed/core/jsonable_encoder.py b/seed/python-sdk/plain-text/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/plain-text/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/plain-text/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/property-access/src/seed/core/jsonable_encoder.py b/seed/python-sdk/property-access/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/property-access/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/property-access/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/public-object/src/seed/core/jsonable_encoder.py b/seed/python-sdk/public-object/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/public-object/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/public-object/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/python-backslash-escape/src/seed/core/jsonable_encoder.py b/seed/python-sdk/python-backslash-escape/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/python-backslash-escape/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/python-backslash-escape/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/python-multi-env-url-templating/no-custom-config/src/seed/core/jsonable_encoder.py b/seed/python-sdk/python-multi-env-url-templating/no-custom-config/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/python-multi-env-url-templating/no-custom-config/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/python-multi-env-url-templating/no-custom-config/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/python-mypy-exclude/no-custom-config/src/seed/core/jsonable_encoder.py b/seed/python-sdk/python-mypy-exclude/no-custom-config/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/python-mypy-exclude/no-custom-config/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/python-mypy-exclude/no-custom-config/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/python-mypy-exclude/with-mypy-exclude/src/seed/core/jsonable_encoder.py b/seed/python-sdk/python-mypy-exclude/with-mypy-exclude/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/python-mypy-exclude/with-mypy-exclude/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/python-mypy-exclude/with-mypy-exclude/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/python-oauth-token-optional/no-custom-config/src/seed/core/jsonable_encoder.py b/seed/python-sdk/python-oauth-token-optional/no-custom-config/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/python-oauth-token-optional/no-custom-config/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/python-oauth-token-optional/no-custom-config/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/python-positional-single-property/no-custom-config/src/seed/core/jsonable_encoder.py b/seed/python-sdk/python-positional-single-property/no-custom-config/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/python-positional-single-property/no-custom-config/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/python-positional-single-property/no-custom-config/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/python-positional-single-property/with-positional-constructors/src/seed/core/jsonable_encoder.py b/seed/python-sdk/python-positional-single-property/with-positional-constructors/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/python-positional-single-property/with-positional-constructors/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/python-positional-single-property/with-positional-constructors/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/python-reserved-keyword-subpackages/src/seed/core/jsonable_encoder.py b/seed/python-sdk/python-reserved-keyword-subpackages/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/python-reserved-keyword-subpackages/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/python-reserved-keyword-subpackages/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/python-streaming-parameter-openapi/with-wire-tests/src/seed/core/jsonable_encoder.py b/seed/python-sdk/python-streaming-parameter-openapi/with-wire-tests/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/python-streaming-parameter-openapi/with-wire-tests/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/python-streaming-parameter-openapi/with-wire-tests/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/python-underscore-subpackages/src/seed/core/jsonable_encoder.py b/seed/python-sdk/python-underscore-subpackages/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/python-underscore-subpackages/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/python-underscore-subpackages/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/query-param-name-conflict/src/seed/core/jsonable_encoder.py b/seed/python-sdk/query-param-name-conflict/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/query-param-name-conflict/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/query-param-name-conflict/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/query-parameters-openapi-as-objects/no-custom-config/src/seed/core/jsonable_encoder.py b/seed/python-sdk/query-parameters-openapi-as-objects/no-custom-config/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/query-parameters-openapi-as-objects/no-custom-config/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/query-parameters-openapi-as-objects/no-custom-config/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/query-parameters-openapi/no-custom-config/src/seed/core/jsonable_encoder.py b/seed/python-sdk/query-parameters-openapi/no-custom-config/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/query-parameters-openapi/no-custom-config/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/query-parameters-openapi/no-custom-config/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/query-parameters/no-custom-config/src/seed/core/jsonable_encoder.py b/seed/python-sdk/query-parameters/no-custom-config/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/query-parameters/no-custom-config/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/query-parameters/no-custom-config/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/request-parameters/src/seed/core/jsonable_encoder.py b/seed/python-sdk/request-parameters/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/request-parameters/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/request-parameters/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/required-nullable/src/seed/core/jsonable_encoder.py b/seed/python-sdk/required-nullable/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/required-nullable/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/required-nullable/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/reserved-keywords/src/seed/core/jsonable_encoder.py b/seed/python-sdk/reserved-keywords/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/reserved-keywords/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/reserved-keywords/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/response-property/src/seed/core/jsonable_encoder.py b/seed/python-sdk/response-property/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/response-property/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/response-property/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/schemaless-request-body-examples/src/seed/core/jsonable_encoder.py b/seed/python-sdk/schemaless-request-body-examples/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/schemaless-request-body-examples/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/schemaless-request-body-examples/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/seed.yml b/seed/python-sdk/seed.yml
index ec9151894967..89255f7edec4 100644
--- a/seed/python-sdk/seed.yml
+++ b/seed/python-sdk/seed.yml
@@ -399,6 +399,12 @@ fixtures:
- customConfig:
offsetSemantics: "page-index"
outputFolder: page-index-semantics
+ path-parameters:
+ - customConfig: null
+ outputFolder: no-custom-config
+ - customConfig:
+ encode_path_params: true
+ outputFolder: encode-path-params
query-parameters:
- customConfig: null
outputFolder: no-custom-config
diff --git a/seed/python-sdk/server-sent-event-examples/src/seed/core/jsonable_encoder.py b/seed/python-sdk/server-sent-event-examples/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/server-sent-event-examples/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/server-sent-event-examples/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/server-sent-events-openapi/with-wire-tests/src/seed/core/jsonable_encoder.py b/seed/python-sdk/server-sent-events-openapi/with-wire-tests/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/server-sent-events-openapi/with-wire-tests/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/server-sent-events-openapi/with-wire-tests/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/server-sent-events-resumable/src/seed/core/jsonable_encoder.py b/seed/python-sdk/server-sent-events-resumable/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/server-sent-events-resumable/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/server-sent-events-resumable/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/server-sent-events/with-wire-tests/src/seed/core/jsonable_encoder.py b/seed/python-sdk/server-sent-events/with-wire-tests/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/server-sent-events/with-wire-tests/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/server-sent-events/with-wire-tests/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/server-url-templating-single-url/src/seed/core/jsonable_encoder.py b/seed/python-sdk/server-url-templating-single-url/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/server-url-templating-single-url/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/server-url-templating-single-url/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/server-url-templating/no-custom-config/src/seed/core/jsonable_encoder.py b/seed/python-sdk/server-url-templating/no-custom-config/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/server-url-templating/no-custom-config/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/server-url-templating/no-custom-config/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/simple-api/optional-auth/src/seed/core/jsonable_encoder.py b/seed/python-sdk/simple-api/optional-auth/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/simple-api/optional-auth/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/simple-api/optional-auth/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/simple-api/src/seed/core/jsonable_encoder.py b/seed/python-sdk/simple-api/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/simple-api/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/simple-api/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/simple-fhir/no-inheritance-for-extended-models/src/seed/core/jsonable_encoder.py b/seed/python-sdk/simple-fhir/no-inheritance-for-extended-models/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/simple-fhir/no-inheritance-for-extended-models/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/simple-fhir/no-inheritance-for-extended-models/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/single-url-environment-default/src/seed/core/jsonable_encoder.py b/seed/python-sdk/single-url-environment-default/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/single-url-environment-default/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/single-url-environment-default/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/single-url-environment-no-default/src/seed/core/jsonable_encoder.py b/seed/python-sdk/single-url-environment-no-default/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/single-url-environment-no-default/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/single-url-environment-no-default/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/streaming-parameter/src/seed/core/jsonable_encoder.py b/seed/python-sdk/streaming-parameter/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/streaming-parameter/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/streaming-parameter/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/streaming/no-custom-config/src/seed/core/jsonable_encoder.py b/seed/python-sdk/streaming/no-custom-config/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/streaming/no-custom-config/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/streaming/no-custom-config/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/streaming/skip-pydantic-validation/src/seed/core/jsonable_encoder.py b/seed/python-sdk/streaming/skip-pydantic-validation/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/streaming/skip-pydantic-validation/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/streaming/skip-pydantic-validation/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/trace/src/seed/core/jsonable_encoder.py b/seed/python-sdk/trace/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/trace/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/trace/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/undiscriminated-union-with-response-property/src/seed/core/jsonable_encoder.py b/seed/python-sdk/undiscriminated-union-with-response-property/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/undiscriminated-union-with-response-property/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/undiscriminated-union-with-response-property/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/undiscriminated-unions/src/seed/core/jsonable_encoder.py b/seed/python-sdk/undiscriminated-unions/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/undiscriminated-unions/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/undiscriminated-unions/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/union-query-parameters/src/seed/core/jsonable_encoder.py b/seed/python-sdk/union-query-parameters/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/union-query-parameters/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/union-query-parameters/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/unions-with-local-date/src/seed/core/jsonable_encoder.py b/seed/python-sdk/unions-with-local-date/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/unions-with-local-date/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/unions-with-local-date/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/unions/flatten-union-request-bodies/src/seed/core/jsonable_encoder.py b/seed/python-sdk/unions/flatten-union-request-bodies/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/unions/flatten-union-request-bodies/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/unions/flatten-union-request-bodies/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/unions/no-custom-config/src/seed/core/jsonable_encoder.py b/seed/python-sdk/unions/no-custom-config/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/unions/no-custom-config/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/unions/no-custom-config/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/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
index 5b0902ebcde3..f638cc9a4252 100644
--- 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
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/unions/union-naming-v1/src/seed/core/jsonable_encoder.py b/seed/python-sdk/unions/union-naming-v1/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/unions/union-naming-v1/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/unions/union-naming-v1/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/unions/union-utils/src/seed/core/jsonable_encoder.py b/seed/python-sdk/unions/union-utils/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/unions/union-utils/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/unions/union-utils/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/unknown/src/seed/core/jsonable_encoder.py b/seed/python-sdk/unknown/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/unknown/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/unknown/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/url-form-encoded/src/seed/core/jsonable_encoder.py b/seed/python-sdk/url-form-encoded/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/url-form-encoded/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/url-form-encoded/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/validation/no-custom-config/src/seed/core/jsonable_encoder.py b/seed/python-sdk/validation/no-custom-config/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/validation/no-custom-config/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/validation/no-custom-config/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/validation/with-defaults-parameters/src/seed/core/jsonable_encoder.py b/seed/python-sdk/validation/with-defaults-parameters/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/validation/with-defaults-parameters/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/validation/with-defaults-parameters/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/validation/with-defaults/src/seed/core/jsonable_encoder.py b/seed/python-sdk/validation/with-defaults/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/validation/with-defaults/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/validation/with-defaults/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/variables/src/seed/core/jsonable_encoder.py b/seed/python-sdk/variables/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/variables/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/variables/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/version-no-default/src/seed/core/jsonable_encoder.py b/seed/python-sdk/version-no-default/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/version-no-default/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/version-no-default/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/version/src/seed/core/jsonable_encoder.py b/seed/python-sdk/version/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/version/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/version/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/webhook-audience/src/seed/core/jsonable_encoder.py b/seed/python-sdk/webhook-audience/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/webhook-audience/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/webhook-audience/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/webhooks/src/seed/core/jsonable_encoder.py b/seed/python-sdk/webhooks/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/webhooks/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/webhooks/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/websocket-bearer-auth/src/seed/core/jsonable_encoder.py b/seed/python-sdk/websocket-bearer-auth/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/websocket-bearer-auth/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/websocket-bearer-auth/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/websocket-inferred-auth/src/seed/core/jsonable_encoder.py b/seed/python-sdk/websocket-inferred-auth/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/websocket-inferred-auth/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/websocket-inferred-auth/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/websocket-multi-url/src/seed/core/jsonable_encoder.py b/seed/python-sdk/websocket-multi-url/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/websocket-multi-url/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/websocket-multi-url/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/websocket/websocket-base/src/seed/core/jsonable_encoder.py b/seed/python-sdk/websocket/websocket-base/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/websocket/websocket-base/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/websocket/websocket-base/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/websocket/websocket-with_generated_clients-skip_validation/src/seed/core/jsonable_encoder.py b/seed/python-sdk/websocket/websocket-with_generated_clients-skip_validation/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/websocket/websocket-with_generated_clients-skip_validation/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/websocket/websocket-with_generated_clients-skip_validation/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/websocket/websocket-with_generated_clients/src/seed/core/jsonable_encoder.py b/seed/python-sdk/websocket/websocket-with_generated_clients/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/websocket/websocket-with_generated_clients/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/websocket/websocket-with_generated_clients/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/x-fern-default/src/seed/core/jsonable_encoder.py b/seed/python-sdk/x-fern-default/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/x-fern-default/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/x-fern-default/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")
diff --git a/seed/python-sdk/x-fern-global-parameters/src/seed/core/jsonable_encoder.py b/seed/python-sdk/x-fern-global-parameters/src/seed/core/jsonable_encoder.py
index 5b0902ebcde3..f638cc9a4252 100644
--- a/seed/python-sdk/x-fern-global-parameters/src/seed/core/jsonable_encoder.py
+++ b/seed/python-sdk/x-fern-global-parameters/src/seed/core/jsonable_encoder.py
@@ -15,6 +15,7 @@
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union
+from urllib.parse import quote
import pydantic
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
if isinstance(obj, bool):
return "true" if obj else "false"
return str(jsonable_encoder(obj))
+
+
+def quote_path_param(obj: Any) -> str:
+ """Encode a value for use in a URL path segment, percent-encoding it.
+
+ Same as encode_path_param, except the result is percent-encoded so
+ that a value containing "/" or ".." cannot change which endpoint
+ the request resolves to.
+ """
+ if isinstance(obj, bool):
+ return "true" if obj else "false"
+ return quote(str(jsonable_encoder(obj)), safe="")