isaacus 0.3.2__tar.gz → 0.4.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.
Files changed (75) hide show
  1. isaacus-0.4.0/.release-please-manifest.json +3 -0
  2. {isaacus-0.3.2 → isaacus-0.4.0}/CHANGELOG.md +42 -0
  3. {isaacus-0.3.2 → isaacus-0.4.0}/PKG-INFO +12 -12
  4. {isaacus-0.3.2 → isaacus-0.4.0}/README.md +11 -11
  5. {isaacus-0.3.2 → isaacus-0.4.0}/pyproject.toml +3 -2
  6. {isaacus-0.3.2 → isaacus-0.4.0}/requirements-dev.lock +1 -1
  7. {isaacus-0.3.2 → isaacus-0.4.0}/src/isaacus/_base_client.py +15 -2
  8. {isaacus-0.3.2 → isaacus-0.4.0}/src/isaacus/_models.py +0 -1
  9. {isaacus-0.3.2 → isaacus-0.4.0}/src/isaacus/_utils/_transform.py +46 -1
  10. {isaacus-0.3.2 → isaacus-0.4.0}/src/isaacus/_utils/_typing.py +3 -1
  11. {isaacus-0.3.2 → isaacus-0.4.0}/src/isaacus/_version.py +1 -1
  12. {isaacus-0.3.2 → isaacus-0.4.0}/src/isaacus/resources/classifications/universal.py +23 -23
  13. {isaacus-0.3.2 → isaacus-0.4.0}/src/isaacus/types/classifications/universal_classification.py +36 -12
  14. {isaacus-0.3.2 → isaacus-0.4.0}/src/isaacus/types/classifications/universal_create_params.py +10 -10
  15. {isaacus-0.3.2 → isaacus-0.4.0}/tests/api_resources/classifications/test_universal.py +8 -8
  16. {isaacus-0.3.2 → isaacus-0.4.0}/tests/conftest.py +1 -1
  17. {isaacus-0.3.2 → isaacus-0.4.0}/tests/test_client.py +10 -10
  18. {isaacus-0.3.2 → isaacus-0.4.0}/tests/test_models.py +4 -1
  19. {isaacus-0.3.2 → isaacus-0.4.0}/tests/test_transform.py +20 -1
  20. isaacus-0.3.2/.release-please-manifest.json +0 -3
  21. {isaacus-0.3.2 → isaacus-0.4.0}/.gitignore +0 -0
  22. {isaacus-0.3.2 → isaacus-0.4.0}/CONTRIBUTING.md +0 -0
  23. {isaacus-0.3.2 → isaacus-0.4.0}/LICENSE +0 -0
  24. {isaacus-0.3.2 → isaacus-0.4.0}/SECURITY.md +0 -0
  25. {isaacus-0.3.2 → isaacus-0.4.0}/api.md +0 -0
  26. {isaacus-0.3.2 → isaacus-0.4.0}/bin/check-release-environment +0 -0
  27. {isaacus-0.3.2 → isaacus-0.4.0}/bin/publish-pypi +0 -0
  28. {isaacus-0.3.2 → isaacus-0.4.0}/examples/.keep +0 -0
  29. {isaacus-0.3.2 → isaacus-0.4.0}/mypy.ini +0 -0
  30. {isaacus-0.3.2 → isaacus-0.4.0}/noxfile.py +0 -0
  31. {isaacus-0.3.2 → isaacus-0.4.0}/release-please-config.json +0 -0
  32. {isaacus-0.3.2 → isaacus-0.4.0}/requirements.lock +0 -0
  33. {isaacus-0.3.2 → isaacus-0.4.0}/src/isaacus/__init__.py +0 -0
  34. {isaacus-0.3.2 → isaacus-0.4.0}/src/isaacus/_client.py +0 -0
  35. {isaacus-0.3.2 → isaacus-0.4.0}/src/isaacus/_compat.py +0 -0
  36. {isaacus-0.3.2 → isaacus-0.4.0}/src/isaacus/_constants.py +0 -0
  37. {isaacus-0.3.2 → isaacus-0.4.0}/src/isaacus/_exceptions.py +0 -0
  38. {isaacus-0.3.2 → isaacus-0.4.0}/src/isaacus/_files.py +0 -0
  39. {isaacus-0.3.2 → isaacus-0.4.0}/src/isaacus/_qs.py +0 -0
  40. {isaacus-0.3.2 → isaacus-0.4.0}/src/isaacus/_resource.py +0 -0
  41. {isaacus-0.3.2 → isaacus-0.4.0}/src/isaacus/_response.py +0 -0
  42. {isaacus-0.3.2 → isaacus-0.4.0}/src/isaacus/_streaming.py +0 -0
  43. {isaacus-0.3.2 → isaacus-0.4.0}/src/isaacus/_types.py +0 -0
  44. {isaacus-0.3.2 → isaacus-0.4.0}/src/isaacus/_utils/__init__.py +0 -0
  45. {isaacus-0.3.2 → isaacus-0.4.0}/src/isaacus/_utils/_logs.py +0 -0
  46. {isaacus-0.3.2 → isaacus-0.4.0}/src/isaacus/_utils/_proxy.py +0 -0
  47. {isaacus-0.3.2 → isaacus-0.4.0}/src/isaacus/_utils/_reflection.py +0 -0
  48. {isaacus-0.3.2 → isaacus-0.4.0}/src/isaacus/_utils/_streams.py +0 -0
  49. {isaacus-0.3.2 → isaacus-0.4.0}/src/isaacus/_utils/_sync.py +0 -0
  50. {isaacus-0.3.2 → isaacus-0.4.0}/src/isaacus/_utils/_utils.py +0 -0
  51. {isaacus-0.3.2 → isaacus-0.4.0}/src/isaacus/lib/.keep +0 -0
  52. {isaacus-0.3.2 → isaacus-0.4.0}/src/isaacus/py.typed +0 -0
  53. {isaacus-0.3.2 → isaacus-0.4.0}/src/isaacus/resources/__init__.py +0 -0
  54. {isaacus-0.3.2 → isaacus-0.4.0}/src/isaacus/resources/classifications/__init__.py +0 -0
  55. {isaacus-0.3.2 → isaacus-0.4.0}/src/isaacus/resources/classifications/classifications.py +0 -0
  56. {isaacus-0.3.2 → isaacus-0.4.0}/src/isaacus/resources/rerankings.py +0 -0
  57. {isaacus-0.3.2 → isaacus-0.4.0}/src/isaacus/types/__init__.py +0 -0
  58. {isaacus-0.3.2 → isaacus-0.4.0}/src/isaacus/types/classifications/__init__.py +0 -0
  59. {isaacus-0.3.2 → isaacus-0.4.0}/src/isaacus/types/reranking.py +0 -0
  60. {isaacus-0.3.2 → isaacus-0.4.0}/src/isaacus/types/reranking_create_params.py +0 -0
  61. {isaacus-0.3.2 → isaacus-0.4.0}/tests/__init__.py +0 -0
  62. {isaacus-0.3.2 → isaacus-0.4.0}/tests/api_resources/__init__.py +0 -0
  63. {isaacus-0.3.2 → isaacus-0.4.0}/tests/api_resources/classifications/__init__.py +0 -0
  64. {isaacus-0.3.2 → isaacus-0.4.0}/tests/api_resources/test_rerankings.py +0 -0
  65. {isaacus-0.3.2 → isaacus-0.4.0}/tests/sample_file.txt +0 -0
  66. {isaacus-0.3.2 → isaacus-0.4.0}/tests/test_deepcopy.py +0 -0
  67. {isaacus-0.3.2 → isaacus-0.4.0}/tests/test_extract_files.py +0 -0
  68. {isaacus-0.3.2 → isaacus-0.4.0}/tests/test_files.py +0 -0
  69. {isaacus-0.3.2 → isaacus-0.4.0}/tests/test_qs.py +0 -0
  70. {isaacus-0.3.2 → isaacus-0.4.0}/tests/test_required_args.py +0 -0
  71. {isaacus-0.3.2 → isaacus-0.4.0}/tests/test_response.py +0 -0
  72. {isaacus-0.3.2 → isaacus-0.4.0}/tests/test_streaming.py +0 -0
  73. {isaacus-0.3.2 → isaacus-0.4.0}/tests/test_utils/test_proxy.py +0 -0
  74. {isaacus-0.3.2 → isaacus-0.4.0}/tests/test_utils/test_typing.py +0 -0
  75. {isaacus-0.3.2 → isaacus-0.4.0}/tests/utils.py +0 -0
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "0.4.0"
3
+ }
@@ -1,5 +1,47 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.0 (2025-04-19)
4
+
5
+ Full Changelog: [v0.3.3...v0.4.0](https://github.com/isaacus-dev/isaacus-python/compare/v0.3.3...v0.4.0)
6
+
7
+ ### ⚠ BREAKING CHANGES
8
+
9
+ * **api:** made universal classification endpoint multi-input only
10
+
11
+ ### Features
12
+
13
+ * **api:** made universal classification endpoint multi-input only ([4fb2535](https://github.com/isaacus-dev/isaacus-python/commit/4fb2535407d88d51c1db1e9a37c9ea767cdf06c0))
14
+
15
+
16
+ ### Chores
17
+
18
+ * **internal:** bump pyright version ([2f992e7](https://github.com/isaacus-dev/isaacus-python/commit/2f992e788860d16739438a021bd8825a7999b1e4))
19
+ * **internal:** update models test ([bb3df78](https://github.com/isaacus-dev/isaacus-python/commit/bb3df7823dd27e6482b5e97ef17019ee0a1e596c))
20
+
21
+ ## 0.3.3 (2025-04-16)
22
+
23
+ Full Changelog: [v0.3.2...v0.3.3](https://github.com/isaacus-dev/isaacus-python/compare/v0.3.2...v0.3.3)
24
+
25
+ ### Bug Fixes
26
+
27
+ * **perf:** optimize some hot paths ([eee757b](https://github.com/isaacus-dev/isaacus-python/commit/eee757ba44a895fcf2052b9981783b6cf233653f))
28
+ * **perf:** skip traversing types for NotGiven values ([7705a99](https://github.com/isaacus-dev/isaacus-python/commit/7705a99e0efd9724eb3260550b4b58081af85878))
29
+
30
+
31
+ ### Chores
32
+
33
+ * **client:** minor internal fixes ([a8dad58](https://github.com/isaacus-dev/isaacus-python/commit/a8dad5881d0f3f5d1929574efba483a8fcdbc322))
34
+ * **internal:** codegen related update ([93cdfa0](https://github.com/isaacus-dev/isaacus-python/commit/93cdfa0c0dfc947ec76f10291887b90324301b32))
35
+ * **internal:** expand CI branch coverage ([cc5df77](https://github.com/isaacus-dev/isaacus-python/commit/cc5df7771a9ea699b0e37533070e1cb5569d7ad9))
36
+ * **internal:** reduce CI branch coverage ([2cb8fb8](https://github.com/isaacus-dev/isaacus-python/commit/2cb8fb81f4cea76d12ae3feeb09e4b43b743e8c4))
37
+ * **internal:** slight transform perf improvement ([6f47eaf](https://github.com/isaacus-dev/isaacus-python/commit/6f47eafa0ebcd31741f24bea539a4c54e88a758e))
38
+ * **internal:** update pyright settings ([7dd9ad4](https://github.com/isaacus-dev/isaacus-python/commit/7dd9ad4a4a25825929a4916168a07d74bcc52fbe))
39
+
40
+
41
+ ### Documentation
42
+
43
+ * **api:** removed description of certain objects due to Mintlify bug ([9099926](https://github.com/isaacus-dev/isaacus-python/commit/90999261a360fef3ba92c52e4ad5361b79b499e6))
44
+
3
45
  ## 0.3.2 (2025-04-04)
4
46
 
5
47
  Full Changelog: [v0.3.1...v0.3.2](https://github.com/isaacus-dev/isaacus-python/compare/v0.3.1...v0.3.2)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: isaacus
3
- Version: 0.3.2
3
+ Version: 0.4.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
@@ -65,9 +65,9 @@ client = Isaacus(
65
65
  universal_classification = client.classifications.universal.create(
66
66
  model="kanon-universal-classifier",
67
67
  query="This is a confidentiality clause.",
68
- text="I agree not to tell anyone about the document.",
68
+ texts=["I agree not to tell anyone about the document."],
69
69
  )
70
- print(universal_classification.chunks)
70
+ print(universal_classification.classifications)
71
71
  ```
72
72
 
73
73
  While you can provide an `api_key` keyword argument,
@@ -93,9 +93,9 @@ async def main() -> None:
93
93
  universal_classification = await client.classifications.universal.create(
94
94
  model="kanon-universal-classifier",
95
95
  query="This is a confidentiality clause.",
96
- text="I agree not to tell anyone about the document.",
96
+ texts=["I agree not to tell anyone about the document."],
97
97
  )
98
- print(universal_classification.chunks)
98
+ print(universal_classification.classifications)
99
99
 
100
100
 
101
101
  asyncio.run(main())
@@ -124,7 +124,7 @@ client = Isaacus()
124
124
  universal_classification = client.classifications.universal.create(
125
125
  model="kanon-universal-classifier",
126
126
  query="This is a confidentiality clause.",
127
- text="I agree not to tell anyone about the document.",
127
+ texts=["I agree not to tell anyone about the document."],
128
128
  chunking_options={
129
129
  "overlap_ratio": 0.1,
130
130
  "overlap_tokens": None,
@@ -153,7 +153,7 @@ try:
153
153
  client.classifications.universal.create(
154
154
  model="kanon-universal-classifier",
155
155
  query="This is a confidentiality clause.",
156
- text="I agree not to tell anyone about the document.",
156
+ texts=["I agree not to tell anyone about the document."],
157
157
  )
158
158
  except isaacus.APIConnectionError as e:
159
159
  print("The server could not be reached")
@@ -200,7 +200,7 @@ client = Isaacus(
200
200
  client.with_options(max_retries=5).classifications.universal.create(
201
201
  model="kanon-universal-classifier",
202
202
  query="This is a confidentiality clause.",
203
- text="I agree not to tell anyone about the document.",
203
+ texts=["I agree not to tell anyone about the document."],
204
204
  )
205
205
  ```
206
206
 
@@ -227,7 +227,7 @@ client = Isaacus(
227
227
  client.with_options(timeout=5.0).classifications.universal.create(
228
228
  model="kanon-universal-classifier",
229
229
  query="This is a confidentiality clause.",
230
- text="I agree not to tell anyone about the document.",
230
+ texts=["I agree not to tell anyone about the document."],
231
231
  )
232
232
  ```
233
233
 
@@ -272,12 +272,12 @@ client = Isaacus()
272
272
  response = client.classifications.universal.with_raw_response.create(
273
273
  model="kanon-universal-classifier",
274
274
  query="This is a confidentiality clause.",
275
- text="I agree not to tell anyone about the document.",
275
+ texts=["I agree not to tell anyone about the document."],
276
276
  )
277
277
  print(response.headers.get('X-My-Header'))
278
278
 
279
279
  universal = response.parse() # get the object that `classifications.universal.create()` would have returned
280
- print(universal.chunks)
280
+ print(universal.classifications)
281
281
  ```
282
282
 
283
283
  These methods return an [`APIResponse`](https://github.com/isaacus-dev/isaacus-python/tree/main/src/isaacus/_response.py) object.
@@ -294,7 +294,7 @@ To stream the response body, use `.with_streaming_response` instead, which requi
294
294
  with client.classifications.universal.with_streaming_response.create(
295
295
  model="kanon-universal-classifier",
296
296
  query="This is a confidentiality clause.",
297
- text="I agree not to tell anyone about the document.",
297
+ texts=["I agree not to tell anyone about the document."],
298
298
  ) as response:
299
299
  print(response.headers.get("X-My-Header"))
300
300
 
@@ -34,9 +34,9 @@ client = Isaacus(
34
34
  universal_classification = client.classifications.universal.create(
35
35
  model="kanon-universal-classifier",
36
36
  query="This is a confidentiality clause.",
37
- text="I agree not to tell anyone about the document.",
37
+ texts=["I agree not to tell anyone about the document."],
38
38
  )
39
- print(universal_classification.chunks)
39
+ print(universal_classification.classifications)
40
40
  ```
41
41
 
42
42
  While you can provide an `api_key` keyword argument,
@@ -62,9 +62,9 @@ async def main() -> None:
62
62
  universal_classification = await client.classifications.universal.create(
63
63
  model="kanon-universal-classifier",
64
64
  query="This is a confidentiality clause.",
65
- text="I agree not to tell anyone about the document.",
65
+ texts=["I agree not to tell anyone about the document."],
66
66
  )
67
- print(universal_classification.chunks)
67
+ print(universal_classification.classifications)
68
68
 
69
69
 
70
70
  asyncio.run(main())
@@ -93,7 +93,7 @@ client = Isaacus()
93
93
  universal_classification = client.classifications.universal.create(
94
94
  model="kanon-universal-classifier",
95
95
  query="This is a confidentiality clause.",
96
- text="I agree not to tell anyone about the document.",
96
+ texts=["I agree not to tell anyone about the document."],
97
97
  chunking_options={
98
98
  "overlap_ratio": 0.1,
99
99
  "overlap_tokens": None,
@@ -122,7 +122,7 @@ try:
122
122
  client.classifications.universal.create(
123
123
  model="kanon-universal-classifier",
124
124
  query="This is a confidentiality clause.",
125
- text="I agree not to tell anyone about the document.",
125
+ texts=["I agree not to tell anyone about the document."],
126
126
  )
127
127
  except isaacus.APIConnectionError as e:
128
128
  print("The server could not be reached")
@@ -169,7 +169,7 @@ client = Isaacus(
169
169
  client.with_options(max_retries=5).classifications.universal.create(
170
170
  model="kanon-universal-classifier",
171
171
  query="This is a confidentiality clause.",
172
- text="I agree not to tell anyone about the document.",
172
+ texts=["I agree not to tell anyone about the document."],
173
173
  )
174
174
  ```
175
175
 
@@ -196,7 +196,7 @@ client = Isaacus(
196
196
  client.with_options(timeout=5.0).classifications.universal.create(
197
197
  model="kanon-universal-classifier",
198
198
  query="This is a confidentiality clause.",
199
- text="I agree not to tell anyone about the document.",
199
+ texts=["I agree not to tell anyone about the document."],
200
200
  )
201
201
  ```
202
202
 
@@ -241,12 +241,12 @@ client = Isaacus()
241
241
  response = client.classifications.universal.with_raw_response.create(
242
242
  model="kanon-universal-classifier",
243
243
  query="This is a confidentiality clause.",
244
- text="I agree not to tell anyone about the document.",
244
+ texts=["I agree not to tell anyone about the document."],
245
245
  )
246
246
  print(response.headers.get('X-My-Header'))
247
247
 
248
248
  universal = response.parse() # get the object that `classifications.universal.create()` would have returned
249
- print(universal.chunks)
249
+ print(universal.classifications)
250
250
  ```
251
251
 
252
252
  These methods return an [`APIResponse`](https://github.com/isaacus-dev/isaacus-python/tree/main/src/isaacus/_response.py) object.
@@ -263,7 +263,7 @@ To stream the response body, use `.with_streaming_response` instead, which requi
263
263
  with client.classifications.universal.with_streaming_response.create(
264
264
  model="kanon-universal-classifier",
265
265
  query="This is a confidentiality clause.",
266
- text="I agree not to tell anyone about the document.",
266
+ texts=["I agree not to tell anyone about the document."],
267
267
  ) as response:
268
268
  print(response.headers.get("X-My-Header"))
269
269
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "isaacus"
3
- version = "0.3.2"
3
+ version = "0.4.0"
4
4
  description = "The official Python library for the isaacus API"
5
5
  dynamic = ["readme"]
6
6
  license = "Apache-2.0"
@@ -42,7 +42,7 @@ Repository = "https://github.com/isaacus-dev/isaacus-python"
42
42
  managed = true
43
43
  # version pins are in requirements-dev.lock
44
44
  dev-dependencies = [
45
- "pyright>=1.1.359",
45
+ "pyright==1.1.399",
46
46
  "mypy",
47
47
  "respx",
48
48
  "pytest",
@@ -147,6 +147,7 @@ exclude = [
147
147
  ]
148
148
 
149
149
  reportImplicitOverride = true
150
+ reportOverlappingOverload = false
150
151
 
151
152
  reportImportCycles = false
152
153
  reportPrivateUsage = false
@@ -69,7 +69,7 @@ pydantic-core==2.27.1
69
69
  # via pydantic
70
70
  pygments==2.18.0
71
71
  # via rich
72
- pyright==1.1.392.post0
72
+ pyright==1.1.399
73
73
  pytest==8.3.3
74
74
  # via pytest-asyncio
75
75
  pytest-asyncio==0.24.0
@@ -98,7 +98,11 @@ _StreamT = TypeVar("_StreamT", bound=Stream[Any])
98
98
  _AsyncStreamT = TypeVar("_AsyncStreamT", bound=AsyncStream[Any])
99
99
 
100
100
  if TYPE_CHECKING:
101
- from httpx._config import DEFAULT_TIMEOUT_CONFIG as HTTPX_DEFAULT_TIMEOUT
101
+ from httpx._config import (
102
+ DEFAULT_TIMEOUT_CONFIG, # pyright: ignore[reportPrivateImportUsage]
103
+ )
104
+
105
+ HTTPX_DEFAULT_TIMEOUT = DEFAULT_TIMEOUT_CONFIG
102
106
  else:
103
107
  try:
104
108
  from httpx._config import DEFAULT_TIMEOUT_CONFIG as HTTPX_DEFAULT_TIMEOUT
@@ -409,7 +413,8 @@ class BaseClient(Generic[_HttpxClientT, _DefaultStreamT]):
409
413
 
410
414
  idempotency_header = self._idempotency_header
411
415
  if idempotency_header and options.method.lower() != "get" and idempotency_header not in headers:
412
- headers[idempotency_header] = options.idempotency_key or self._idempotency_key()
416
+ options.idempotency_key = options.idempotency_key or self._idempotency_key()
417
+ headers[idempotency_header] = options.idempotency_key
413
418
 
414
419
  # Don't set these headers if they were already set or removed by the caller. We check
415
420
  # `custom_headers`, which can contain `Omit()`, instead of `headers` to account for the removal case.
@@ -943,6 +948,10 @@ class SyncAPIClient(BaseClient[httpx.Client, Stream[Any]]):
943
948
  request = self._build_request(options, retries_taken=retries_taken)
944
949
  self._prepare_request(request)
945
950
 
951
+ if options.idempotency_key:
952
+ # ensure the idempotency key is reused between requests
953
+ input_options.idempotency_key = options.idempotency_key
954
+
946
955
  kwargs: HttpxSendArgs = {}
947
956
  if self.custom_auth is not None:
948
957
  kwargs["auth"] = self.custom_auth
@@ -1475,6 +1484,10 @@ class AsyncAPIClient(BaseClient[httpx.AsyncClient, AsyncStream[Any]]):
1475
1484
  request = self._build_request(options, retries_taken=retries_taken)
1476
1485
  await self._prepare_request(request)
1477
1486
 
1487
+ if options.idempotency_key:
1488
+ # ensure the idempotency key is reused between requests
1489
+ input_options.idempotency_key = options.idempotency_key
1490
+
1478
1491
  kwargs: HttpxSendArgs = {}
1479
1492
  if self.custom_auth is not None:
1480
1493
  kwargs["auth"] = self.custom_auth
@@ -19,7 +19,6 @@ from typing_extensions import (
19
19
  )
20
20
 
21
21
  import pydantic
22
- import pydantic.generics
23
22
  from pydantic.fields import FieldInfo
24
23
 
25
24
  from ._types import (
@@ -5,13 +5,15 @@ import base64
5
5
  import pathlib
6
6
  from typing import Any, Mapping, TypeVar, cast
7
7
  from datetime import date, datetime
8
- from typing_extensions import Literal, get_args, override, get_type_hints
8
+ from typing_extensions import Literal, get_args, override, get_type_hints as _get_type_hints
9
9
 
10
10
  import anyio
11
11
  import pydantic
12
12
 
13
13
  from ._utils import (
14
14
  is_list,
15
+ is_given,
16
+ lru_cache,
15
17
  is_mapping,
16
18
  is_iterable,
17
19
  )
@@ -108,6 +110,7 @@ def transform(
108
110
  return cast(_T, transformed)
109
111
 
110
112
 
113
+ @lru_cache(maxsize=8096)
111
114
  def _get_annotated_type(type_: type) -> type | None:
112
115
  """If the given type is an `Annotated` type then it is returned, if not `None` is returned.
113
116
 
@@ -142,6 +145,10 @@ def _maybe_transform_key(key: str, type_: type) -> str:
142
145
  return key
143
146
 
144
147
 
148
+ def _no_transform_needed(annotation: type) -> bool:
149
+ return annotation == float or annotation == int
150
+
151
+
145
152
  def _transform_recursive(
146
153
  data: object,
147
154
  *,
@@ -184,6 +191,15 @@ def _transform_recursive(
184
191
  return cast(object, data)
185
192
 
186
193
  inner_type = extract_type_arg(stripped_type, 0)
194
+ if _no_transform_needed(inner_type):
195
+ # for some types there is no need to transform anything, so we can get a small
196
+ # perf boost from skipping that work.
197
+ #
198
+ # but we still need to convert to a list to ensure the data is json-serializable
199
+ if is_list(data):
200
+ return data
201
+ return list(data)
202
+
187
203
  return [_transform_recursive(d, annotation=annotation, inner_type=inner_type) for d in data]
188
204
 
189
205
  if is_union_type(stripped_type):
@@ -245,6 +261,11 @@ def _transform_typeddict(
245
261
  result: dict[str, object] = {}
246
262
  annotations = get_type_hints(expected_type, include_extras=True)
247
263
  for key, value in data.items():
264
+ if not is_given(value):
265
+ # we don't need to include `NotGiven` values here as they'll
266
+ # be stripped out before the request is sent anyway
267
+ continue
268
+
248
269
  type_ = annotations.get(key)
249
270
  if type_ is None:
250
271
  # we do not have a type annotation for this field, leave it as is
@@ -332,6 +353,15 @@ async def _async_transform_recursive(
332
353
  return cast(object, data)
333
354
 
334
355
  inner_type = extract_type_arg(stripped_type, 0)
356
+ if _no_transform_needed(inner_type):
357
+ # for some types there is no need to transform anything, so we can get a small
358
+ # perf boost from skipping that work.
359
+ #
360
+ # but we still need to convert to a list to ensure the data is json-serializable
361
+ if is_list(data):
362
+ return data
363
+ return list(data)
364
+
335
365
  return [await _async_transform_recursive(d, annotation=annotation, inner_type=inner_type) for d in data]
336
366
 
337
367
  if is_union_type(stripped_type):
@@ -393,6 +423,11 @@ async def _async_transform_typeddict(
393
423
  result: dict[str, object] = {}
394
424
  annotations = get_type_hints(expected_type, include_extras=True)
395
425
  for key, value in data.items():
426
+ if not is_given(value):
427
+ # we don't need to include `NotGiven` values here as they'll
428
+ # be stripped out before the request is sent anyway
429
+ continue
430
+
396
431
  type_ = annotations.get(key)
397
432
  if type_ is None:
398
433
  # we do not have a type annotation for this field, leave it as is
@@ -400,3 +435,13 @@ async def _async_transform_typeddict(
400
435
  else:
401
436
  result[_maybe_transform_key(key, type_)] = await _async_transform_recursive(value, annotation=type_)
402
437
  return result
438
+
439
+
440
+ @lru_cache(maxsize=8096)
441
+ def get_type_hints(
442
+ obj: Any,
443
+ globalns: dict[str, Any] | None = None,
444
+ localns: Mapping[str, Any] | None = None,
445
+ include_extras: bool = False,
446
+ ) -> dict[str, Any]:
447
+ return _get_type_hints(obj, globalns=globalns, localns=localns, include_extras=include_extras)
@@ -13,6 +13,7 @@ from typing_extensions import (
13
13
  get_origin,
14
14
  )
15
15
 
16
+ from ._utils import lru_cache
16
17
  from .._types import InheritsGeneric
17
18
  from .._compat import is_union as _is_union
18
19
 
@@ -66,6 +67,7 @@ def is_type_alias_type(tp: Any, /) -> TypeIs[typing_extensions.TypeAliasType]:
66
67
 
67
68
 
68
69
  # Extracts T from Annotated[T, ...] or from Required[Annotated[T, ...]]
70
+ @lru_cache(maxsize=8096)
69
71
  def strip_annotated_type(typ: type) -> type:
70
72
  if is_required_type(typ) or is_annotated_type(typ):
71
73
  return strip_annotated_type(cast(type, get_args(typ)[0]))
@@ -108,7 +110,7 @@ def extract_type_var_from_base(
108
110
  ```
109
111
  """
110
112
  cls = cast(object, get_origin(typ) or typ)
111
- if cls in generic_bases:
113
+ if cls in generic_bases: # pyright: ignore[reportUnnecessaryContains]
112
114
  # we're given the class directly
113
115
  return extract_type_arg(typ, index)
114
116
 
@@ -1,4 +1,4 @@
1
1
  # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
2
2
 
3
3
  __title__ = "isaacus"
4
- __version__ = "0.3.2" # x-release-please-version
4
+ __version__ = "0.4.0" # x-release-please-version
@@ -2,7 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from typing import Optional
5
+ from typing import List, Optional
6
6
  from typing_extensions import Literal
7
7
 
8
8
  import httpx
@@ -52,7 +52,7 @@ class UniversalResource(SyncAPIResource):
52
52
  *,
53
53
  model: Literal["kanon-universal-classifier", "kanon-universal-classifier-mini"],
54
54
  query: str,
55
- text: str,
55
+ texts: List[str],
56
56
  chunking_options: Optional[universal_create_params.ChunkingOptions] | NotGiven = NOT_GIVEN,
57
57
  is_iql: bool | NotGiven = NOT_GIVEN,
58
58
  scoring_method: Literal["auto", "chunk_max", "chunk_avg", "chunk_min"] | NotGiven = NOT_GIVEN,
@@ -64,7 +64,7 @@ class UniversalResource(SyncAPIResource):
64
64
  timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
65
65
  ) -> UniversalClassification:
66
66
  """
67
- Classify the relevance of a legal document to a query with an Isaacus universal
67
+ Classify the relevance of legal documents to a query with an Isaacus universal
68
68
  legal AI classifier.
69
69
 
70
70
  Args:
@@ -72,16 +72,16 @@ class UniversalResource(SyncAPIResource):
72
72
  to use for universal classification.
73
73
 
74
74
  query: The [Isaacus Query Language (IQL)](https://docs.isaacus.com/iql) query or, if
75
- IQL is disabled, the statement, to evaluate the text against.
75
+ IQL is disabled, the statement, to evaluate the texts against.
76
76
 
77
77
  The query must contain at least one non-whitespace character.
78
78
 
79
- Unlike the text being classified, the query cannot be so long that it exceeds
79
+ Unlike the texts being classified, the query cannot be so long that it exceeds
80
80
  the maximum input length of the universal classifier.
81
81
 
82
- text: The text to classify.
82
+ texts: The texts to classify.
83
83
 
84
- The text must contain at least one non-whitespace character.
84
+ The texts must contain at least one non-whitespace character.
85
85
 
86
86
  chunking_options: Options for how to split text into smaller chunks.
87
87
 
@@ -92,13 +92,13 @@ class UniversalResource(SyncAPIResource):
92
92
 
93
93
  `auto` is the default scoring method and is recommended for most use cases.
94
94
  Currently, it is equivalent to `chunk_max`. In the future, it will automatically
95
- select the best method based on the model and input.
95
+ select the best method based on the model and inputs.
96
96
 
97
- `chunk_max` uses the highest confidence score of all of the text's chunks.
97
+ `chunk_max` uses the highest confidence score of all of the texts' chunks.
98
98
 
99
- `chunk_avg` averages the confidence scores of all of the text's chunks.
99
+ `chunk_avg` averages the confidence scores of all of the texts' chunks.
100
100
 
101
- `chunk_min` uses the lowest confidence score of all of the text's chunks.
101
+ `chunk_min` uses the lowest confidence score of all of the texts' chunks.
102
102
 
103
103
  extra_headers: Send extra headers
104
104
 
@@ -114,7 +114,7 @@ class UniversalResource(SyncAPIResource):
114
114
  {
115
115
  "model": model,
116
116
  "query": query,
117
- "text": text,
117
+ "texts": texts,
118
118
  "chunking_options": chunking_options,
119
119
  "is_iql": is_iql,
120
120
  "scoring_method": scoring_method,
@@ -153,7 +153,7 @@ class AsyncUniversalResource(AsyncAPIResource):
153
153
  *,
154
154
  model: Literal["kanon-universal-classifier", "kanon-universal-classifier-mini"],
155
155
  query: str,
156
- text: str,
156
+ texts: List[str],
157
157
  chunking_options: Optional[universal_create_params.ChunkingOptions] | NotGiven = NOT_GIVEN,
158
158
  is_iql: bool | NotGiven = NOT_GIVEN,
159
159
  scoring_method: Literal["auto", "chunk_max", "chunk_avg", "chunk_min"] | NotGiven = NOT_GIVEN,
@@ -165,7 +165,7 @@ class AsyncUniversalResource(AsyncAPIResource):
165
165
  timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
166
166
  ) -> UniversalClassification:
167
167
  """
168
- Classify the relevance of a legal document to a query with an Isaacus universal
168
+ Classify the relevance of legal documents to a query with an Isaacus universal
169
169
  legal AI classifier.
170
170
 
171
171
  Args:
@@ -173,16 +173,16 @@ class AsyncUniversalResource(AsyncAPIResource):
173
173
  to use for universal classification.
174
174
 
175
175
  query: The [Isaacus Query Language (IQL)](https://docs.isaacus.com/iql) query or, if
176
- IQL is disabled, the statement, to evaluate the text against.
176
+ IQL is disabled, the statement, to evaluate the texts against.
177
177
 
178
178
  The query must contain at least one non-whitespace character.
179
179
 
180
- Unlike the text being classified, the query cannot be so long that it exceeds
180
+ Unlike the texts being classified, the query cannot be so long that it exceeds
181
181
  the maximum input length of the universal classifier.
182
182
 
183
- text: The text to classify.
183
+ texts: The texts to classify.
184
184
 
185
- The text must contain at least one non-whitespace character.
185
+ The texts must contain at least one non-whitespace character.
186
186
 
187
187
  chunking_options: Options for how to split text into smaller chunks.
188
188
 
@@ -193,13 +193,13 @@ class AsyncUniversalResource(AsyncAPIResource):
193
193
 
194
194
  `auto` is the default scoring method and is recommended for most use cases.
195
195
  Currently, it is equivalent to `chunk_max`. In the future, it will automatically
196
- select the best method based on the model and input.
196
+ select the best method based on the model and inputs.
197
197
 
198
- `chunk_max` uses the highest confidence score of all of the text's chunks.
198
+ `chunk_max` uses the highest confidence score of all of the texts' chunks.
199
199
 
200
- `chunk_avg` averages the confidence scores of all of the text's chunks.
200
+ `chunk_avg` averages the confidence scores of all of the texts' chunks.
201
201
 
202
- `chunk_min` uses the lowest confidence score of all of the text's chunks.
202
+ `chunk_min` uses the lowest confidence score of all of the texts' chunks.
203
203
 
204
204
  extra_headers: Send extra headers
205
205
 
@@ -215,7 +215,7 @@ class AsyncUniversalResource(AsyncAPIResource):
215
215
  {
216
216
  "model": model,
217
217
  "query": query,
218
- "text": text,
218
+ "texts": texts,
219
219
  "chunking_options": chunking_options,
220
220
  "is_iql": is_iql,
221
221
  "scoring_method": scoring_method,
@@ -4,15 +4,22 @@ from typing import List, Optional
4
4
 
5
5
  from ..._models import BaseModel
6
6
 
7
- __all__ = ["UniversalClassification", "Chunk", "Usage"]
7
+ __all__ = ["UniversalClassification", "Classification", "ClassificationChunk", "Usage"]
8
8
 
9
9
 
10
- class Chunk(BaseModel):
10
+ class ClassificationChunk(BaseModel):
11
11
  end: int
12
- """The end index of the chunk in the original text."""
12
+ """
13
+ The index of the character in the original text where the chunk ends, beginning
14
+ from `0` (such that, in Python, the chunk is equivalent to `text[start:end+1]`).
15
+ """
13
16
 
14
17
  index: int
15
- """The index of the chunk in the list of chunks."""
18
+ """
19
+ The original position of the chunk in the outputted list of chunks before
20
+ sorting, starting from `0` (and, therefore, ending at the number of chunks minus
21
+ `1`).
22
+ """
16
23
 
17
24
  score: float
18
25
  """
@@ -24,19 +31,17 @@ class Chunk(BaseModel):
24
31
  """
25
32
 
26
33
  start: int
27
- """The start index of the chunk in the original text."""
34
+ """
35
+ The index of the character in the original text where the chunk starts,
36
+ beginning from `0`.
37
+ """
28
38
 
29
39
  text: str
30
40
  """The text of the chunk."""
31
41
 
32
42
 
33
- class Usage(BaseModel):
34
- input_tokens: int
35
- """The number of tokens inputted to the model."""
36
-
37
-
38
- class UniversalClassification(BaseModel):
39
- chunks: Optional[List[Chunk]] = None
43
+ class Classification(BaseModel):
44
+ chunks: Optional[List[ClassificationChunk]] = None
40
45
  """
41
46
  The text as broken into chunks by
42
47
  [semchunk](https://github.com/isaacus-dev/semchunk), each chunk with its own
@@ -45,6 +50,12 @@ class UniversalClassification(BaseModel):
45
50
  If no chunking occurred, this will be `null`.
46
51
  """
47
52
 
53
+ index: int
54
+ """
55
+ The index of the text in the input array of texts, starting from `0` (and,
56
+ therefore, ending at the number of texts minus `1`).
57
+ """
58
+
48
59
  score: float
49
60
  """
50
61
  A score of the likelihood that the query expressed about the text is supported
@@ -54,5 +65,18 @@ class UniversalClassification(BaseModel):
54
65
  score less than `0.5` indicates that the text does not support the query.
55
66
  """
56
67
 
68
+
69
+ class Usage(BaseModel):
70
+ input_tokens: int
71
+ """The number of tokens inputted to the model."""
72
+
73
+
74
+ class UniversalClassification(BaseModel):
75
+ classifications: List[Classification]
76
+ """
77
+ The classifications of the texts, by relevance to the query, in order from
78
+ highest to lowest relevance score.
79
+ """
80
+
57
81
  usage: Usage
58
82
  """Statistics about the usage of resources in the process of classifying the text."""
@@ -2,7 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from typing import Optional
5
+ from typing import List, Optional
6
6
  from typing_extensions import Literal, Required, TypedDict
7
7
 
8
8
  __all__ = ["UniversalCreateParams", "ChunkingOptions"]
@@ -18,18 +18,18 @@ class UniversalCreateParams(TypedDict, total=False):
18
18
  query: Required[str]
19
19
  """
20
20
  The [Isaacus Query Language (IQL)](https://docs.isaacus.com/iql) query or, if
21
- IQL is disabled, the statement, to evaluate the text against.
21
+ IQL is disabled, the statement, to evaluate the texts against.
22
22
 
23
23
  The query must contain at least one non-whitespace character.
24
24
 
25
- Unlike the text being classified, the query cannot be so long that it exceeds
25
+ Unlike the texts being classified, the query cannot be so long that it exceeds
26
26
  the maximum input length of the universal classifier.
27
27
  """
28
28
 
29
- text: Required[str]
30
- """The text to classify.
29
+ texts: Required[List[str]]
30
+ """The texts to classify.
31
31
 
32
- The text must contain at least one non-whitespace character.
32
+ The texts must contain at least one non-whitespace character.
33
33
  """
34
34
 
35
35
  chunking_options: Optional[ChunkingOptions]
@@ -46,13 +46,13 @@ class UniversalCreateParams(TypedDict, total=False):
46
46
 
47
47
  `auto` is the default scoring method and is recommended for most use cases.
48
48
  Currently, it is equivalent to `chunk_max`. In the future, it will automatically
49
- select the best method based on the model and input.
49
+ select the best method based on the model and inputs.
50
50
 
51
- `chunk_max` uses the highest confidence score of all of the text's chunks.
51
+ `chunk_max` uses the highest confidence score of all of the texts' chunks.
52
52
 
53
- `chunk_avg` averages the confidence scores of all of the text's chunks.
53
+ `chunk_avg` averages the confidence scores of all of the texts' chunks.
54
54
 
55
- `chunk_min` uses the lowest confidence score of all of the text's chunks.
55
+ `chunk_min` uses the lowest confidence score of all of the texts' chunks.
56
56
  """
57
57
 
58
58
 
@@ -23,7 +23,7 @@ class TestUniversal:
23
23
  universal = client.classifications.universal.create(
24
24
  model="kanon-universal-classifier",
25
25
  query="This is a confidentiality clause.",
26
- text="I agree not to tell anyone about the document.",
26
+ texts=["I agree not to tell anyone about the document."],
27
27
  )
28
28
  assert_matches_type(UniversalClassification, universal, path=["response"])
29
29
 
@@ -33,7 +33,7 @@ class TestUniversal:
33
33
  universal = client.classifications.universal.create(
34
34
  model="kanon-universal-classifier",
35
35
  query="This is a confidentiality clause.",
36
- text="I agree not to tell anyone about the document.",
36
+ texts=["I agree not to tell anyone about the document."],
37
37
  chunking_options={
38
38
  "overlap_ratio": 0.1,
39
39
  "overlap_tokens": 0,
@@ -50,7 +50,7 @@ class TestUniversal:
50
50
  response = client.classifications.universal.with_raw_response.create(
51
51
  model="kanon-universal-classifier",
52
52
  query="This is a confidentiality clause.",
53
- text="I agree not to tell anyone about the document.",
53
+ texts=["I agree not to tell anyone about the document."],
54
54
  )
55
55
 
56
56
  assert response.is_closed is True
@@ -64,7 +64,7 @@ class TestUniversal:
64
64
  with client.classifications.universal.with_streaming_response.create(
65
65
  model="kanon-universal-classifier",
66
66
  query="This is a confidentiality clause.",
67
- text="I agree not to tell anyone about the document.",
67
+ texts=["I agree not to tell anyone about the document."],
68
68
  ) as response:
69
69
  assert not response.is_closed
70
70
  assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -84,7 +84,7 @@ class TestAsyncUniversal:
84
84
  universal = await async_client.classifications.universal.create(
85
85
  model="kanon-universal-classifier",
86
86
  query="This is a confidentiality clause.",
87
- text="I agree not to tell anyone about the document.",
87
+ texts=["I agree not to tell anyone about the document."],
88
88
  )
89
89
  assert_matches_type(UniversalClassification, universal, path=["response"])
90
90
 
@@ -94,7 +94,7 @@ class TestAsyncUniversal:
94
94
  universal = await async_client.classifications.universal.create(
95
95
  model="kanon-universal-classifier",
96
96
  query="This is a confidentiality clause.",
97
- text="I agree not to tell anyone about the document.",
97
+ texts=["I agree not to tell anyone about the document."],
98
98
  chunking_options={
99
99
  "overlap_ratio": 0.1,
100
100
  "overlap_tokens": 0,
@@ -111,7 +111,7 @@ class TestAsyncUniversal:
111
111
  response = await async_client.classifications.universal.with_raw_response.create(
112
112
  model="kanon-universal-classifier",
113
113
  query="This is a confidentiality clause.",
114
- text="I agree not to tell anyone about the document.",
114
+ texts=["I agree not to tell anyone about the document."],
115
115
  )
116
116
 
117
117
  assert response.is_closed is True
@@ -125,7 +125,7 @@ class TestAsyncUniversal:
125
125
  async with async_client.classifications.universal.with_streaming_response.create(
126
126
  model="kanon-universal-classifier",
127
127
  query="This is a confidentiality clause.",
128
- text="I agree not to tell anyone about the document.",
128
+ texts=["I agree not to tell anyone about the document."],
129
129
  ) as response:
130
130
  assert not response.is_closed
131
131
  assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -10,7 +10,7 @@ from pytest_asyncio import is_async_test
10
10
  from isaacus import Isaacus, AsyncIsaacus
11
11
 
12
12
  if TYPE_CHECKING:
13
- from _pytest.fixtures import FixtureRequest
13
+ from _pytest.fixtures import FixtureRequest # pyright: ignore[reportPrivateImportUsage]
14
14
 
15
15
  pytest.register_assert_rewrite("tests.utils")
16
16
 
@@ -723,7 +723,7 @@ class TestIsaacus:
723
723
  dict(
724
724
  model="kanon-universal-classifier",
725
725
  query="This is a confidentiality clause.",
726
- text="I agree not to tell anyone about the document.",
726
+ texts=["I agree not to tell anyone about the document."],
727
727
  ),
728
728
  UniversalCreateParams,
729
729
  ),
@@ -748,7 +748,7 @@ class TestIsaacus:
748
748
  dict(
749
749
  model="kanon-universal-classifier",
750
750
  query="This is a confidentiality clause.",
751
- text="I agree not to tell anyone about the document.",
751
+ texts=["I agree not to tell anyone about the document."],
752
752
  ),
753
753
  UniversalCreateParams,
754
754
  ),
@@ -788,7 +788,7 @@ class TestIsaacus:
788
788
  response = client.classifications.universal.with_raw_response.create(
789
789
  model="kanon-universal-classifier",
790
790
  query="This is a confidentiality clause.",
791
- text="I agree not to tell anyone about the document.",
791
+ texts=["I agree not to tell anyone about the document."],
792
792
  )
793
793
 
794
794
  assert response.retries_taken == failures_before_success
@@ -816,7 +816,7 @@ class TestIsaacus:
816
816
  response = client.classifications.universal.with_raw_response.create(
817
817
  model="kanon-universal-classifier",
818
818
  query="This is a confidentiality clause.",
819
- text="I agree not to tell anyone about the document.",
819
+ texts=["I agree not to tell anyone about the document."],
820
820
  extra_headers={"x-stainless-retry-count": Omit()},
821
821
  )
822
822
 
@@ -844,7 +844,7 @@ class TestIsaacus:
844
844
  response = client.classifications.universal.with_raw_response.create(
845
845
  model="kanon-universal-classifier",
846
846
  query="This is a confidentiality clause.",
847
- text="I agree not to tell anyone about the document.",
847
+ texts=["I agree not to tell anyone about the document."],
848
848
  extra_headers={"x-stainless-retry-count": "42"},
849
849
  )
850
850
 
@@ -1533,7 +1533,7 @@ class TestAsyncIsaacus:
1533
1533
  dict(
1534
1534
  model="kanon-universal-classifier",
1535
1535
  query="This is a confidentiality clause.",
1536
- text="I agree not to tell anyone about the document.",
1536
+ texts=["I agree not to tell anyone about the document."],
1537
1537
  ),
1538
1538
  UniversalCreateParams,
1539
1539
  ),
@@ -1558,7 +1558,7 @@ class TestAsyncIsaacus:
1558
1558
  dict(
1559
1559
  model="kanon-universal-classifier",
1560
1560
  query="This is a confidentiality clause.",
1561
- text="I agree not to tell anyone about the document.",
1561
+ texts=["I agree not to tell anyone about the document."],
1562
1562
  ),
1563
1563
  UniversalCreateParams,
1564
1564
  ),
@@ -1599,7 +1599,7 @@ class TestAsyncIsaacus:
1599
1599
  response = await client.classifications.universal.with_raw_response.create(
1600
1600
  model="kanon-universal-classifier",
1601
1601
  query="This is a confidentiality clause.",
1602
- text="I agree not to tell anyone about the document.",
1602
+ texts=["I agree not to tell anyone about the document."],
1603
1603
  )
1604
1604
 
1605
1605
  assert response.retries_taken == failures_before_success
@@ -1628,7 +1628,7 @@ class TestAsyncIsaacus:
1628
1628
  response = await client.classifications.universal.with_raw_response.create(
1629
1629
  model="kanon-universal-classifier",
1630
1630
  query="This is a confidentiality clause.",
1631
- text="I agree not to tell anyone about the document.",
1631
+ texts=["I agree not to tell anyone about the document."],
1632
1632
  extra_headers={"x-stainless-retry-count": Omit()},
1633
1633
  )
1634
1634
 
@@ -1657,7 +1657,7 @@ class TestAsyncIsaacus:
1657
1657
  response = await client.classifications.universal.with_raw_response.create(
1658
1658
  model="kanon-universal-classifier",
1659
1659
  query="This is a confidentiality clause.",
1660
- text="I agree not to tell anyone about the document.",
1660
+ texts=["I agree not to tell anyone about the document."],
1661
1661
  extra_headers={"x-stainless-retry-count": "42"},
1662
1662
  )
1663
1663
 
@@ -492,12 +492,15 @@ def test_omitted_fields() -> None:
492
492
  resource_id: Optional[str] = None
493
493
 
494
494
  m = Model.construct()
495
+ assert m.resource_id is None
495
496
  assert "resource_id" not in m.model_fields_set
496
497
 
497
498
  m = Model.construct(resource_id=None)
499
+ assert m.resource_id is None
498
500
  assert "resource_id" in m.model_fields_set
499
501
 
500
502
  m = Model.construct(resource_id="foo")
503
+ assert m.resource_id == "foo"
501
504
  assert "resource_id" in m.model_fields_set
502
505
 
503
506
 
@@ -832,7 +835,7 @@ def test_discriminated_unions_invalid_data_uses_cache() -> None:
832
835
 
833
836
  @pytest.mark.skipif(not PYDANTIC_V2, reason="TypeAliasType is not supported in Pydantic v1")
834
837
  def test_type_alias_type() -> None:
835
- Alias = TypeAliasType("Alias", str)
838
+ Alias = TypeAliasType("Alias", str) # pyright: ignore
836
839
 
837
840
  class Model(BaseModel):
838
841
  alias: Alias
@@ -8,7 +8,7 @@ from typing_extensions import Required, Annotated, TypedDict
8
8
 
9
9
  import pytest
10
10
 
11
- from isaacus._types import Base64FileInput
11
+ from isaacus._types import NOT_GIVEN, Base64FileInput
12
12
  from isaacus._utils import (
13
13
  PropertyInfo,
14
14
  transform as _transform,
@@ -432,3 +432,22 @@ async def test_base64_file_input(use_async: bool) -> None:
432
432
  assert await transform({"foo": io.BytesIO(b"Hello, world!")}, TypedDictBase64Input, use_async) == {
433
433
  "foo": "SGVsbG8sIHdvcmxkIQ=="
434
434
  } # type: ignore[comparison-overlap]
435
+
436
+
437
+ @parametrize
438
+ @pytest.mark.asyncio
439
+ async def test_transform_skipping(use_async: bool) -> None:
440
+ # lists of ints are left as-is
441
+ data = [1, 2, 3]
442
+ assert await transform(data, List[int], use_async) is data
443
+
444
+ # iterables of ints are converted to a list
445
+ data = iter([1, 2, 3])
446
+ assert await transform(data, Iterable[int], use_async) == [1, 2, 3]
447
+
448
+
449
+ @parametrize
450
+ @pytest.mark.asyncio
451
+ async def test_strips_notgiven(use_async: bool) -> None:
452
+ assert await transform({"foo_bar": "bar"}, Foo1, use_async) == {"fooBar": "bar"}
453
+ assert await transform({"foo_bar": NOT_GIVEN}, Foo1, use_async) == {}
@@ -1,3 +0,0 @@
1
- {
2
- ".": "0.3.2"
3
- }
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