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
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
"""Output validation framework for agent responses.
|
|
2
|
+
|
|
3
|
+
Provides validators for checking agent outputs against schemas,
|
|
4
|
+
semantic constraints, and content policies.
|
|
5
|
+
|
|
6
|
+
Classes
|
|
7
|
+
-------
|
|
8
|
+
ValidationRule
|
|
9
|
+
Base class for validation rules.
|
|
10
|
+
JSONSchemaValidator
|
|
11
|
+
Validate outputs against JSON schemas.
|
|
12
|
+
SemanticValidator
|
|
13
|
+
Validate outputs semantically (e.g., must reference sources).
|
|
14
|
+
LengthValidator
|
|
15
|
+
Validate output length constraints.
|
|
16
|
+
OutputValidator
|
|
17
|
+
Composite validator for multiple rules.
|
|
18
|
+
|
|
19
|
+
Functions
|
|
20
|
+
---------
|
|
21
|
+
validate_output
|
|
22
|
+
Convenience function for validating outputs.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import json
|
|
28
|
+
import re
|
|
29
|
+
from abc import ABC, abstractmethod
|
|
30
|
+
from typing import Any
|
|
31
|
+
|
|
32
|
+
from pydantic import BaseModel, ValidationError
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class ValidationResult(BaseModel):
|
|
36
|
+
"""Result of output validation.
|
|
37
|
+
|
|
38
|
+
Attributes
|
|
39
|
+
----------
|
|
40
|
+
valid : bool
|
|
41
|
+
Whether validation passed.
|
|
42
|
+
errors : list[str]
|
|
43
|
+
List of validation error messages.
|
|
44
|
+
warnings : list[str]
|
|
45
|
+
List of validation warnings.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
valid: bool
|
|
49
|
+
errors: list[str] = []
|
|
50
|
+
warnings: list[str] = []
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class ValidationRule(ABC):
|
|
54
|
+
"""Base class for validation rules.
|
|
55
|
+
|
|
56
|
+
Methods
|
|
57
|
+
-------
|
|
58
|
+
validate
|
|
59
|
+
Validate output and return ValidationResult.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
@abstractmethod
|
|
63
|
+
def validate(self, output: Any) -> ValidationResult:
|
|
64
|
+
"""Validate output.
|
|
65
|
+
|
|
66
|
+
Parameters
|
|
67
|
+
----------
|
|
68
|
+
output : Any
|
|
69
|
+
Output to validate.
|
|
70
|
+
|
|
71
|
+
Returns
|
|
72
|
+
-------
|
|
73
|
+
ValidationResult
|
|
74
|
+
Validation result.
|
|
75
|
+
"""
|
|
76
|
+
pass
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class JSONSchemaValidator(ValidationRule):
|
|
80
|
+
"""Validate outputs against JSON schemas.
|
|
81
|
+
|
|
82
|
+
Parameters
|
|
83
|
+
----------
|
|
84
|
+
schema : dict
|
|
85
|
+
JSON schema to validate against.
|
|
86
|
+
|
|
87
|
+
Examples
|
|
88
|
+
--------
|
|
89
|
+
>>> schema = {"type": "object", "required": ["name"]}
|
|
90
|
+
>>> validator = JSONSchemaValidator(schema)
|
|
91
|
+
>>> result = validator.validate({"name": "test"})
|
|
92
|
+
>>> result.valid
|
|
93
|
+
True
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
def __init__(self, schema: dict[str, Any]) -> None:
|
|
97
|
+
"""Initialize JSON schema validator.
|
|
98
|
+
|
|
99
|
+
Parameters
|
|
100
|
+
----------
|
|
101
|
+
schema : dict
|
|
102
|
+
JSON schema dictionary.
|
|
103
|
+
"""
|
|
104
|
+
self.schema = schema
|
|
105
|
+
|
|
106
|
+
def validate(self, output: Any) -> ValidationResult:
|
|
107
|
+
"""Validate output against JSON schema.
|
|
108
|
+
|
|
109
|
+
Parameters
|
|
110
|
+
----------
|
|
111
|
+
output : Any
|
|
112
|
+
Output to validate.
|
|
113
|
+
|
|
114
|
+
Returns
|
|
115
|
+
-------
|
|
116
|
+
ValidationResult
|
|
117
|
+
Validation result.
|
|
118
|
+
"""
|
|
119
|
+
try:
|
|
120
|
+
# Try using jsonschema if available
|
|
121
|
+
import jsonschema
|
|
122
|
+
except ImportError:
|
|
123
|
+
# Fallback to basic type checking
|
|
124
|
+
return self._basic_validate(output)
|
|
125
|
+
|
|
126
|
+
try:
|
|
127
|
+
jsonschema.validate(instance=output, schema=self.schema)
|
|
128
|
+
return ValidationResult(valid=True)
|
|
129
|
+
except jsonschema.ValidationError as e:
|
|
130
|
+
return ValidationResult(valid=False, errors=[str(e)])
|
|
131
|
+
|
|
132
|
+
def _basic_validate(self, output: Any) -> ValidationResult:
|
|
133
|
+
"""Perform basic validation without jsonschema library."""
|
|
134
|
+
errors = []
|
|
135
|
+
|
|
136
|
+
# Check type
|
|
137
|
+
expected_type = self.schema.get("type")
|
|
138
|
+
if expected_type == "object" and not isinstance(output, dict):
|
|
139
|
+
errors.append(f"Expected object, got {type(output).__name__}")
|
|
140
|
+
elif expected_type == "array" and not isinstance(output, list):
|
|
141
|
+
errors.append(f"Expected array, got {type(output).__name__}")
|
|
142
|
+
|
|
143
|
+
# Check required fields
|
|
144
|
+
if expected_type == "object" and isinstance(output, dict):
|
|
145
|
+
required = self.schema.get("required", [])
|
|
146
|
+
for field in required:
|
|
147
|
+
if field not in output:
|
|
148
|
+
errors.append(f"Missing required field: {field}")
|
|
149
|
+
|
|
150
|
+
return ValidationResult(valid=len(errors) == 0, errors=errors)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class SemanticValidator(ValidationRule):
|
|
154
|
+
"""Validate outputs semantically.
|
|
155
|
+
|
|
156
|
+
Parameters
|
|
157
|
+
----------
|
|
158
|
+
must_contain : list[str], optional
|
|
159
|
+
Phrases that must appear in output.
|
|
160
|
+
must_not_contain : list[str], optional
|
|
161
|
+
Phrases that must not appear in output.
|
|
162
|
+
must_reference_sources : bool
|
|
163
|
+
Whether output must reference source documents. Default is False.
|
|
164
|
+
|
|
165
|
+
Examples
|
|
166
|
+
--------
|
|
167
|
+
>>> validator = SemanticValidator(must_contain=["summary", "conclusion"])
|
|
168
|
+
>>> result = validator.validate("Here is the summary and conclusion.")
|
|
169
|
+
>>> result.valid
|
|
170
|
+
True
|
|
171
|
+
"""
|
|
172
|
+
|
|
173
|
+
def __init__(
|
|
174
|
+
self,
|
|
175
|
+
must_contain: list[str] | None = None,
|
|
176
|
+
must_not_contain: list[str] | None = None,
|
|
177
|
+
must_reference_sources: bool = False,
|
|
178
|
+
) -> None:
|
|
179
|
+
"""Initialize semantic validator.
|
|
180
|
+
|
|
181
|
+
Parameters
|
|
182
|
+
----------
|
|
183
|
+
must_contain : list[str], optional
|
|
184
|
+
Phrases that must appear.
|
|
185
|
+
must_not_contain : list[str], optional
|
|
186
|
+
Phrases that must not appear.
|
|
187
|
+
must_reference_sources : bool
|
|
188
|
+
Check for source references.
|
|
189
|
+
"""
|
|
190
|
+
self.must_contain = must_contain or []
|
|
191
|
+
self.must_not_contain = must_not_contain or []
|
|
192
|
+
self.must_reference_sources = must_reference_sources
|
|
193
|
+
|
|
194
|
+
def validate(self, output: Any) -> ValidationResult:
|
|
195
|
+
"""Validate output semantically.
|
|
196
|
+
|
|
197
|
+
Parameters
|
|
198
|
+
----------
|
|
199
|
+
output : Any
|
|
200
|
+
Output to validate (converted to string).
|
|
201
|
+
|
|
202
|
+
Returns
|
|
203
|
+
-------
|
|
204
|
+
ValidationResult
|
|
205
|
+
Validation result.
|
|
206
|
+
"""
|
|
207
|
+
text = str(output).lower()
|
|
208
|
+
errors = []
|
|
209
|
+
warnings = []
|
|
210
|
+
|
|
211
|
+
# Check must contain
|
|
212
|
+
for phrase in self.must_contain:
|
|
213
|
+
if phrase.lower() not in text:
|
|
214
|
+
errors.append(f"Output must contain: '{phrase}'")
|
|
215
|
+
|
|
216
|
+
# Check must not contain
|
|
217
|
+
for phrase in self.must_not_contain:
|
|
218
|
+
if phrase.lower() in text:
|
|
219
|
+
errors.append(f"Output must not contain: '{phrase}'")
|
|
220
|
+
|
|
221
|
+
# Check for source references
|
|
222
|
+
if self.must_reference_sources:
|
|
223
|
+
# Look for common citation patterns
|
|
224
|
+
citation_patterns = [
|
|
225
|
+
r"\[\d+\]", # [1]
|
|
226
|
+
r"\(\d+\)", # (1)
|
|
227
|
+
r"source:", # source:
|
|
228
|
+
r"according to", # according to
|
|
229
|
+
]
|
|
230
|
+
has_citations = any(
|
|
231
|
+
re.search(pattern, text, re.IGNORECASE) for pattern in citation_patterns
|
|
232
|
+
)
|
|
233
|
+
if not has_citations:
|
|
234
|
+
warnings.append("Output should reference sources")
|
|
235
|
+
|
|
236
|
+
return ValidationResult(
|
|
237
|
+
valid=len(errors) == 0,
|
|
238
|
+
errors=errors,
|
|
239
|
+
warnings=warnings,
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
class LengthValidator(ValidationRule):
|
|
244
|
+
"""Validate output length constraints.
|
|
245
|
+
|
|
246
|
+
Parameters
|
|
247
|
+
----------
|
|
248
|
+
min_length : int, optional
|
|
249
|
+
Minimum output length in characters.
|
|
250
|
+
max_length : int, optional
|
|
251
|
+
Maximum output length in characters.
|
|
252
|
+
min_words : int, optional
|
|
253
|
+
Minimum word count.
|
|
254
|
+
max_words : int, optional
|
|
255
|
+
Maximum word count.
|
|
256
|
+
|
|
257
|
+
Examples
|
|
258
|
+
--------
|
|
259
|
+
>>> validator = LengthValidator(min_words=10, max_words=100)
|
|
260
|
+
>>> result = validator.validate("Short text")
|
|
261
|
+
>>> result.valid
|
|
262
|
+
False
|
|
263
|
+
"""
|
|
264
|
+
|
|
265
|
+
def __init__(
|
|
266
|
+
self,
|
|
267
|
+
min_length: int | None = None,
|
|
268
|
+
max_length: int | None = None,
|
|
269
|
+
min_words: int | None = None,
|
|
270
|
+
max_words: int | None = None,
|
|
271
|
+
) -> None:
|
|
272
|
+
"""Initialize length validator.
|
|
273
|
+
|
|
274
|
+
Parameters
|
|
275
|
+
----------
|
|
276
|
+
min_length : int, optional
|
|
277
|
+
Minimum character count.
|
|
278
|
+
max_length : int, optional
|
|
279
|
+
Maximum character count.
|
|
280
|
+
min_words : int, optional
|
|
281
|
+
Minimum word count.
|
|
282
|
+
max_words : int, optional
|
|
283
|
+
Maximum word count.
|
|
284
|
+
"""
|
|
285
|
+
self.min_length = min_length
|
|
286
|
+
self.max_length = max_length
|
|
287
|
+
self.min_words = min_words
|
|
288
|
+
self.max_words = max_words
|
|
289
|
+
|
|
290
|
+
def validate(self, output: Any) -> ValidationResult:
|
|
291
|
+
"""Validate output length.
|
|
292
|
+
|
|
293
|
+
Parameters
|
|
294
|
+
----------
|
|
295
|
+
output : Any
|
|
296
|
+
Output to validate (converted to string).
|
|
297
|
+
|
|
298
|
+
Returns
|
|
299
|
+
-------
|
|
300
|
+
ValidationResult
|
|
301
|
+
Validation result.
|
|
302
|
+
"""
|
|
303
|
+
text = str(output)
|
|
304
|
+
errors = []
|
|
305
|
+
|
|
306
|
+
# Check character length
|
|
307
|
+
if self.min_length and len(text) < self.min_length:
|
|
308
|
+
errors.append(
|
|
309
|
+
f"Output too short: {len(text)} chars (min: {self.min_length})"
|
|
310
|
+
)
|
|
311
|
+
if self.max_length and len(text) > self.max_length:
|
|
312
|
+
errors.append(
|
|
313
|
+
f"Output too long: {len(text)} chars (max: {self.max_length})"
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
# Check word count
|
|
317
|
+
words = text.split()
|
|
318
|
+
if self.min_words and len(words) < self.min_words:
|
|
319
|
+
errors.append(f"Too few words: {len(words)} (min: {self.min_words})")
|
|
320
|
+
if self.max_words and len(words) > self.max_words:
|
|
321
|
+
errors.append(f"Too many words: {len(words)} (max: {self.max_words})")
|
|
322
|
+
|
|
323
|
+
return ValidationResult(valid=len(errors) == 0, errors=errors)
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
class OutputValidator:
|
|
327
|
+
"""Composite validator for multiple rules.
|
|
328
|
+
|
|
329
|
+
Parameters
|
|
330
|
+
----------
|
|
331
|
+
rules : list[ValidationRule]
|
|
332
|
+
List of validation rules to apply.
|
|
333
|
+
|
|
334
|
+
Methods
|
|
335
|
+
-------
|
|
336
|
+
validate
|
|
337
|
+
Validate output against all rules.
|
|
338
|
+
add_rule
|
|
339
|
+
Add a validation rule.
|
|
340
|
+
|
|
341
|
+
Examples
|
|
342
|
+
--------
|
|
343
|
+
>>> validator = OutputValidator([
|
|
344
|
+
... LengthValidator(min_words=10),
|
|
345
|
+
... SemanticValidator(must_contain=["summary"])
|
|
346
|
+
... ])
|
|
347
|
+
>>> result = validator.validate("This is a brief summary with enough words.")
|
|
348
|
+
"""
|
|
349
|
+
|
|
350
|
+
def __init__(self, rules: list[ValidationRule] | None = None) -> None:
|
|
351
|
+
"""Initialize output validator.
|
|
352
|
+
|
|
353
|
+
Parameters
|
|
354
|
+
----------
|
|
355
|
+
rules : list[ValidationRule], optional
|
|
356
|
+
Initial validation rules.
|
|
357
|
+
"""
|
|
358
|
+
self.rules = rules or []
|
|
359
|
+
|
|
360
|
+
def add_rule(self, rule: ValidationRule) -> None:
|
|
361
|
+
"""Add a validation rule.
|
|
362
|
+
|
|
363
|
+
Parameters
|
|
364
|
+
----------
|
|
365
|
+
rule : ValidationRule
|
|
366
|
+
Rule to add.
|
|
367
|
+
"""
|
|
368
|
+
self.rules.append(rule)
|
|
369
|
+
|
|
370
|
+
def validate(self, output: Any) -> ValidationResult:
|
|
371
|
+
"""Validate output against all rules.
|
|
372
|
+
|
|
373
|
+
Parameters
|
|
374
|
+
----------
|
|
375
|
+
output : Any
|
|
376
|
+
Output to validate.
|
|
377
|
+
|
|
378
|
+
Returns
|
|
379
|
+
-------
|
|
380
|
+
ValidationResult
|
|
381
|
+
Combined validation result.
|
|
382
|
+
"""
|
|
383
|
+
all_errors: list[str] = []
|
|
384
|
+
all_warnings: list[str] = []
|
|
385
|
+
|
|
386
|
+
for rule in self.rules:
|
|
387
|
+
result = rule.validate(output)
|
|
388
|
+
all_errors.extend(result.errors)
|
|
389
|
+
all_warnings.extend(result.warnings)
|
|
390
|
+
|
|
391
|
+
return ValidationResult(
|
|
392
|
+
valid=len(all_errors) == 0,
|
|
393
|
+
errors=all_errors,
|
|
394
|
+
warnings=all_warnings,
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def validate_output(
|
|
399
|
+
output: Any,
|
|
400
|
+
schema: dict[str, Any] | None = None,
|
|
401
|
+
min_length: int | None = None,
|
|
402
|
+
max_length: int | None = None,
|
|
403
|
+
must_contain: list[str] | None = None,
|
|
404
|
+
) -> ValidationResult:
|
|
405
|
+
"""Validate outputs with common validation rules.
|
|
406
|
+
|
|
407
|
+
Parameters
|
|
408
|
+
----------
|
|
409
|
+
output : Any
|
|
410
|
+
Output to validate.
|
|
411
|
+
schema : dict, optional
|
|
412
|
+
JSON schema to validate against.
|
|
413
|
+
min_length : int, optional
|
|
414
|
+
Minimum character length.
|
|
415
|
+
max_length : int, optional
|
|
416
|
+
Maximum character length.
|
|
417
|
+
must_contain : list[str], optional
|
|
418
|
+
Phrases that must appear.
|
|
419
|
+
|
|
420
|
+
Returns
|
|
421
|
+
-------
|
|
422
|
+
ValidationResult
|
|
423
|
+
Validation result.
|
|
424
|
+
|
|
425
|
+
Examples
|
|
426
|
+
--------
|
|
427
|
+
>>> result = validate_output(
|
|
428
|
+
... "Short",
|
|
429
|
+
... min_length=10,
|
|
430
|
+
... must_contain=["summary"]
|
|
431
|
+
... )
|
|
432
|
+
>>> result.valid
|
|
433
|
+
False
|
|
434
|
+
"""
|
|
435
|
+
validator = OutputValidator()
|
|
436
|
+
|
|
437
|
+
if schema:
|
|
438
|
+
validator.add_rule(JSONSchemaValidator(schema))
|
|
439
|
+
|
|
440
|
+
if min_length or max_length:
|
|
441
|
+
validator.add_rule(
|
|
442
|
+
LengthValidator(min_length=min_length, max_length=max_length)
|
|
443
|
+
)
|
|
444
|
+
|
|
445
|
+
if must_contain:
|
|
446
|
+
validator.add_rule(SemanticValidator(must_contain=must_contain))
|
|
447
|
+
|
|
448
|
+
return validator.validate(output)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""File and path utilities."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def check_filepath(
|
|
9
|
+
filepath: Path | None = None, *, fullfilepath: str | None = None
|
|
10
|
+
) -> Path:
|
|
11
|
+
"""Ensure the parent directory for a file path exists.
|
|
12
|
+
|
|
13
|
+
Creates parent directories as needed. Exactly one of ``filepath`` or
|
|
14
|
+
``fullfilepath`` must be provided.
|
|
15
|
+
|
|
16
|
+
Parameters
|
|
17
|
+
----------
|
|
18
|
+
filepath : Path or None, optional
|
|
19
|
+
Path object to validate. Mutually exclusive with ``fullfilepath``.
|
|
20
|
+
fullfilepath : str or None, optional
|
|
21
|
+
String path to validate. Mutually exclusive with ``filepath``.
|
|
22
|
+
|
|
23
|
+
Returns
|
|
24
|
+
-------
|
|
25
|
+
Path
|
|
26
|
+
Path object representing the validated file path.
|
|
27
|
+
"""
|
|
28
|
+
if filepath is None and fullfilepath is None:
|
|
29
|
+
raise ValueError("filepath or fullfilepath is required.")
|
|
30
|
+
if fullfilepath is not None:
|
|
31
|
+
target = Path(fullfilepath)
|
|
32
|
+
elif filepath is not None:
|
|
33
|
+
target = Path(filepath)
|
|
34
|
+
else:
|
|
35
|
+
raise ValueError("filepath or fullfilepath is required.")
|
|
36
|
+
ensure_directory(target.parent)
|
|
37
|
+
return target
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def ensure_directory(path: Path) -> Path:
|
|
41
|
+
"""Ensure a directory exists and return it."""
|
|
42
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
43
|
+
return path
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
__all__ = ["check_filepath", "ensure_directory"]
|
|
@@ -16,7 +16,7 @@ V = TypeVar("V")
|
|
|
16
16
|
U = TypeVar("U")
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
def validate_non_empty_string(value: str, field_name: str) -> str:
|
|
19
|
+
def validate_non_empty_string(value: str, *, field_name: str) -> str:
|
|
20
20
|
"""Validate that a string is non-empty.
|
|
21
21
|
|
|
22
22
|
Parameters
|
|
@@ -46,7 +46,7 @@ def validate_non_empty_string(value: str, field_name: str) -> str:
|
|
|
46
46
|
return stripped
|
|
47
47
|
|
|
48
48
|
|
|
49
|
-
def validate_max_length(value: str, max_len: int, field_name: str) -> str:
|
|
49
|
+
def validate_max_length(value: str, max_len: int, *, field_name: str) -> str:
|
|
50
50
|
"""Validate that a string doesn't exceed maximum length.
|
|
51
51
|
|
|
52
52
|
Parameters
|
|
@@ -76,7 +76,7 @@ def validate_max_length(value: str, max_len: int, field_name: str) -> str:
|
|
|
76
76
|
return value
|
|
77
77
|
|
|
78
78
|
|
|
79
|
-
def validate_url_format(url: str, field_name: str = "URL") -> str:
|
|
79
|
+
def validate_url_format(url: str, *, field_name: str = "URL") -> str:
|
|
80
80
|
"""Validate that a string is a valid URL.
|
|
81
81
|
|
|
82
82
|
Parameters
|
|
@@ -106,6 +106,7 @@ def validate_url_format(url: str, field_name: str = "URL") -> str:
|
|
|
106
106
|
def validate_dict_mapping(
|
|
107
107
|
mapping: Mapping[K, V],
|
|
108
108
|
expected_keys: set[K],
|
|
109
|
+
*,
|
|
109
110
|
field_name: str,
|
|
110
111
|
allow_extra: bool = False,
|
|
111
112
|
) -> dict[K, V]:
|
|
@@ -156,6 +157,7 @@ def validate_dict_mapping(
|
|
|
156
157
|
def validate_list_items(
|
|
157
158
|
items: list[U],
|
|
158
159
|
item_validator: Callable[[U], T],
|
|
160
|
+
*,
|
|
159
161
|
field_name: str,
|
|
160
162
|
allow_empty: bool = False,
|
|
161
163
|
) -> list[T]:
|
|
@@ -203,6 +205,7 @@ def validate_list_items(
|
|
|
203
205
|
def validate_choice(
|
|
204
206
|
value: U,
|
|
205
207
|
allowed_values: set[U],
|
|
208
|
+
*,
|
|
206
209
|
field_name: str,
|
|
207
210
|
) -> U:
|
|
208
211
|
"""Validate that a value is one of allowed choices.
|
|
@@ -235,6 +238,7 @@ def validate_choice(
|
|
|
235
238
|
|
|
236
239
|
def validate_safe_path(
|
|
237
240
|
path: Path | str,
|
|
241
|
+
*,
|
|
238
242
|
base_dir: Path | None = None,
|
|
239
243
|
field_name: str = "path",
|
|
240
244
|
) -> Path:
|