string-schema 0.1.6__tar.gz → 0.1.7__tar.gz

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.
Files changed (43) hide show
  1. {string_schema-0.1.6/string_schema.egg-info → string_schema-0.1.7}/PKG-INFO +1 -1
  2. {string_schema-0.1.6 → string_schema-0.1.7}/docs/string-syntax.md +50 -0
  3. string_schema-0.1.7/examples/defaults_descriptions_demo.py +203 -0
  4. {string_schema-0.1.6 → string_schema-0.1.7}/pyproject.toml +1 -1
  5. {string_schema-0.1.6 → string_schema-0.1.7}/setup.py +1 -1
  6. {string_schema-0.1.6 → string_schema-0.1.7}/string_schema/__init__.py +1 -1
  7. {string_schema-0.1.6 → string_schema-0.1.7}/string_schema/core/builders.py +2 -2
  8. {string_schema-0.1.6 → string_schema-0.1.7}/string_schema/core/fields.py +12 -7
  9. {string_schema-0.1.6 → string_schema-0.1.7}/string_schema/integrations/pydantic.py +1 -1
  10. {string_schema-0.1.6 → string_schema-0.1.7}/string_schema/parsing/string_parser.py +143 -18
  11. {string_schema-0.1.6 → string_schema-0.1.7}/string_schema/utilities.py +1 -1
  12. {string_schema-0.1.6 → string_schema-0.1.7/string_schema.egg-info}/PKG-INFO +1 -1
  13. {string_schema-0.1.6 → string_schema-0.1.7}/string_schema.egg-info/SOURCES.txt +1 -0
  14. {string_schema-0.1.6 → string_schema-0.1.7}/LICENSE +0 -0
  15. {string_schema-0.1.6 → string_schema-0.1.7}/MANIFEST.in +0 -0
  16. {string_schema-0.1.6 → string_schema-0.1.7}/README.md +0 -0
  17. {string_schema-0.1.6 → string_schema-0.1.7}/docs/README.md +0 -0
  18. {string_schema-0.1.6 → string_schema-0.1.7}/docs/advanced-usage.md +0 -0
  19. {string_schema-0.1.6 → string_schema-0.1.7}/docs/api-reference.md +0 -0
  20. {string_schema-0.1.6 → string_schema-0.1.7}/docs/examples.md +0 -0
  21. {string_schema-0.1.6 → string_schema-0.1.7}/docs/faq.md +0 -0
  22. {string_schema-0.1.6 → string_schema-0.1.7}/docs/getting-started.md +0 -0
  23. {string_schema-0.1.6 → string_schema-0.1.7}/docs/pydantic-utilities.md +0 -0
  24. {string_schema-0.1.6 → string_schema-0.1.7}/docs/troubleshooting.md +0 -0
  25. {string_schema-0.1.6 → string_schema-0.1.7}/examples/demo.py +0 -0
  26. {string_schema-0.1.6 → string_schema-0.1.7}/examples/pydantic_utility_demo.py +0 -0
  27. {string_schema-0.1.6 → string_schema-0.1.7}/setup.cfg +0 -0
  28. {string_schema-0.1.6 → string_schema-0.1.7}/string_schema/core/__init__.py +0 -0
  29. {string_schema-0.1.6 → string_schema-0.1.7}/string_schema/core/validators.py +0 -0
  30. {string_schema-0.1.6 → string_schema-0.1.7}/string_schema/examples/__init__.py +0 -0
  31. {string_schema-0.1.6 → string_schema-0.1.7}/string_schema/examples/presets.py +0 -0
  32. {string_schema-0.1.6 → string_schema-0.1.7}/string_schema/examples/recipes.py +0 -0
  33. {string_schema-0.1.6 → string_schema-0.1.7}/string_schema/integrations/__init__.py +0 -0
  34. {string_schema-0.1.6 → string_schema-0.1.7}/string_schema/integrations/json_schema.py +0 -0
  35. {string_schema-0.1.6 → string_schema-0.1.7}/string_schema/integrations/openapi.py +0 -0
  36. {string_schema-0.1.6 → string_schema-0.1.7}/string_schema/integrations/reverse.py +0 -0
  37. {string_schema-0.1.6 → string_schema-0.1.7}/string_schema/parsing/__init__.py +0 -0
  38. {string_schema-0.1.6 → string_schema-0.1.7}/string_schema/parsing/optimizer.py +0 -0
  39. {string_schema-0.1.6 → string_schema-0.1.7}/string_schema/parsing/syntax.py +0 -0
  40. {string_schema-0.1.6 → string_schema-0.1.7}/string_schema/py.typed +0 -0
  41. {string_schema-0.1.6 → string_schema-0.1.7}/string_schema.egg-info/dependency_links.txt +0 -0
  42. {string_schema-0.1.6 → string_schema-0.1.7}/string_schema.egg-info/requires.txt +0 -0
  43. {string_schema-0.1.6 → string_schema-0.1.7}/string_schema.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: string-schema
