string-schema 0.1.5__py3-none-any.whl → 0.1.7__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.
- string_schema/__init__.py +1 -1
- string_schema/core/builders.py +2 -2
- string_schema/core/fields.py +12 -7
- string_schema/integrations/pydantic.py +1 -1
- string_schema/parsing/string_parser.py +143 -18
- string_schema/utilities.py +62 -8
- {string_schema-0.1.5.dist-info → string_schema-0.1.7.dist-info}/METADATA +8 -1
- {string_schema-0.1.5.dist-info → string_schema-0.1.7.dist-info}/RECORD +11 -11
- {string_schema-0.1.5.dist-info → string_schema-0.1.7.dist-info}/WHEEL +1 -1
- {string_schema-0.1.5.dist-info → string_schema-0.1.7.dist-info}/licenses/LICENSE +0 -0
- {string_schema-0.1.5.dist-info → string_schema-0.1.7.dist-info}/top_level.txt +0 -0
string_schema/__init__.py
CHANGED
string_schema/core/builders.py
CHANGED
|
@@ -67,10 +67,10 @@ def _simple_field_to_json_schema(field: SimpleField) -> Dict[str, Any]:
|
|
|
67
67
|
|
|
68
68
|
# Regular single type
|
|
69
69
|
prop = {"type": field.field_type}
|
|
70
|
-
|
|
70
|
+
|
|
71
71
|
if field.description:
|
|
72
72
|
prop["description"] = field.description
|
|
73
|
-
if field.
|
|
73
|
+
if field.has_default:
|
|
74
74
|
prop["default"] = field.default
|
|
75
75
|
|
|
76
76
|
# Handle enum/choices
|
string_schema/core/fields.py
CHANGED
|
@@ -10,23 +10,27 @@ import logging
|
|
|
10
10
|
logger = logging.getLogger(__name__)
|
|
11
11
|
|
|
12
12
|
|
|
13
|
+
# Sentinel value to distinguish "no default" from "default is None"
|
|
14
|
+
_NO_DEFAULT = object()
|
|
15
|
+
|
|
16
|
+
|
|
13
17
|
class SimpleField:
|
|
14
18
|
"""Enhanced SimpleField with support for all new features"""
|
|
15
|
-
|
|
19
|
+
|
|
16
20
|
def __init__(self, field_type: str, description: str = "", required: bool = True,
|
|
17
|
-
default: Any =
|
|
21
|
+
default: Any = _NO_DEFAULT, min_val: Optional[Union[int, float]] = None,
|
|
18
22
|
max_val: Optional[Union[int, float]] = None, min_length: Optional[int] = None,
|
|
19
23
|
max_length: Optional[int] = None, choices: Optional[List[Any]] = None,
|
|
20
24
|
min_items: Optional[int] = None, max_items: Optional[int] = None,
|
|
21
25
|
format_hint: Optional[str] = None, union_types: Optional[List[str]] = None):
|
|
22
26
|
"""
|
|
23
27
|
Initialize a SimpleField with comprehensive validation options.
|
|
24
|
-
|
|
28
|
+
|
|
25
29
|
Args:
|
|
26
30
|
field_type: The base type (string, integer, number, boolean)
|
|
27
31
|
description: Human-readable description of the field
|
|
28
32
|
required: Whether the field is required (default: True)
|
|
29
|
-
default: Default value if field is not provided
|
|
33
|
+
default: Default value if field is not provided (use _NO_DEFAULT sentinel for no default)
|
|
30
34
|
min_val: Minimum value for numeric types
|
|
31
35
|
max_val: Maximum value for numeric types
|
|
32
36
|
min_length: Minimum length for string types
|
|
@@ -41,6 +45,7 @@ class SimpleField:
|
|
|
41
45
|
self.description = description
|
|
42
46
|
self.required = required
|
|
43
47
|
self.default = default
|
|
48
|
+
self.has_default = default is not _NO_DEFAULT
|
|
44
49
|
self.min_val = min_val
|
|
45
50
|
self.max_val = max_val
|
|
46
51
|
self.min_length = min_length
|
|
@@ -70,10 +75,10 @@ class SimpleField:
|
|
|
70
75
|
'type': self.field_type,
|
|
71
76
|
'required': self.required
|
|
72
77
|
}
|
|
73
|
-
|
|
78
|
+
|
|
74
79
|
if self.description:
|
|
75
80
|
result['description'] = self.description
|
|
76
|
-
if self.
|
|
81
|
+
if self.has_default:
|
|
77
82
|
result['default'] = self.default
|
|
78
83
|
if self.min_val is not None:
|
|
79
84
|
result['min_val'] = self.min_val
|
|
@@ -93,7 +98,7 @@ class SimpleField:
|
|
|
93
98
|
result['format_hint'] = self.format_hint
|
|
94
99
|
if self.union_types:
|
|
95
100
|
result['union_types'] = self.union_types
|
|
96
|
-
|
|
101
|
+
|
|
97
102
|
return result
|
|
98
103
|
|
|
99
104
|
@classmethod
|
|
@@ -90,7 +90,7 @@ def _simple_field_to_pydantic(field: SimpleField) -> tuple:
|
|
|
90
90
|
if field.description:
|
|
91
91
|
field_kwargs['description'] = field.description
|
|
92
92
|
|
|
93
|
-
if field.
|
|
93
|
+
if field.has_default:
|
|
94
94
|
field_kwargs['default'] = field.default
|
|
95
95
|
elif not field.required:
|
|
96
96
|
field_kwargs['default'] = None
|
|
@@ -8,7 +8,7 @@ import re
|
|
|
8
8
|
from typing import Dict, Any, List, Union, Optional
|
|
9
9
|
import logging
|
|
10
10
|
|
|
11
|
-
from ..core.fields import SimpleField
|
|
11
|
+
from ..core.fields import SimpleField, _NO_DEFAULT
|
|
12
12
|
from ..core.builders import simple_schema, list_of_objects_schema
|
|
13
13
|
|
|
14
14
|
logger = logging.getLogger(__name__)
|
|
@@ -136,52 +136,110 @@ def _parse_object_fields(fields_str: str) -> Dict[str, Any]:
|
|
|
136
136
|
|
|
137
137
|
|
|
138
138
|
def _parse_single_field_with_nesting(field_str: str) -> tuple:
|
|
139
|
-
"""
|
|
139
|
+
"""
|
|
140
|
+
Parse a single field with enhanced syntax support.
|
|
141
|
+
|
|
142
|
+
New syntax support:
|
|
143
|
+
- Descriptions: field:type | description
|
|
144
|
+
- Defaults: field:type=default
|
|
145
|
+
- Combined: field:type(constraints)=default | description
|
|
146
|
+
|
|
147
|
+
Examples:
|
|
148
|
+
"name:string | User name"
|
|
149
|
+
"age:int=18"
|
|
150
|
+
"active:bool=true | Whether user is active"
|
|
151
|
+
"count:int(1,100)=1 | Item count"
|
|
152
|
+
"""
|
|
140
153
|
if not field_str:
|
|
141
154
|
return None, None
|
|
142
|
-
|
|
143
|
-
#
|
|
155
|
+
|
|
156
|
+
# Step 1: Extract description (after |)
|
|
157
|
+
# Note: Must be done BEFORE checking for union types (which also use |)
|
|
158
|
+
description = ""
|
|
159
|
+
# Only split on | if it's not part of a union type definition
|
|
160
|
+
# Union types are like "string|int|null" (no spaces, part of type definition)
|
|
161
|
+
# Descriptions are like "type | description" (with space before |)
|
|
162
|
+
# Look for " |" pattern to distinguish from union types
|
|
163
|
+
if ' |' in field_str:
|
|
164
|
+
# This is a description separator, not a union type
|
|
165
|
+
parts = field_str.split(' |', 1)
|
|
166
|
+
field_str = parts[0].strip()
|
|
167
|
+
if len(parts) > 1:
|
|
168
|
+
description = parts[1].strip()
|
|
169
|
+
|
|
170
|
+
# Step 2: Check for optional marker (?)
|
|
171
|
+
# Must be done BEFORE extracting default to handle "string?=null" correctly
|
|
144
172
|
required = True
|
|
145
|
-
if
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
173
|
+
if '?' in field_str:
|
|
174
|
+
# Check if ? is before = (e.g., "string?=null")
|
|
175
|
+
if '=' in field_str:
|
|
176
|
+
# Find position of ? and =
|
|
177
|
+
q_pos = field_str.find('?')
|
|
178
|
+
eq_pos = field_str.find('=')
|
|
179
|
+
if q_pos < eq_pos:
|
|
180
|
+
# ? comes before =, so it's an optional marker
|
|
181
|
+
field_str = field_str[:q_pos] + field_str[q_pos+1:]
|
|
182
|
+
required = False
|
|
183
|
+
elif field_str.endswith('?'):
|
|
184
|
+
# Simple optional marker at the end
|
|
185
|
+
required = False
|
|
186
|
+
field_str = field_str[:-1].strip()
|
|
187
|
+
|
|
188
|
+
# Step 3: Extract default value (after =)
|
|
189
|
+
# Must be done BEFORE parsing type definition to avoid conflicts with constraints
|
|
190
|
+
default_value = _NO_DEFAULT # Use sentinel to distinguish "no default" from "default is None"
|
|
191
|
+
if '=' in field_str:
|
|
192
|
+
# Check if = is after the type definition (not in constraints)
|
|
193
|
+
if ':' in field_str:
|
|
194
|
+
name_part, type_part = field_str.split(':', 1)
|
|
195
|
+
# Split type_part on = outside parentheses
|
|
196
|
+
parts = _split_on_equals_outside_parens(type_part)
|
|
197
|
+
if len(parts) == 2:
|
|
198
|
+
field_str = name_part + ':' + parts[0].strip()
|
|
199
|
+
default_str = parts[1].strip()
|
|
200
|
+
default_value = _parse_default_value(default_str)
|
|
201
|
+
|
|
202
|
+
# Step 4: Split field name and definition
|
|
150
203
|
if ':' in field_str:
|
|
151
204
|
field_name, field_def = field_str.split(':', 1)
|
|
152
205
|
field_name = field_name.strip()
|
|
153
206
|
field_def = field_def.strip()
|
|
154
|
-
|
|
207
|
+
|
|
155
208
|
# Handle nested structures
|
|
156
209
|
if field_def.startswith('[') or field_def.startswith('{'):
|
|
157
210
|
nested_structure = _parse_schema_structure(field_def)
|
|
158
211
|
nested_structure['required'] = required
|
|
159
212
|
return field_name, nested_structure
|
|
160
|
-
|
|
213
|
+
|
|
161
214
|
# Handle union types: string|int|null
|
|
215
|
+
# Union types have | without spaces around them
|
|
162
216
|
elif '|' in field_def:
|
|
163
217
|
union_types = [t.strip() for t in field_def.split('|')]
|
|
164
218
|
field_type = _normalize_type_name(union_types[0]) # Use first type as primary
|
|
165
|
-
|
|
219
|
+
|
|
166
220
|
# Create field with union support
|
|
167
221
|
field_obj = SimpleField(
|
|
168
222
|
field_type=field_type,
|
|
223
|
+
description=description,
|
|
224
|
+
default=default_value,
|
|
169
225
|
required=required
|
|
170
226
|
)
|
|
171
227
|
# Store union info for JSON schema generation
|
|
172
228
|
field_obj.union_types = [_normalize_type_name(t) for t in union_types]
|
|
173
229
|
return field_name, field_obj
|
|
174
|
-
|
|
230
|
+
|
|
175
231
|
# Handle enum types: enum(value1,value2,value3) or choice(...)
|
|
176
232
|
elif field_def.startswith(('enum(', 'choice(', 'select(')):
|
|
177
233
|
enum_values = _parse_enum_values(field_def)
|
|
178
234
|
field_obj = SimpleField(
|
|
179
235
|
field_type="string", # Enums are string-based
|
|
236
|
+
description=description,
|
|
237
|
+
default=default_value,
|
|
180
238
|
required=required,
|
|
181
239
|
choices=enum_values
|
|
182
240
|
)
|
|
183
241
|
return field_name, field_obj
|
|
184
|
-
|
|
242
|
+
|
|
185
243
|
# Handle array types: array(string,max=5) or list(int,min=1)
|
|
186
244
|
elif field_def.startswith(('array(', 'list(')):
|
|
187
245
|
array_type, constraints = _parse_array_type_definition(field_def)
|
|
@@ -195,7 +253,7 @@ def _parse_single_field_with_nesting(field_str: str) -> tuple:
|
|
|
195
253
|
"constraints": constraints,
|
|
196
254
|
"required": required
|
|
197
255
|
}
|
|
198
|
-
|
|
256
|
+
|
|
199
257
|
# Handle regular type with constraints
|
|
200
258
|
else:
|
|
201
259
|
original_type = field_def.split('(')[0].strip() # Get type before any constraints
|
|
@@ -208,16 +266,20 @@ def _parse_single_field_with_nesting(field_str: str) -> tuple:
|
|
|
208
266
|
|
|
209
267
|
field_obj = SimpleField(
|
|
210
268
|
field_type=field_type,
|
|
269
|
+
description=description,
|
|
270
|
+
default=default_value,
|
|
211
271
|
required=required,
|
|
212
272
|
**constraints
|
|
213
273
|
)
|
|
214
274
|
return field_name, field_obj
|
|
215
|
-
|
|
275
|
+
|
|
216
276
|
# Field name only (default to string)
|
|
217
277
|
else:
|
|
218
278
|
field_name = field_str.strip()
|
|
219
279
|
field_obj = SimpleField(
|
|
220
280
|
field_type="string",
|
|
281
|
+
description=description,
|
|
282
|
+
default=default_value,
|
|
221
283
|
required=required
|
|
222
284
|
)
|
|
223
285
|
return field_name, field_obj
|
|
@@ -229,12 +291,75 @@ def _parse_enum_values(enum_def: str) -> List[str]:
|
|
|
229
291
|
match = re.match(r'^(?:enum|choice|select)\(([^)]+)\)$', enum_def)
|
|
230
292
|
if not match:
|
|
231
293
|
return []
|
|
232
|
-
|
|
294
|
+
|
|
233
295
|
values_str = match.group(1)
|
|
234
296
|
values = [v.strip() for v in values_str.split(',')]
|
|
235
297
|
return values
|
|
236
298
|
|
|
237
299
|
|
|
300
|
+
def _split_on_equals_outside_parens(s: str) -> List[str]:
|
|
301
|
+
"""
|
|
302
|
+
Split string on FIRST = outside parentheses only.
|
|
303
|
+
|
|
304
|
+
Example:
|
|
305
|
+
"int(1,100)=5" -> ["int(1,100)", "5"]
|
|
306
|
+
"string=hello" -> ["string", "hello"]
|
|
307
|
+
"string=a=b" -> ["string", "a=b"] # Only split on first =
|
|
308
|
+
"""
|
|
309
|
+
depth = 0
|
|
310
|
+
|
|
311
|
+
for i, char in enumerate(s):
|
|
312
|
+
if char == '(':
|
|
313
|
+
depth += 1
|
|
314
|
+
elif char == ')':
|
|
315
|
+
depth -= 1
|
|
316
|
+
elif char == '=' and depth == 0:
|
|
317
|
+
# Found first = outside parentheses - split here
|
|
318
|
+
return [s[:i], s[i+1:]]
|
|
319
|
+
|
|
320
|
+
# No = found outside parentheses
|
|
321
|
+
return [s]
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def _parse_default_value(default_str: str) -> Any:
|
|
325
|
+
"""
|
|
326
|
+
Parse default value from string.
|
|
327
|
+
|
|
328
|
+
Supports:
|
|
329
|
+
- Booleans: true, false
|
|
330
|
+
- Null: null, none
|
|
331
|
+
- Numbers: 123, 45.67
|
|
332
|
+
- Strings: "hello", 'world', or unquoted
|
|
333
|
+
"""
|
|
334
|
+
default_str = default_str.strip()
|
|
335
|
+
|
|
336
|
+
# Boolean
|
|
337
|
+
if default_str.lower() in ['true', 'false']:
|
|
338
|
+
return default_str.lower() == 'true'
|
|
339
|
+
|
|
340
|
+
# Null/None
|
|
341
|
+
if default_str.lower() in ['null', 'none']:
|
|
342
|
+
return None
|
|
343
|
+
|
|
344
|
+
# Number
|
|
345
|
+
try:
|
|
346
|
+
if '.' in default_str:
|
|
347
|
+
return float(default_str)
|
|
348
|
+
else:
|
|
349
|
+
return int(default_str)
|
|
350
|
+
except ValueError:
|
|
351
|
+
pass
|
|
352
|
+
|
|
353
|
+
# String (remove quotes if present)
|
|
354
|
+
if default_str.startswith('"') and default_str.endswith('"'):
|
|
355
|
+
return default_str[1:-1]
|
|
356
|
+
if default_str.startswith("'") and default_str.endswith("'"):
|
|
357
|
+
return default_str[1:-1]
|
|
358
|
+
|
|
359
|
+
# Return as-is (string)
|
|
360
|
+
return default_str
|
|
361
|
+
|
|
362
|
+
|
|
238
363
|
def _parse_array_type_definition(array_def: str) -> tuple:
|
|
239
364
|
"""Parse array(type,constraints) or list(type,constraints)"""
|
|
240
365
|
# Extract content between parentheses
|
|
@@ -363,7 +488,7 @@ def _simple_field_to_json_schema(field: SimpleField) -> Dict[str, Any]:
|
|
|
363
488
|
# Basic metadata
|
|
364
489
|
if field.description:
|
|
365
490
|
prop["description"] = field.description
|
|
366
|
-
if field.
|
|
491
|
+
if field.has_default:
|
|
367
492
|
prop["default"] = field.default
|
|
368
493
|
|
|
369
494
|
# Handle union types
|
string_schema/utilities.py
CHANGED
|
@@ -15,6 +15,7 @@ Key Functions:
|
|
|
15
15
|
|
|
16
16
|
import functools
|
|
17
17
|
import uuid
|
|
18
|
+
from datetime import datetime, timezone
|
|
18
19
|
from typing import Any, Dict, Type, Union, Callable, Optional, List
|
|
19
20
|
import logging
|
|
20
21
|
|
|
@@ -30,6 +31,48 @@ except ImportError:
|
|
|
30
31
|
logger = logging.getLogger(__name__)
|
|
31
32
|
|
|
32
33
|
|
|
34
|
+
def _ensure_timezone_aware_dict(data: Dict[str, Any]) -> Dict[str, Any]:
|
|
35
|
+
"""
|
|
36
|
+
Ensure all datetime values in a dictionary have timezone information.
|
|
37
|
+
|
|
38
|
+
This function recursively processes dictionaries and lists to add UTC timezone
|
|
39
|
+
information to naive datetime objects, ensuring consistent API responses.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
data: Dictionary that may contain datetime values
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
Dictionary with timezone-aware datetime values converted to ISO format
|
|
46
|
+
"""
|
|
47
|
+
if not isinstance(data, dict):
|
|
48
|
+
return data
|
|
49
|
+
|
|
50
|
+
result = {}
|
|
51
|
+
for key, value in data.items():
|
|
52
|
+
if isinstance(value, datetime):
|
|
53
|
+
# Add UTC timezone to naive datetime objects
|
|
54
|
+
if value.tzinfo is None:
|
|
55
|
+
value = value.replace(tzinfo=timezone.utc)
|
|
56
|
+
# Convert to ISO format string for consistent API responses
|
|
57
|
+
result[key] = value.isoformat()
|
|
58
|
+
elif isinstance(value, dict):
|
|
59
|
+
# Recursively process nested dictionaries
|
|
60
|
+
result[key] = _ensure_timezone_aware_dict(value)
|
|
61
|
+
elif isinstance(value, list):
|
|
62
|
+
# Process lists that may contain dictionaries or datetime objects
|
|
63
|
+
result[key] = [
|
|
64
|
+
_ensure_timezone_aware_dict(item) if isinstance(item, dict)
|
|
65
|
+
else item.isoformat() if isinstance(item, datetime) and item.tzinfo is None
|
|
66
|
+
else item.replace(tzinfo=timezone.utc).isoformat() if isinstance(item, datetime)
|
|
67
|
+
else item
|
|
68
|
+
for item in value
|
|
69
|
+
]
|
|
70
|
+
else:
|
|
71
|
+
result[key] = value
|
|
72
|
+
|
|
73
|
+
return result
|
|
74
|
+
|
|
75
|
+
|
|
33
76
|
def string_to_model(schema_str: str, name: Optional[str] = None) -> Type[BaseModel]:
|
|
34
77
|
"""
|
|
35
78
|
Create Pydantic model from string schema.
|
|
@@ -218,13 +261,21 @@ def validate_to_dict(data: Union[Dict[str, Any], Any], schema_str: str) -> Union
|
|
|
218
261
|
try:
|
|
219
262
|
# Try Pydantic v2 RootModel style
|
|
220
263
|
validated_instance = TempModel(data)
|
|
221
|
-
# Return the validated array data
|
|
222
|
-
|
|
264
|
+
# Return the validated array data with timezone-aware conversion
|
|
265
|
+
result_data = validated_instance.model_dump() if hasattr(validated_instance, 'model_dump') else validated_instance.dict()
|
|
266
|
+
# Process array items for timezone-aware datetime conversion
|
|
267
|
+
if isinstance(result_data, list):
|
|
268
|
+
return [_ensure_timezone_aware_dict(item) if isinstance(item, dict) else item for item in result_data]
|
|
269
|
+
return result_data
|
|
223
270
|
except:
|
|
224
271
|
# Fallback to Pydantic v1 style
|
|
225
272
|
validated_instance = TempModel(__root__=data)
|
|
226
|
-
# Return the validated array data
|
|
227
|
-
|
|
273
|
+
# Return the validated array data with timezone-aware conversion
|
|
274
|
+
result_data = validated_instance.model_dump()['__root__'] if hasattr(validated_instance, 'model_dump') else validated_instance.dict()['__root__']
|
|
275
|
+
# Process array items for timezone-aware datetime conversion
|
|
276
|
+
if isinstance(result_data, list):
|
|
277
|
+
return [_ensure_timezone_aware_dict(item) if isinstance(item, dict) else item for item in result_data]
|
|
278
|
+
return result_data
|
|
228
279
|
else:
|
|
229
280
|
# Handle different input types for object schemas
|
|
230
281
|
if isinstance(data, dict):
|
|
@@ -236,11 +287,14 @@ def validate_to_dict(data: Union[Dict[str, Any], Any], schema_str: str) -> Union
|
|
|
236
287
|
# Try direct validation
|
|
237
288
|
validated_instance = TempModel(data)
|
|
238
289
|
|
|
239
|
-
# Return as dictionary
|
|
290
|
+
# Return as dictionary with timezone-aware datetime handling
|
|
240
291
|
if hasattr(validated_instance, 'model_dump'):
|
|
241
|
-
|
|
292
|
+
result_dict = validated_instance.model_dump()
|
|
242
293
|
else:
|
|
243
|
-
|
|
294
|
+
result_dict = validated_instance.dict()
|
|
295
|
+
|
|
296
|
+
# Ensure timezone-aware datetime conversion for consistent API responses
|
|
297
|
+
return _ensure_timezone_aware_dict(result_dict)
|
|
244
298
|
|
|
245
299
|
except ValidationError as e:
|
|
246
300
|
# Re-raise the original validation error
|
|
@@ -403,7 +457,7 @@ def get_model_info(model_class) -> Dict[str, Any]:
|
|
|
403
457
|
Returns:
|
|
404
458
|
Dictionary with model information including fields, types, and constraints
|
|
405
459
|
"""
|
|
406
|
-
if not HAS_PYDANTIC or not issubclass(model_class, BaseModel):
|
|
460
|
+
if not HAS_PYDANTIC or not isinstance(model_class, type) or not issubclass(model_class, BaseModel):
|
|
407
461
|
raise ValueError("Input must be a Pydantic model class")
|
|
408
462
|
|
|
409
463
|
info = {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: string-schema
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.7
|
|
4
4
|
Summary: A simple, LLM-friendly schema definition library for converting string syntax to structured schemas
|
|
5
5
|
Home-page: https://github.com/xychenmsn/string-schema
|
|
6
6
|
Author: Michael Chen
|
|
@@ -151,6 +151,7 @@ String Schema takes human-readable text descriptions and converts them into stru
|
|
|
151
151
|
- **🤖 LLM Data Extraction**: Define extraction schemas that LLMs can easily follow
|
|
152
152
|
- **🔧 API Development**: Generate Pydantic models and OpenAPI docs from simple syntax
|
|
153
153
|
- **✅ Data Validation**: Create robust validation schemas with minimal code
|
|
154
|
+
- **🌍 Timezone-Aware APIs**: Automatic UTC conversion for consistent datetime handling
|
|
154
155
|
- **📋 Configuration**: Define and validate application configuration schemas
|
|
155
156
|
- **🔄 Data Transformation**: Convert between different schema formats
|
|
156
157
|
|
|
@@ -175,6 +176,12 @@ print(user_dict) # {"name": "John Doe", "email": "john@example.com", "age": 25}
|
|
|
175
176
|
user_model = validate_to_model(raw_data, "name:string, email:email, age:int?")
|
|
176
177
|
print(user_model.name) # "John Doe" - Full type safety
|
|
177
178
|
print(user_model.age) # 25 - Converted to int
|
|
179
|
+
|
|
180
|
+
# 🌍 Automatic timezone handling for datetime fields
|
|
181
|
+
from datetime import datetime
|
|
182
|
+
event_data = {"name": "Meeting", "start_time": datetime(2025, 8, 13, 14, 30)}
|
|
183
|
+
event_dict = validate_to_dict(event_data, "name:string, start_time:datetime")
|
|
184
|
+
print(event_dict) # {"name": "Meeting", "start_time": "2025-08-13T14:30:00+00:00"}
|
|
178
185
|
```
|
|
179
186
|
|
|
180
187
|
### 🎨 Function Decorators
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
string_schema/__init__.py,sha256=
|
|
1
|
+
string_schema/__init__.py,sha256=Vosc4ZtNqbdr06YwSbAYt16tfJxliONVEx5ekLXx5vM,4052
|
|
2
2
|
string_schema/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
3
|
-
string_schema/utilities.py,sha256=
|
|
3
|
+
string_schema/utilities.py,sha256=57fzXusitdYL2AFVitY0_3d8JSlnNGC5GEqw7tzd_YI,22388
|
|
4
4
|
string_schema/core/__init__.py,sha256=jeukkZ1WoCSXrBafumHVH2GkCJ6QjnkH_jAr3DESFio,481
|
|
5
|
-
string_schema/core/builders.py,sha256=
|
|
6
|
-
string_schema/core/fields.py,sha256=
|
|
5
|
+
string_schema/core/builders.py,sha256=DLloMAxeBSUSS_h7cqQ6RJPF4wnl4MyzdmJgMfj2M-M,7797
|
|
6
|
+
string_schema/core/fields.py,sha256=sqIG7zw1gm85zPnAgotgjsl9vXdki72JTgewrbV0asw,5690
|
|
7
7
|
string_schema/core/validators.py,sha256=6inSZGou0zKpN47ttVWOxWsN-nPN3EewUNRacB9UUkA,8303
|
|
8
8
|
string_schema/examples/__init__.py,sha256=wmdWey3ggt3yXG2XQygfHeghq-P31VdTkoqyki_qY1k,639
|
|
9
9
|
string_schema/examples/presets.py,sha256=T7OQOsvVrPrzS2_1C6Hls17neeIGOBfVs0xOkZehaSQ,14642
|
|
@@ -11,14 +11,14 @@ string_schema/examples/recipes.py,sha256=bOX0zH-m5NVYFnfBIGzxag1BpNexe6OPJ55ssn0
|
|
|
11
11
|
string_schema/integrations/__init__.py,sha256=yLRtfeVEDntULjMXKv_TWKXh860lKnQzCMMYcZvOr90,323
|
|
12
12
|
string_schema/integrations/json_schema.py,sha256=cNr9chGc5RSfFLNXJCTH1ZOVd91OH9oeaAvf_Nqy8Zs,12627
|
|
13
13
|
string_schema/integrations/openapi.py,sha256=bUHzJWOZEDtXs0R6493jAhA0-PrKwaUTkDW5UWE6-nI,15637
|
|
14
|
-
string_schema/integrations/pydantic.py,sha256=
|
|
14
|
+
string_schema/integrations/pydantic.py,sha256=m8cap2d8VHILKGnpqhh26IX1PDUFz2WPqBi1S1pUVq8,21810
|
|
15
15
|
string_schema/integrations/reverse.py,sha256=qN8OCRjaH0nerZFrljAnvFLjZjo9gln2H-HBiaevLoI,9023
|
|
16
16
|
string_schema/parsing/__init__.py,sha256=dFW68DqJIwP4w_aYaZMjjVK15X7tCv8A0SPqmIgGEjQ,411
|
|
17
17
|
string_schema/parsing/optimizer.py,sha256=Ofte8Tb7sKmdEv7RrVIEBQ4Qqr-Whanp1vh8BakcOgw,8479
|
|
18
|
-
string_schema/parsing/string_parser.py,sha256
|
|
18
|
+
string_schema/parsing/string_parser.py,sha256=xsHMYdMEwGirQn0GJWsOy26LqVTangzMxz8BrQ5meEw,29230
|
|
19
19
|
string_schema/parsing/syntax.py,sha256=RO3BIAnWfDBupowOnoJocHtAe-kwE-SgRWXKknVwGdg,8900
|
|
20
|
-
string_schema-0.1.
|
|
21
|
-
string_schema-0.1.
|
|
22
|
-
string_schema-0.1.
|
|
23
|
-
string_schema-0.1.
|
|
24
|
-
string_schema-0.1.
|
|
20
|
+
string_schema-0.1.7.dist-info/licenses/LICENSE,sha256=wCD2KBeSlVSkJy0apl6zmVj04uw97UJhRRi-en_3Qfk,1065
|
|
21
|
+
string_schema-0.1.7.dist-info/METADATA,sha256=NSS3KDt63-2VzhB6i_EYG8cN7N76ckOxL3GIReystGk,18662
|
|
22
|
+
string_schema-0.1.7.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
23
|
+
string_schema-0.1.7.dist-info/top_level.txt,sha256=1uTmLPYIRrCDVxUW1fDFRMaWvGO48NRcGmbW4oq89I0,14
|
|
24
|
+
string_schema-0.1.7.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|