openai-sdk-helpers 0.1.0__py3-none-any.whl → 0.1.2__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 +44 -7
- 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 +93 -2
- 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/files_api.py +373 -0
- openai_sdk_helpers/logging_config.py +24 -95
- openai_sdk_helpers/prompt/base.py +1 -1
- openai_sdk_helpers/response/__init__.py +7 -3
- openai_sdk_helpers/response/base.py +217 -147
- openai_sdk_helpers/response/config.py +16 -1
- openai_sdk_helpers/response/files.py +392 -0
- openai_sdk_helpers/response/messages.py +1 -0
- openai_sdk_helpers/retry.py +1 -1
- openai_sdk_helpers/streamlit_app/app.py +97 -7
- openai_sdk_helpers/streamlit_app/streamlit_web_search.py +15 -8
- openai_sdk_helpers/structure/base.py +6 -6
- openai_sdk_helpers/structure/plan/helpers.py +1 -0
- openai_sdk_helpers/structure/plan/task.py +7 -7
- openai_sdk_helpers/tools.py +116 -13
- openai_sdk_helpers/utils/__init__.py +100 -35
- 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/encoding.py +189 -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 +59 -28
- {openai_sdk_helpers-0.1.0.dist-info → openai_sdk_helpers-0.1.2.dist-info}/METADATA +152 -3
- openai_sdk_helpers-0.1.2.dist-info/RECORD +79 -0
- openai_sdk_helpers-0.1.2.dist-info/entry_points.txt +2 -0
- openai_sdk_helpers/utils/core.py +0 -596
- openai_sdk_helpers-0.1.0.dist-info/RECORD +0 -69
- {openai_sdk_helpers-0.1.0.dist-info → openai_sdk_helpers-0.1.2.dist-info}/WHEEL +0 -0
- {openai_sdk_helpers-0.1.0.dist-info → openai_sdk_helpers-0.1.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -140,13 +140,13 @@ class TaskStructure(BaseStructure):
|
|
|
140
140
|
"""
|
|
141
141
|
return "\n".join(
|
|
142
142
|
[
|
|
143
|
-
BaseStructure.format_output("Task type", self.task_type),
|
|
144
|
-
BaseStructure.format_output("Prompt", self.prompt),
|
|
145
|
-
BaseStructure.format_output("Context", self.context),
|
|
146
|
-
BaseStructure.format_output("Status", self.status),
|
|
147
|
-
BaseStructure.format_output("Start date", self.start_date),
|
|
148
|
-
BaseStructure.format_output("End date", self.end_date),
|
|
149
|
-
BaseStructure.format_output("Results", self.results),
|
|
143
|
+
BaseStructure.format_output("Task type", value=self.task_type),
|
|
144
|
+
BaseStructure.format_output("Prompt", value=self.prompt),
|
|
145
|
+
BaseStructure.format_output("Context", value=self.context),
|
|
146
|
+
BaseStructure.format_output("Status", value=self.status),
|
|
147
|
+
BaseStructure.format_output("Start date", value=self.start_date),
|
|
148
|
+
BaseStructure.format_output("End date", value=self.end_date),
|
|
149
|
+
BaseStructure.format_output("Results", value=self.results),
|
|
150
150
|
]
|
|
151
151
|
)
|
|
152
152
|
|
openai_sdk_helpers/tools.py
CHANGED
|
@@ -3,19 +3,26 @@
|
|
|
3
3
|
This module provides generic tool handling infrastructure including argument
|
|
4
4
|
parsing, Pydantic validation, function execution, and result serialization.
|
|
5
5
|
These utilities reduce boilerplate and ensure consistent tool behavior.
|
|
6
|
+
|
|
7
|
+
Also provides declarative tool specification helpers for building tool
|
|
8
|
+
definitions from named metadata structures.
|
|
6
9
|
"""
|
|
7
10
|
|
|
8
11
|
from __future__ import annotations
|
|
9
12
|
|
|
10
13
|
import inspect
|
|
11
|
-
import
|
|
12
|
-
from typing import Any, Callable, TypeVar
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
from typing import Any, Callable, TypeAlias, TypeVar
|
|
13
16
|
|
|
14
17
|
from pydantic import BaseModel, ValidationError
|
|
15
18
|
|
|
16
19
|
from openai_sdk_helpers.response.tool_call import parse_tool_arguments
|
|
20
|
+
from openai_sdk_helpers.structure.base import BaseStructure
|
|
21
|
+
from openai_sdk_helpers.utils import coerce_jsonable, customJSONEncoder
|
|
22
|
+
import json
|
|
17
23
|
|
|
18
24
|
T = TypeVar("T", bound=BaseModel)
|
|
25
|
+
StructureType: TypeAlias = type[BaseStructure]
|
|
19
26
|
|
|
20
27
|
|
|
21
28
|
def serialize_tool_result(result: Any) -> str:
|
|
@@ -53,24 +60,16 @@ def serialize_tool_result(result: Any) -> str:
|
|
|
53
60
|
>>> serialize_tool_result({"key": "value"})
|
|
54
61
|
'{"key": "value"}'
|
|
55
62
|
"""
|
|
56
|
-
# Handle Pydantic models
|
|
57
63
|
if isinstance(result, BaseModel):
|
|
58
64
|
return result.model_dump_json()
|
|
59
65
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
return json.dumps(result)
|
|
63
|
-
|
|
64
|
-
# Handle other JSON-serializable types (lists, dicts, primitives)
|
|
65
|
-
try:
|
|
66
|
-
return json.dumps(result)
|
|
67
|
-
except (TypeError, ValueError):
|
|
68
|
-
# Fallback to string representation for non-JSON types
|
|
69
|
-
return json.dumps(str(result))
|
|
66
|
+
payload = coerce_jsonable(result)
|
|
67
|
+
return json.dumps(payload, cls=customJSONEncoder)
|
|
70
68
|
|
|
71
69
|
|
|
72
70
|
def tool_handler_factory(
|
|
73
71
|
func: Callable[..., Any],
|
|
72
|
+
*,
|
|
74
73
|
input_model: type[T] | None = None,
|
|
75
74
|
) -> Callable[[Any], str]:
|
|
76
75
|
"""Create a generic tool handler that parses, validates, and serializes.
|
|
@@ -187,7 +186,111 @@ def tool_handler_factory(
|
|
|
187
186
|
return handler
|
|
188
187
|
|
|
189
188
|
|
|
189
|
+
@dataclass(frozen=True)
|
|
190
|
+
class ToolSpec:
|
|
191
|
+
"""Capture tool metadata for response configuration.
|
|
192
|
+
|
|
193
|
+
Provides a named structure for representing tool specifications, making
|
|
194
|
+
tool definitions explicit and eliminating ambiguous tuple ordering.
|
|
195
|
+
|
|
196
|
+
Supports tools with separate input and output structures, where the input
|
|
197
|
+
structure defines the tool's parameter schema and the output structure
|
|
198
|
+
documents the expected return type (for reference only).
|
|
199
|
+
|
|
200
|
+
Attributes
|
|
201
|
+
----------
|
|
202
|
+
structure : StructureType
|
|
203
|
+
The BaseStructure class that defines the tool's input parameter schema.
|
|
204
|
+
Used to generate the OpenAI tool definition.
|
|
205
|
+
tool_name : str
|
|
206
|
+
Name identifier for the tool.
|
|
207
|
+
tool_description : str
|
|
208
|
+
Human-readable description of what the tool does.
|
|
209
|
+
output_structure : StructureType or None, default=None
|
|
210
|
+
Optional BaseStructure class that defines the tool's output schema.
|
|
211
|
+
This is for documentation/reference only and is not sent to OpenAI.
|
|
212
|
+
Useful when a tool accepts one type of input but returns a different
|
|
213
|
+
structured output.
|
|
214
|
+
|
|
215
|
+
Examples
|
|
216
|
+
--------
|
|
217
|
+
Define a tool with same input/output structure:
|
|
218
|
+
|
|
219
|
+
>>> from openai_sdk_helpers import ToolSpec
|
|
220
|
+
>>> from openai_sdk_helpers.structure import PromptStructure
|
|
221
|
+
>>> spec = ToolSpec(
|
|
222
|
+
... structure=PromptStructure,
|
|
223
|
+
... tool_name="web_agent",
|
|
224
|
+
... tool_description="Run a web research workflow"
|
|
225
|
+
... )
|
|
226
|
+
|
|
227
|
+
Define a tool with different input and output structures:
|
|
228
|
+
|
|
229
|
+
>>> from openai_sdk_helpers.structure import PromptStructure, SummaryStructure
|
|
230
|
+
>>> spec = ToolSpec(
|
|
231
|
+
... structure=PromptStructure,
|
|
232
|
+
... tool_name="summarizer",
|
|
233
|
+
... tool_description="Summarize the provided prompt",
|
|
234
|
+
... output_structure=SummaryStructure
|
|
235
|
+
... )
|
|
236
|
+
"""
|
|
237
|
+
|
|
238
|
+
structure: StructureType
|
|
239
|
+
tool_name: str
|
|
240
|
+
tool_description: str
|
|
241
|
+
output_structure: StructureType | None = None
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def build_tool_definitions(tool_specs: list[ToolSpec]) -> list[dict]:
|
|
245
|
+
"""Build tool definitions from named tool specs.
|
|
246
|
+
|
|
247
|
+
Converts a list of ToolSpec objects into OpenAI-compatible tool
|
|
248
|
+
definitions for use in response configurations. Each ToolSpec is
|
|
249
|
+
transformed into a tool definition using the structure's
|
|
250
|
+
response_tool_definition method.
|
|
251
|
+
|
|
252
|
+
Parameters
|
|
253
|
+
----------
|
|
254
|
+
tool_specs : list[ToolSpec]
|
|
255
|
+
List of tool specifications to convert.
|
|
256
|
+
|
|
257
|
+
Returns
|
|
258
|
+
-------
|
|
259
|
+
list[dict]
|
|
260
|
+
List of tool definition dictionaries ready for OpenAI API.
|
|
261
|
+
|
|
262
|
+
Examples
|
|
263
|
+
--------
|
|
264
|
+
Build multiple tool definitions:
|
|
265
|
+
|
|
266
|
+
>>> from openai_sdk_helpers import ToolSpec, build_tool_definitions
|
|
267
|
+
>>> from openai_sdk_helpers.structure import PromptStructure
|
|
268
|
+
>>> tools = build_tool_definitions([
|
|
269
|
+
... ToolSpec(
|
|
270
|
+
... structure=PromptStructure,
|
|
271
|
+
... tool_name="web_agent",
|
|
272
|
+
... tool_description="Run a web research workflow"
|
|
273
|
+
... ),
|
|
274
|
+
... ToolSpec(
|
|
275
|
+
... structure=PromptStructure,
|
|
276
|
+
... tool_name="vector_agent",
|
|
277
|
+
... tool_description="Run a vector search workflow"
|
|
278
|
+
... ),
|
|
279
|
+
... ])
|
|
280
|
+
"""
|
|
281
|
+
return [
|
|
282
|
+
spec.structure.response_tool_definition(
|
|
283
|
+
tool_name=spec.tool_name,
|
|
284
|
+
tool_description=spec.tool_description,
|
|
285
|
+
)
|
|
286
|
+
for spec in tool_specs
|
|
287
|
+
]
|
|
288
|
+
|
|
289
|
+
|
|
190
290
|
__all__ = [
|
|
191
291
|
"serialize_tool_result",
|
|
192
292
|
"tool_handler_factory",
|
|
293
|
+
"StructureType",
|
|
294
|
+
"ToolSpec",
|
|
295
|
+
"build_tool_definitions",
|
|
193
296
|
]
|
|
@@ -1,54 +1,90 @@
|
|
|
1
1
|
"""Utility helpers for openai-sdk-helpers.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
handling, JSON serialization, logging, and OpenAI settings construction.
|
|
5
|
-
These utilities are used 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
|
-
|
|
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.
|
|
23
|
-
build_openai_settings(**kwargs)
|
|
24
|
-
Build OpenAI settings from environment with validation.
|
|
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.
|
|
10
|
+
* Encoding: base64 encoding for images and files.
|
|
25
11
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
12
|
+
Import style
|
|
13
|
+
------------
|
|
14
|
+
Public helpers are re-exported from ``openai_sdk_helpers.utils`` for a
|
|
15
|
+
consistent import surface. You can import from submodules when you need a
|
|
16
|
+
smaller surface area, but top-level imports remain stable.
|
|
17
|
+
|
|
18
|
+
Submodules
|
|
19
|
+
----------
|
|
20
|
+
coercion
|
|
21
|
+
Numeric coercion helpers and list normalization.
|
|
22
|
+
path_utils
|
|
23
|
+
File and path helpers.
|
|
24
|
+
json_utils
|
|
25
|
+
JSON encoding helpers and mixins.
|
|
26
|
+
logging_config
|
|
27
|
+
Centralized logger factory and convenience log helper.
|
|
28
|
+
validation
|
|
29
|
+
Input validation helpers for strings, URLs, collections, and paths.
|
|
30
|
+
async_utils
|
|
31
|
+
Async-to-sync bridging helpers.
|
|
32
|
+
output_validation
|
|
33
|
+
JSON Schema and semantic output validation utilities.
|
|
34
|
+
deprecation
|
|
35
|
+
Deprecation helpers and warning utilities.
|
|
36
|
+
encoding
|
|
37
|
+
Base64 encoding helpers for images and files.
|
|
32
38
|
"""
|
|
33
39
|
|
|
34
40
|
from __future__ import annotations
|
|
35
41
|
|
|
36
|
-
from .
|
|
37
|
-
JSONSerializable,
|
|
38
|
-
build_openai_settings,
|
|
39
|
-
check_filepath,
|
|
40
|
-
coerce_jsonable,
|
|
42
|
+
from .coercion import (
|
|
41
43
|
coerce_dict,
|
|
42
44
|
coerce_optional_float,
|
|
43
45
|
coerce_optional_int,
|
|
44
|
-
customJSONEncoder,
|
|
45
46
|
ensure_list,
|
|
46
|
-
|
|
47
|
+
)
|
|
48
|
+
from .json_utils import (
|
|
49
|
+
JSONSerializable,
|
|
50
|
+
coerce_jsonable,
|
|
51
|
+
customJSONEncoder,
|
|
52
|
+
)
|
|
53
|
+
from .path_utils import check_filepath, ensure_directory
|
|
54
|
+
from openai_sdk_helpers.logging_config import log
|
|
55
|
+
from .validation import (
|
|
56
|
+
validate_choice,
|
|
57
|
+
validate_dict_mapping,
|
|
58
|
+
validate_list_items,
|
|
59
|
+
validate_max_length,
|
|
60
|
+
validate_non_empty_string,
|
|
61
|
+
validate_safe_path,
|
|
62
|
+
validate_url_format,
|
|
63
|
+
)
|
|
64
|
+
from .async_utils import run_coroutine_thread_safe, run_coroutine_with_fallback
|
|
65
|
+
from .output_validation import (
|
|
66
|
+
JSONSchemaValidator,
|
|
67
|
+
LengthValidator,
|
|
68
|
+
OutputValidator,
|
|
69
|
+
SemanticValidator,
|
|
70
|
+
ValidationResult,
|
|
71
|
+
ValidationRule,
|
|
72
|
+
validate_output,
|
|
73
|
+
)
|
|
74
|
+
from .deprecation import DeprecationHelper, deprecated, warn_deprecated
|
|
75
|
+
from .encoding import (
|
|
76
|
+
create_file_data_url,
|
|
77
|
+
create_image_data_url,
|
|
78
|
+
encode_file,
|
|
79
|
+
encode_image,
|
|
80
|
+
get_mime_type,
|
|
81
|
+
is_image_file,
|
|
47
82
|
)
|
|
48
83
|
|
|
49
84
|
__all__ = [
|
|
50
85
|
"ensure_list",
|
|
51
86
|
"check_filepath",
|
|
87
|
+
"ensure_directory",
|
|
52
88
|
"coerce_optional_float",
|
|
53
89
|
"coerce_optional_int",
|
|
54
90
|
"coerce_dict",
|
|
@@ -56,5 +92,34 @@ __all__ = [
|
|
|
56
92
|
"JSONSerializable",
|
|
57
93
|
"customJSONEncoder",
|
|
58
94
|
"log",
|
|
59
|
-
|
|
95
|
+
# Validation helpers
|
|
96
|
+
"validate_non_empty_string",
|
|
97
|
+
"validate_max_length",
|
|
98
|
+
"validate_url_format",
|
|
99
|
+
"validate_dict_mapping",
|
|
100
|
+
"validate_list_items",
|
|
101
|
+
"validate_choice",
|
|
102
|
+
"validate_safe_path",
|
|
103
|
+
# Async helpers
|
|
104
|
+
"run_coroutine_thread_safe",
|
|
105
|
+
"run_coroutine_with_fallback",
|
|
106
|
+
# Output validation
|
|
107
|
+
"ValidationResult",
|
|
108
|
+
"ValidationRule",
|
|
109
|
+
"JSONSchemaValidator",
|
|
110
|
+
"SemanticValidator",
|
|
111
|
+
"LengthValidator",
|
|
112
|
+
"OutputValidator",
|
|
113
|
+
"validate_output",
|
|
114
|
+
# Deprecation
|
|
115
|
+
"deprecated",
|
|
116
|
+
"warn_deprecated",
|
|
117
|
+
"DeprecationHelper",
|
|
118
|
+
# Encoding
|
|
119
|
+
"encode_image",
|
|
120
|
+
"encode_file",
|
|
121
|
+
"get_mime_type",
|
|
122
|
+
"create_image_data_url",
|
|
123
|
+
"create_file_data_url",
|
|
124
|
+
"is_image_file",
|
|
60
125
|
]
|
|
@@ -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)
|