relaxai 0.0.1__tar.gz → 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of relaxai might be problematic. Click here for more details.

Files changed (87) hide show
  1. {relaxai-0.0.1 → relaxai-0.1.0}/.gitignore +0 -1
  2. relaxai-0.1.0/.release-please-manifest.json +3 -0
  3. relaxai-0.1.0/CHANGELOG.md +40 -0
  4. {relaxai-0.0.1 → relaxai-0.1.0}/PKG-INFO +6 -5
  5. {relaxai-0.0.1 → relaxai-0.1.0}/README.md +3 -3
  6. {relaxai-0.0.1 → relaxai-0.1.0}/pyproject.toml +3 -2
  7. {relaxai-0.0.1 → relaxai-0.1.0}/requirements-dev.lock +3 -3
  8. {relaxai-0.0.1 → relaxai-0.1.0}/requirements.lock +3 -3
  9. {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/_base_client.py +9 -2
  10. {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/_client.py +2 -2
  11. {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/_models.py +31 -7
  12. {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/_version.py +1 -1
  13. {relaxai-0.0.1 → relaxai-0.1.0}/tests/test_client.py +2 -2
  14. {relaxai-0.0.1 → relaxai-0.1.0}/tests/test_models.py +73 -1
  15. relaxai-0.0.1/.release-please-manifest.json +0 -3
  16. relaxai-0.0.1/CHANGELOG.md +0 -10
  17. {relaxai-0.0.1 → relaxai-0.1.0}/CONTRIBUTING.md +0 -0
  18. {relaxai-0.0.1 → relaxai-0.1.0}/LICENSE +0 -0
  19. {relaxai-0.0.1 → relaxai-0.1.0}/SECURITY.md +0 -0
  20. {relaxai-0.0.1 → relaxai-0.1.0}/api.md +0 -0
  21. {relaxai-0.0.1 → relaxai-0.1.0}/bin/check-release-environment +0 -0
  22. {relaxai-0.0.1 → relaxai-0.1.0}/bin/publish-pypi +0 -0
  23. {relaxai-0.0.1 → relaxai-0.1.0}/examples/.keep +0 -0
  24. {relaxai-0.0.1 → relaxai-0.1.0}/mypy.ini +0 -0
  25. {relaxai-0.0.1 → relaxai-0.1.0}/noxfile.py +0 -0
  26. {relaxai-0.0.1 → relaxai-0.1.0}/release-please-config.json +0 -0
  27. {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/__init__.py +0 -0
  28. {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/_compat.py +0 -0
  29. {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/_constants.py +0 -0
  30. {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/_exceptions.py +0 -0
  31. {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/_files.py +0 -0
  32. {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/_qs.py +0 -0
  33. {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/_resource.py +0 -0
  34. {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/_response.py +0 -0
  35. {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/_streaming.py +0 -0
  36. {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/_types.py +0 -0
  37. {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/_utils/__init__.py +0 -0
  38. {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/_utils/_logs.py +0 -0
  39. {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/_utils/_proxy.py +0 -0
  40. {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/_utils/_reflection.py +0 -0
  41. {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/_utils/_resources_proxy.py +0 -0
  42. {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/_utils/_streams.py +0 -0
  43. {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/_utils/_sync.py +0 -0
  44. {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/_utils/_transform.py +0 -0
  45. {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/_utils/_typing.py +0 -0
  46. {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/_utils/_utils.py +0 -0
  47. {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/lib/.keep +0 -0
  48. {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/py.typed +0 -0
  49. {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/resources/__init__.py +0 -0
  50. {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/resources/chat.py +0 -0
  51. {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/resources/embeddings.py +0 -0
  52. {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/resources/health.py +0 -0
  53. {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/resources/models.py +0 -0
  54. {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/types/__init__.py +0 -0
  55. {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/types/chat_completion_message.py +0 -0
  56. {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/types/chat_completion_message_param.py +0 -0
  57. {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/types/chat_create_completion_params.py +0 -0
  58. {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/types/chat_create_completion_response.py +0 -0
  59. {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/types/content_filter_results.py +0 -0
  60. {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/types/embedding_create_params.py +0 -0
  61. {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/types/embedding_create_response.py +0 -0
  62. {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/types/function_call.py +0 -0
  63. {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/types/function_call_param.py +0 -0
  64. {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/types/function_definition_param.py +0 -0
  65. {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/types/health_check_response.py +0 -0
  66. {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/types/model.py +0 -0
  67. {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/types/model_list_response.py +0 -0
  68. {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/types/usage.py +0 -0
  69. {relaxai-0.0.1 → relaxai-0.1.0}/tests/__init__.py +0 -0
  70. {relaxai-0.0.1 → relaxai-0.1.0}/tests/api_resources/__init__.py +0 -0
  71. {relaxai-0.0.1 → relaxai-0.1.0}/tests/api_resources/test_chat.py +0 -0
  72. {relaxai-0.0.1 → relaxai-0.1.0}/tests/api_resources/test_embeddings.py +0 -0
  73. {relaxai-0.0.1 → relaxai-0.1.0}/tests/api_resources/test_health.py +0 -0
  74. {relaxai-0.0.1 → relaxai-0.1.0}/tests/api_resources/test_models.py +0 -0
  75. {relaxai-0.0.1 → relaxai-0.1.0}/tests/conftest.py +0 -0
  76. {relaxai-0.0.1 → relaxai-0.1.0}/tests/sample_file.txt +0 -0
  77. {relaxai-0.0.1 → relaxai-0.1.0}/tests/test_deepcopy.py +0 -0
  78. {relaxai-0.0.1 → relaxai-0.1.0}/tests/test_extract_files.py +0 -0
  79. {relaxai-0.0.1 → relaxai-0.1.0}/tests/test_files.py +0 -0
  80. {relaxai-0.0.1 → relaxai-0.1.0}/tests/test_qs.py +0 -0
  81. {relaxai-0.0.1 → relaxai-0.1.0}/tests/test_required_args.py +0 -0
  82. {relaxai-0.0.1 → relaxai-0.1.0}/tests/test_response.py +0 -0
  83. {relaxai-0.0.1 → relaxai-0.1.0}/tests/test_streaming.py +0 -0
  84. {relaxai-0.0.1 → relaxai-0.1.0}/tests/test_transform.py +0 -0
  85. {relaxai-0.0.1 → relaxai-0.1.0}/tests/test_utils/test_proxy.py +0 -0
  86. {relaxai-0.0.1 → relaxai-0.1.0}/tests/test_utils/test_typing.py +0 -0
  87. {relaxai-0.0.1 → relaxai-0.1.0}/tests/utils.py +0 -0
@@ -1,5 +1,4 @@
1
1
  .prism.log
2
- .vscode
3
2
  _dev
4
3
 
5
4
  __pycache__
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "0.1.0"
3
+ }
@@ -0,0 +1,40 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 (2025-07-25)
4
+
5
+ Full Changelog: [v0.0.1...v0.1.0](https://github.com/relax-ai/python-sdk/compare/v0.0.1...v0.1.0)
6
+
7
+ ### Features
8
+
9
+ * **api:** update via SDK Studio ([53fd3c1](https://github.com/relax-ai/python-sdk/commit/53fd3c18d19447b5a2314af8c2626dcc83ab6176))
10
+ * clean up environment call outs ([fd52000](https://github.com/relax-ai/python-sdk/commit/fd52000dd6823e3f52759ffb32457b1b6bb777e8))
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * **ci:** correct conditional ([7b9dd73](https://github.com/relax-ai/python-sdk/commit/7b9dd73dec12d098f3908c46804fea6323991933))
16
+ * **client:** don't send Content-Type header on GET requests ([134d444](https://github.com/relax-ai/python-sdk/commit/134d444d1e46fa968b03c1d2c29a81a3f50260bc))
17
+ * **parsing:** correctly handle nested discriminated unions ([f3f2481](https://github.com/relax-ai/python-sdk/commit/f3f2481e3b294c4859dce817620beeda0750df70))
18
+ * **parsing:** ignore empty metadata ([5a69522](https://github.com/relax-ai/python-sdk/commit/5a695221f0607e7140856e41cc84057fed534cf5))
19
+ * **parsing:** parse extra field types ([058ebec](https://github.com/relax-ai/python-sdk/commit/058ebec52bdfbdd0fa5d094507ec0151954d7611))
20
+
21
+
22
+ ### Chores
23
+
24
+ * **ci:** change upload type ([d0eac9e](https://github.com/relax-ai/python-sdk/commit/d0eac9e1c21f5af782d7b6b0e9d4e12a7ae1ba4b))
25
+ * **ci:** only run for pushes and fork pull requests ([2823330](https://github.com/relax-ai/python-sdk/commit/28233300e18b343582f000c7809c6d5f9e5c9dfd))
26
+ * **internal:** bump pinned h11 dep ([2fcd331](https://github.com/relax-ai/python-sdk/commit/2fcd331182be60fe3186a12bf076c0792312d340))
27
+ * **internal:** codegen related update ([cf132b1](https://github.com/relax-ai/python-sdk/commit/cf132b191e08d91e189d3dd910f020121511ec4b))
28
+ * **internal:** version bump ([0a019aa](https://github.com/relax-ai/python-sdk/commit/0a019aa4888b574cc3892e8a1ed188330c97f971))
29
+ * **package:** mark python 3.13 as supported ([75f9e16](https://github.com/relax-ai/python-sdk/commit/75f9e16cf24b3ce117503d87083ef95511230c95))
30
+ * **project:** add settings file for vscode ([d91c008](https://github.com/relax-ai/python-sdk/commit/d91c008238bb40bfe103413b61798695821f2e66))
31
+ * **readme:** fix version rendering on pypi ([ba4beb8](https://github.com/relax-ai/python-sdk/commit/ba4beb8326b69e8dc5e54e014620c35681e21114))
32
+
33
+ ## 0.0.1 (2025-06-27)
34
+
35
+ Full Changelog: [v0.0.1-alpha.0...v0.0.1](https://github.com/relax-ai/python-sdk/compare/v0.0.1-alpha.0...v0.0.1)
36
+
37
+ ### Chores
38
+
39
+ * update SDK settings ([b6c7f5a](https://github.com/relax-ai/python-sdk/commit/b6c7f5aebefb986948527773a67b92fe2fb15954))
40
+ * update SDK settings ([fc9f194](https://github.com/relax-ai/python-sdk/commit/fc9f194e0d241fb70577baba8285b31f76677d19))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: relaxai
3
- Version: 0.0.1
3
+ Version: 0.1.0
4
4
  Summary: The official Python library for the relaxai API
5
5
  Project-URL: Homepage, https://github.com/relax-ai/python-sdk
6
6
  Project-URL: Repository, https://github.com/relax-ai/python-sdk
@@ -18,6 +18,7 @@ Classifier: Programming Language :: Python :: 3.9
18
18
  Classifier: Programming Language :: Python :: 3.10
19
19
  Classifier: Programming Language :: Python :: 3.11
20
20
  Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
21
22
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
23
  Classifier: Typing :: Typed
23
24
  Requires-Python: >=3.8
@@ -29,12 +30,13 @@ Requires-Dist: sniffio
29
30
  Requires-Dist: typing-extensions<5,>=4.10
30
31
  Provides-Extra: aiohttp
31
32
  Requires-Dist: aiohttp; extra == 'aiohttp'
32
- Requires-Dist: httpx-aiohttp>=0.1.6; extra == 'aiohttp'
33
+ Requires-Dist: httpx-aiohttp>=0.1.8; extra == 'aiohttp'
33
34
  Description-Content-Type: text/markdown
34
35
 
35
36
  # Relaxai Python API library
36
37
 
37
- [![PyPI version](https://github.com/relax-ai/python-sdk/tree/main/<https://img.shields.io/pypi/v/relaxai.svg?label=pypi%20(stable)>)](https://pypi.org/project/relaxai/)
38
+ <!-- prettier-ignore -->
39
+ [![PyPI version](https://img.shields.io/pypi/v/relaxai.svg?label=pypi%20(stable))](https://pypi.org/project/relaxai/)
38
40
 
39
41
  The Relaxai Python library provides convenient access to the Relaxai REST API from any Python 3.8+
40
42
  application. The library includes type definitions for all request params and response fields,
@@ -128,7 +130,6 @@ pip install relaxai[aiohttp]
128
130
  Then you can enable it by instantiating the client with `http_client=DefaultAioHttpClient()`:
129
131
 
130
132
  ```python
131
- import os
132
133
  import asyncio
133
134
  from relaxai import DefaultAioHttpClient
134
135
  from relaxai import AsyncRelaxai
@@ -136,7 +137,7 @@ from relaxai import AsyncRelaxai
136
137
 
137
138
  async def main() -> None:
138
139
  async with AsyncRelaxai(
139
- api_key=os.environ.get("RELAXAI_API_KEY"), # This is the default and can be omitted
140
+ api_key="My API Key",
140
141
  http_client=DefaultAioHttpClient(),
141
142
  ) as client:
142
143
  response = await client.chat.create_completion(
@@ -1,6 +1,7 @@
1
1
  # Relaxai Python API library
2
2
 
3
- [![PyPI version](<https://img.shields.io/pypi/v/relaxai.svg?label=pypi%20(stable)>)](https://pypi.org/project/relaxai/)
3
+ <!-- prettier-ignore -->
4
+ [![PyPI version](https://img.shields.io/pypi/v/relaxai.svg?label=pypi%20(stable))](https://pypi.org/project/relaxai/)
4
5
 
5
6
  The Relaxai Python library provides convenient access to the Relaxai REST API from any Python 3.8+
6
7
  application. The library includes type definitions for all request params and response fields,
@@ -94,7 +95,6 @@ pip install relaxai[aiohttp]
94
95
  Then you can enable it by instantiating the client with `http_client=DefaultAioHttpClient()`:
95
96
 
96
97
  ```python
97
- import os
98
98
  import asyncio
99
99
  from relaxai import DefaultAioHttpClient
100
100
  from relaxai import AsyncRelaxai
@@ -102,7 +102,7 @@ from relaxai import AsyncRelaxai
102
102
 
103
103
  async def main() -> None:
104
104
  async with AsyncRelaxai(
105
- api_key=os.environ.get("RELAXAI_API_KEY"), # This is the default and can be omitted
105
+ api_key="My API Key",
106
106
  http_client=DefaultAioHttpClient(),
107
107
  ) as client:
108
108
  response = await client.chat.create_completion(
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "relaxai"
3
- version = "0.0.1"
3
+ version = "0.1.0"
4
4
  description = "The official Python library for the relaxai API"
5
5
  dynamic = ["readme"]
6
6
  license = "Apache-2.0"
@@ -24,6 +24,7 @@ classifiers = [
24
24
  "Programming Language :: Python :: 3.10",
25
25
  "Programming Language :: Python :: 3.11",
26
26
  "Programming Language :: Python :: 3.12",
27
+ "Programming Language :: Python :: 3.13",
27
28
  "Operating System :: OS Independent",
28
29
  "Operating System :: POSIX",
29
30
  "Operating System :: MacOS",
@@ -38,7 +39,7 @@ Homepage = "https://github.com/relax-ai/python-sdk"
38
39
  Repository = "https://github.com/relax-ai/python-sdk"
39
40
 
40
41
  [project.optional-dependencies]
41
- aiohttp = ["aiohttp", "httpx_aiohttp>=0.1.6"]
42
+ aiohttp = ["aiohttp", "httpx_aiohttp>=0.1.8"]
42
43
 
43
44
  [tool.rye]
44
45
  managed = true
@@ -48,15 +48,15 @@ filelock==3.12.4
48
48
  frozenlist==1.6.2
49
49
  # via aiohttp
50
50
  # via aiosignal
51
- h11==0.14.0
51
+ h11==0.16.0
52
52
  # via httpcore
53
- httpcore==1.0.2
53
+ httpcore==1.0.9
54
54
  # via httpx
55
55
  httpx==0.28.1
56
56
  # via httpx-aiohttp
57
57
  # via relaxai
58
58
  # via respx
59
- httpx-aiohttp==0.1.6
59
+ httpx-aiohttp==0.1.8
60
60
  # via relaxai
61
61
  idna==3.4
62
62
  # via anyio
@@ -36,14 +36,14 @@ exceptiongroup==1.2.2
36
36
  frozenlist==1.6.2
37
37
  # via aiohttp
38
38
  # via aiosignal
39
- h11==0.14.0
39
+ h11==0.16.0
40
40
  # via httpcore
41
- httpcore==1.0.2
41
+ httpcore==1.0.9
42
42
  # via httpx
43
43
  httpx==0.28.1
44
44
  # via httpx-aiohttp
45
45
  # via relaxai
46
- httpx-aiohttp==0.1.6
46
+ httpx-aiohttp==0.1.8
47
47
  # via relaxai
48
48
  idna==3.4
49
49
  # via anyio
@@ -529,6 +529,15 @@ class BaseClient(Generic[_HttpxClientT, _DefaultStreamT]):
529
529
  # work around https://github.com/encode/httpx/discussions/2880
530
530
  kwargs["extensions"] = {"sni_hostname": prepared_url.host.replace("_", "-")}
531
531
 
532
+ is_body_allowed = options.method.lower() != "get"
533
+
534
+ if is_body_allowed:
535
+ kwargs["json"] = json_data if is_given(json_data) else None
536
+ kwargs["files"] = files
537
+ else:
538
+ headers.pop("Content-Type", None)
539
+ kwargs.pop("data", None)
540
+
532
541
  # TODO: report this error to httpx
533
542
  return self._client.build_request( # pyright: ignore[reportUnknownMemberType]
534
543
  headers=headers,
@@ -540,8 +549,6 @@ class BaseClient(Generic[_HttpxClientT, _DefaultStreamT]):
540
549
  # so that passing a `TypedDict` doesn't cause an error.
541
550
  # https://github.com/microsoft/pyright/issues/3526#event-6715453066
542
551
  params=self.qs.stringify(cast(Mapping[str, Any], params)) if params else None,
543
- json=json_data if is_given(json_data) else None,
544
- files=files,
545
552
  **kwargs,
546
553
  )
547
554
 
@@ -79,7 +79,7 @@ class Relaxai(SyncAPIClient):
79
79
  if base_url is None:
80
80
  base_url = os.environ.get("RELAXAI_BASE_URL")
81
81
  if base_url is None:
82
- base_url = f"/"
82
+ base_url = f"http://127.0.0.1"
83
83
 
84
84
  super().__init__(
85
85
  version=__version__,
@@ -262,7 +262,7 @@ class AsyncRelaxai(AsyncAPIClient):
262
262
  if base_url is None:
263
263
  base_url = os.environ.get("RELAXAI_BASE_URL")
264
264
  if base_url is None:
265
- base_url = f"/"
265
+ base_url = f"http://127.0.0.1"
266
266
 
267
267
  super().__init__(
268
268
  version=__version__,
@@ -2,9 +2,10 @@ from __future__ import annotations
2
2
 
3
3
  import os
4
4
  import inspect
5
- from typing import TYPE_CHECKING, Any, Type, Union, Generic, TypeVar, Callable, cast
5
+ from typing import TYPE_CHECKING, Any, Type, Union, Generic, TypeVar, Callable, Optional, cast
6
6
  from datetime import date, datetime
7
7
  from typing_extensions import (
8
+ List,
8
9
  Unpack,
9
10
  Literal,
10
11
  ClassVar,
@@ -207,14 +208,18 @@ class BaseModel(pydantic.BaseModel):
207
208
  else:
208
209
  fields_values[name] = field_get_default(field)
209
210
 
211
+ extra_field_type = _get_extra_fields_type(__cls)
212
+
210
213
  _extra = {}
211
214
  for key, value in values.items():
212
215
  if key not in model_fields:
216
+ parsed = construct_type(value=value, type_=extra_field_type) if extra_field_type is not None else value
217
+
213
218
  if PYDANTIC_V2:
214
- _extra[key] = value
219
+ _extra[key] = parsed
215
220
  else:
216
221
  _fields_set.add(key)
217
- fields_values[key] = value
222
+ fields_values[key] = parsed
218
223
 
219
224
  object.__setattr__(m, "__dict__", fields_values)
220
225
 
@@ -366,7 +371,24 @@ def _construct_field(value: object, field: FieldInfo, key: str) -> object:
366
371
  if type_ is None:
367
372
  raise RuntimeError(f"Unexpected field type is None for {key}")
368
373
 
369
- return construct_type(value=value, type_=type_)
374
+ return construct_type(value=value, type_=type_, metadata=getattr(field, "metadata", None))
375
+
376
+
377
+ def _get_extra_fields_type(cls: type[pydantic.BaseModel]) -> type | None:
378
+ if not PYDANTIC_V2:
379
+ # TODO
380
+ return None
381
+
382
+ schema = cls.__pydantic_core_schema__
383
+ if schema["type"] == "model":
384
+ fields = schema["schema"]
385
+ if fields["type"] == "model-fields":
386
+ extras = fields.get("extras_schema")
387
+ if extras and "cls" in extras:
388
+ # mypy can't narrow the type
389
+ return extras["cls"] # type: ignore[no-any-return]
390
+
391
+ return None
370
392
 
371
393
 
372
394
  def is_basemodel(type_: type) -> bool:
@@ -420,7 +442,7 @@ def construct_type_unchecked(*, value: object, type_: type[_T]) -> _T:
420
442
  return cast(_T, construct_type(value=value, type_=type_))
421
443
 
422
444
 
423
- def construct_type(*, value: object, type_: object) -> object:
445
+ def construct_type(*, value: object, type_: object, metadata: Optional[List[Any]] = None) -> object:
424
446
  """Loose coercion to the expected type with construction of nested values.
425
447
 
426
448
  If the given value does not match the expected type then it is returned as-is.
@@ -438,8 +460,10 @@ def construct_type(*, value: object, type_: object) -> object:
438
460
  type_ = type_.__value__ # type: ignore[unreachable]
439
461
 
440
462
  # unwrap `Annotated[T, ...]` -> `T`
441
- if is_annotated_type(type_):
442
- meta: tuple[Any, ...] = get_args(type_)[1:]
463
+ if metadata is not None and len(metadata) > 0:
464
+ meta: tuple[Any, ...] = tuple(metadata)
465
+ elif is_annotated_type(type_):
466
+ meta = get_args(type_)[1:]
443
467
  type_ = extract_type_arg(type_, 0)
444
468
  else:
445
469
  meta = tuple()
@@ -1,4 +1,4 @@
1
1
  # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
2
2
 
3
3
  __title__ = "relaxai"
4
- __version__ = "0.0.1" # x-release-please-version
4
+ __version__ = "0.1.0" # x-release-please-version
@@ -471,7 +471,7 @@ class TestRelaxai:
471
471
  def test_multipart_repeating_array(self, client: Relaxai) -> None:
472
472
  request = client._build_request(
473
473
  FinalRequestOptions.construct(
474
- method="get",
474
+ method="post",
475
475
  url="/foo",
476
476
  headers={"Content-Type": "multipart/form-data; boundary=6b7ba517decee4a450543ea6ae821c82"},
477
477
  json_data={"array": ["foo", "bar"]},
@@ -1323,7 +1323,7 @@ class TestAsyncRelaxai:
1323
1323
  def test_multipart_repeating_array(self, async_client: AsyncRelaxai) -> None:
1324
1324
  request = async_client._build_request(
1325
1325
  FinalRequestOptions.construct(
1326
- method="get",
1326
+ method="post",
1327
1327
  url="/foo",
1328
1328
  headers={"Content-Type": "multipart/form-data; boundary=6b7ba517decee4a450543ea6ae821c82"},
1329
1329
  json_data={"array": ["foo", "bar"]},
@@ -1,5 +1,5 @@
1
1
  import json
2
- from typing import Any, Dict, List, Union, Optional, cast
2
+ from typing import TYPE_CHECKING, Any, Dict, List, Union, Optional, cast
3
3
  from datetime import datetime, timezone
4
4
  from typing_extensions import Literal, Annotated, TypeAliasType
5
5
 
@@ -889,3 +889,75 @@ def test_discriminated_union_case() -> None:
889
889
  )
890
890
 
891
891
  assert isinstance(m, ModelB)
892
+
893
+
894
+ def test_nested_discriminated_union() -> None:
895
+ class InnerType1(BaseModel):
896
+ type: Literal["type_1"]
897
+
898
+ class InnerModel(BaseModel):
899
+ inner_value: str
900
+
901
+ class InnerType2(BaseModel):
902
+ type: Literal["type_2"]
903
+ some_inner_model: InnerModel
904
+
905
+ class Type1(BaseModel):
906
+ base_type: Literal["base_type_1"]
907
+ value: Annotated[
908
+ Union[
909
+ InnerType1,
910
+ InnerType2,
911
+ ],
912
+ PropertyInfo(discriminator="type"),
913
+ ]
914
+
915
+ class Type2(BaseModel):
916
+ base_type: Literal["base_type_2"]
917
+
918
+ T = Annotated[
919
+ Union[
920
+ Type1,
921
+ Type2,
922
+ ],
923
+ PropertyInfo(discriminator="base_type"),
924
+ ]
925
+
926
+ model = construct_type(
927
+ type_=T,
928
+ value={
929
+ "base_type": "base_type_1",
930
+ "value": {
931
+ "type": "type_2",
932
+ },
933
+ },
934
+ )
935
+ assert isinstance(model, Type1)
936
+ assert isinstance(model.value, InnerType2)
937
+
938
+
939
+ @pytest.mark.skipif(not PYDANTIC_V2, reason="this is only supported in pydantic v2 for now")
940
+ def test_extra_properties() -> None:
941
+ class Item(BaseModel):
942
+ prop: int
943
+
944
+ class Model(BaseModel):
945
+ __pydantic_extra__: Dict[str, Item] = Field(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
946
+
947
+ other: str
948
+
949
+ if TYPE_CHECKING:
950
+
951
+ def __getattr__(self, attr: str) -> Item: ...
952
+
953
+ model = construct_type(
954
+ type_=Model,
955
+ value={
956
+ "a": {"prop": 1},
957
+ "other": "foo",
958
+ },
959
+ )
960
+ assert isinstance(model, Model)
961
+ assert model.a.prop == 1
962
+ assert isinstance(model.a, Item)
963
+ assert model.other == "foo"
@@ -1,3 +0,0 @@
1
- {
2
- ".": "0.0.1"
3
- }
@@ -1,10 +0,0 @@
1
- # Changelog
2
-
3
- ## 0.0.1 (2025-06-27)
4
-
5
- Full Changelog: [v0.0.1-alpha.0...v0.0.1](https://github.com/relax-ai/python-sdk/compare/v0.0.1-alpha.0...v0.0.1)
6
-
7
- ### Chores
8
-
9
- * update SDK settings ([b6c7f5a](https://github.com/relax-ai/python-sdk/commit/b6c7f5aebefb986948527773a67b92fe2fb15954))
10
- * update SDK settings ([fc9f194](https://github.com/relax-ai/python-sdk/commit/fc9f194e0d241fb70577baba8285b31f76677d19))
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes