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.
- {relaxai-0.0.1 → relaxai-0.1.0}/.gitignore +0 -1
- relaxai-0.1.0/.release-please-manifest.json +3 -0
- relaxai-0.1.0/CHANGELOG.md +40 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/PKG-INFO +6 -5
- {relaxai-0.0.1 → relaxai-0.1.0}/README.md +3 -3
- {relaxai-0.0.1 → relaxai-0.1.0}/pyproject.toml +3 -2
- {relaxai-0.0.1 → relaxai-0.1.0}/requirements-dev.lock +3 -3
- {relaxai-0.0.1 → relaxai-0.1.0}/requirements.lock +3 -3
- {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/_base_client.py +9 -2
- {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/_client.py +2 -2
- {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/_models.py +31 -7
- {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/_version.py +1 -1
- {relaxai-0.0.1 → relaxai-0.1.0}/tests/test_client.py +2 -2
- {relaxai-0.0.1 → relaxai-0.1.0}/tests/test_models.py +73 -1
- relaxai-0.0.1/.release-please-manifest.json +0 -3
- relaxai-0.0.1/CHANGELOG.md +0 -10
- {relaxai-0.0.1 → relaxai-0.1.0}/CONTRIBUTING.md +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/LICENSE +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/SECURITY.md +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/api.md +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/bin/check-release-environment +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/bin/publish-pypi +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/examples/.keep +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/mypy.ini +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/noxfile.py +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/release-please-config.json +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/__init__.py +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/_compat.py +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/_constants.py +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/_exceptions.py +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/_files.py +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/_qs.py +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/_resource.py +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/_response.py +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/_streaming.py +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/_types.py +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/_utils/__init__.py +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/_utils/_logs.py +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/_utils/_proxy.py +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/_utils/_reflection.py +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/_utils/_resources_proxy.py +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/_utils/_streams.py +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/_utils/_sync.py +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/_utils/_transform.py +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/_utils/_typing.py +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/_utils/_utils.py +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/lib/.keep +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/py.typed +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/resources/__init__.py +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/resources/chat.py +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/resources/embeddings.py +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/resources/health.py +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/resources/models.py +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/types/__init__.py +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/types/chat_completion_message.py +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/types/chat_completion_message_param.py +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/types/chat_create_completion_params.py +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/types/chat_create_completion_response.py +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/types/content_filter_results.py +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/types/embedding_create_params.py +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/types/embedding_create_response.py +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/types/function_call.py +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/types/function_call_param.py +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/types/function_definition_param.py +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/types/health_check_response.py +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/types/model.py +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/types/model_list_response.py +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/src/relaxai/types/usage.py +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/tests/__init__.py +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/tests/api_resources/__init__.py +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/tests/api_resources/test_chat.py +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/tests/api_resources/test_embeddings.py +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/tests/api_resources/test_health.py +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/tests/api_resources/test_models.py +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/tests/conftest.py +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/tests/sample_file.txt +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/tests/test_deepcopy.py +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/tests/test_extract_files.py +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/tests/test_files.py +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/tests/test_qs.py +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/tests/test_required_args.py +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/tests/test_response.py +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/tests/test_streaming.py +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/tests/test_transform.py +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/tests/test_utils/test_proxy.py +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/tests/test_utils/test_typing.py +0 -0
- {relaxai-0.0.1 → relaxai-0.1.0}/tests/utils.py +0 -0
|
@@ -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
|
|
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.
|
|
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
|
-
|
|
38
|
+
<!-- prettier-ignore -->
|
|
39
|
+
[)](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=
|
|
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
|
-
|
|
3
|
+
<!-- prettier-ignore -->
|
|
4
|
+
[)](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=
|
|
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
|
|
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.
|
|
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.
|
|
51
|
+
h11==0.16.0
|
|
52
52
|
# via httpcore
|
|
53
|
-
httpcore==1.0.
|
|
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.
|
|
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.
|
|
39
|
+
h11==0.16.0
|
|
40
40
|
# via httpcore
|
|
41
|
-
httpcore==1.0.
|
|
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.
|
|
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] =
|
|
219
|
+
_extra[key] = parsed
|
|
215
220
|
else:
|
|
216
221
|
_fields_set.add(key)
|
|
217
|
-
fields_values[key] =
|
|
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
|
|
442
|
-
meta: tuple[Any, ...] =
|
|
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()
|
|
@@ -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="
|
|
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="
|
|
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"
|
relaxai-0.0.1/CHANGELOG.md
DELETED
|
@@ -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
|
|
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
|
|
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
|