string-schema 0.1.2__py3-none-any.whl → 0.1.4__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/core/__init__.py +23 -0
- string_schema/core/builders.py +244 -0
- string_schema/core/fields.py +138 -0
- string_schema/core/validators.py +242 -0
- string_schema/examples/__init__.py +36 -0
- string_schema/examples/presets.py +345 -0
- string_schema/examples/recipes.py +380 -0
- string_schema/integrations/__init__.py +15 -0
- string_schema/integrations/json_schema.py +385 -0
- string_schema/integrations/openapi.py +484 -0
- string_schema/integrations/pydantic.py +662 -0
- string_schema/integrations/reverse.py +275 -0
- string_schema/parsing/__init__.py +16 -0
- string_schema/parsing/optimizer.py +246 -0
- string_schema/parsing/string_parser.py +703 -0
- string_schema/parsing/syntax.py +250 -0
- string_schema/utilities.py +3 -3
- {string_schema-0.1.2.dist-info → string_schema-0.1.4.dist-info}/METADATA +1 -1
- string_schema-0.1.4.dist-info/RECORD +24 -0
- string_schema-0.1.2.dist-info/RECORD +0 -8
- {string_schema-0.1.2.dist-info → string_schema-0.1.4.dist-info}/WHEEL +0 -0
- {string_schema-0.1.2.dist-info → string_schema-0.1.4.dist-info}/licenses/LICENSE +0 -0
- {string_schema-0.1.2.dist-info → string_schema-0.1.4.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
"""
|
|
2
|
+
JSON Schema integration for Simple Schema
|
|
3
|
+
|
|
4
|
+
Contains functions for converting Simple Schema definitions to standard JSON Schema.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any, Dict, List, Union
|
|
8
|
+
import logging
|
|
9
|
+
|
|
10
|
+
from ..core.fields import SimpleField
|
|
11
|
+
from ..core.builders import simple_schema, _simple_field_to_json_schema
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def to_json_schema(fields: Dict[str, Union[str, SimpleField]],
|
|
17
|
+
title: str = "Generated Schema",
|
|
18
|
+
description: str = "",
|
|
19
|
+
schema_version: str = "https://json-schema.org/draft/2020-12/schema") -> Dict[str, Any]:
|
|
20
|
+
"""
|
|
21
|
+
Convert Simple Schema fields to standard JSON Schema.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
fields: Dictionary of field definitions
|
|
25
|
+
title: Schema title
|
|
26
|
+
description: Schema description
|
|
27
|
+
schema_version: JSON Schema version URI
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
Standard JSON Schema dictionary
|
|
31
|
+
"""
|
|
32
|
+
# Generate the basic schema
|
|
33
|
+
schema = simple_schema(fields)
|
|
34
|
+
|
|
35
|
+
# Add JSON Schema metadata
|
|
36
|
+
schema["$schema"] = schema_version
|
|
37
|
+
schema["title"] = title
|
|
38
|
+
|
|
39
|
+
if description:
|
|
40
|
+
schema["description"] = description
|
|
41
|
+
|
|
42
|
+
# Add additional properties control
|
|
43
|
+
schema["additionalProperties"] = False
|
|
44
|
+
|
|
45
|
+
return schema
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def to_json_schema_with_examples(fields: Dict[str, Union[str, SimpleField]],
|
|
49
|
+
examples: List[Dict[str, Any]] = None,
|
|
50
|
+
title: str = "Generated Schema",
|
|
51
|
+
description: str = "") -> Dict[str, Any]:
|
|
52
|
+
"""
|
|
53
|
+
Convert Simple Schema fields to JSON Schema with examples.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
fields: Dictionary of field definitions
|
|
57
|
+
examples: List of example data objects
|
|
58
|
+
title: Schema title
|
|
59
|
+
description: Schema description
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
JSON Schema with examples
|
|
63
|
+
"""
|
|
64
|
+
schema = to_json_schema(fields, title, description)
|
|
65
|
+
|
|
66
|
+
if examples:
|
|
67
|
+
schema["examples"] = examples
|
|
68
|
+
|
|
69
|
+
return schema
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def validate_json_schema_compliance(schema: Dict[str, Any]) -> Dict[str, Any]:
|
|
73
|
+
"""
|
|
74
|
+
Validate that a schema complies with JSON Schema standards.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
schema: Schema dictionary to validate
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
Validation result dictionary
|
|
81
|
+
"""
|
|
82
|
+
result = {
|
|
83
|
+
'valid': True,
|
|
84
|
+
'errors': [],
|
|
85
|
+
'warnings': [],
|
|
86
|
+
'compliance_level': 'full'
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
# Check required top-level fields
|
|
90
|
+
if 'type' not in schema:
|
|
91
|
+
result['errors'].append("Missing required 'type' field")
|
|
92
|
+
|
|
93
|
+
# Check schema version
|
|
94
|
+
if '$schema' not in schema:
|
|
95
|
+
result['warnings'].append("Missing '$schema' field - recommended for JSON Schema")
|
|
96
|
+
|
|
97
|
+
# Validate object schema
|
|
98
|
+
if schema.get('type') == 'object':
|
|
99
|
+
result.update(_validate_object_schema_compliance(schema))
|
|
100
|
+
elif schema.get('type') == 'array':
|
|
101
|
+
result.update(_validate_array_schema_compliance(schema))
|
|
102
|
+
|
|
103
|
+
# Check for non-standard extensions
|
|
104
|
+
non_standard_fields = []
|
|
105
|
+
for key in schema.keys():
|
|
106
|
+
if key.startswith('x-') or key not in _get_standard_json_schema_fields():
|
|
107
|
+
non_standard_fields.append(key)
|
|
108
|
+
|
|
109
|
+
if non_standard_fields:
|
|
110
|
+
result['warnings'].append(f"Non-standard fields detected: {', '.join(non_standard_fields)}")
|
|
111
|
+
result['compliance_level'] = 'partial'
|
|
112
|
+
|
|
113
|
+
result['valid'] = len(result['errors']) == 0
|
|
114
|
+
return result
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _validate_object_schema_compliance(schema: Dict[str, Any]) -> Dict[str, Any]:
|
|
118
|
+
"""Validate object schema compliance"""
|
|
119
|
+
result = {'errors': [], 'warnings': []}
|
|
120
|
+
|
|
121
|
+
properties = schema.get('properties', {})
|
|
122
|
+
required = schema.get('required', [])
|
|
123
|
+
|
|
124
|
+
# Validate properties
|
|
125
|
+
for field_name, field_schema in properties.items():
|
|
126
|
+
if not isinstance(field_schema, dict):
|
|
127
|
+
result['errors'].append(f"Property '{field_name}' must be an object")
|
|
128
|
+
continue
|
|
129
|
+
|
|
130
|
+
if 'type' not in field_schema and 'anyOf' not in field_schema and '$ref' not in field_schema:
|
|
131
|
+
result['warnings'].append(f"Property '{field_name}' missing type information")
|
|
132
|
+
|
|
133
|
+
# Validate required fields
|
|
134
|
+
for req_field in required:
|
|
135
|
+
if req_field not in properties:
|
|
136
|
+
result['errors'].append(f"Required field '{req_field}' not found in properties")
|
|
137
|
+
|
|
138
|
+
return result
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _validate_array_schema_compliance(schema: Dict[str, Any]) -> Dict[str, Any]:
|
|
142
|
+
"""Validate array schema compliance"""
|
|
143
|
+
result = {'errors': [], 'warnings': []}
|
|
144
|
+
|
|
145
|
+
if 'items' not in schema:
|
|
146
|
+
result['errors'].append("Array schema must have 'items' definition")
|
|
147
|
+
|
|
148
|
+
items = schema.get('items', {})
|
|
149
|
+
if items and not isinstance(items, dict):
|
|
150
|
+
result['errors'].append("Array 'items' must be an object")
|
|
151
|
+
|
|
152
|
+
return result
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _get_standard_json_schema_fields() -> set:
|
|
156
|
+
"""Get set of standard JSON Schema fields"""
|
|
157
|
+
return {
|
|
158
|
+
'$schema', '$id', '$ref', '$defs', 'title', 'description', 'type', 'properties',
|
|
159
|
+
'required', 'additionalProperties', 'items', 'minItems', 'maxItems', 'uniqueItems',
|
|
160
|
+
'minimum', 'maximum', 'exclusiveMinimum', 'exclusiveMaximum', 'multipleOf',
|
|
161
|
+
'minLength', 'maxLength', 'pattern', 'format', 'enum', 'const', 'anyOf', 'oneOf',
|
|
162
|
+
'allOf', 'not', 'if', 'then', 'else', 'examples', 'default', 'readOnly', 'writeOnly'
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def optimize_json_schema(schema: Dict[str, Any]) -> Dict[str, Any]:
|
|
167
|
+
"""
|
|
168
|
+
Optimize JSON Schema for better performance and readability.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
schema: JSON Schema to optimize
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
Optimized JSON Schema
|
|
175
|
+
"""
|
|
176
|
+
optimized = schema.copy()
|
|
177
|
+
|
|
178
|
+
# Remove empty arrays and objects
|
|
179
|
+
optimized = _remove_empty_values(optimized)
|
|
180
|
+
|
|
181
|
+
# Consolidate similar constraints
|
|
182
|
+
optimized = _consolidate_constraints(optimized)
|
|
183
|
+
|
|
184
|
+
# Order fields for better readability
|
|
185
|
+
optimized = _order_schema_fields(optimized)
|
|
186
|
+
|
|
187
|
+
return optimized
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _remove_empty_values(obj: Any) -> Any:
|
|
191
|
+
"""Remove empty arrays and objects from schema"""
|
|
192
|
+
if isinstance(obj, dict):
|
|
193
|
+
result = {}
|
|
194
|
+
for key, value in obj.items():
|
|
195
|
+
cleaned_value = _remove_empty_values(value)
|
|
196
|
+
if cleaned_value is not None and cleaned_value != [] and cleaned_value != {}:
|
|
197
|
+
result[key] = cleaned_value
|
|
198
|
+
return result
|
|
199
|
+
elif isinstance(obj, list):
|
|
200
|
+
return [_remove_empty_values(item) for item in obj if item is not None]
|
|
201
|
+
else:
|
|
202
|
+
return obj
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _consolidate_constraints(schema: Dict[str, Any]) -> Dict[str, Any]:
|
|
206
|
+
"""Consolidate similar constraints in schema"""
|
|
207
|
+
# This is a placeholder for more sophisticated optimization
|
|
208
|
+
return schema
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def _order_schema_fields(schema: Dict[str, Any]) -> Dict[str, Any]:
|
|
212
|
+
"""Order schema fields for better readability"""
|
|
213
|
+
if not isinstance(schema, dict):
|
|
214
|
+
return schema
|
|
215
|
+
|
|
216
|
+
# Define preferred field order
|
|
217
|
+
field_order = [
|
|
218
|
+
'$schema', '$id', 'title', 'description', 'type', 'properties', 'required',
|
|
219
|
+
'items', 'minItems', 'maxItems', 'minimum', 'maximum', 'minLength', 'maxLength',
|
|
220
|
+
'format', 'enum', 'anyOf', 'oneOf', 'allOf', 'examples', 'default',
|
|
221
|
+
'additionalProperties'
|
|
222
|
+
]
|
|
223
|
+
|
|
224
|
+
ordered = {}
|
|
225
|
+
|
|
226
|
+
# Add fields in preferred order
|
|
227
|
+
for field in field_order:
|
|
228
|
+
if field in schema:
|
|
229
|
+
value = schema[field]
|
|
230
|
+
if isinstance(value, dict):
|
|
231
|
+
ordered[field] = _order_schema_fields(value)
|
|
232
|
+
elif isinstance(value, list):
|
|
233
|
+
ordered[field] = [_order_schema_fields(item) if isinstance(item, dict) else item for item in value]
|
|
234
|
+
else:
|
|
235
|
+
ordered[field] = value
|
|
236
|
+
|
|
237
|
+
# Add any remaining fields
|
|
238
|
+
for field, value in schema.items():
|
|
239
|
+
if field not in ordered:
|
|
240
|
+
if isinstance(value, dict):
|
|
241
|
+
ordered[field] = _order_schema_fields(value)
|
|
242
|
+
elif isinstance(value, list):
|
|
243
|
+
ordered[field] = [_order_schema_fields(item) if isinstance(item, dict) else item for item in value]
|
|
244
|
+
else:
|
|
245
|
+
ordered[field] = value
|
|
246
|
+
|
|
247
|
+
return ordered
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
# Clear function name alias
|
|
251
|
+
def json_schema_to_openapi(json_schema: Dict[str, Any]) -> Dict[str, Any]:
|
|
252
|
+
"""
|
|
253
|
+
Convert JSON Schema to OpenAPI 3.0 schema format.
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
json_schema: Standard JSON Schema dictionary
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
OpenAPI compatible schema dictionary
|
|
260
|
+
|
|
261
|
+
Example:
|
|
262
|
+
openapi_schema = json_schema_to_openapi(json_schema)
|
|
263
|
+
"""
|
|
264
|
+
return convert_to_openapi_schema(json_schema)
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
# Reverse conversion functions
|
|
268
|
+
def json_schema_to_string(json_schema: Dict[str, Any]) -> str:
|
|
269
|
+
"""
|
|
270
|
+
Convert JSON Schema to Simple Schema string syntax.
|
|
271
|
+
|
|
272
|
+
Args:
|
|
273
|
+
json_schema: JSON Schema dictionary
|
|
274
|
+
|
|
275
|
+
Returns:
|
|
276
|
+
String representation in Simple Schema syntax
|
|
277
|
+
|
|
278
|
+
Example:
|
|
279
|
+
json_schema = {"type": "object", "properties": {"name": {"type": "string"}}}
|
|
280
|
+
schema_str = json_schema_to_string(json_schema)
|
|
281
|
+
# Returns: "name:string"
|
|
282
|
+
"""
|
|
283
|
+
from .reverse import json_schema_to_string as _json_schema_to_string
|
|
284
|
+
return _json_schema_to_string(json_schema)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def convert_to_openapi_schema(json_schema: Dict[str, Any]) -> Dict[str, Any]:
|
|
288
|
+
"""
|
|
289
|
+
Convert JSON Schema to OpenAPI 3.0 schema format.
|
|
290
|
+
|
|
291
|
+
Args:
|
|
292
|
+
json_schema: Standard JSON Schema
|
|
293
|
+
|
|
294
|
+
Returns:
|
|
295
|
+
OpenAPI compatible schema
|
|
296
|
+
"""
|
|
297
|
+
openapi_schema = json_schema.copy()
|
|
298
|
+
|
|
299
|
+
# Remove JSON Schema specific fields that aren't supported in OpenAPI
|
|
300
|
+
fields_to_remove = ['$schema', '$id']
|
|
301
|
+
for field in fields_to_remove:
|
|
302
|
+
openapi_schema.pop(field, None)
|
|
303
|
+
|
|
304
|
+
# Convert format fields if needed
|
|
305
|
+
if 'properties' in openapi_schema:
|
|
306
|
+
for prop_name, prop_schema in openapi_schema['properties'].items():
|
|
307
|
+
if isinstance(prop_schema, dict):
|
|
308
|
+
openapi_schema['properties'][prop_name] = _convert_property_to_openapi(prop_schema)
|
|
309
|
+
|
|
310
|
+
return openapi_schema
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def _convert_property_to_openapi(prop_schema: Dict[str, Any]) -> Dict[str, Any]:
|
|
314
|
+
"""Convert individual property to OpenAPI format"""
|
|
315
|
+
openapi_prop = prop_schema.copy()
|
|
316
|
+
|
|
317
|
+
# OpenAPI 3.0 doesn't support some JSON Schema features
|
|
318
|
+
# This is a basic conversion - could be enhanced
|
|
319
|
+
|
|
320
|
+
return openapi_prop
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def generate_schema_documentation(schema: Dict[str, Any]) -> str:
|
|
324
|
+
"""
|
|
325
|
+
Generate human-readable documentation from JSON Schema.
|
|
326
|
+
|
|
327
|
+
Args:
|
|
328
|
+
schema: JSON Schema to document
|
|
329
|
+
|
|
330
|
+
Returns:
|
|
331
|
+
Markdown documentation string
|
|
332
|
+
"""
|
|
333
|
+
lines = []
|
|
334
|
+
|
|
335
|
+
# Title and description
|
|
336
|
+
title = schema.get('title', 'Schema Documentation')
|
|
337
|
+
lines.append(f"# {title}")
|
|
338
|
+
|
|
339
|
+
if 'description' in schema:
|
|
340
|
+
lines.append(f"\n{schema['description']}")
|
|
341
|
+
|
|
342
|
+
# Schema type
|
|
343
|
+
schema_type = schema.get('type', 'unknown')
|
|
344
|
+
lines.append(f"\n**Type:** {schema_type}")
|
|
345
|
+
|
|
346
|
+
# Properties
|
|
347
|
+
if schema_type == 'object' and 'properties' in schema:
|
|
348
|
+
lines.append("\n## Properties")
|
|
349
|
+
|
|
350
|
+
properties = schema['properties']
|
|
351
|
+
required_fields = set(schema.get('required', []))
|
|
352
|
+
|
|
353
|
+
for prop_name, prop_schema in properties.items():
|
|
354
|
+
lines.append(f"\n### {prop_name}")
|
|
355
|
+
|
|
356
|
+
# Required indicator
|
|
357
|
+
if prop_name in required_fields:
|
|
358
|
+
lines.append("**Required**")
|
|
359
|
+
else:
|
|
360
|
+
lines.append("*Optional*")
|
|
361
|
+
|
|
362
|
+
# Type and description
|
|
363
|
+
prop_type = prop_schema.get('type', 'unknown')
|
|
364
|
+
lines.append(f"- **Type:** {prop_type}")
|
|
365
|
+
|
|
366
|
+
if 'description' in prop_schema:
|
|
367
|
+
lines.append(f"- **Description:** {prop_schema['description']}")
|
|
368
|
+
|
|
369
|
+
# Constraints
|
|
370
|
+
constraints = []
|
|
371
|
+
if 'minimum' in prop_schema:
|
|
372
|
+
constraints.append(f"minimum: {prop_schema['minimum']}")
|
|
373
|
+
if 'maximum' in prop_schema:
|
|
374
|
+
constraints.append(f"maximum: {prop_schema['maximum']}")
|
|
375
|
+
if 'minLength' in prop_schema:
|
|
376
|
+
constraints.append(f"min length: {prop_schema['minLength']}")
|
|
377
|
+
if 'maxLength' in prop_schema:
|
|
378
|
+
constraints.append(f"max length: {prop_schema['maxLength']}")
|
|
379
|
+
if 'enum' in prop_schema:
|
|
380
|
+
constraints.append(f"allowed values: {', '.join(map(str, prop_schema['enum']))}")
|
|
381
|
+
|
|
382
|
+
if constraints:
|
|
383
|
+
lines.append(f"- **Constraints:** {', '.join(constraints)}")
|
|
384
|
+
|
|
385
|
+
return '\n'.join(lines)
|