isaacus 0.7.0__py3-none-any.whl → 0.8.0__py3-none-any.whl
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.
- isaacus/__init__.py +2 -1
- isaacus/_base_client.py +47 -4
- isaacus/_models.py +31 -7
- isaacus/_version.py +1 -1
- {isaacus-0.7.0.dist-info → isaacus-0.8.0.dist-info}/METADATA +43 -3
- {isaacus-0.7.0.dist-info → isaacus-0.8.0.dist-info}/RECORD +8 -8
- {isaacus-0.7.0.dist-info → isaacus-0.8.0.dist-info}/WHEEL +0 -0
- {isaacus-0.7.0.dist-info → isaacus-0.8.0.dist-info}/licenses/LICENSE +0 -0
isaacus/__init__.py
CHANGED
@@ -26,7 +26,7 @@ from ._exceptions import (
|
|
26
26
|
UnprocessableEntityError,
|
27
27
|
APIResponseValidationError,
|
28
28
|
)
|
29
|
-
from ._base_client import DefaultHttpxClient, DefaultAsyncHttpxClient
|
29
|
+
from ._base_client import DefaultHttpxClient, DefaultAioHttpClient, DefaultAsyncHttpxClient
|
30
30
|
from ._utils._logs import setup_logging as _setup_logging
|
31
31
|
|
32
32
|
__all__ = [
|
@@ -68,6 +68,7 @@ __all__ = [
|
|
68
68
|
"DEFAULT_CONNECTION_LIMITS",
|
69
69
|
"DefaultHttpxClient",
|
70
70
|
"DefaultAsyncHttpxClient",
|
71
|
+
"DefaultAioHttpClient",
|
71
72
|
]
|
72
73
|
|
73
74
|
if not _t.TYPE_CHECKING:
|
isaacus/_base_client.py
CHANGED
@@ -504,6 +504,15 @@ class BaseClient(Generic[_HttpxClientT, _DefaultStreamT]):
|
|
504
504
|
# work around https://github.com/encode/httpx/discussions/2880
|
505
505
|
kwargs["extensions"] = {"sni_hostname": prepared_url.host.replace("_", "-")}
|
506
506
|
|
507
|
+
is_body_allowed = options.method.lower() != "get"
|
508
|
+
|
509
|
+
if is_body_allowed:
|
510
|
+
kwargs["json"] = json_data if is_given(json_data) else None
|
511
|
+
kwargs["files"] = files
|
512
|
+
else:
|
513
|
+
headers.pop("Content-Type", None)
|
514
|
+
kwargs.pop("data", None)
|
515
|
+
|
507
516
|
# TODO: report this error to httpx
|
508
517
|
return self._client.build_request( # pyright: ignore[reportUnknownMemberType]
|
509
518
|
headers=headers,
|
@@ -515,8 +524,6 @@ class BaseClient(Generic[_HttpxClientT, _DefaultStreamT]):
|
|
515
524
|
# so that passing a `TypedDict` doesn't cause an error.
|
516
525
|
# https://github.com/microsoft/pyright/issues/3526#event-6715453066
|
517
526
|
params=self.qs.stringify(cast(Mapping[str, Any], params)) if params else None,
|
518
|
-
json=json_data if is_given(json_data) else None,
|
519
|
-
files=files,
|
520
527
|
**kwargs,
|
521
528
|
)
|
522
529
|
|
@@ -1046,7 +1053,14 @@ class SyncAPIClient(BaseClient[httpx.Client, Stream[Any]]):
|
|
1046
1053
|
) -> ResponseT:
|
1047
1054
|
origin = get_origin(cast_to) or cast_to
|
1048
1055
|
|
1049
|
-
if
|
1056
|
+
if (
|
1057
|
+
inspect.isclass(origin)
|
1058
|
+
and issubclass(origin, BaseAPIResponse)
|
1059
|
+
# we only want to actually return the custom BaseAPIResponse class if we're
|
1060
|
+
# returning the raw response, or if we're not streaming SSE, as if we're streaming
|
1061
|
+
# SSE then `cast_to` doesn't actively reflect the type we need to parse into
|
1062
|
+
and (not stream or bool(response.request.headers.get(RAW_RESPONSE_HEADER)))
|
1063
|
+
):
|
1050
1064
|
if not issubclass(origin, APIResponse):
|
1051
1065
|
raise TypeError(f"API Response types must subclass {APIResponse}; Received {origin}")
|
1052
1066
|
|
@@ -1257,6 +1271,24 @@ class _DefaultAsyncHttpxClient(httpx.AsyncClient):
|
|
1257
1271
|
super().__init__(**kwargs)
|
1258
1272
|
|
1259
1273
|
|
1274
|
+
try:
|
1275
|
+
import httpx_aiohttp
|
1276
|
+
except ImportError:
|
1277
|
+
|
1278
|
+
class _DefaultAioHttpClient(httpx.AsyncClient):
|
1279
|
+
def __init__(self, **_kwargs: Any) -> None:
|
1280
|
+
raise RuntimeError("To use the aiohttp client you must have installed the package with the `aiohttp` extra")
|
1281
|
+
else:
|
1282
|
+
|
1283
|
+
class _DefaultAioHttpClient(httpx_aiohttp.HttpxAiohttpClient): # type: ignore
|
1284
|
+
def __init__(self, **kwargs: Any) -> None:
|
1285
|
+
kwargs.setdefault("timeout", DEFAULT_TIMEOUT)
|
1286
|
+
kwargs.setdefault("limits", DEFAULT_CONNECTION_LIMITS)
|
1287
|
+
kwargs.setdefault("follow_redirects", True)
|
1288
|
+
|
1289
|
+
super().__init__(**kwargs)
|
1290
|
+
|
1291
|
+
|
1260
1292
|
if TYPE_CHECKING:
|
1261
1293
|
DefaultAsyncHttpxClient = httpx.AsyncClient
|
1262
1294
|
"""An alias to `httpx.AsyncClient` that provides the same defaults that this SDK
|
@@ -1265,8 +1297,12 @@ if TYPE_CHECKING:
|
|
1265
1297
|
This is useful because overriding the `http_client` with your own instance of
|
1266
1298
|
`httpx.AsyncClient` will result in httpx's defaults being used, not ours.
|
1267
1299
|
"""
|
1300
|
+
|
1301
|
+
DefaultAioHttpClient = httpx.AsyncClient
|
1302
|
+
"""An alias to `httpx.AsyncClient` that changes the default HTTP transport to `aiohttp`."""
|
1268
1303
|
else:
|
1269
1304
|
DefaultAsyncHttpxClient = _DefaultAsyncHttpxClient
|
1305
|
+
DefaultAioHttpClient = _DefaultAioHttpClient
|
1270
1306
|
|
1271
1307
|
|
1272
1308
|
class AsyncHttpxClientWrapper(DefaultAsyncHttpxClient):
|
@@ -1549,7 +1585,14 @@ class AsyncAPIClient(BaseClient[httpx.AsyncClient, AsyncStream[Any]]):
|
|
1549
1585
|
) -> ResponseT:
|
1550
1586
|
origin = get_origin(cast_to) or cast_to
|
1551
1587
|
|
1552
|
-
if
|
1588
|
+
if (
|
1589
|
+
inspect.isclass(origin)
|
1590
|
+
and issubclass(origin, BaseAPIResponse)
|
1591
|
+
# we only want to actually return the custom BaseAPIResponse class if we're
|
1592
|
+
# returning the raw response, or if we're not streaming SSE, as if we're streaming
|
1593
|
+
# SSE then `cast_to` doesn't actively reflect the type we need to parse into
|
1594
|
+
and (not stream or bool(response.request.headers.get(RAW_RESPONSE_HEADER)))
|
1595
|
+
):
|
1553
1596
|
if not issubclass(origin, AsyncAPIResponse):
|
1554
1597
|
raise TypeError(f"API Response types must subclass {AsyncAPIResponse}; Received {origin}")
|
1555
1598
|
|
isaacus/_models.py
CHANGED
@@ -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()
|
isaacus/_version.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: isaacus
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.8.0
|
4
4
|
Summary: The official Python library for the isaacus API
|
5
5
|
Project-URL: Homepage, https://github.com/isaacus-dev/isaacus-python
|
6
6
|
Project-URL: Repository, https://github.com/isaacus-dev/isaacus-python
|
@@ -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
|
@@ -27,11 +28,15 @@ Requires-Dist: httpx<1,>=0.23.0
|
|
27
28
|
Requires-Dist: pydantic<3,>=1.9.0
|
28
29
|
Requires-Dist: sniffio
|
29
30
|
Requires-Dist: typing-extensions<5,>=4.10
|
31
|
+
Provides-Extra: aiohttp
|
32
|
+
Requires-Dist: aiohttp; extra == 'aiohttp'
|
33
|
+
Requires-Dist: httpx-aiohttp>=0.1.8; extra == 'aiohttp'
|
30
34
|
Description-Content-Type: text/markdown
|
31
35
|
|
32
36
|
# Isaacus Python API library
|
33
37
|
|
34
|
-
|
38
|
+
<!-- prettier-ignore -->
|
39
|
+
[)](https://pypi.org/project/isaacus/)
|
35
40
|
|
36
41
|
The Isaacus Python library provides convenient access to the Isaacus REST API from any Python 3.8+
|
37
42
|
application. The library includes type definitions for all request params and response fields,
|
@@ -103,6 +108,41 @@ asyncio.run(main())
|
|
103
108
|
|
104
109
|
Functionality between the synchronous and asynchronous clients is otherwise identical.
|
105
110
|
|
111
|
+
### With aiohttp
|
112
|
+
|
113
|
+
By default, the async client uses `httpx` for HTTP requests. However, for improved concurrency performance you may also use `aiohttp` as the HTTP backend.
|
114
|
+
|
115
|
+
You can enable this by installing `aiohttp`:
|
116
|
+
|
117
|
+
```sh
|
118
|
+
# install from PyPI
|
119
|
+
pip install isaacus[aiohttp]
|
120
|
+
```
|
121
|
+
|
122
|
+
Then you can enable it by instantiating the client with `http_client=DefaultAioHttpClient()`:
|
123
|
+
|
124
|
+
```python
|
125
|
+
import asyncio
|
126
|
+
from isaacus import DefaultAioHttpClient
|
127
|
+
from isaacus import AsyncIsaacus
|
128
|
+
|
129
|
+
|
130
|
+
async def main() -> None:
|
131
|
+
async with AsyncIsaacus(
|
132
|
+
api_key="My API Key",
|
133
|
+
http_client=DefaultAioHttpClient(),
|
134
|
+
) as client:
|
135
|
+
universal_classification = await client.classifications.universal.create(
|
136
|
+
model="kanon-universal-classifier",
|
137
|
+
query="This is a confidentiality clause.",
|
138
|
+
texts=["I agree not to tell anyone about the document."],
|
139
|
+
)
|
140
|
+
print(universal_classification.classifications)
|
141
|
+
|
142
|
+
|
143
|
+
asyncio.run(main())
|
144
|
+
```
|
145
|
+
|
106
146
|
## Using types
|
107
147
|
|
108
148
|
Nested request parameters are [TypedDicts](https://docs.python.org/3/library/typing.html#typing.TypedDict). Responses are [Pydantic models](https://docs.pydantic.dev) which also provide helper methods for things like:
|
@@ -207,7 +247,7 @@ client.with_options(max_retries=5).classifications.universal.create(
|
|
207
247
|
### Timeouts
|
208
248
|
|
209
249
|
By default requests time out after 1 minute. You can configure this with a `timeout` option,
|
210
|
-
which accepts a float or an [`httpx.Timeout`](https://www.python-httpx.org/advanced/#fine-tuning-the-configuration) object:
|
250
|
+
which accepts a float or an [`httpx.Timeout`](https://www.python-httpx.org/advanced/timeouts/#fine-tuning-the-configuration) object:
|
211
251
|
|
212
252
|
```python
|
213
253
|
from isaacus import Isaacus
|
@@ -1,17 +1,17 @@
|
|
1
|
-
isaacus/__init__.py,sha256=
|
2
|
-
isaacus/_base_client.py,sha256=
|
1
|
+
isaacus/__init__.py,sha256=UFaPvzUXNjeEhBaZBmS5pHsOT4jdgePx9tN-_-7rQvQ,2587
|
2
|
+
isaacus/_base_client.py,sha256=d6I9vr1FKcXgokcfwYkXtgkHKrrsJmeKS14QJ6wOIDQ,66133
|
3
3
|
isaacus/_client.py,sha256=-2sN8vyAm_qN2P6JuLxKel6BfvA6mragbo4TPOwLs3Y,16544
|
4
4
|
isaacus/_compat.py,sha256=VWemUKbj6DDkQ-O4baSpHVLJafotzeXmCQGJugfVTIw,6580
|
5
5
|
isaacus/_constants.py,sha256=S14PFzyN9-I31wiV7SmIlL5Ga0MLHxdvegInGdXH7tM,462
|
6
6
|
isaacus/_exceptions.py,sha256=L82uluhizzc94VydHIaJkNxkcG-2DAe74tNhrE2eN2A,3222
|
7
7
|
isaacus/_files.py,sha256=mf4dOgL4b0ryyZlbqLhggD3GVgDf6XxdGFAgce01ugE,3549
|
8
|
-
isaacus/_models.py,sha256=
|
8
|
+
isaacus/_models.py,sha256=KvjsMfb88XZlFUKVoOxr8OyDj47MhoH2OKqWNEbBhk4,30010
|
9
9
|
isaacus/_qs.py,sha256=AOkSz4rHtK4YI3ZU_kzea-zpwBUgEY8WniGmTPyEimc,4846
|
10
10
|
isaacus/_resource.py,sha256=iP_oYhz5enCI58mK7hlwLoPMPh4Q5s8-KBv-jGfv2aM,1106
|
11
11
|
isaacus/_response.py,sha256=aXLF5ia58bjjQXTxY574lh7JfKXiGL2tDTX09klm8lw,28794
|
12
12
|
isaacus/_streaming.py,sha256=tMBfwrfEFWm0v7vWFgjn_lizsoD70lPkYigIBuADaCM,10104
|
13
13
|
isaacus/_types.py,sha256=OWOeOlYfnHj59_Lq34ua9FIuR_UTSHQzo4SxlaQBS9Y,6198
|
14
|
-
isaacus/_version.py,sha256=
|
14
|
+
isaacus/_version.py,sha256=zwbXJiibrQFE8HHRyah1KPLOSRL6I7y8cADvmmrXCHw,159
|
15
15
|
isaacus/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
16
16
|
isaacus/_utils/__init__.py,sha256=PNZ_QJuzZEgyYXqkO1HVhGkj5IU9bglVUcw7H-Knjzw,2062
|
17
17
|
isaacus/_utils/_logs.py,sha256=rwa1Yzjbs2JaFn9KQ06rH5c_GSNa--BVwWnWhvvT1tY,777
|
@@ -41,7 +41,7 @@ isaacus/types/classifications/universal_create_params.py,sha256=0RYreNEY9j5BqxJL
|
|
41
41
|
isaacus/types/extractions/__init__.py,sha256=9FahSE48xCUSAdkb1DaqRBBznuQ805lmRdlY22iLtQw,254
|
42
42
|
isaacus/types/extractions/answer_extraction.py,sha256=KIqfIsdlYAAjDDwzM8XuFOW_1u5zv34RWbIa3WchBxc,2152
|
43
43
|
isaacus/types/extractions/qa_create_params.py,sha256=iaGIDnuJ8Kek5jW6QMQdbAQJYYiihcjZ1k_iSDbt1Mk,2065
|
44
|
-
isaacus-0.
|
45
|
-
isaacus-0.
|
46
|
-
isaacus-0.
|
47
|
-
isaacus-0.
|
44
|
+
isaacus-0.8.0.dist-info/METADATA,sha256=iSs7rQtVwrt8TEiuZgMoeoGGsctLaus2Dytg9VYL0ww,15546
|
45
|
+
isaacus-0.8.0.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
|
46
|
+
isaacus-0.8.0.dist-info/licenses/LICENSE,sha256=lUen4LYVFVGEVXBsntBAPsQsOWgMkno1e9WfgWkpZ-k,11337
|
47
|
+
isaacus-0.8.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|