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.
@@ -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)