structx 0.4.8__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,233 @@
1
+ import functools
2
+ import json
3
+ from functools import wraps
4
+ from typing import Any, Dict, Type
5
+
6
+ from loguru import logger
7
+
8
+ from structx.core.exceptions import ExtractionError
9
+ from structx.core.models import ExtractionRequest, ModelField
10
+ from structx.utils.types import P, R
11
+
12
+
13
+ def handle_errors(
14
+ error_message: str,
15
+ error_type: Type[Exception] = ExtractionError,
16
+ default_return: Any = None,
17
+ ):
18
+ """
19
+ Decorator for consistent error handling and logging
20
+
21
+ Args:
22
+ error_message: Base message for the error
23
+ error_type: Type of exception to raise
24
+ default_return: Default return value if an error occurs
25
+ """
26
+
27
+ def decorator(func):
28
+ @wraps(func)
29
+ def wrapper(*args, **kwargs):
30
+ try:
31
+ # Call the function
32
+ return func(*args, **kwargs)
33
+
34
+ except Exception as e:
35
+ logger.error(
36
+ f"{error_message}: {str(e)}\n" f"Function: {func.__name__}"
37
+ )
38
+
39
+ if default_return is not None:
40
+ return default_return
41
+
42
+ raise error_type(f"{error_message}: {str(e)}") from e
43
+
44
+ return wrapper
45
+
46
+ return decorator
47
+
48
+
49
+ def flatten_extracted_data(data: Dict[str, Any], prefix: str = "") -> Dict[str, Any]:
50
+ """
51
+ Flatten nested structures for DataFrame storage
52
+
53
+ Args:
54
+ data: Nested dictionary of extracted data
55
+ prefix: Prefix for nested keys
56
+ """
57
+ flattened = {}
58
+
59
+ for key, value in data.items():
60
+ new_key = f"{prefix}_{key}" if prefix else key
61
+
62
+ if isinstance(value, dict):
63
+ nested = flatten_extracted_data(value, new_key)
64
+ flattened.update(nested)
65
+ elif isinstance(value, list):
66
+ if value and isinstance(value[0], dict):
67
+ for i, item in enumerate(value):
68
+ nested = flatten_extracted_data(item, f"{new_key}_{i}")
69
+ flattened.update(nested)
70
+ else:
71
+ flattened[new_key] = json.dumps(value)
72
+ else:
73
+ flattened[new_key] = value
74
+
75
+ return flattened
76
+
77
+
78
+ def convert_pydantic_v1_to_v2(request: ExtractionRequest) -> ExtractionRequest:
79
+ """
80
+ Comprehensively convert Pydantic v1 syntax to v2 syntax in an extraction request
81
+
82
+ Args:
83
+ request: The extraction request potentially containing v1 syntax
84
+
85
+ Returns:
86
+ Updated extraction request with v2 syntax
87
+ """
88
+
89
+ def convert_field(field: ModelField) -> ModelField:
90
+ """Convert a single field from v1 to v2 syntax"""
91
+ # Create a copy of the validation dict
92
+ validation = field.validation.copy() if field.validation else {}
93
+
94
+ # Field validation conversions
95
+ v1_to_v2_mappings = {
96
+ # String validations
97
+ "regex": "pattern",
98
+ "min_items": "min_length",
99
+ "max_items": "max_length",
100
+ "anystr_strip_whitespace": "strip_whitespace",
101
+ "anystr_lower": "to_lower",
102
+ "anystr_upper": "to_upper",
103
+ # Numeric validations
104
+ "gt": "gt", # Same in v2, but included for completeness
105
+ "ge": "ge", # Same in v2, but included for completeness
106
+ "lt": "lt", # Same in v2, but included for completeness
107
+ "le": "le", # Same in v2, but included for completeness
108
+ "multiple_of": "multiple_of", # Same in v2, but included for completeness
109
+ # Collection validations
110
+ "min_items": "min_length",
111
+ "max_items": "max_length",
112
+ "unique_items": "unique_items", # Same in v2, but included for completeness
113
+ # Other validations
114
+ "const": "frozen",
115
+ "allow_mutation": "frozen", # Inverse logic: !allow_mutation = frozen
116
+ }
117
+
118
+ # Apply mappings
119
+ for v1_key, v2_key in v1_to_v2_mappings.items():
120
+ if v1_key in validation:
121
+ # Special case for allow_mutation which has inverse logic
122
+ if v1_key == "allow_mutation":
123
+ validation[v2_key] = not validation.pop(v1_key)
124
+ else:
125
+ validation[v2_key] = validation.pop(v1_key)
126
+
127
+ # Handle special cases and complex conversions
128
+
129
+ # Convert type strings from v1 to v2 format
130
+ field_type = field.type
131
+
132
+ # Handle EmailStr which moved from pydantic.EmailStr to pydantic.EmailStr
133
+ if field_type == "EmailStr" or field_type == "pydantic.EmailStr":
134
+ field_type = "EmailStr"
135
+ # Add import hint in validation for model generator
136
+ validation["_import_"] = "from pydantic import EmailStr"
137
+
138
+ # Handle PositiveInt, NegativeInt, etc. which were removed in v2
139
+ type_conversions = {
140
+ "PositiveInt": "int",
141
+ "NegativeInt": "int",
142
+ "PositiveFloat": "float",
143
+ "NegativeFloat": "float",
144
+ "conint": "int",
145
+ "confloat": "float",
146
+ "constr": "str",
147
+ "conlist": "List",
148
+ "conset": "Set",
149
+ "confrozenset": "FrozenSet",
150
+ }
151
+
152
+ for old_type, new_type in type_conversions.items():
153
+ if old_type in field_type:
154
+ field_type = new_type
155
+ # Add appropriate constraints based on the original type
156
+ if old_type == "PositiveInt":
157
+ validation["gt"] = 0
158
+ elif old_type == "NegativeInt":
159
+ validation["lt"] = 0
160
+ elif old_type == "PositiveFloat":
161
+ validation["gt"] = 0.0
162
+ elif old_type == "NegativeFloat":
163
+ validation["lt"] = 0.0
164
+
165
+ # Process nested fields if present
166
+ nested_fields = None
167
+ if hasattr(field, "nested_fields") and field.nested_fields:
168
+ nested_fields = [
169
+ convert_field(nested_field) for nested_field in field.nested_fields
170
+ ]
171
+
172
+ # Create updated field with converted validation
173
+ return ModelField(
174
+ name=field.name,
175
+ type=field_type,
176
+ description=field.description,
177
+ validation=validation,
178
+ nested_fields=nested_fields,
179
+ )
180
+
181
+ # Convert all fields
182
+ updated_fields = [convert_field(field) for field in request.fields]
183
+
184
+ # Create updated request
185
+ return ExtractionRequest(
186
+ model_name=request.model_name,
187
+ model_description=request.model_description,
188
+ fields=updated_fields,
189
+ )
190
+
191
+
192
+ def sanitize_regex_patterns(
193
+ extraction_request: "ExtractionRequest",
194
+ ) -> "ExtractionRequest":
195
+ """
196
+ Sanitize regex patterns in field validations to make them compatible with Pydantic V2
197
+
198
+ Args:
199
+ extraction_request: The extraction request to sanitize
200
+
201
+ Returns:
202
+ Sanitized extraction request
203
+ """
204
+
205
+ def fix_field_validation(field):
206
+ # Fix regex patterns in validation dict
207
+ if field.validation:
208
+ # For pattern/regex fields, sanitize the pattern
209
+ for pattern_key in ["pattern", "regex"]:
210
+ if pattern_key in field.validation:
211
+ try:
212
+ # Just test if the pattern compiles - if it does, leave it alone
213
+ import re
214
+
215
+ re.compile(field.validation[pattern_key])
216
+ except re.error:
217
+ # If pattern doesn't compile, we'll use a simpler approach:
218
+ # Remove the pattern validation entirely rather than trying to fix it
219
+ logger.warning(
220
+ f"Removing invalid regex pattern: {field.validation[pattern_key]}"
221
+ )
222
+ del field.validation[pattern_key]
223
+
224
+ # Process nested fields recursively
225
+ if field.nested_fields:
226
+ for nested_field in field.nested_fields:
227
+ fix_field_validation(nested_field)
228
+
229
+ # Process all fields
230
+ for field in extraction_request.fields:
231
+ fix_field_validation(field)
232
+
233
+ return extraction_request
@@ -0,0 +1,183 @@
1
+ from string import Template
2
+
3
+ query_refinement_system_prompt = """You are a data structuring specialist.
4
+ Analyze queries to determine the inherent structure of the data
5
+ and provide clear requirements for extraction."""
6
+
7
+ query_refinement_template = Template(
8
+ """
9
+ Analyze and expand the following query to ensure structured extraction.
10
+ Consider:
11
+ 1. What are the inherent characteristics of the data?
12
+ 2. What structural patterns would best represent it?
13
+ 3. How should multiple related items be organized?
14
+ 4. Are there nested relationships or hierarchies?
15
+ 5. What data types would best represent each piece?
16
+
17
+ Query: ${query}
18
+
19
+ """
20
+ )
21
+
22
+
23
+ schema_system_prompt = """You are a schema design specialist.
24
+ Create detailed schemas that capture complex data structures."""
25
+
26
+ schema_template = Template(
27
+ """
28
+ Design a data extraction schema that enforces structured organization.
29
+
30
+ Refined Query: ${refined_query}
31
+
32
+ Data Characteristics:
33
+ ${data_characteristics}
34
+
35
+ Structural Requirements:
36
+ ${structural_requirements}
37
+
38
+ Organization Principles:
39
+ ${organization_principles}
40
+
41
+ Sample text:
42
+ ${sample_text}
43
+
44
+ Important:
45
+ 1. Create structured objects for complex information
46
+ 2. Define appropriate data types
47
+ 3. Include nested models where needed
48
+ 4. Maintain consistent patterns
49
+ 5. Preserve relationships
50
+ """
51
+ )
52
+
53
+
54
+ guide_system_prompt = """You are a data modeling specialist.
55
+ Create clear patterns for organizing complex, nested data structures.
56
+ Provide patterns as simple string descriptions and identify the correct target columns to analyze."""
57
+
58
+
59
+ guide_template = Template(
60
+ """
61
+ Create guidelines for structured data extraction based on these characteristics:
62
+ ${data_characteristics}
63
+
64
+ here is the list of available columns in the dataset:
65
+ ${available_columns}
66
+
67
+ Provide:
68
+ 1. Structural patterns as key-value pairs of string descriptions
69
+ 2. Relationship rules as a list of clear instructions
70
+ 3. Organization principles as a list of guidelines
71
+ 4. Target columns (target_columns) as a list of column names that contain the text to analyze
72
+
73
+ The target_columns field is very important - it must contain only column names that actually exist in the dataset.
74
+ If unsure, use the first column or the 'text' column if available.
75
+ Keep all values as simple strings.
76
+ """
77
+ )
78
+
79
+ # Specialized template for custom model extraction
80
+ custom_model_guide_template = Template(
81
+ """
82
+ Create guidelines for structured data extraction based on these model field characteristics:
83
+ ${data_characteristics}
84
+
85
+ These are the available columns in the dataset:
86
+ ${available_columns}
87
+
88
+ The extraction needs to fill these model fields:
89
+ ${model_fields}
90
+
91
+ Here are suggested column mappings for each model field:
92
+ ${column_suggestions}
93
+
94
+ Provide:
95
+ 1. Structural patterns describing how each field should be extracted
96
+ 2. Relationship rules as a list of clear instructions for extracting each required field
97
+ 3. Organization principles for populating the model consistently
98
+ 4. Target columns (target_columns) as a list of data columns that should be analyzed
99
+
100
+ For target_columns:
101
+ - Include all columns from the suggested mappings that are likely to contain relevant information
102
+ - If text/description columns exist, include them
103
+ - Only include column names that actually exist in the dataset
104
+ - Do not omit any columns that might contain information needed for the model fields
105
+
106
+ For relationship_rules:
107
+ - Include explicit rules for mapping specific columns to model fields
108
+ - Specify how to extract each field from the appropriate column(s)
109
+ - Include rules for handling special field types (dates, numbers, enumerations)
110
+ - Specify that fields should be left as null/None when no reliable information exists
111
+ - Add rules to prevent invented or fabricated values for any field
112
+
113
+ Keep all values as simple strings.
114
+ """
115
+ )
116
+
117
+
118
+ extraction_system_prompt = """
119
+ You are a precise data extraction system specialized in transforming raw data into structured formats. Always:
120
+ 1. Return structured objects for complex data according to the specified model
121
+ 2. Maintain consistent formats and data types as defined in the schema
122
+ 3. Use exact field types as specified (strings, numbers, dates, enumerations)
123
+ 4. Use null/None for fields when no reliable information exists in the source data
124
+ 5. Follow structural patterns exactly as specified
125
+ 6. Return lists when the field expects a list of items
126
+ 7. Be thorough but do not invent data that isn't present in the source
127
+ 8. Only make inferences when there is strong supporting evidence in the data
128
+ 9. When working with custom models, leave fields as null rather than inventing values
129
+ """
130
+
131
+ extraction_template = Template(
132
+ """
133
+ Extract structured information following these guidelines:
134
+
135
+ Query: ${query}
136
+ Patterns: ${patterns}
137
+ Rules: ${rules}
138
+
139
+ Important Notes:
140
+ - Always return a list of structured objects
141
+ - For list fields, return an array of items
142
+ - Each item in a list should follow the specified structure
143
+ - Use null/None when no reliable information is available (do NOT invent data)
144
+ - Dates should be in ISO format (YYYY-MM-DDTHH:MM:SS)
145
+ - For fields with enumerated values, use only values from the provided options
146
+ - It's better to leave a field null than to fill it with incorrect information
147
+
148
+ Text to analyze:
149
+ ${text}
150
+ """
151
+ )
152
+
153
+ # Refinement prompt for existing models
154
+ refinement_system_prompt = """You are a data model refinement specialist.
155
+ Analyze the existing model and the refinement instructions to create
156
+ a new model that incorporates the requested changes."""
157
+
158
+ refinement_template = Template(
159
+ """
160
+ Refine the following data model according to these instructions:
161
+
162
+ EXISTING MODEL SCHEMA:
163
+ ```json
164
+ ${model_schema}
165
+ ```
166
+
167
+ REFINEMENT INSTRUCTIONS:
168
+ ${instructions}
169
+
170
+ Create a new model schema that:
171
+ 1. Keeps fields from the original model that shouldn't change
172
+ 2. Modifies fields as specified in the instructions
173
+ 3. Adds new fields as specified in the instructions
174
+ 4. Removes fields as specified in the instructions
175
+
176
+ Important: Use Pydantic v2 syntax:
177
+ - Use `pattern` instead of `regex` for string patterns
178
+ - Use `model_config` instead of `Config` class
179
+ - Use `Field` with validation parameters instead of validators where possible
180
+
181
+ Include a clear description of the model and each field.
182
+ """
183
+ )
structx/utils/types.py ADDED
@@ -0,0 +1,12 @@
1
+ from typing import Any, Dict, ParamSpec, TypeVar
2
+
3
+ from pydantic import BaseModel
4
+
5
+ ResponseType = TypeVar("ResponseType")
6
+
7
+ DictStrAny = Dict[str, Any]
8
+
9
+ # Type variables for parameters and return type
10
+ T = TypeVar("T", bound=BaseModel)
11
+ R = TypeVar("R")
12
+ P = ParamSpec("P")