additory 0.1.0a2__py3-none-any.whl → 0.1.0a3__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.
- additory/__init__.py +4 -0
- additory/common/__init__.py +2 -2
- additory/common/backend.py +20 -4
- additory/common/distributions.py +1 -1
- additory/common/sample_data.py +19 -19
- additory/core/backends/arrow_bridge.py +7 -0
- additory/core/polars_expression_engine.py +66 -16
- additory/dynamic_api.py +42 -46
- additory/expressions/proxy.py +4 -1
- additory/synthetic/__init__.py +7 -95
- additory/synthetic/column_name_resolver.py +149 -0
- additory/{augment → synthetic}/distributions.py +2 -2
- additory/{augment → synthetic}/forecast.py +1 -1
- additory/synthetic/linked_list_parser.py +415 -0
- additory/synthetic/namespace_lookup.py +129 -0
- additory/{augment → synthetic}/smote.py +1 -1
- additory/{augment → synthetic}/strategies.py +11 -44
- additory/{augment/augmentor.py → synthetic/synthesizer.py} +75 -15
- additory/utilities/units.py +4 -1
- {additory-0.1.0a2.dist-info → additory-0.1.0a3.dist-info}/METADATA +10 -17
- {additory-0.1.0a2.dist-info → additory-0.1.0a3.dist-info}/RECORD +24 -40
- {additory-0.1.0a2.dist-info → additory-0.1.0a3.dist-info}/WHEEL +1 -1
- additory/augment/__init__.py +0 -24
- additory/augment/builtin_lists.py +0 -430
- additory/augment/list_registry.py +0 -177
- additory/synthetic/api.py +0 -220
- additory/synthetic/common_integration.py +0 -314
- additory/synthetic/config.py +0 -262
- additory/synthetic/engines.py +0 -529
- additory/synthetic/exceptions.py +0 -180
- additory/synthetic/file_managers.py +0 -518
- additory/synthetic/generator.py +0 -702
- additory/synthetic/generator_parser.py +0 -68
- additory/synthetic/integration.py +0 -319
- additory/synthetic/models.py +0 -241
- additory/synthetic/pattern_resolver.py +0 -573
- additory/synthetic/performance.py +0 -469
- additory/synthetic/polars_integration.py +0 -464
- additory/synthetic/proxy.py +0 -60
- additory/synthetic/schema_parser.py +0 -685
- additory/synthetic/validator.py +0 -553
- {additory-0.1.0a2.dist-info → additory-0.1.0a3.dist-info}/licenses/LICENSE +0 -0
- {additory-0.1.0a2.dist-info → additory-0.1.0a3.dist-info}/top_level.txt +0 -0
additory/synthetic/exceptions.py
DELETED
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Exception classes for synthetic data generation system.
|
|
3
|
-
|
|
4
|
-
Provides a hierarchy of exceptions for different error conditions with
|
|
5
|
-
clear error messages and actionable suggestions.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
from typing import Optional, List, Dict, Any
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class SyntheticDataError(Exception):
|
|
12
|
-
"""Base exception for all synthetic data generation errors."""
|
|
13
|
-
|
|
14
|
-
def __init__(self, message: str, suggestions: Optional[List[str]] = None):
|
|
15
|
-
super().__init__(message)
|
|
16
|
-
self.suggestions = suggestions or []
|
|
17
|
-
|
|
18
|
-
def __str__(self) -> str:
|
|
19
|
-
msg = super().__str__()
|
|
20
|
-
if self.suggestions:
|
|
21
|
-
suggestions_text = "\n".join(f" - {s}" for s in self.suggestions)
|
|
22
|
-
msg += f"\n\nSuggestions:\n{suggestions_text}"
|
|
23
|
-
return msg
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
class PatternResolutionError(SyntheticDataError):
|
|
27
|
-
"""Raised when pattern resolution fails in the hierarchy."""
|
|
28
|
-
|
|
29
|
-
def __init__(self, message: str, pattern_name: str, searched_sources: List[str],
|
|
30
|
-
details: Optional[str] = None):
|
|
31
|
-
self.pattern_name = pattern_name
|
|
32
|
-
self.searched_sources = searched_sources
|
|
33
|
-
self.details = details
|
|
34
|
-
|
|
35
|
-
suggestions = [
|
|
36
|
-
f"Define pattern '{pattern_name}' in one of the hierarchy sources",
|
|
37
|
-
"Check if pattern name is spelled correctly",
|
|
38
|
-
"Verify import declarations in schema files",
|
|
39
|
-
"Ensure pattern files are accessible and properly formatted"
|
|
40
|
-
]
|
|
41
|
-
|
|
42
|
-
if details:
|
|
43
|
-
suggestions.append(f"Additional details: {details}")
|
|
44
|
-
|
|
45
|
-
super().__init__(message, suggestions)
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
class ValidationError(SyntheticDataError):
|
|
49
|
-
"""Raised when validation fails for patterns, schemas, or configurations."""
|
|
50
|
-
|
|
51
|
-
def __init__(self, message: str, file_path: Optional[str] = None,
|
|
52
|
-
line_number: Optional[int] = None, suggestions: Optional[List[str]] = None):
|
|
53
|
-
self.file_path = file_path
|
|
54
|
-
self.line_number = line_number
|
|
55
|
-
|
|
56
|
-
if file_path:
|
|
57
|
-
location = f" in {file_path}"
|
|
58
|
-
if line_number:
|
|
59
|
-
location += f" at line {line_number}"
|
|
60
|
-
message += location
|
|
61
|
-
|
|
62
|
-
super().__init__(message, suggestions)
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
class DistributionError(SyntheticDataError):
|
|
66
|
-
"""Raised when distribution strategy application fails."""
|
|
67
|
-
|
|
68
|
-
def __init__(self, strategy_type: str, column_name: str, reason: str):
|
|
69
|
-
self.strategy_type = strategy_type
|
|
70
|
-
self.column_name = column_name
|
|
71
|
-
self.reason = reason
|
|
72
|
-
|
|
73
|
-
message = f"Distribution strategy '{strategy_type}' failed for column '{column_name}': {reason}"
|
|
74
|
-
|
|
75
|
-
suggestions = [
|
|
76
|
-
f"Check if '{strategy_type}' is compatible with the data type of '{column_name}'",
|
|
77
|
-
"Verify distribution parameters are within valid ranges",
|
|
78
|
-
"Ensure the pattern generates appropriate data for the distribution"
|
|
79
|
-
]
|
|
80
|
-
|
|
81
|
-
super().__init__(message, suggestions)
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
class FileFormatError(SyntheticDataError):
|
|
85
|
-
"""Raised when file format validation fails."""
|
|
86
|
-
|
|
87
|
-
def __init__(self, file_path: str, expected_format: str, actual_format: Optional[str] = None):
|
|
88
|
-
self.file_path = file_path
|
|
89
|
-
self.expected_format = expected_format
|
|
90
|
-
self.actual_format = actual_format
|
|
91
|
-
|
|
92
|
-
message = f"Invalid file format for '{file_path}'. Expected: {expected_format}"
|
|
93
|
-
if actual_format:
|
|
94
|
-
message += f", Got: {actual_format}"
|
|
95
|
-
|
|
96
|
-
suggestions = [
|
|
97
|
-
f"Ensure the file has the correct extension (.{expected_format})",
|
|
98
|
-
f"Verify the file content follows {expected_format} syntax",
|
|
99
|
-
"Check file permissions and accessibility"
|
|
100
|
-
]
|
|
101
|
-
|
|
102
|
-
super().__init__(message, suggestions)
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
class RegexValidationError(ValidationError):
|
|
109
|
-
"""Raised when regex pattern validation fails."""
|
|
110
|
-
|
|
111
|
-
def __init__(self, pattern: str, regex_error: str, pattern_name: Optional[str] = None):
|
|
112
|
-
self.pattern = pattern
|
|
113
|
-
self.regex_error = regex_error
|
|
114
|
-
self.pattern_name = pattern_name
|
|
115
|
-
|
|
116
|
-
name_part = f" for pattern '{pattern_name}'" if pattern_name else ""
|
|
117
|
-
message = f"Invalid regex pattern{name_part}: {regex_error}"
|
|
118
|
-
|
|
119
|
-
suggestions = [
|
|
120
|
-
"Check regex syntax for polars compatibility",
|
|
121
|
-
"Escape special characters properly",
|
|
122
|
-
"Test the regex pattern with online validators",
|
|
123
|
-
"Refer to polars regex documentation for supported features"
|
|
124
|
-
]
|
|
125
|
-
|
|
126
|
-
super().__init__(message, suggestions=suggestions)
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
class SchemaParsingError(ValidationError):
|
|
130
|
-
"""Raised when schema file parsing fails."""
|
|
131
|
-
|
|
132
|
-
def __init__(self, file_path: str, parsing_error: str, line_number: Optional[int] = None):
|
|
133
|
-
self.parsing_error = parsing_error
|
|
134
|
-
|
|
135
|
-
suggestions = [
|
|
136
|
-
"Check TOML syntax for proper formatting",
|
|
137
|
-
"Ensure all strings are properly quoted",
|
|
138
|
-
"Verify section headers are correctly formatted",
|
|
139
|
-
"Check for missing commas or brackets"
|
|
140
|
-
]
|
|
141
|
-
|
|
142
|
-
super().__init__(f"Schema parsing failed: {parsing_error}",
|
|
143
|
-
file_path, line_number, suggestions)
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
class PatternImportError(SyntheticDataError):
|
|
147
|
-
"""Raised when pattern file import fails."""
|
|
148
|
-
|
|
149
|
-
def __init__(self, import_name: str, file_path: Optional[str] = None, reason: str = "File not found"):
|
|
150
|
-
self.import_name = import_name
|
|
151
|
-
self.file_path = file_path
|
|
152
|
-
self.reason = reason
|
|
153
|
-
|
|
154
|
-
message = f"Failed to import pattern file '{import_name}': {reason}"
|
|
155
|
-
if file_path:
|
|
156
|
-
message += f" (looked for: {file_path})"
|
|
157
|
-
|
|
158
|
-
suggestions = [
|
|
159
|
-
f"Ensure '{import_name}.properties' exists in reference/schema_definitions/",
|
|
160
|
-
"Check file permissions and accessibility",
|
|
161
|
-
"Verify the import name matches the filename exactly",
|
|
162
|
-
"Check for typos in the import declaration"
|
|
163
|
-
]
|
|
164
|
-
|
|
165
|
-
super().__init__(message, suggestions)
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
class DistributionValidationError(SyntheticDataError):
|
|
169
|
-
"""Raised when distribution strategy validation fails."""
|
|
170
|
-
|
|
171
|
-
def __init__(self, message: str, distribution_type: str, details: str = ""):
|
|
172
|
-
super().__init__(message, ["Check distribution strategy parameters and syntax"])
|
|
173
|
-
self.distribution_type = distribution_type
|
|
174
|
-
self.details = details
|
|
175
|
-
|
|
176
|
-
def __str__(self) -> str:
|
|
177
|
-
base_msg = super().__str__()
|
|
178
|
-
if self.details:
|
|
179
|
-
return f"{base_msg}\nDistribution Type: {self.distribution_type}\nDetails: {self.details}"
|
|
180
|
-
return f"{base_msg}\nDistribution Type: {self.distribution_type}"
|
|
@@ -1,518 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
File management for synthetic data generation system.
|
|
3
|
-
|
|
4
|
-
Handles loading and validation of .properties and .toml files
|
|
5
|
-
with proper error handling and syntax validation.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
import re
|
|
9
|
-
import toml
|
|
10
|
-
from pathlib import Path
|
|
11
|
-
from typing import Dict, List, Optional, Tuple
|
|
12
|
-
from dataclasses import dataclass
|
|
13
|
-
|
|
14
|
-
from .models import ValidationResult, PatternDefinition, PatternSource, ValidationStatus
|
|
15
|
-
from .exceptions import FileFormatError, ValidationError, PatternImportError, SchemaParsingError
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
@dataclass
|
|
19
|
-
class ParsedPropertiesFile:
|
|
20
|
-
"""Result of parsing a .properties file."""
|
|
21
|
-
patterns: Dict[str, str]
|
|
22
|
-
file_path: str
|
|
23
|
-
line_count: int
|
|
24
|
-
comments: List[str]
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
@dataclass
|
|
28
|
-
class ParsedSchemaFile:
|
|
29
|
-
"""Result of parsing a .toml schema file."""
|
|
30
|
-
imports: List[str]
|
|
31
|
-
inline_patterns: Dict[str, str]
|
|
32
|
-
schema_definitions: Dict[str, str]
|
|
33
|
-
metadata: Dict[str, str]
|
|
34
|
-
file_path: str
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
class PatternFileManager:
|
|
38
|
-
"""
|
|
39
|
-
Manages loading and validation of .properties pattern files.
|
|
40
|
-
|
|
41
|
-
Handles the parsing of .properties files with proper syntax validation,
|
|
42
|
-
comment handling, and error reporting with line numbers.
|
|
43
|
-
"""
|
|
44
|
-
|
|
45
|
-
def __init__(self):
|
|
46
|
-
self._pattern_cache: Dict[str, ParsedPropertiesFile] = {}
|
|
47
|
-
self._validation_cache: Dict[str, ValidationResult] = {}
|
|
48
|
-
|
|
49
|
-
def load_properties_file(self, file_path: str) -> ParsedPropertiesFile:
|
|
50
|
-
"""
|
|
51
|
-
Load and parse a .properties file.
|
|
52
|
-
|
|
53
|
-
Args:
|
|
54
|
-
file_path: Path to the .properties file
|
|
55
|
-
|
|
56
|
-
Returns:
|
|
57
|
-
ParsedPropertiesFile with patterns and metadata
|
|
58
|
-
|
|
59
|
-
Raises:
|
|
60
|
-
FileFormatError: If file format is invalid
|
|
61
|
-
ValidationError: If file syntax is invalid
|
|
62
|
-
FileNotFoundError: If file doesn't exist
|
|
63
|
-
"""
|
|
64
|
-
path = Path(file_path)
|
|
65
|
-
|
|
66
|
-
# Check file extension
|
|
67
|
-
if not path.suffix == '.properties':
|
|
68
|
-
raise FileFormatError(file_path, "properties", path.suffix[1:] if path.suffix else "unknown")
|
|
69
|
-
|
|
70
|
-
# Check if file exists
|
|
71
|
-
if not path.exists():
|
|
72
|
-
raise FileNotFoundError(f"Properties file not found: {file_path}")
|
|
73
|
-
|
|
74
|
-
# Check cache
|
|
75
|
-
cache_key = str(path.absolute())
|
|
76
|
-
if cache_key in self._pattern_cache:
|
|
77
|
-
return self._pattern_cache[cache_key]
|
|
78
|
-
|
|
79
|
-
try:
|
|
80
|
-
content = path.read_text(encoding='utf-8')
|
|
81
|
-
except UnicodeDecodeError as e:
|
|
82
|
-
raise ValidationError(f"File encoding error: {e}", file_path)
|
|
83
|
-
|
|
84
|
-
# Parse the content
|
|
85
|
-
parsed = self._parse_properties_content(content, file_path)
|
|
86
|
-
|
|
87
|
-
# Cache the result
|
|
88
|
-
self._pattern_cache[cache_key] = parsed
|
|
89
|
-
|
|
90
|
-
return parsed
|
|
91
|
-
|
|
92
|
-
def _parse_properties_content(self, content: str, file_path: str) -> ParsedPropertiesFile:
|
|
93
|
-
"""
|
|
94
|
-
Parse the content of a .properties file.
|
|
95
|
-
|
|
96
|
-
Args:
|
|
97
|
-
content: File content as string
|
|
98
|
-
file_path: Path for error reporting
|
|
99
|
-
|
|
100
|
-
Returns:
|
|
101
|
-
ParsedPropertiesFile with parsed data
|
|
102
|
-
|
|
103
|
-
Raises:
|
|
104
|
-
ValidationError: If syntax is invalid
|
|
105
|
-
"""
|
|
106
|
-
patterns = {}
|
|
107
|
-
comments = []
|
|
108
|
-
line_number = 0
|
|
109
|
-
|
|
110
|
-
for line in content.splitlines():
|
|
111
|
-
line_number += 1
|
|
112
|
-
line = line.strip()
|
|
113
|
-
|
|
114
|
-
# Skip empty lines
|
|
115
|
-
if not line:
|
|
116
|
-
continue
|
|
117
|
-
|
|
118
|
-
# Handle comments
|
|
119
|
-
if line.startswith('#'):
|
|
120
|
-
comments.append(line[1:].strip())
|
|
121
|
-
continue
|
|
122
|
-
|
|
123
|
-
# Parse key=value pairs
|
|
124
|
-
if '=' not in line:
|
|
125
|
-
raise ValidationError(
|
|
126
|
-
f"Invalid syntax: missing '=' separator",
|
|
127
|
-
file_path,
|
|
128
|
-
line_number,
|
|
129
|
-
["Each line should be in format: pattern_name=regex_pattern"]
|
|
130
|
-
)
|
|
131
|
-
|
|
132
|
-
# Split on first '=' only
|
|
133
|
-
key, value = line.split('=', 1)
|
|
134
|
-
key = key.strip()
|
|
135
|
-
value = value.strip()
|
|
136
|
-
|
|
137
|
-
# Validate key
|
|
138
|
-
if not key:
|
|
139
|
-
raise ValidationError(
|
|
140
|
-
"Empty pattern name",
|
|
141
|
-
file_path,
|
|
142
|
-
line_number,
|
|
143
|
-
["Pattern names must not be empty"]
|
|
144
|
-
)
|
|
145
|
-
|
|
146
|
-
if not self._is_valid_pattern_name(key):
|
|
147
|
-
raise ValidationError(
|
|
148
|
-
f"Invalid pattern name '{key}'",
|
|
149
|
-
file_path,
|
|
150
|
-
line_number,
|
|
151
|
-
["Pattern names must start with a letter and contain only letters, numbers, and underscores"]
|
|
152
|
-
)
|
|
153
|
-
|
|
154
|
-
# Validate value
|
|
155
|
-
if not value:
|
|
156
|
-
raise ValidationError(
|
|
157
|
-
f"Empty regex pattern for '{key}'",
|
|
158
|
-
file_path,
|
|
159
|
-
line_number,
|
|
160
|
-
["Regex patterns must not be empty"]
|
|
161
|
-
)
|
|
162
|
-
|
|
163
|
-
# Check for duplicates
|
|
164
|
-
if key in patterns:
|
|
165
|
-
raise ValidationError(
|
|
166
|
-
f"Duplicate pattern name '{key}'",
|
|
167
|
-
file_path,
|
|
168
|
-
line_number,
|
|
169
|
-
[f"Pattern '{key}' is already defined in this file"]
|
|
170
|
-
)
|
|
171
|
-
|
|
172
|
-
patterns[key] = value
|
|
173
|
-
|
|
174
|
-
return ParsedPropertiesFile(
|
|
175
|
-
patterns=patterns,
|
|
176
|
-
file_path=file_path,
|
|
177
|
-
line_count=line_number,
|
|
178
|
-
comments=comments
|
|
179
|
-
)
|
|
180
|
-
|
|
181
|
-
def _is_valid_pattern_name(self, name: str) -> bool:
|
|
182
|
-
"""
|
|
183
|
-
Validate pattern name format.
|
|
184
|
-
|
|
185
|
-
Args:
|
|
186
|
-
name: Pattern name to validate
|
|
187
|
-
|
|
188
|
-
Returns:
|
|
189
|
-
True if valid, False otherwise
|
|
190
|
-
"""
|
|
191
|
-
# Pattern names must start with letter, contain only letters, numbers, underscores
|
|
192
|
-
pattern = r'^[a-zA-Z][a-zA-Z0-9_]*$'
|
|
193
|
-
return bool(re.match(pattern, name))
|
|
194
|
-
|
|
195
|
-
def validate_properties_syntax(self, content: str) -> ValidationResult:
|
|
196
|
-
"""
|
|
197
|
-
Validate .properties file syntax without full parsing.
|
|
198
|
-
|
|
199
|
-
Args:
|
|
200
|
-
content: File content to validate
|
|
201
|
-
|
|
202
|
-
Returns:
|
|
203
|
-
ValidationResult with validation status
|
|
204
|
-
"""
|
|
205
|
-
result = ValidationResult(is_valid=True)
|
|
206
|
-
line_number = 0
|
|
207
|
-
seen_keys = set()
|
|
208
|
-
|
|
209
|
-
for line in content.splitlines():
|
|
210
|
-
line_number += 1
|
|
211
|
-
line = line.strip()
|
|
212
|
-
|
|
213
|
-
# Skip empty lines and comments
|
|
214
|
-
if not line or line.startswith('#'):
|
|
215
|
-
continue
|
|
216
|
-
|
|
217
|
-
# Check for '=' separator
|
|
218
|
-
if '=' not in line:
|
|
219
|
-
result.add_error(
|
|
220
|
-
f"Line {line_number}: Missing '=' separator",
|
|
221
|
-
"Each line should be in format: pattern_name=regex_pattern"
|
|
222
|
-
)
|
|
223
|
-
continue
|
|
224
|
-
|
|
225
|
-
# Split and validate
|
|
226
|
-
key, value = line.split('=', 1)
|
|
227
|
-
key = key.strip()
|
|
228
|
-
value = value.strip()
|
|
229
|
-
|
|
230
|
-
# Validate key
|
|
231
|
-
if not key:
|
|
232
|
-
result.add_error(f"Line {line_number}: Empty pattern name")
|
|
233
|
-
elif not self._is_valid_pattern_name(key):
|
|
234
|
-
result.add_error(
|
|
235
|
-
f"Line {line_number}: Invalid pattern name '{key}'",
|
|
236
|
-
"Pattern names must start with a letter and contain only letters, numbers, and underscores"
|
|
237
|
-
)
|
|
238
|
-
elif key in seen_keys:
|
|
239
|
-
result.add_error(f"Line {line_number}: Duplicate pattern name '{key}'")
|
|
240
|
-
else:
|
|
241
|
-
seen_keys.add(key)
|
|
242
|
-
|
|
243
|
-
# Validate value
|
|
244
|
-
if not value:
|
|
245
|
-
result.add_error(f"Line {line_number}: Empty regex pattern for '{key}'")
|
|
246
|
-
|
|
247
|
-
return result
|
|
248
|
-
|
|
249
|
-
def create_pattern_definitions(self, parsed_file: ParsedPropertiesFile,
|
|
250
|
-
source: PatternSource) -> List[PatternDefinition]:
|
|
251
|
-
"""
|
|
252
|
-
Create PatternDefinition objects from parsed file.
|
|
253
|
-
|
|
254
|
-
Args:
|
|
255
|
-
parsed_file: Parsed .properties file
|
|
256
|
-
source: Source type for the patterns
|
|
257
|
-
|
|
258
|
-
Returns:
|
|
259
|
-
List of PatternDefinition objects
|
|
260
|
-
"""
|
|
261
|
-
definitions = []
|
|
262
|
-
|
|
263
|
-
for name, regex in parsed_file.patterns.items():
|
|
264
|
-
definition = PatternDefinition(
|
|
265
|
-
name=name,
|
|
266
|
-
regex=regex,
|
|
267
|
-
source=source,
|
|
268
|
-
validation_status=ValidationStatus.NOT_VALIDATED,
|
|
269
|
-
polars_compatible=False, # Will be validated later
|
|
270
|
-
source_file=parsed_file.file_path
|
|
271
|
-
)
|
|
272
|
-
definitions.append(definition)
|
|
273
|
-
|
|
274
|
-
return definitions
|
|
275
|
-
|
|
276
|
-
def clear_cache(self):
|
|
277
|
-
"""Clear the file cache."""
|
|
278
|
-
self._pattern_cache.clear()
|
|
279
|
-
self._validation_cache.clear()
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
class SchemaFileManager:
|
|
283
|
-
"""
|
|
284
|
-
Manages loading and validation of .toml schema files.
|
|
285
|
-
|
|
286
|
-
Handles the parsing of TOML schema files with proper structure validation,
|
|
287
|
-
import resolution, and error reporting.
|
|
288
|
-
"""
|
|
289
|
-
|
|
290
|
-
def __init__(self):
|
|
291
|
-
self._schema_cache: Dict[str, ParsedSchemaFile] = {}
|
|
292
|
-
|
|
293
|
-
def load_toml_schema(self, file_path: str) -> ParsedSchemaFile:
|
|
294
|
-
"""
|
|
295
|
-
Load and parse a .toml schema file.
|
|
296
|
-
|
|
297
|
-
Args:
|
|
298
|
-
file_path: Path to the .toml file
|
|
299
|
-
|
|
300
|
-
Returns:
|
|
301
|
-
ParsedSchemaFile with schema data
|
|
302
|
-
|
|
303
|
-
Raises:
|
|
304
|
-
FileFormatError: If file format is invalid
|
|
305
|
-
SchemaParsingError: If TOML parsing fails
|
|
306
|
-
FileNotFoundError: If file doesn't exist
|
|
307
|
-
"""
|
|
308
|
-
path = Path(file_path)
|
|
309
|
-
|
|
310
|
-
# Check file extension
|
|
311
|
-
if not path.suffix == '.toml':
|
|
312
|
-
raise FileFormatError(file_path, "toml", path.suffix[1:] if path.suffix else "unknown")
|
|
313
|
-
|
|
314
|
-
# Check if file exists
|
|
315
|
-
if not path.exists():
|
|
316
|
-
raise FileNotFoundError(f"Schema file not found: {file_path}")
|
|
317
|
-
|
|
318
|
-
# Check cache
|
|
319
|
-
cache_key = str(path.absolute())
|
|
320
|
-
if cache_key in self._schema_cache:
|
|
321
|
-
return self._schema_cache[cache_key]
|
|
322
|
-
|
|
323
|
-
try:
|
|
324
|
-
content = path.read_text(encoding='utf-8')
|
|
325
|
-
except UnicodeDecodeError as e:
|
|
326
|
-
raise SchemaParsingError(file_path, f"File encoding error: {e}")
|
|
327
|
-
|
|
328
|
-
# Parse TOML content
|
|
329
|
-
try:
|
|
330
|
-
toml_data = toml.loads(content)
|
|
331
|
-
except toml.TomlDecodeError as e:
|
|
332
|
-
raise SchemaParsingError(file_path, f"TOML parsing error: {e}")
|
|
333
|
-
|
|
334
|
-
# Parse the schema structure
|
|
335
|
-
parsed = self._parse_schema_structure(toml_data, file_path)
|
|
336
|
-
|
|
337
|
-
# Cache the result
|
|
338
|
-
self._schema_cache[cache_key] = parsed
|
|
339
|
-
|
|
340
|
-
return parsed
|
|
341
|
-
|
|
342
|
-
def _parse_schema_structure(self, toml_data: Dict, file_path: str) -> ParsedSchemaFile:
|
|
343
|
-
"""
|
|
344
|
-
Parse the structure of a TOML schema file.
|
|
345
|
-
|
|
346
|
-
Supports two formats:
|
|
347
|
-
1. Legacy format: [generator] section with import and inline patterns
|
|
348
|
-
2. New format: [generation] section with imports, prefer_mode, and patterns
|
|
349
|
-
|
|
350
|
-
Args:
|
|
351
|
-
toml_data: Parsed TOML data
|
|
352
|
-
file_path: Path for error reporting
|
|
353
|
-
|
|
354
|
-
Returns:
|
|
355
|
-
ParsedSchemaFile with structured data
|
|
356
|
-
|
|
357
|
-
Raises:
|
|
358
|
-
SchemaParsingError: If structure is invalid
|
|
359
|
-
"""
|
|
360
|
-
imports = []
|
|
361
|
-
inline_patterns = {}
|
|
362
|
-
prefer_mode = "default" # Default prefer mode
|
|
363
|
-
|
|
364
|
-
# Try new format first: [generation] section
|
|
365
|
-
generation_section = toml_data.get('generation', {})
|
|
366
|
-
if generation_section:
|
|
367
|
-
# Handle imports (array format)
|
|
368
|
-
import_value = generation_section.get('imports', [])
|
|
369
|
-
if isinstance(import_value, str):
|
|
370
|
-
imports = [import_value]
|
|
371
|
-
elif isinstance(import_value, list):
|
|
372
|
-
imports = import_value
|
|
373
|
-
elif import_value: # Not None or empty
|
|
374
|
-
raise SchemaParsingError(
|
|
375
|
-
file_path,
|
|
376
|
-
"imports declaration must be a string or list of strings"
|
|
377
|
-
)
|
|
378
|
-
|
|
379
|
-
# Handle prefer_mode
|
|
380
|
-
prefer_mode_value = generation_section.get('prefer_mode', 'default')
|
|
381
|
-
if not isinstance(prefer_mode_value, str):
|
|
382
|
-
raise SchemaParsingError(
|
|
383
|
-
file_path,
|
|
384
|
-
"prefer_mode must be a string (default, list_only, or regex_only)"
|
|
385
|
-
)
|
|
386
|
-
if prefer_mode_value not in ['default', 'list_only', 'regex_only']:
|
|
387
|
-
raise SchemaParsingError(
|
|
388
|
-
file_path,
|
|
389
|
-
f"Invalid prefer_mode '{prefer_mode_value}'. Valid values: default, list_only, regex_only"
|
|
390
|
-
)
|
|
391
|
-
prefer_mode = prefer_mode_value
|
|
392
|
-
|
|
393
|
-
# Handle inline patterns (everything except 'imports' and 'prefer_mode')
|
|
394
|
-
for key, value in generation_section.items():
|
|
395
|
-
if key not in ['imports', 'prefer_mode']:
|
|
396
|
-
# Support both string (regex) and array (list) patterns
|
|
397
|
-
if isinstance(value, str):
|
|
398
|
-
inline_patterns[key] = value
|
|
399
|
-
elif isinstance(value, list):
|
|
400
|
-
inline_patterns[key] = value
|
|
401
|
-
else:
|
|
402
|
-
raise SchemaParsingError(
|
|
403
|
-
file_path,
|
|
404
|
-
f"Inline pattern '{key}' must be a string (regex) or array (list)"
|
|
405
|
-
)
|
|
406
|
-
|
|
407
|
-
# Fallback to legacy format: [generator] section
|
|
408
|
-
generator_section = toml_data.get('generator', {})
|
|
409
|
-
if generator_section and not generation_section:
|
|
410
|
-
# Handle imports (legacy: 'import' instead of 'imports')
|
|
411
|
-
import_value = generator_section.get('import', [])
|
|
412
|
-
if isinstance(import_value, str):
|
|
413
|
-
imports = [import_value]
|
|
414
|
-
elif isinstance(import_value, list):
|
|
415
|
-
imports = import_value
|
|
416
|
-
elif import_value: # Not None or empty
|
|
417
|
-
raise SchemaParsingError(
|
|
418
|
-
file_path,
|
|
419
|
-
"Import declaration must be a string or list of strings"
|
|
420
|
-
)
|
|
421
|
-
|
|
422
|
-
# Handle inline patterns (everything except 'import')
|
|
423
|
-
for key, value in generator_section.items():
|
|
424
|
-
if key != 'import':
|
|
425
|
-
if not isinstance(value, str):
|
|
426
|
-
raise SchemaParsingError(
|
|
427
|
-
file_path,
|
|
428
|
-
f"Inline pattern '{key}' must be a string"
|
|
429
|
-
)
|
|
430
|
-
inline_patterns[key] = value
|
|
431
|
-
|
|
432
|
-
# Extract schema section
|
|
433
|
-
schema_section = toml_data.get('schema', {})
|
|
434
|
-
schema_definitions = {}
|
|
435
|
-
|
|
436
|
-
for key, value in schema_section.items():
|
|
437
|
-
# Support both string (reference/regex) and array (list) patterns
|
|
438
|
-
if isinstance(value, str):
|
|
439
|
-
schema_definitions[key] = value
|
|
440
|
-
elif isinstance(value, list):
|
|
441
|
-
schema_definitions[key] = value
|
|
442
|
-
else:
|
|
443
|
-
raise SchemaParsingError(
|
|
444
|
-
file_path,
|
|
445
|
-
f"Schema definition '{key}' must be a string or array"
|
|
446
|
-
)
|
|
447
|
-
|
|
448
|
-
# Extract metadata (any other sections)
|
|
449
|
-
metadata = {}
|
|
450
|
-
for key, value in toml_data.items():
|
|
451
|
-
if key not in ['generator', 'generation', 'schema']:
|
|
452
|
-
metadata[key] = value
|
|
453
|
-
|
|
454
|
-
# Store prefer_mode in metadata
|
|
455
|
-
metadata['prefer_mode'] = prefer_mode
|
|
456
|
-
|
|
457
|
-
return ParsedSchemaFile(
|
|
458
|
-
imports=imports,
|
|
459
|
-
inline_patterns=inline_patterns,
|
|
460
|
-
schema_definitions=schema_definitions,
|
|
461
|
-
metadata=metadata,
|
|
462
|
-
file_path=file_path
|
|
463
|
-
)
|
|
464
|
-
|
|
465
|
-
def validate_toml_syntax(self, content: str) -> ValidationResult:
|
|
466
|
-
"""
|
|
467
|
-
Validate TOML syntax without full parsing.
|
|
468
|
-
|
|
469
|
-
Args:
|
|
470
|
-
content: TOML content to validate
|
|
471
|
-
|
|
472
|
-
Returns:
|
|
473
|
-
ValidationResult with validation status
|
|
474
|
-
"""
|
|
475
|
-
result = ValidationResult(is_valid=True)
|
|
476
|
-
|
|
477
|
-
try:
|
|
478
|
-
toml_data = toml.loads(content)
|
|
479
|
-
|
|
480
|
-
# Validate expected sections
|
|
481
|
-
valid_sections = {'generator', 'schema'}
|
|
482
|
-
for section in toml_data:
|
|
483
|
-
if section not in valid_sections and not isinstance(toml_data[section], dict):
|
|
484
|
-
result.add_warning(f"Unexpected section '{section}' - will be treated as metadata")
|
|
485
|
-
|
|
486
|
-
# Validate generator section structure
|
|
487
|
-
if 'generator' in toml_data:
|
|
488
|
-
generator = toml_data['generator']
|
|
489
|
-
if not isinstance(generator, dict):
|
|
490
|
-
result.add_error("Generator section must be a table/dictionary")
|
|
491
|
-
else:
|
|
492
|
-
# Validate import format
|
|
493
|
-
if 'import' in generator:
|
|
494
|
-
import_val = generator['import']
|
|
495
|
-
if not isinstance(import_val, (str, list)):
|
|
496
|
-
result.add_error("Import declaration must be a string or list of strings")
|
|
497
|
-
|
|
498
|
-
# Validate schema section structure
|
|
499
|
-
if 'schema' in toml_data:
|
|
500
|
-
schema = toml_data['schema']
|
|
501
|
-
if not isinstance(schema, dict):
|
|
502
|
-
result.add_error("Schema section must be a table/dictionary")
|
|
503
|
-
else:
|
|
504
|
-
for key, value in schema.items():
|
|
505
|
-
if not isinstance(value, str):
|
|
506
|
-
result.add_error(f"Schema definition '{key}' must be a string")
|
|
507
|
-
|
|
508
|
-
except toml.TomlDecodeError as e:
|
|
509
|
-
result.add_error(
|
|
510
|
-
f"TOML syntax error: {e}",
|
|
511
|
-
"Check TOML syntax for proper formatting, quotes, and brackets"
|
|
512
|
-
)
|
|
513
|
-
|
|
514
|
-
return result
|
|
515
|
-
|
|
516
|
-
def clear_cache(self):
|
|
517
|
-
"""Clear the schema cache."""
|
|
518
|
-
self._schema_cache.clear()
|