openai-sdk-helpers 0.0.9__py3-none-any.whl → 0.1.1__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 +63 -5
- openai_sdk_helpers/agent/base.py +5 -1
- openai_sdk_helpers/agent/coordination.py +4 -5
- openai_sdk_helpers/agent/runner.py +4 -1
- openai_sdk_helpers/agent/search/base.py +1 -0
- openai_sdk_helpers/agent/search/vector.py +2 -0
- openai_sdk_helpers/cli.py +265 -0
- openai_sdk_helpers/config.py +120 -31
- openai_sdk_helpers/context_manager.py +1 -1
- openai_sdk_helpers/deprecation.py +167 -0
- openai_sdk_helpers/environment.py +3 -2
- openai_sdk_helpers/errors.py +0 -12
- openai_sdk_helpers/logging_config.py +24 -95
- openai_sdk_helpers/prompt/base.py +56 -6
- openai_sdk_helpers/response/__init__.py +5 -2
- openai_sdk_helpers/response/base.py +84 -115
- openai_sdk_helpers/response/config.py +142 -0
- openai_sdk_helpers/response/messages.py +1 -0
- openai_sdk_helpers/response/tool_call.py +15 -4
- openai_sdk_helpers/retry.py +1 -1
- openai_sdk_helpers/streamlit_app/app.py +14 -3
- openai_sdk_helpers/streamlit_app/streamlit_web_search.py +15 -8
- openai_sdk_helpers/structure/__init__.py +3 -0
- openai_sdk_helpers/structure/base.py +6 -6
- openai_sdk_helpers/structure/plan/__init__.py +15 -1
- openai_sdk_helpers/structure/plan/helpers.py +173 -0
- openai_sdk_helpers/structure/plan/plan.py +13 -9
- openai_sdk_helpers/structure/plan/task.py +7 -7
- openai_sdk_helpers/structure/plan/types.py +15 -0
- openai_sdk_helpers/tools.py +296 -0
- openai_sdk_helpers/utils/__init__.py +82 -31
- openai_sdk_helpers/{async_utils.py → utils/async_utils.py} +5 -6
- openai_sdk_helpers/utils/coercion.py +138 -0
- openai_sdk_helpers/utils/deprecation.py +167 -0
- openai_sdk_helpers/utils/json_utils.py +98 -0
- openai_sdk_helpers/utils/output_validation.py +448 -0
- openai_sdk_helpers/utils/path_utils.py +46 -0
- openai_sdk_helpers/{validation.py → utils/validation.py} +7 -3
- openai_sdk_helpers/vector_storage/storage.py +9 -6
- {openai_sdk_helpers-0.0.9.dist-info → openai_sdk_helpers-0.1.1.dist-info}/METADATA +59 -3
- openai_sdk_helpers-0.1.1.dist-info/RECORD +76 -0
- openai_sdk_helpers-0.1.1.dist-info/entry_points.txt +2 -0
- openai_sdk_helpers/utils/core.py +0 -468
- openai_sdk_helpers-0.0.9.dist-info/RECORD +0 -66
- {openai_sdk_helpers-0.0.9.dist-info → openai_sdk_helpers-0.1.1.dist-info}/WHEEL +0 -0
- {openai_sdk_helpers-0.0.9.dist-info → openai_sdk_helpers-0.1.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,51 +1,79 @@
|
|
|
1
1
|
"""Utility helpers for openai-sdk-helpers.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
handling, JSON serialization, and logging. These utilities are used
|
|
5
|
-
throughout the openai_sdk_helpers package.
|
|
3
|
+
The utils package collects cross-cutting helpers used across the project:
|
|
6
4
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
Ensure the parent directory for a file path exists.
|
|
13
|
-
coerce_optional_float(value)
|
|
14
|
-
Convert a value to float or None.
|
|
15
|
-
coerce_optional_int(value)
|
|
16
|
-
Convert a value to int or None.
|
|
17
|
-
coerce_dict(value)
|
|
18
|
-
Convert a value to a string-keyed dictionary.
|
|
19
|
-
coerce_jsonable(value)
|
|
20
|
-
Convert a value into a JSON-serializable representation.
|
|
21
|
-
log(message, level)
|
|
22
|
-
Log a message with basic configuration.
|
|
5
|
+
* Core helpers: coercion, path handling, JSON encoding, and logging basics.
|
|
6
|
+
* Validation: input validation helpers for strings, choices, URLs, etc.
|
|
7
|
+
* Concurrency: async bridging helpers.
|
|
8
|
+
* Output validation: JSON Schema and semantic validators.
|
|
9
|
+
* Instrumentation helpers: deprecation utilities.
|
|
23
10
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
11
|
+
Import style
|
|
12
|
+
------------
|
|
13
|
+
Public helpers are re-exported from ``openai_sdk_helpers.utils`` for a
|
|
14
|
+
consistent import surface. You can import from submodules when you need a
|
|
15
|
+
smaller surface area, but top-level imports remain stable.
|
|
16
|
+
|
|
17
|
+
Submodules
|
|
18
|
+
----------
|
|
19
|
+
coercion
|
|
20
|
+
Numeric coercion helpers and list normalization.
|
|
21
|
+
path_utils
|
|
22
|
+
File and path helpers.
|
|
23
|
+
json_utils
|
|
24
|
+
JSON encoding helpers and mixins.
|
|
25
|
+
logging_config
|
|
26
|
+
Centralized logger factory and convenience log helper.
|
|
27
|
+
validation
|
|
28
|
+
Input validation helpers for strings, URLs, collections, and paths.
|
|
29
|
+
async_utils
|
|
30
|
+
Async-to-sync bridging helpers.
|
|
31
|
+
output_validation
|
|
32
|
+
JSON Schema and semantic output validation utilities.
|
|
33
|
+
deprecation
|
|
34
|
+
Deprecation helpers and warning utilities.
|
|
30
35
|
"""
|
|
31
36
|
|
|
32
37
|
from __future__ import annotations
|
|
33
38
|
|
|
34
|
-
from .
|
|
35
|
-
JSONSerializable,
|
|
36
|
-
check_filepath,
|
|
37
|
-
coerce_jsonable,
|
|
39
|
+
from .coercion import (
|
|
38
40
|
coerce_dict,
|
|
39
41
|
coerce_optional_float,
|
|
40
42
|
coerce_optional_int,
|
|
41
|
-
customJSONEncoder,
|
|
42
43
|
ensure_list,
|
|
43
|
-
log,
|
|
44
44
|
)
|
|
45
|
+
from .json_utils import (
|
|
46
|
+
JSONSerializable,
|
|
47
|
+
coerce_jsonable,
|
|
48
|
+
customJSONEncoder,
|
|
49
|
+
)
|
|
50
|
+
from .path_utils import check_filepath, ensure_directory
|
|
51
|
+
from openai_sdk_helpers.logging_config import log
|
|
52
|
+
from .validation import (
|
|
53
|
+
validate_choice,
|
|
54
|
+
validate_dict_mapping,
|
|
55
|
+
validate_list_items,
|
|
56
|
+
validate_max_length,
|
|
57
|
+
validate_non_empty_string,
|
|
58
|
+
validate_safe_path,
|
|
59
|
+
validate_url_format,
|
|
60
|
+
)
|
|
61
|
+
from .async_utils import run_coroutine_thread_safe, run_coroutine_with_fallback
|
|
62
|
+
from .output_validation import (
|
|
63
|
+
JSONSchemaValidator,
|
|
64
|
+
LengthValidator,
|
|
65
|
+
OutputValidator,
|
|
66
|
+
SemanticValidator,
|
|
67
|
+
ValidationResult,
|
|
68
|
+
ValidationRule,
|
|
69
|
+
validate_output,
|
|
70
|
+
)
|
|
71
|
+
from .deprecation import DeprecationHelper, deprecated, warn_deprecated
|
|
45
72
|
|
|
46
73
|
__all__ = [
|
|
47
74
|
"ensure_list",
|
|
48
75
|
"check_filepath",
|
|
76
|
+
"ensure_directory",
|
|
49
77
|
"coerce_optional_float",
|
|
50
78
|
"coerce_optional_int",
|
|
51
79
|
"coerce_dict",
|
|
@@ -53,4 +81,27 @@ __all__ = [
|
|
|
53
81
|
"JSONSerializable",
|
|
54
82
|
"customJSONEncoder",
|
|
55
83
|
"log",
|
|
84
|
+
# Validation helpers
|
|
85
|
+
"validate_non_empty_string",
|
|
86
|
+
"validate_max_length",
|
|
87
|
+
"validate_url_format",
|
|
88
|
+
"validate_dict_mapping",
|
|
89
|
+
"validate_list_items",
|
|
90
|
+
"validate_choice",
|
|
91
|
+
"validate_safe_path",
|
|
92
|
+
# Async helpers
|
|
93
|
+
"run_coroutine_thread_safe",
|
|
94
|
+
"run_coroutine_with_fallback",
|
|
95
|
+
# Output validation
|
|
96
|
+
"ValidationResult",
|
|
97
|
+
"ValidationRule",
|
|
98
|
+
"JSONSchemaValidator",
|
|
99
|
+
"SemanticValidator",
|
|
100
|
+
"LengthValidator",
|
|
101
|
+
"OutputValidator",
|
|
102
|
+
"validate_output",
|
|
103
|
+
# Deprecation
|
|
104
|
+
"deprecated",
|
|
105
|
+
"warn_deprecated",
|
|
106
|
+
"DeprecationHelper",
|
|
56
107
|
]
|
|
@@ -10,17 +10,17 @@ import threading
|
|
|
10
10
|
from typing import Any, Coroutine, Generic, TypeVar
|
|
11
11
|
|
|
12
12
|
from openai_sdk_helpers.errors import AsyncExecutionError
|
|
13
|
-
from openai_sdk_helpers.utils.core import log
|
|
14
13
|
|
|
15
14
|
T = TypeVar("T")
|
|
16
15
|
|
|
17
16
|
# Default timeout constants
|
|
18
17
|
DEFAULT_COROUTINE_TIMEOUT = 300.0 # 5 minutes
|
|
19
|
-
THREAD_JOIN_TIMEOUT = 5
|
|
18
|
+
THREAD_JOIN_TIMEOUT = 0.5 # 0.5 seconds
|
|
20
19
|
|
|
21
20
|
|
|
22
21
|
def run_coroutine_thread_safe(
|
|
23
22
|
coro: Coroutine[Any, Any, T],
|
|
23
|
+
*,
|
|
24
24
|
timeout: float = DEFAULT_COROUTINE_TIMEOUT,
|
|
25
25
|
) -> T:
|
|
26
26
|
"""Run a coroutine in a thread-safe manner from a sync context.
|
|
@@ -80,10 +80,9 @@ def run_coroutine_thread_safe(
|
|
|
80
80
|
# Ensure thread is cleaned up
|
|
81
81
|
thread.join(timeout=THREAD_JOIN_TIMEOUT)
|
|
82
82
|
if thread.is_alive():
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
)
|
|
83
|
+
# Thread did not terminate, likely still running async operation
|
|
84
|
+
# This is expected for timeout scenarios, so we don't log here
|
|
85
|
+
pass
|
|
87
86
|
|
|
88
87
|
|
|
89
88
|
def run_coroutine_with_fallback(
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""Type coercion and collection normalization helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Iterable, Mapping
|
|
6
|
+
from typing import Any, TypeVar
|
|
7
|
+
|
|
8
|
+
T = TypeVar("T")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def coerce_optional_float(value: object) -> float | None:
|
|
12
|
+
"""Return a float when the provided value can be coerced, otherwise None.
|
|
13
|
+
|
|
14
|
+
Handles float, int, and string inputs. Empty strings or None return None.
|
|
15
|
+
|
|
16
|
+
Parameters
|
|
17
|
+
----------
|
|
18
|
+
value : object
|
|
19
|
+
Value to convert into a float. Strings must be parseable as floats.
|
|
20
|
+
|
|
21
|
+
Returns
|
|
22
|
+
-------
|
|
23
|
+
float or None
|
|
24
|
+
Converted float value or None if the input is None.
|
|
25
|
+
|
|
26
|
+
Raises
|
|
27
|
+
------
|
|
28
|
+
ValueError
|
|
29
|
+
If a non-empty string cannot be converted to a float.
|
|
30
|
+
TypeError
|
|
31
|
+
If the value is not a float-compatible type.
|
|
32
|
+
"""
|
|
33
|
+
if value is None:
|
|
34
|
+
return None
|
|
35
|
+
if isinstance(value, (float, int)):
|
|
36
|
+
return float(value)
|
|
37
|
+
if isinstance(value, str) and value.strip():
|
|
38
|
+
try:
|
|
39
|
+
return float(value)
|
|
40
|
+
except ValueError as exc:
|
|
41
|
+
raise ValueError("timeout must be a float-compatible value") from exc
|
|
42
|
+
raise TypeError("timeout must be a float, int, str, or None")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def coerce_optional_int(value: object) -> int | None:
|
|
46
|
+
"""Return an int when the provided value can be coerced, otherwise None.
|
|
47
|
+
|
|
48
|
+
Handles int, float (if whole number), and string inputs. Empty strings
|
|
49
|
+
or None return None. Booleans are not considered valid integers.
|
|
50
|
+
|
|
51
|
+
Parameters
|
|
52
|
+
----------
|
|
53
|
+
value : object
|
|
54
|
+
Value to convert into an int. Strings must be parseable as integers.
|
|
55
|
+
|
|
56
|
+
Returns
|
|
57
|
+
-------
|
|
58
|
+
int or None
|
|
59
|
+
Converted integer value or None if the input is None.
|
|
60
|
+
|
|
61
|
+
Raises
|
|
62
|
+
------
|
|
63
|
+
ValueError
|
|
64
|
+
If a non-empty string cannot be converted to an integer.
|
|
65
|
+
TypeError
|
|
66
|
+
If the value is not an int-compatible type.
|
|
67
|
+
"""
|
|
68
|
+
if value is None:
|
|
69
|
+
return None
|
|
70
|
+
if isinstance(value, int) and not isinstance(value, bool):
|
|
71
|
+
return value
|
|
72
|
+
if isinstance(value, float) and value.is_integer():
|
|
73
|
+
return int(value)
|
|
74
|
+
if isinstance(value, str) and value.strip():
|
|
75
|
+
try:
|
|
76
|
+
return int(value)
|
|
77
|
+
except ValueError as exc:
|
|
78
|
+
raise ValueError("max_retries must be an int-compatible value") from exc
|
|
79
|
+
raise TypeError("max_retries must be an int, str, or None")
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def coerce_dict(value: object) -> dict[str, Any]:
|
|
83
|
+
"""Return a string-keyed dictionary built from value if possible.
|
|
84
|
+
|
|
85
|
+
Converts Mapping objects to dictionaries. None returns an empty dict.
|
|
86
|
+
|
|
87
|
+
Parameters
|
|
88
|
+
----------
|
|
89
|
+
value : object
|
|
90
|
+
Mapping-like value to convert. None yields an empty dictionary.
|
|
91
|
+
|
|
92
|
+
Returns
|
|
93
|
+
-------
|
|
94
|
+
dict[str, Any]
|
|
95
|
+
Dictionary representation of value.
|
|
96
|
+
|
|
97
|
+
Raises
|
|
98
|
+
------
|
|
99
|
+
TypeError
|
|
100
|
+
If the value cannot be treated as a mapping.
|
|
101
|
+
"""
|
|
102
|
+
if value is None:
|
|
103
|
+
return {}
|
|
104
|
+
if isinstance(value, Mapping):
|
|
105
|
+
return dict(value)
|
|
106
|
+
raise TypeError("extra_client_kwargs must be a mapping or None")
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def ensure_list(value: Iterable[T] | T | None) -> list[T]:
|
|
110
|
+
"""Normalize a single item or iterable into a list.
|
|
111
|
+
|
|
112
|
+
Converts None to empty list, tuples to lists, and wraps single items in a list.
|
|
113
|
+
|
|
114
|
+
Parameters
|
|
115
|
+
----------
|
|
116
|
+
value : Iterable[T] | T | None
|
|
117
|
+
Item or iterable to wrap. None yields an empty list.
|
|
118
|
+
|
|
119
|
+
Returns
|
|
120
|
+
-------
|
|
121
|
+
list[T]
|
|
122
|
+
Normalized list representation of value.
|
|
123
|
+
"""
|
|
124
|
+
if value is None:
|
|
125
|
+
return []
|
|
126
|
+
if isinstance(value, list):
|
|
127
|
+
return value
|
|
128
|
+
if isinstance(value, tuple):
|
|
129
|
+
return list(value)
|
|
130
|
+
return [value] # type: ignore[list-item]
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
__all__ = [
|
|
134
|
+
"coerce_optional_float",
|
|
135
|
+
"coerce_optional_int",
|
|
136
|
+
"coerce_dict",
|
|
137
|
+
"ensure_list",
|
|
138
|
+
]
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""Deprecation utilities for managing deprecated features.
|
|
2
|
+
|
|
3
|
+
This module provides infrastructure for marking and managing deprecated
|
|
4
|
+
functions, classes, and features with consistent warning messages.
|
|
5
|
+
|
|
6
|
+
Functions
|
|
7
|
+
---------
|
|
8
|
+
deprecated
|
|
9
|
+
Decorator to mark functions or classes as deprecated.
|
|
10
|
+
warn_deprecated
|
|
11
|
+
Emit a deprecation warning with optional custom message.
|
|
12
|
+
|
|
13
|
+
Classes
|
|
14
|
+
-------
|
|
15
|
+
DeprecationHelper
|
|
16
|
+
Utility class for managing deprecation warnings and versions.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import functools
|
|
22
|
+
import warnings
|
|
23
|
+
from typing import Any, Callable, TypeVar
|
|
24
|
+
|
|
25
|
+
F = TypeVar("F", bound=Callable[..., Any])
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class DeprecationHelper:
|
|
29
|
+
"""Utility class for managing deprecation warnings.
|
|
30
|
+
|
|
31
|
+
Provides consistent formatting and control of deprecation warnings
|
|
32
|
+
across the package.
|
|
33
|
+
|
|
34
|
+
Methods
|
|
35
|
+
-------
|
|
36
|
+
warn
|
|
37
|
+
Emit a deprecation warning with standard formatting.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
@staticmethod
|
|
41
|
+
def warn(
|
|
42
|
+
feature_name: str,
|
|
43
|
+
removal_version: str,
|
|
44
|
+
alternative: str | None = None,
|
|
45
|
+
extra_message: str | None = None,
|
|
46
|
+
) -> None:
|
|
47
|
+
"""Emit a deprecation warning for a feature.
|
|
48
|
+
|
|
49
|
+
Parameters
|
|
50
|
+
----------
|
|
51
|
+
feature_name : str
|
|
52
|
+
Name of the deprecated feature (e.g., "MyClass.old_method").
|
|
53
|
+
removal_version : str
|
|
54
|
+
Version in which the feature will be removed.
|
|
55
|
+
alternative : str, optional
|
|
56
|
+
Recommended alternative to use instead.
|
|
57
|
+
extra_message : str, optional
|
|
58
|
+
Additional context or migration instructions.
|
|
59
|
+
|
|
60
|
+
Raises
|
|
61
|
+
------
|
|
62
|
+
DeprecationWarning
|
|
63
|
+
Always issues a DeprecationWarning to stderr.
|
|
64
|
+
"""
|
|
65
|
+
msg = f"{feature_name} is deprecated and will be removed in version {removal_version}."
|
|
66
|
+
if alternative:
|
|
67
|
+
msg += f" Use {alternative} instead."
|
|
68
|
+
if extra_message:
|
|
69
|
+
msg += f" {extra_message}"
|
|
70
|
+
|
|
71
|
+
warnings.warn(msg, DeprecationWarning, stacklevel=3)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def deprecated(
|
|
75
|
+
removal_version: str,
|
|
76
|
+
alternative: str | None = None,
|
|
77
|
+
extra_message: str | None = None,
|
|
78
|
+
) -> Callable[[F], F]:
|
|
79
|
+
"""Mark a function or class as deprecated.
|
|
80
|
+
|
|
81
|
+
Parameters
|
|
82
|
+
----------
|
|
83
|
+
removal_version : str
|
|
84
|
+
Version in which the decorated feature will be removed.
|
|
85
|
+
alternative : str, optional
|
|
86
|
+
Recommended alternative to use instead.
|
|
87
|
+
extra_message : str, optional
|
|
88
|
+
Additional context or migration instructions.
|
|
89
|
+
|
|
90
|
+
Returns
|
|
91
|
+
-------
|
|
92
|
+
Callable
|
|
93
|
+
Decorator function that wraps the target function or class.
|
|
94
|
+
|
|
95
|
+
Examples
|
|
96
|
+
--------
|
|
97
|
+
>>> @deprecated("1.0.0", "new_function")
|
|
98
|
+
... def old_function():
|
|
99
|
+
... pass
|
|
100
|
+
|
|
101
|
+
>>> class OldClass:
|
|
102
|
+
... @deprecated("1.0.0", "NewClass")
|
|
103
|
+
... def old_method(self):
|
|
104
|
+
... pass
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
def decorator(func_or_class: F) -> F:
|
|
108
|
+
feature_name = f"{func_or_class.__module__}.{func_or_class.__qualname__}"
|
|
109
|
+
|
|
110
|
+
if isinstance(func_or_class, type):
|
|
111
|
+
# Handle class deprecation
|
|
112
|
+
original_init = func_or_class.__init__
|
|
113
|
+
|
|
114
|
+
@functools.wraps(original_init)
|
|
115
|
+
def new_init(self: Any, *args: Any, **kwargs: Any) -> None:
|
|
116
|
+
DeprecationHelper.warn(
|
|
117
|
+
feature_name,
|
|
118
|
+
removal_version,
|
|
119
|
+
alternative,
|
|
120
|
+
extra_message,
|
|
121
|
+
)
|
|
122
|
+
original_init(self, *args, **kwargs)
|
|
123
|
+
|
|
124
|
+
func_or_class.__init__ = new_init
|
|
125
|
+
else:
|
|
126
|
+
# Handle function deprecation
|
|
127
|
+
@functools.wraps(func_or_class)
|
|
128
|
+
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
129
|
+
DeprecationHelper.warn(
|
|
130
|
+
feature_name,
|
|
131
|
+
removal_version,
|
|
132
|
+
alternative,
|
|
133
|
+
extra_message,
|
|
134
|
+
)
|
|
135
|
+
return func_or_class(*args, **kwargs)
|
|
136
|
+
|
|
137
|
+
return wrapper # type: ignore
|
|
138
|
+
|
|
139
|
+
return func_or_class # type: ignore[return-value]
|
|
140
|
+
|
|
141
|
+
return decorator
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def warn_deprecated(
|
|
145
|
+
feature_name: str,
|
|
146
|
+
removal_version: str,
|
|
147
|
+
alternative: str | None = None,
|
|
148
|
+
extra_message: str | None = None,
|
|
149
|
+
) -> None:
|
|
150
|
+
"""Issue a deprecation warning.
|
|
151
|
+
|
|
152
|
+
Parameters
|
|
153
|
+
----------
|
|
154
|
+
feature_name : str
|
|
155
|
+
Name of the deprecated feature.
|
|
156
|
+
removal_version : str
|
|
157
|
+
Version in which the feature will be removed.
|
|
158
|
+
alternative : str, optional
|
|
159
|
+
Recommended alternative to use instead.
|
|
160
|
+
extra_message : str, optional
|
|
161
|
+
Additional context or migration instructions.
|
|
162
|
+
|
|
163
|
+
Examples
|
|
164
|
+
--------
|
|
165
|
+
>>> warn_deprecated("old_config_key", "1.0.0", "new_config_key")
|
|
166
|
+
"""
|
|
167
|
+
DeprecationHelper.warn(feature_name, removal_version, alternative, extra_message)
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""JSON serialization helpers for helper types."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from dataclasses import asdict, is_dataclass
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
from enum import Enum
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from .path_utils import check_filepath
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _to_jsonable(value: Any) -> Any:
|
|
16
|
+
"""Convert common helper types to JSON-serializable forms."""
|
|
17
|
+
from openai_sdk_helpers.structure.base import BaseStructure
|
|
18
|
+
|
|
19
|
+
if value is None:
|
|
20
|
+
return None
|
|
21
|
+
if isinstance(value, Enum):
|
|
22
|
+
return value.value
|
|
23
|
+
if isinstance(value, Path):
|
|
24
|
+
return str(value)
|
|
25
|
+
if isinstance(value, datetime):
|
|
26
|
+
return value.isoformat()
|
|
27
|
+
if is_dataclass(value) and not isinstance(value, type):
|
|
28
|
+
return {k: _to_jsonable(v) for k, v in asdict(value).items()}
|
|
29
|
+
if hasattr(value, "model_dump"):
|
|
30
|
+
model_dump = getattr(value, "model_dump")
|
|
31
|
+
return model_dump()
|
|
32
|
+
if isinstance(value, dict):
|
|
33
|
+
return {str(k): _to_jsonable(v) for k, v in value.items()}
|
|
34
|
+
if isinstance(value, (list, tuple, set)):
|
|
35
|
+
return [_to_jsonable(v) for v in value]
|
|
36
|
+
if isinstance(value, BaseStructure):
|
|
37
|
+
return value.model_dump()
|
|
38
|
+
return value
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def coerce_jsonable(value: Any) -> Any:
|
|
42
|
+
"""Convert value into a JSON-serializable representation."""
|
|
43
|
+
from openai_sdk_helpers.response.base import BaseResponse
|
|
44
|
+
|
|
45
|
+
if value is None:
|
|
46
|
+
return None
|
|
47
|
+
if isinstance(value, BaseResponse):
|
|
48
|
+
return coerce_jsonable(value.messages.to_json())
|
|
49
|
+
if is_dataclass(value) and not isinstance(value, type):
|
|
50
|
+
return {key: coerce_jsonable(item) for key, item in asdict(value).items()}
|
|
51
|
+
coerced = _to_jsonable(value)
|
|
52
|
+
try:
|
|
53
|
+
json.dumps(coerced)
|
|
54
|
+
return coerced
|
|
55
|
+
except TypeError:
|
|
56
|
+
return str(coerced)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class customJSONEncoder(json.JSONEncoder):
|
|
60
|
+
"""JSON encoder for common helper types like enums and paths."""
|
|
61
|
+
|
|
62
|
+
def default(self, o: Any) -> Any: # noqa: D401
|
|
63
|
+
"""Return JSON-serializable representation of ``o``."""
|
|
64
|
+
return _to_jsonable(o)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class JSONSerializable:
|
|
68
|
+
"""Mixin for classes that can be serialized to JSON."""
|
|
69
|
+
|
|
70
|
+
def to_json(self) -> dict[str, Any]:
|
|
71
|
+
"""Return a JSON-compatible dict representation."""
|
|
72
|
+
if is_dataclass(self) and not isinstance(self, type):
|
|
73
|
+
return {k: _to_jsonable(v) for k, v in asdict(self).items()}
|
|
74
|
+
if hasattr(self, "model_dump"):
|
|
75
|
+
model_dump = getattr(self, "model_dump")
|
|
76
|
+
return _to_jsonable(model_dump())
|
|
77
|
+
return _to_jsonable(self.__dict__)
|
|
78
|
+
|
|
79
|
+
def to_json_file(self, filepath: str | Path) -> str:
|
|
80
|
+
"""Write serialized JSON data to a file path."""
|
|
81
|
+
target = Path(filepath)
|
|
82
|
+
check_filepath(fullfilepath=str(target))
|
|
83
|
+
with open(target, "w", encoding="utf-8") as handle:
|
|
84
|
+
json.dump(
|
|
85
|
+
self.to_json(),
|
|
86
|
+
handle,
|
|
87
|
+
indent=2,
|
|
88
|
+
ensure_ascii=False,
|
|
89
|
+
cls=customJSONEncoder,
|
|
90
|
+
)
|
|
91
|
+
return str(target)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
__all__ = [
|
|
95
|
+
"coerce_jsonable",
|
|
96
|
+
"JSONSerializable",
|
|
97
|
+
"customJSONEncoder",
|
|
98
|
+
]
|