3
- Version: 0.1.6
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
@@ -40,6 +40,56 @@ price:number(min=0)
40
40
  description:text(max=500)
41
41
  ```
42
42
 
43
+ ### Default Values
44
+
45
+ Add default values using `=`:
46
+
47
+ ```
48
+ active:bool=true
49
+ count:int=0
50
+ status:string=pending
51
+ price:number=9.99
52
+ ```
53
+
54
+ Default values can be combined with constraints:
55
+
56
+ ```
57
+ age:int(0,120)=18
58
+ limit:int(1,1000)=100
59
+ ```
60
+
61
+ ### Field Descriptions
62
+
63
+ Add human-readable descriptions using ` |` (space before pipe):
64
+
65
+ ```
66
+ name:string | User's full name
67
+ age:int | User's age in years
68
+ email:email | Contact email address
69
+ ```
70
+
71
+ ### Combined Syntax
72
+
73
+ You can combine all features:
74
+
75
+ ```
76
+ name:string(min=1,max=100) | User's full name
77
+ age:int(0,120)=18 | User's age in years
78
+ active:bool=true | Whether the account is active
79
+ email:string? | Optional email address
80
+ count:int(1,1000)=1 | Number of items to process
81
+ ```
82
+
83
+ **Syntax Order:**
84
+
85
+ ```
86
+ field_name:type(constraints)?=default | description
87
+ ```
88
+
89
+ - `?` = optional marker (before `=`)
90
+ - `=value` = default value
91
+ - ` | text` = description (space before pipe)
92
+
43
93
  ## Type System
44
94
 
45
95
  ### Basic Types
@@ -0,0 +1,203 @@
1
+ """
2
+ Demo: Enhanced String Schema with Defaults and Descriptions
3
+
4
+ This example demonstrates the new syntax for defining default values
5
+ and descriptions in string-schema.
6
+
7
+ New syntax:
8
+ - field:type=default
9
+ - field:type | description
10
+ - field:type=default | description
11
+ """
12
+
13
+ from string_schema import parse_string_schema, validate_to_dict
14
+ import json
15
+
16
+
17
+ def print_section(title):
18
+ """Print a formatted section header"""
19
+ print(f"\n{'='*60}")
20
+ print(f" {title}")
21
+ print('='*60)
22
+
23
+
24
+ def demo_default_values():
25
+ """Demonstrate default value syntax"""
26
+ print_section("Default Values")
27
+
28
+ # Define schema with defaults
29
+ schema_str = """
30
+ active:bool=true,
31
+ count:int=0,
32
+ status:string=pending,
33
+ price:number=9.99
34
+ """
35
+
36
+ print("\nSchema definition:")
37
+ print(schema_str)
38
+
39
+ # Parse schema
40
+ schema = parse_string_schema(schema_str)
41
+ print("\nGenerated JSON Schema:")
42
+ print(json.dumps(schema, indent=2))
43
+
44
+ # Validate data with defaults
45
+ print("\n--- Test 1: Empty data (uses all defaults) ---")
46
+ data = {}
47
+ result = validate_to_dict(data, schema_str)
48
+ print(f"Input: {data}")
49
+ print(f"Output: {result}")
50
+
51
+ print("\n--- Test 2: Partial data (some defaults) ---")
52
+ data = {"count": 5}
53
+ result = validate_to_dict(data, schema_str)
54
+ print(f"Input: {data}")
55
+ print(f"Output: {result}")
56
+
57
+ print("\n--- Test 3: Override defaults ---")
58
+ data = {"active": False, "count": 10, "status": "completed", "price": 19.99}
59
+ result = validate_to_dict(data, schema_str)
60
+ print(f"Input: {data}")
61
+ print(f"Output: {result}")
62
+
63
+
64
+ def demo_descriptions():
65
+ """Demonstrate description syntax"""
66
+ print_section("Field Descriptions")
67
+
68
+ # Define schema with descriptions
69
+ schema_str = """
70
+ name:string | User's full name,
71
+ age:int | User's age in years,
72
+ email:email | Contact email address,
73
+ created:datetime | Account creation timestamp
74
+ """
75
+
76
+ print("\nSchema definition:")
77
+ print(schema_str)
78
+
79
+ # Parse schema
80
+ schema = parse_string_schema(schema_str)
81
+ print("\nGenerated JSON Schema (with descriptions):")
82
+ print(json.dumps(schema, indent=2))
83
+
84
+
85
+ def demo_combined_syntax():
86
+ """Demonstrate combined defaults and descriptions"""
87
+ print_section("Combined: Defaults + Descriptions")
88
+
89
+ # Define schema with both defaults and descriptions
90
+ schema_str = """
91
+ name:string | User's full name,
92
+ age:int(0,120)=18 | User's age in years,
93
+ active:bool=true | Whether the account is active,
94
+ email:string? | Optional email address,
95
+ count:int(1,1000)=1 | Number of items to process
96
+ """
97
+
98
+ print("\nSchema definition:")
99
+ print(schema_str)
100
+
101
+ # Parse schema
102
+ schema = parse_string_schema(schema_str)
103
+ print("\nGenerated JSON Schema:")
104
+ print(json.dumps(schema, indent=2))
105
+
106
+ # Validate data
107
+ print("\n--- Test: Minimal data (uses defaults) ---")
108
+ data = {"name": "John Doe"}
109
+ result = validate_to_dict(data, schema_str)
110
+ print(f"Input: {data}")
111
+ print(f"Output: {result}")
112
+
113
+
114
+ def demo_worker_parameters():
115
+ """Demonstrate using enhanced syntax for worker parameters"""
116
+ print_section("Real-World Example: Worker Parameters")
117
+
118
+ # Define worker parameters schema
119
+ schema_str = """
120
+ enable_cleanup:bool=true | Enable article cleanup step,
121
+ enable_cat_tag:bool=true | Enable category and tag assignment,
122
+ enable_create_summary:bool=true | Enable summary generation,
123
+ enable_embedding_generation:bool=true | Enable embedding generation,
124
+ max_articles_per_iteration:int(1,1000)=1 | Maximum articles to process per run,
125
+ processing_days_limit:int(1,365)=7 | Number of days to look back for articles,
126
+ model_name:string? | LLM model to use (leave empty for default)
127
+ """
128
+
129
+ print("\nWorker Parameters Schema:")
130
+ print(schema_str)
131
+
132
+ # Parse schema
133
+ schema = parse_string_schema(schema_str)
134
+ print("\nGenerated JSON Schema:")
135
+ print(json.dumps(schema, indent=2))
136
+
137
+ # Example 1: Default configuration
138
+ print("\n--- Example 1: Default configuration ---")
139
+ params = {}
140
+ result = validate_to_dict(params, schema_str)
141
+ print(f"Input: {params}")
142
+ print(f"Output: {json.dumps(result, indent=2)}")
143
+
144
+ # Example 2: Custom configuration
145
+ print("\n--- Example 2: Custom configuration ---")
146
+ params = {
147
+ "enable_cleanup": False,
148
+ "max_articles_per_iteration": 10,
149
+ "model_name": "openrouter:google/gemini-2.5-flash-lite"
150
+ }
151
+ result = validate_to_dict(params, schema_str)
152
+ print(f"Input: {json.dumps(params, indent=2)}")
153
+ print(f"Output: {json.dumps(result, indent=2)}")
154
+
155
+
156
+ def demo_null_defaults():
157
+ """Demonstrate null default values"""
158
+ print_section("Null Default Values")
159
+
160
+ # Define schema with null defaults
161
+ schema_str = """
162
+ name:string,
163
+ email:string?=null | Optional email (defaults to null),
164
+ phone:string?=null | Optional phone (defaults to null),
165
+ notes:string? | Optional notes (no default)
166
+ """
167
+
168
+ print("\nSchema definition:")
169
+ print(schema_str)
170
+
171
+ # Parse schema
172
+ schema = parse_string_schema(schema_str)
173
+ print("\nGenerated JSON Schema:")
174
+ print(json.dumps(schema, indent=2))
175
+
176
+ # Validate data
177
+ print("\n--- Test: Minimal data ---")
178
+ data = {"name": "John Doe"}
179
+ result = validate_to_dict(data, schema_str)
180
+ print(f"Input: {data}")
181
+ print(f"Output: {result}")
182
+
183
+
184
+ def main():
185
+ """Run all demos"""
186
+ print("\n" + "="*60)
187
+ print(" String Schema: Defaults and Descriptions Demo")
188
+ print("="*60)
189
+
190
+ demo_default_values()
191
+ demo_descriptions()
192
+ demo_combined_syntax()
193
+ demo_worker_parameters()
194
+ demo_null_defaults()
195
+
196
+ print("\n" + "="*60)
197
+ print(" Demo Complete!")
198
+ print("="*60 + "\n")
199
+
200
+
201
+ if __name__ == "__main__":
202
+ main()
203
+
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "string-schema"
7
- version = "0.1.6"
7
+ version = "0.1.7"
8
8
  description = "A simple, LLM-friendly schema definition library for converting string syntax to structured schemas"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -9,7 +9,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
9
9
 
10
10
  setup(
11
11
  name="string-schema",
12
- version="0.1.6",
12
+ version="0.1.7",
13
13
  author="Michael Chen",
14
14
  author_email="xychen@msn.com",
15
15
  description="A simple, LLM-friendly schema definition library for converting string syntax to structured schemas",
@@ -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
@@ -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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: string-schema
3
- Version: 0.1.6
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
@@ -31,6 +31,7 @@ docs/getting-started.md
31
31
  docs/pydantic-utilities.md
32
32
  docs/string-syntax.md
33
33
  docs/troubleshooting.md
34
+ examples/defaults_descriptions_demo.py
34
35
  examples/demo.py
35
36
  examples/pydantic_utility_demo.py
36
37
  string_schema/__init__.py
File without changes
File without changes
File without changes
File without changes
File without changes