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 CHANGED
@@ -64,7 +64,7 @@ from .integrations.json_schema import (
64
64
  # Note: Built-in presets moved to examples/ directory
65
65
  # Import them from simple_schema.examples.presets if needed
66
66
 
67
- __version__ = "0.1.0"
67
+ __version__ = "0.1.7"
68
68
  __author__ = "importal"
69
69
 
70
70
  __all__ = [
@@ -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.default is not None:
73
+ if field.has_default:
74
74
  prop["default"] = field.default
75
75
 
76
76
  # Handle enum/choices
@@ -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 = None, min_val: Optional[Union[int, float]] = None,
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.default is not None:
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.default is not None:
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
- """Parse a single field with enhanced syntax support"""
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
- # Check for optional marker
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 field_str.endswith('?'):
146
- required = False
147
- field_str = field_str[:-1].strip()
148
-
149
- # Split field name and definition
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.default is not None:
491
+ if field.has_default:
367
492
  prop["default"] = field.default
368
493
 
369
494
  # Handle union types
@@ -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
- return validated_instance.model_dump() if hasattr(validated_instance, 'model_dump') else validated_instance.dict()
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
- return validated_instance.model_dump()['__root__'] if hasattr(validated_instance, 'model_dump') else validated_instance.dict()['__root__']
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 (use model_dump for Pydantic v2, fallback to dict for v1)
290
+ # Return as dictionary with timezone-aware datetime handling
240
291
  if hasattr(validated_instance, 'model_dump'):
241
- return validated_instance.model_dump()
292
+ result_dict = validated_instance.model_dump()
242
293
  else:
243
- return validated_instance.dict()
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.5
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=J0_B0TNO0AK_piEcT5gFFHllywjx16DTP59ps_Fp-WU,4052
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=iLyqCKFd0qTL13xyZvt3Dia2-gjZCjHyDVVm59GxLK4,19918
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=AHDTqtCnSe5ZN-DwedisqXqktjgl6nypd-mebucjI3k,7809
6
- string_schema/core/fields.py,sha256=iLUR-w3pZCr7OkQHhb2DFkVLtObnpyJ_mEUdc0qAqXY,5534
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=hYrnl2hKcmbp7ypEINqn065dpLaCWxb39T1A8va1YrM,21818
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=-a_143fjNkxcRu24JdpqA6GltR7a_2AUXPNgHsbwYv4,24989
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.5.dist-info/licenses/LICENSE,sha256=wCD2KBeSlVSkJy0apl6zmVj04uw97UJhRRi-en_3Qfk,1065
21
- string_schema-0.1.5.dist-info/METADATA,sha256=W8XGHUtUAz-SlPW1Mw1Y_SHy1SjuntWS9AolofnE64E,18246
22
- string_schema-0.1.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
23
- string_schema-0.1.5.dist-info/top_level.txt,sha256=1uTmLPYIRrCDVxUW1fDFRMaWvGO48NRcGmbW4oq89I0,14
24
- string_schema-0.1.5.dist-info/RECORD,,
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5