string-schema 0.1.6__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 +1 -1
- {string_schema-0.1.6.dist-info → string_schema-0.1.7.dist-info}/METADATA +1 -1
- {string_schema-0.1.6.dist-info → string_schema-0.1.7.dist-info}/RECORD +11 -11
- {string_schema-0.1.6.dist-info → string_schema-0.1.7.dist-info}/WHEEL +1 -1
- {string_schema-0.1.6.dist-info → string_schema-0.1.7.dist-info}/licenses/LICENSE +0 -0
- {string_schema-0.1.6.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
|
@@ -457,7 +457,7 @@ def get_model_info(model_class) -> Dict[str, Any]:
|
|
|
457
457
|
Returns:
|
|
458
458
|
Dictionary with model information including fields, types, and constraints
|
|
459
459
|
"""
|
|
460
|
-
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):
|
|
461
461
|
raise ValueError("Input must be a Pydantic model class")
|
|
462
462
|
|
|
463
463
|
info = {
|
|
@@ -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
|