openai-sdk-helpers 0.0.8__py3-none-any.whl → 0.1.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.
- openai_sdk_helpers/__init__.py +90 -2
- openai_sdk_helpers/agent/__init__.py +8 -4
- openai_sdk_helpers/agent/base.py +80 -45
- openai_sdk_helpers/agent/config.py +6 -4
- openai_sdk_helpers/agent/{project_manager.py → coordination.py} +29 -45
- openai_sdk_helpers/agent/prompt_utils.py +7 -1
- openai_sdk_helpers/agent/runner.py +67 -141
- openai_sdk_helpers/agent/search/__init__.py +33 -0
- openai_sdk_helpers/agent/search/base.py +297 -0
- openai_sdk_helpers/agent/{vector_search.py → search/vector.py} +89 -157
- openai_sdk_helpers/agent/{web_search.py → search/web.py} +77 -156
- openai_sdk_helpers/agent/summarizer.py +29 -8
- openai_sdk_helpers/agent/translator.py +40 -13
- openai_sdk_helpers/agent/validation.py +32 -8
- openai_sdk_helpers/async_utils.py +132 -0
- openai_sdk_helpers/config.py +101 -65
- openai_sdk_helpers/context_manager.py +241 -0
- openai_sdk_helpers/enums/__init__.py +9 -1
- openai_sdk_helpers/enums/base.py +67 -8
- openai_sdk_helpers/environment.py +33 -6
- openai_sdk_helpers/errors.py +133 -0
- openai_sdk_helpers/logging_config.py +105 -0
- openai_sdk_helpers/prompt/__init__.py +10 -71
- openai_sdk_helpers/prompt/base.py +222 -0
- openai_sdk_helpers/response/__init__.py +38 -3
- openai_sdk_helpers/response/base.py +363 -210
- openai_sdk_helpers/response/config.py +318 -0
- openai_sdk_helpers/response/messages.py +56 -40
- openai_sdk_helpers/response/runner.py +77 -33
- openai_sdk_helpers/response/tool_call.py +62 -27
- openai_sdk_helpers/response/vector_store.py +27 -14
- openai_sdk_helpers/retry.py +175 -0
- openai_sdk_helpers/streamlit_app/__init__.py +19 -2
- openai_sdk_helpers/streamlit_app/app.py +114 -39
- openai_sdk_helpers/streamlit_app/config.py +502 -0
- openai_sdk_helpers/streamlit_app/streamlit_web_search.py +5 -6
- openai_sdk_helpers/structure/__init__.py +72 -3
- openai_sdk_helpers/structure/agent_blueprint.py +82 -19
- openai_sdk_helpers/structure/base.py +208 -93
- openai_sdk_helpers/structure/plan/__init__.py +29 -1
- openai_sdk_helpers/structure/plan/enum.py +41 -5
- openai_sdk_helpers/structure/plan/helpers.py +172 -0
- openai_sdk_helpers/structure/plan/plan.py +109 -49
- openai_sdk_helpers/structure/plan/task.py +38 -6
- openai_sdk_helpers/structure/plan/types.py +15 -0
- openai_sdk_helpers/structure/prompt.py +21 -2
- openai_sdk_helpers/structure/responses.py +52 -11
- openai_sdk_helpers/structure/summary.py +55 -7
- openai_sdk_helpers/structure/validation.py +34 -6
- openai_sdk_helpers/structure/vector_search.py +132 -18
- openai_sdk_helpers/structure/web_search.py +125 -13
- openai_sdk_helpers/tools.py +193 -0
- openai_sdk_helpers/types.py +57 -0
- openai_sdk_helpers/utils/__init__.py +34 -1
- openai_sdk_helpers/utils/core.py +296 -34
- openai_sdk_helpers/validation.py +302 -0
- openai_sdk_helpers/vector_storage/__init__.py +21 -1
- openai_sdk_helpers/vector_storage/cleanup.py +25 -13
- openai_sdk_helpers/vector_storage/storage.py +123 -64
- openai_sdk_helpers/vector_storage/types.py +20 -19
- openai_sdk_helpers-0.1.0.dist-info/METADATA +550 -0
- openai_sdk_helpers-0.1.0.dist-info/RECORD +69 -0
- openai_sdk_helpers/streamlit_app/configuration.py +0 -324
- openai_sdk_helpers-0.0.8.dist-info/METADATA +0 -194
- openai_sdk_helpers-0.0.8.dist-info/RECORD +0 -55
- {openai_sdk_helpers-0.0.8.dist-info → openai_sdk_helpers-0.1.0.dist-info}/WHEEL +0 -0
- {openai_sdk_helpers-0.0.8.dist-info → openai_sdk_helpers-0.1.0.dist-info}/licenses/LICENSE +0 -0
openai_sdk_helpers/utils/core.py
CHANGED
|
@@ -1,29 +1,37 @@
|
|
|
1
|
-
"""Core utility helpers for openai-sdk-helpers.
|
|
1
|
+
"""Core utility helpers for openai-sdk-helpers.
|
|
2
|
+
|
|
3
|
+
This module provides foundational utility functions for type coercion,
|
|
4
|
+
file path validation, JSON serialization, and logging. These utilities
|
|
5
|
+
support consistent data handling across the package.
|
|
6
|
+
"""
|
|
2
7
|
|
|
3
8
|
from __future__ import annotations
|
|
4
9
|
|
|
5
10
|
import json
|
|
6
11
|
import logging
|
|
7
12
|
import ast
|
|
13
|
+
from collections.abc import Iterable, Mapping
|
|
8
14
|
from dataclasses import asdict, is_dataclass
|
|
9
15
|
from datetime import datetime
|
|
10
16
|
from enum import Enum
|
|
11
17
|
from pathlib import Path
|
|
12
|
-
from typing import Any,
|
|
18
|
+
from typing import Any, TypeVar
|
|
19
|
+
|
|
13
20
|
|
|
21
|
+
def coerce_optional_float(value: object) -> float | None:
|
|
22
|
+
"""Return a float when the provided value can be coerced, otherwise None.
|
|
14
23
|
|
|
15
|
-
|
|
16
|
-
"""Return a float when the provided value can be coerced, otherwise ``None``.
|
|
24
|
+
Handles float, int, and string inputs. Empty strings or None return None.
|
|
17
25
|
|
|
18
26
|
Parameters
|
|
19
27
|
----------
|
|
20
|
-
value :
|
|
28
|
+
value : object
|
|
21
29
|
Value to convert into a float. Strings must be parseable as floats.
|
|
22
30
|
|
|
23
31
|
Returns
|
|
24
32
|
-------
|
|
25
|
-
float
|
|
26
|
-
Converted float value or
|
|
33
|
+
float or None
|
|
34
|
+
Converted float value or None if the input is None.
|
|
27
35
|
|
|
28
36
|
Raises
|
|
29
37
|
------
|
|
@@ -31,6 +39,15 @@ def coerce_optional_float(value: Any) -> Optional[float]:
|
|
|
31
39
|
If a non-empty string cannot be converted to a float.
|
|
32
40
|
TypeError
|
|
33
41
|
If the value is not a float-compatible type.
|
|
42
|
+
|
|
43
|
+
Examples
|
|
44
|
+
--------
|
|
45
|
+
>>> coerce_optional_float(3.14)
|
|
46
|
+
3.14
|
|
47
|
+
>>> coerce_optional_float("2.5")
|
|
48
|
+
2.5
|
|
49
|
+
>>> coerce_optional_float(None) is None
|
|
50
|
+
True
|
|
34
51
|
"""
|
|
35
52
|
if value is None:
|
|
36
53
|
return None
|
|
@@ -44,18 +61,21 @@ def coerce_optional_float(value: Any) -> Optional[float]:
|
|
|
44
61
|
raise TypeError("timeout must be a float, int, str, or None")
|
|
45
62
|
|
|
46
63
|
|
|
47
|
-
def coerce_optional_int(value:
|
|
48
|
-
"""Return an int when the provided value can be coerced, otherwise
|
|
64
|
+
def coerce_optional_int(value: object) -> int | None:
|
|
65
|
+
"""Return an int when the provided value can be coerced, otherwise None.
|
|
66
|
+
|
|
67
|
+
Handles int, float (if whole number), and string inputs. Empty strings
|
|
68
|
+
or None return None. Booleans are not considered valid integers.
|
|
49
69
|
|
|
50
70
|
Parameters
|
|
51
71
|
----------
|
|
52
|
-
value :
|
|
72
|
+
value : object
|
|
53
73
|
Value to convert into an int. Strings must be parseable as integers.
|
|
54
74
|
|
|
55
75
|
Returns
|
|
56
76
|
-------
|
|
57
|
-
int
|
|
58
|
-
Converted integer value or
|
|
77
|
+
int or None
|
|
78
|
+
Converted integer value or None if the input is None.
|
|
59
79
|
|
|
60
80
|
Raises
|
|
61
81
|
------
|
|
@@ -63,6 +83,17 @@ def coerce_optional_int(value: Any) -> Optional[int]:
|
|
|
63
83
|
If a non-empty string cannot be converted to an integer.
|
|
64
84
|
TypeError
|
|
65
85
|
If the value is not an int-compatible type.
|
|
86
|
+
|
|
87
|
+
Examples
|
|
88
|
+
--------
|
|
89
|
+
>>> coerce_optional_int(42)
|
|
90
|
+
42
|
|
91
|
+
>>> coerce_optional_int("100")
|
|
92
|
+
100
|
|
93
|
+
>>> coerce_optional_int(3.0)
|
|
94
|
+
3
|
|
95
|
+
>>> coerce_optional_int(None) is None
|
|
96
|
+
True
|
|
66
97
|
"""
|
|
67
98
|
if value is None:
|
|
68
99
|
return None
|
|
@@ -78,23 +109,32 @@ def coerce_optional_int(value: Any) -> Optional[int]:
|
|
|
78
109
|
raise TypeError("max_retries must be an int, str, or None")
|
|
79
110
|
|
|
80
111
|
|
|
81
|
-
def coerce_dict(value:
|
|
82
|
-
"""Return a string-keyed dictionary built from
|
|
112
|
+
def coerce_dict(value: object) -> dict[str, Any]:
|
|
113
|
+
"""Return a string-keyed dictionary built from value if possible.
|
|
114
|
+
|
|
115
|
+
Converts Mapping objects to dictionaries. None returns an empty dict.
|
|
83
116
|
|
|
84
117
|
Parameters
|
|
85
118
|
----------
|
|
86
|
-
value :
|
|
87
|
-
Mapping-like value to convert.
|
|
119
|
+
value : object
|
|
120
|
+
Mapping-like value to convert. None yields an empty dictionary.
|
|
88
121
|
|
|
89
122
|
Returns
|
|
90
123
|
-------
|
|
91
124
|
dict[str, Any]
|
|
92
|
-
Dictionary representation of
|
|
125
|
+
Dictionary representation of value.
|
|
93
126
|
|
|
94
127
|
Raises
|
|
95
128
|
------
|
|
96
129
|
TypeError
|
|
97
130
|
If the value cannot be treated as a mapping.
|
|
131
|
+
|
|
132
|
+
Examples
|
|
133
|
+
--------
|
|
134
|
+
>>> coerce_dict({"a": 1})
|
|
135
|
+
{'a': 1}
|
|
136
|
+
>>> coerce_dict(None)
|
|
137
|
+
{}
|
|
98
138
|
"""
|
|
99
139
|
if value is None:
|
|
100
140
|
return {}
|
|
@@ -103,22 +143,160 @@ def coerce_dict(value: Any) -> Dict[str, Any]:
|
|
|
103
143
|
raise TypeError("extra_client_kwargs must be a mapping or None")
|
|
104
144
|
|
|
105
145
|
|
|
146
|
+
def build_openai_settings(
|
|
147
|
+
api_key: str | None = None,
|
|
148
|
+
org_id: str | None = None,
|
|
149
|
+
project_id: str | None = None,
|
|
150
|
+
base_url: str | None = None,
|
|
151
|
+
default_model: str | None = None,
|
|
152
|
+
timeout: float | str | None = None,
|
|
153
|
+
max_retries: int | str | None = None,
|
|
154
|
+
dotenv_path: Path | None = None,
|
|
155
|
+
**extra_kwargs: Any,
|
|
156
|
+
) -> Any: # Returns OpenAISettings but use Any to avoid circular import
|
|
157
|
+
"""Build OpenAI settings from environment with explicit validation.
|
|
158
|
+
|
|
159
|
+
Convenience function for creating OpenAISettings with validation and
|
|
160
|
+
clear error messages. Reads from environment variables and validates
|
|
161
|
+
required fields, with explicit type coercion for timeout and max_retries.
|
|
162
|
+
|
|
163
|
+
Parameters
|
|
164
|
+
----------
|
|
165
|
+
api_key : str or None, default None
|
|
166
|
+
API key for OpenAI authentication. If None, reads from OPENAI_API_KEY.
|
|
167
|
+
org_id : str or None, default None
|
|
168
|
+
Organization ID. If None, reads from OPENAI_ORG_ID.
|
|
169
|
+
project_id : str or None, default None
|
|
170
|
+
Project ID. If None, reads from OPENAI_PROJECT_ID.
|
|
171
|
+
base_url : str or None, default None
|
|
172
|
+
Base URL for API requests. If None, reads from OPENAI_BASE_URL.
|
|
173
|
+
default_model : str or None, default None
|
|
174
|
+
Default model name. If None, reads from OPENAI_MODEL.
|
|
175
|
+
timeout : float, str, or None, default None
|
|
176
|
+
Request timeout in seconds. If None, reads from OPENAI_TIMEOUT.
|
|
177
|
+
Can be string that will be parsed to float.
|
|
178
|
+
max_retries : int, str, or None, default None
|
|
179
|
+
Maximum retry attempts. If None, reads from OPENAI_MAX_RETRIES.
|
|
180
|
+
Can be string that will be parsed to int.
|
|
181
|
+
dotenv_path : Path or None, default None
|
|
182
|
+
Path to .env file. If None, searches for .env in current directory.
|
|
183
|
+
**extra_kwargs : Any
|
|
184
|
+
Additional keyword arguments for extra_client_kwargs.
|
|
185
|
+
|
|
186
|
+
Returns
|
|
187
|
+
-------
|
|
188
|
+
OpenAISettings
|
|
189
|
+
Configured settings instance.
|
|
190
|
+
|
|
191
|
+
Raises
|
|
192
|
+
------
|
|
193
|
+
ValueError
|
|
194
|
+
If OPENAI_API_KEY is not found in environment or parameters.
|
|
195
|
+
If timeout cannot be parsed as float.
|
|
196
|
+
If max_retries cannot be parsed as int.
|
|
197
|
+
TypeError
|
|
198
|
+
If timeout or max_retries have invalid types.
|
|
199
|
+
|
|
200
|
+
Examples
|
|
201
|
+
--------
|
|
202
|
+
Build from explicit parameters:
|
|
203
|
+
|
|
204
|
+
>>> settings = build_openai_settings(
|
|
205
|
+
... api_key="sk-...",
|
|
206
|
+
... default_model="gpt-4o",
|
|
207
|
+
... timeout=30.0
|
|
208
|
+
... )
|
|
209
|
+
|
|
210
|
+
Build from environment:
|
|
211
|
+
|
|
212
|
+
>>> settings = build_openai_settings() # doctest: +SKIP
|
|
213
|
+
|
|
214
|
+
With custom .env location:
|
|
215
|
+
|
|
216
|
+
>>> settings = build_openai_settings(
|
|
217
|
+
... dotenv_path=Path("/path/to/.env")
|
|
218
|
+
... ) # doctest: +SKIP
|
|
219
|
+
"""
|
|
220
|
+
# Import at runtime to avoid circular import
|
|
221
|
+
from openai_sdk_helpers.config import OpenAISettings
|
|
222
|
+
|
|
223
|
+
# Parse timeout with validation
|
|
224
|
+
parsed_timeout: float | None = None
|
|
225
|
+
if timeout is not None:
|
|
226
|
+
try:
|
|
227
|
+
parsed_timeout = coerce_optional_float(timeout)
|
|
228
|
+
except (ValueError, TypeError) as exc:
|
|
229
|
+
raise ValueError(
|
|
230
|
+
f"Invalid timeout value '{timeout}'. Must be a number or numeric string."
|
|
231
|
+
) from exc
|
|
232
|
+
|
|
233
|
+
# Parse max_retries with validation
|
|
234
|
+
parsed_max_retries: int | None = None
|
|
235
|
+
if max_retries is not None:
|
|
236
|
+
try:
|
|
237
|
+
parsed_max_retries = coerce_optional_int(max_retries)
|
|
238
|
+
except (ValueError, TypeError) as exc:
|
|
239
|
+
raise ValueError(
|
|
240
|
+
f"Invalid max_retries value '{max_retries}'. "
|
|
241
|
+
"Must be an integer or numeric string."
|
|
242
|
+
) from exc
|
|
243
|
+
|
|
244
|
+
# Build settings using from_env with overrides
|
|
245
|
+
overrides = {}
|
|
246
|
+
if api_key is not None:
|
|
247
|
+
overrides["api_key"] = api_key
|
|
248
|
+
if org_id is not None:
|
|
249
|
+
overrides["org_id"] = org_id
|
|
250
|
+
if project_id is not None:
|
|
251
|
+
overrides["project_id"] = project_id
|
|
252
|
+
if base_url is not None:
|
|
253
|
+
overrides["base_url"] = base_url
|
|
254
|
+
if default_model is not None:
|
|
255
|
+
overrides["default_model"] = default_model
|
|
256
|
+
if parsed_timeout is not None:
|
|
257
|
+
overrides["timeout"] = parsed_timeout
|
|
258
|
+
if parsed_max_retries is not None:
|
|
259
|
+
overrides["max_retries"] = parsed_max_retries
|
|
260
|
+
if extra_kwargs:
|
|
261
|
+
overrides["extra_client_kwargs"] = extra_kwargs
|
|
262
|
+
|
|
263
|
+
try:
|
|
264
|
+
return OpenAISettings.from_env(dotenv_path=dotenv_path, **overrides)
|
|
265
|
+
except ValueError as exc:
|
|
266
|
+
# Re-raise with more context but preserve original message
|
|
267
|
+
raise ValueError(f"Failed to build OpenAI settings: {exc}") from exc
|
|
268
|
+
|
|
269
|
+
|
|
106
270
|
T = TypeVar("T")
|
|
107
271
|
_configured_logging = False
|
|
108
272
|
|
|
109
273
|
|
|
110
|
-
def ensure_list(value: Iterable[T] | T | None) ->
|
|
274
|
+
def ensure_list(value: Iterable[T] | T | None) -> list[T]:
|
|
111
275
|
"""Normalize a single item or iterable into a list.
|
|
112
276
|
|
|
277
|
+
Converts None to empty list, tuples to lists, and wraps single
|
|
278
|
+
items in a list.
|
|
279
|
+
|
|
113
280
|
Parameters
|
|
114
281
|
----------
|
|
115
282
|
value : Iterable[T] | T | None
|
|
116
|
-
Item or iterable to wrap.
|
|
283
|
+
Item or iterable to wrap. None yields an empty list.
|
|
117
284
|
|
|
118
285
|
Returns
|
|
119
286
|
-------
|
|
120
287
|
list[T]
|
|
121
|
-
Normalized list representation of
|
|
288
|
+
Normalized list representation of value.
|
|
289
|
+
|
|
290
|
+
Examples
|
|
291
|
+
--------
|
|
292
|
+
>>> ensure_list(None)
|
|
293
|
+
[]
|
|
294
|
+
>>> ensure_list(5)
|
|
295
|
+
[5]
|
|
296
|
+
>>> ensure_list([1, 2, 3])
|
|
297
|
+
[1, 2, 3]
|
|
298
|
+
>>> ensure_list(("a", "b"))
|
|
299
|
+
['a', 'b']
|
|
122
300
|
"""
|
|
123
301
|
if value is None:
|
|
124
302
|
return []
|
|
@@ -134,12 +312,15 @@ def check_filepath(
|
|
|
134
312
|
) -> Path:
|
|
135
313
|
"""Ensure the parent directory for a file path exists.
|
|
136
314
|
|
|
315
|
+
Creates parent directories as needed. Exactly one of filepath or
|
|
316
|
+
fullfilepath must be provided.
|
|
317
|
+
|
|
137
318
|
Parameters
|
|
138
319
|
----------
|
|
139
|
-
filepath : Path
|
|
140
|
-
Path object to validate. Mutually exclusive with
|
|
141
|
-
fullfilepath : str
|
|
142
|
-
String path to validate. Mutually exclusive with
|
|
320
|
+
filepath : Path or None, optional
|
|
321
|
+
Path object to validate. Mutually exclusive with fullfilepath.
|
|
322
|
+
fullfilepath : str or None, optional
|
|
323
|
+
String path to validate. Mutually exclusive with filepath.
|
|
143
324
|
|
|
144
325
|
Returns
|
|
145
326
|
-------
|
|
@@ -149,7 +330,14 @@ def check_filepath(
|
|
|
149
330
|
Raises
|
|
150
331
|
------
|
|
151
332
|
ValueError
|
|
152
|
-
If neither
|
|
333
|
+
If neither filepath nor fullfilepath is provided.
|
|
334
|
+
|
|
335
|
+
Examples
|
|
336
|
+
--------
|
|
337
|
+
>>> from pathlib import Path
|
|
338
|
+
>>> path = check_filepath(filepath=Path("/tmp/test.txt"))
|
|
339
|
+
>>> isinstance(path, Path)
|
|
340
|
+
True
|
|
153
341
|
"""
|
|
154
342
|
if filepath is None and fullfilepath is None:
|
|
155
343
|
raise ValueError("filepath or fullfilepath is required.")
|
|
@@ -166,6 +354,9 @@ def check_filepath(
|
|
|
166
354
|
def _to_jsonable(value: Any) -> Any:
|
|
167
355
|
"""Convert common helper types to JSON-serializable forms.
|
|
168
356
|
|
|
357
|
+
Handles Enum, Path, datetime, dataclasses, Pydantic models, dicts,
|
|
358
|
+
lists, tuples, and sets.
|
|
359
|
+
|
|
169
360
|
Parameters
|
|
170
361
|
----------
|
|
171
362
|
value : Any
|
|
@@ -174,7 +365,11 @@ def _to_jsonable(value: Any) -> Any:
|
|
|
174
365
|
Returns
|
|
175
366
|
-------
|
|
176
367
|
Any
|
|
177
|
-
A JSON-safe representation of
|
|
368
|
+
A JSON-safe representation of value.
|
|
369
|
+
|
|
370
|
+
Notes
|
|
371
|
+
-----
|
|
372
|
+
This is an internal helper function. Use coerce_jsonable for public API.
|
|
178
373
|
"""
|
|
179
374
|
if value is None:
|
|
180
375
|
return None
|
|
@@ -197,7 +392,10 @@ def _to_jsonable(value: Any) -> Any:
|
|
|
197
392
|
|
|
198
393
|
|
|
199
394
|
def coerce_jsonable(value: Any) -> Any:
|
|
200
|
-
"""Convert
|
|
395
|
+
"""Convert value into a JSON-serializable representation.
|
|
396
|
+
|
|
397
|
+
Handles BaseStructure, BaseResponse, dataclasses, and other complex
|
|
398
|
+
types by recursively converting them to JSON-compatible forms.
|
|
201
399
|
|
|
202
400
|
Parameters
|
|
203
401
|
----------
|
|
@@ -207,7 +405,14 @@ def coerce_jsonable(value: Any) -> Any:
|
|
|
207
405
|
Returns
|
|
208
406
|
-------
|
|
209
407
|
Any
|
|
210
|
-
JSON-serializable representation of
|
|
408
|
+
JSON-serializable representation of value.
|
|
409
|
+
|
|
410
|
+
Examples
|
|
411
|
+
--------
|
|
412
|
+
>>> from datetime import datetime
|
|
413
|
+
>>> result = coerce_jsonable({"date": datetime(2024, 1, 1)})
|
|
414
|
+
>>> isinstance(result, dict)
|
|
415
|
+
True
|
|
211
416
|
"""
|
|
212
417
|
from openai_sdk_helpers.response.base import BaseResponse
|
|
213
418
|
from openai_sdk_helpers.structure.base import BaseStructure
|
|
@@ -229,16 +434,29 @@ def coerce_jsonable(value: Any) -> Any:
|
|
|
229
434
|
|
|
230
435
|
|
|
231
436
|
class customJSONEncoder(json.JSONEncoder):
|
|
232
|
-
"""
|
|
437
|
+
"""JSON encoder for common helper types like enums and paths.
|
|
438
|
+
|
|
439
|
+
Extends json.JSONEncoder to handle Enum, Path, datetime, dataclasses,
|
|
440
|
+
and Pydantic models automatically.
|
|
233
441
|
|
|
234
442
|
Methods
|
|
235
443
|
-------
|
|
236
444
|
default(o)
|
|
237
|
-
Return a JSON-serializable representation of
|
|
445
|
+
Return a JSON-serializable representation of o.
|
|
446
|
+
|
|
447
|
+
Examples
|
|
448
|
+
--------
|
|
449
|
+
>>> import json
|
|
450
|
+
>>> from pathlib import Path
|
|
451
|
+
>>> json.dumps({"path": Path("/tmp")}, cls=customJSONEncoder)
|
|
452
|
+
'{"path": "/tmp"}'
|
|
238
453
|
"""
|
|
239
454
|
|
|
240
455
|
def default(self, o: Any) -> Any:
|
|
241
|
-
"""Return a JSON-serializable representation of
|
|
456
|
+
"""Return a JSON-serializable representation of o.
|
|
457
|
+
|
|
458
|
+
Called by the json module when the default serialization fails.
|
|
459
|
+
Delegates to _to_jsonable for type-specific conversions.
|
|
242
460
|
|
|
243
461
|
Parameters
|
|
244
462
|
----------
|
|
@@ -248,7 +466,7 @@ class customJSONEncoder(json.JSONEncoder):
|
|
|
248
466
|
Returns
|
|
249
467
|
-------
|
|
250
468
|
Any
|
|
251
|
-
JSON-safe representation of
|
|
469
|
+
JSON-safe representation of o.
|
|
252
470
|
"""
|
|
253
471
|
return _to_jsonable(o)
|
|
254
472
|
|
|
@@ -256,21 +474,44 @@ class customJSONEncoder(json.JSONEncoder):
|
|
|
256
474
|
class JSONSerializable:
|
|
257
475
|
"""Mixin for classes that can be serialized to JSON.
|
|
258
476
|
|
|
477
|
+
Provides to_json() and to_json_file() methods for any class. Works
|
|
478
|
+
with dataclasses, Pydantic models, and regular classes with __dict__.
|
|
479
|
+
|
|
259
480
|
Methods
|
|
260
481
|
-------
|
|
261
482
|
to_json()
|
|
262
483
|
Return a JSON-compatible dict representation of the instance.
|
|
263
484
|
to_json_file(filepath)
|
|
264
485
|
Write serialized JSON data to a file path.
|
|
486
|
+
|
|
487
|
+
Examples
|
|
488
|
+
--------
|
|
489
|
+
>>> from dataclasses import dataclass
|
|
490
|
+
>>> @dataclass
|
|
491
|
+
... class MyClass(JSONSerializable):
|
|
492
|
+
... value: int
|
|
493
|
+
>>> obj = MyClass(value=42)
|
|
494
|
+
>>> obj.to_json()
|
|
495
|
+
{'value': 42}
|
|
265
496
|
"""
|
|
266
497
|
|
|
267
|
-
def to_json(self) ->
|
|
498
|
+
def to_json(self) -> dict[str, Any]:
|
|
268
499
|
"""Return a JSON-compatible dict representation.
|
|
269
500
|
|
|
501
|
+
Automatically handles dataclasses, Pydantic models, and objects
|
|
502
|
+
with __dict__ attributes.
|
|
503
|
+
|
|
270
504
|
Returns
|
|
271
505
|
-------
|
|
272
506
|
dict[str, Any]
|
|
273
507
|
Mapping with only JSON-serializable values.
|
|
508
|
+
|
|
509
|
+
Examples
|
|
510
|
+
--------
|
|
511
|
+
>>> obj = JSONSerializable()
|
|
512
|
+
>>> result = obj.to_json()
|
|
513
|
+
>>> isinstance(result, dict)
|
|
514
|
+
True
|
|
274
515
|
"""
|
|
275
516
|
if is_dataclass(self) and not isinstance(self, type):
|
|
276
517
|
return {k: _to_jsonable(v) for k, v in asdict(self).items()}
|
|
@@ -282,6 +523,9 @@ class JSONSerializable:
|
|
|
282
523
|
def to_json_file(self, filepath: str | Path) -> str:
|
|
283
524
|
"""Write serialized JSON data to a file path.
|
|
284
525
|
|
|
526
|
+
Creates parent directories as needed. Uses customJSONEncoder for
|
|
527
|
+
handling special types.
|
|
528
|
+
|
|
285
529
|
Parameters
|
|
286
530
|
----------
|
|
287
531
|
filepath : str | Path
|
|
@@ -291,6 +535,11 @@ class JSONSerializable:
|
|
|
291
535
|
-------
|
|
292
536
|
str
|
|
293
537
|
String representation of the file path written.
|
|
538
|
+
|
|
539
|
+
Examples
|
|
540
|
+
--------
|
|
541
|
+
>>> obj = JSONSerializable()
|
|
542
|
+
>>> path = obj.to_json_file("/tmp/output.json") # doctest: +SKIP
|
|
294
543
|
"""
|
|
295
544
|
target = Path(filepath)
|
|
296
545
|
check_filepath(fullfilepath=str(target))
|
|
@@ -308,12 +557,21 @@ class JSONSerializable:
|
|
|
308
557
|
def log(message: str, level: int = logging.INFO) -> None:
|
|
309
558
|
"""Log a message with a basic configuration.
|
|
310
559
|
|
|
560
|
+
Configures logging on first use with a simple timestamp format.
|
|
561
|
+
Subsequent calls use the existing configuration.
|
|
562
|
+
|
|
311
563
|
Parameters
|
|
312
564
|
----------
|
|
313
565
|
message : str
|
|
314
566
|
Message to emit.
|
|
315
567
|
level : int, optional
|
|
316
|
-
Logging level, by default
|
|
568
|
+
Logging level (e.g., logging.INFO, logging.WARNING), by default
|
|
569
|
+
logging.INFO.
|
|
570
|
+
|
|
571
|
+
Examples
|
|
572
|
+
--------
|
|
573
|
+
>>> import logging
|
|
574
|
+
>>> log("Test message", level=logging.INFO) # doctest: +SKIP
|
|
317
575
|
"""
|
|
318
576
|
global _configured_logging
|
|
319
577
|
if not _configured_logging:
|
|
@@ -331,4 +589,8 @@ __all__ = [
|
|
|
331
589
|
"JSONSerializable",
|
|
332
590
|
"customJSONEncoder",
|
|
333
591
|
"log",
|
|
592
|
+
"coerce_optional_float",
|
|
593
|
+
"coerce_optional_int",
|
|
594
|
+
"coerce_dict",
|
|
595
|
+
"build_openai_settings",
|
|
334
596
|
]
|