genxai-framework 0.1.0__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.
- cli/__init__.py +3 -0
- cli/commands/__init__.py +6 -0
- cli/commands/approval.py +85 -0
- cli/commands/audit.py +127 -0
- cli/commands/metrics.py +25 -0
- cli/commands/tool.py +389 -0
- cli/main.py +32 -0
- genxai/__init__.py +81 -0
- genxai/api/__init__.py +5 -0
- genxai/api/app.py +21 -0
- genxai/config/__init__.py +5 -0
- genxai/config/settings.py +37 -0
- genxai/connectors/__init__.py +19 -0
- genxai/connectors/base.py +122 -0
- genxai/connectors/kafka.py +92 -0
- genxai/connectors/postgres_cdc.py +95 -0
- genxai/connectors/registry.py +44 -0
- genxai/connectors/sqs.py +94 -0
- genxai/connectors/webhook.py +73 -0
- genxai/core/__init__.py +37 -0
- genxai/core/agent/__init__.py +32 -0
- genxai/core/agent/base.py +206 -0
- genxai/core/agent/config_io.py +59 -0
- genxai/core/agent/registry.py +98 -0
- genxai/core/agent/runtime.py +970 -0
- genxai/core/communication/__init__.py +6 -0
- genxai/core/communication/collaboration.py +44 -0
- genxai/core/communication/message_bus.py +192 -0
- genxai/core/communication/protocols.py +35 -0
- genxai/core/execution/__init__.py +22 -0
- genxai/core/execution/metadata.py +181 -0
- genxai/core/execution/queue.py +201 -0
- genxai/core/graph/__init__.py +30 -0
- genxai/core/graph/checkpoints.py +77 -0
- genxai/core/graph/edges.py +131 -0
- genxai/core/graph/engine.py +813 -0
- genxai/core/graph/executor.py +516 -0
- genxai/core/graph/nodes.py +161 -0
- genxai/core/graph/trigger_runner.py +40 -0
- genxai/core/memory/__init__.py +19 -0
- genxai/core/memory/base.py +72 -0
- genxai/core/memory/embedding.py +327 -0
- genxai/core/memory/episodic.py +448 -0
- genxai/core/memory/long_term.py +467 -0
- genxai/core/memory/manager.py +543 -0
- genxai/core/memory/persistence.py +297 -0
- genxai/core/memory/procedural.py +461 -0
- genxai/core/memory/semantic.py +526 -0
- genxai/core/memory/shared.py +62 -0
- genxai/core/memory/short_term.py +303 -0
- genxai/core/memory/vector_store.py +508 -0
- genxai/core/memory/working.py +211 -0
- genxai/core/state/__init__.py +6 -0
- genxai/core/state/manager.py +293 -0
- genxai/core/state/schema.py +115 -0
- genxai/llm/__init__.py +14 -0
- genxai/llm/base.py +150 -0
- genxai/llm/factory.py +329 -0
- genxai/llm/providers/__init__.py +1 -0
- genxai/llm/providers/anthropic.py +249 -0
- genxai/llm/providers/cohere.py +274 -0
- genxai/llm/providers/google.py +334 -0
- genxai/llm/providers/ollama.py +147 -0
- genxai/llm/providers/openai.py +257 -0
- genxai/llm/routing.py +83 -0
- genxai/observability/__init__.py +6 -0
- genxai/observability/logging.py +327 -0
- genxai/observability/metrics.py +494 -0
- genxai/observability/tracing.py +372 -0
- genxai/performance/__init__.py +39 -0
- genxai/performance/cache.py +256 -0
- genxai/performance/pooling.py +289 -0
- genxai/security/audit.py +304 -0
- genxai/security/auth.py +315 -0
- genxai/security/cost_control.py +528 -0
- genxai/security/default_policies.py +44 -0
- genxai/security/jwt.py +142 -0
- genxai/security/oauth.py +226 -0
- genxai/security/pii.py +366 -0
- genxai/security/policy_engine.py +82 -0
- genxai/security/rate_limit.py +341 -0
- genxai/security/rbac.py +247 -0
- genxai/security/validation.py +218 -0
- genxai/tools/__init__.py +21 -0
- genxai/tools/base.py +383 -0
- genxai/tools/builtin/__init__.py +131 -0
- genxai/tools/builtin/communication/__init__.py +15 -0
- genxai/tools/builtin/communication/email_sender.py +159 -0
- genxai/tools/builtin/communication/notification_manager.py +167 -0
- genxai/tools/builtin/communication/slack_notifier.py +118 -0
- genxai/tools/builtin/communication/sms_sender.py +118 -0
- genxai/tools/builtin/communication/webhook_caller.py +136 -0
- genxai/tools/builtin/computation/__init__.py +15 -0
- genxai/tools/builtin/computation/calculator.py +101 -0
- genxai/tools/builtin/computation/code_executor.py +183 -0
- genxai/tools/builtin/computation/data_validator.py +259 -0
- genxai/tools/builtin/computation/hash_generator.py +129 -0
- genxai/tools/builtin/computation/regex_matcher.py +201 -0
- genxai/tools/builtin/data/__init__.py +15 -0
- genxai/tools/builtin/data/csv_processor.py +213 -0
- genxai/tools/builtin/data/data_transformer.py +299 -0
- genxai/tools/builtin/data/json_processor.py +233 -0
- genxai/tools/builtin/data/text_analyzer.py +288 -0
- genxai/tools/builtin/data/xml_processor.py +175 -0
- genxai/tools/builtin/database/__init__.py +15 -0
- genxai/tools/builtin/database/database_inspector.py +157 -0
- genxai/tools/builtin/database/mongodb_query.py +196 -0
- genxai/tools/builtin/database/redis_cache.py +167 -0
- genxai/tools/builtin/database/sql_query.py +145 -0
- genxai/tools/builtin/database/vector_search.py +163 -0
- genxai/tools/builtin/file/__init__.py +17 -0
- genxai/tools/builtin/file/directory_scanner.py +214 -0
- genxai/tools/builtin/file/file_compressor.py +237 -0
- genxai/tools/builtin/file/file_reader.py +102 -0
- genxai/tools/builtin/file/file_writer.py +122 -0
- genxai/tools/builtin/file/image_processor.py +186 -0
- genxai/tools/builtin/file/pdf_parser.py +144 -0
- genxai/tools/builtin/test/__init__.py +15 -0
- genxai/tools/builtin/test/async_simulator.py +62 -0
- genxai/tools/builtin/test/data_transformer.py +99 -0
- genxai/tools/builtin/test/error_generator.py +82 -0
- genxai/tools/builtin/test/simple_math.py +94 -0
- genxai/tools/builtin/test/string_processor.py +72 -0
- genxai/tools/builtin/web/__init__.py +15 -0
- genxai/tools/builtin/web/api_caller.py +161 -0
- genxai/tools/builtin/web/html_parser.py +330 -0
- genxai/tools/builtin/web/http_client.py +187 -0
- genxai/tools/builtin/web/url_validator.py +162 -0
- genxai/tools/builtin/web/web_scraper.py +170 -0
- genxai/tools/custom/my_test_tool_2.py +9 -0
- genxai/tools/dynamic.py +105 -0
- genxai/tools/mcp_server.py +167 -0
- genxai/tools/persistence/__init__.py +6 -0
- genxai/tools/persistence/models.py +55 -0
- genxai/tools/persistence/service.py +322 -0
- genxai/tools/registry.py +227 -0
- genxai/tools/security/__init__.py +11 -0
- genxai/tools/security/limits.py +214 -0
- genxai/tools/security/policy.py +20 -0
- genxai/tools/security/sandbox.py +248 -0
- genxai/tools/templates.py +435 -0
- genxai/triggers/__init__.py +19 -0
- genxai/triggers/base.py +104 -0
- genxai/triggers/file_watcher.py +75 -0
- genxai/triggers/queue.py +68 -0
- genxai/triggers/registry.py +82 -0
- genxai/triggers/schedule.py +66 -0
- genxai/triggers/webhook.py +68 -0
- genxai/utils/__init__.py +1 -0
- genxai/utils/tokens.py +295 -0
- genxai_framework-0.1.0.dist-info/METADATA +495 -0
- genxai_framework-0.1.0.dist-info/RECORD +156 -0
- genxai_framework-0.1.0.dist-info/WHEEL +5 -0
- genxai_framework-0.1.0.dist-info/entry_points.txt +2 -0
- genxai_framework-0.1.0.dist-info/licenses/LICENSE +21 -0
- genxai_framework-0.1.0.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"""CSV processor tool for parsing and manipulating CSV data."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, List, Optional
|
|
4
|
+
import logging
|
|
5
|
+
import csv
|
|
6
|
+
import io
|
|
7
|
+
|
|
8
|
+
from genxai.tools.base import Tool, ToolMetadata, ToolParameter, ToolCategory
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class CSVProcessorTool(Tool):
|
|
14
|
+
"""Process, parse, and manipulate CSV data."""
|
|
15
|
+
|
|
16
|
+
def __init__(self) -> None:
|
|
17
|
+
"""Initialize CSV processor tool."""
|
|
18
|
+
metadata = ToolMetadata(
|
|
19
|
+
name="csv_processor",
|
|
20
|
+
description="Parse, validate, filter, and transform CSV data",
|
|
21
|
+
category=ToolCategory.DATA,
|
|
22
|
+
tags=["csv", "data", "parsing", "tabular", "spreadsheet"],
|
|
23
|
+
version="1.0.0",
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
parameters = [
|
|
27
|
+
ToolParameter(
|
|
28
|
+
name="data",
|
|
29
|
+
type="string",
|
|
30
|
+
description="CSV string to process",
|
|
31
|
+
required=True,
|
|
32
|
+
),
|
|
33
|
+
ToolParameter(
|
|
34
|
+
name="operation",
|
|
35
|
+
type="string",
|
|
36
|
+
description="Operation to perform",
|
|
37
|
+
required=True,
|
|
38
|
+
enum=["parse", "filter", "transform", "aggregate", "validate"],
|
|
39
|
+
),
|
|
40
|
+
ToolParameter(
|
|
41
|
+
name="delimiter",
|
|
42
|
+
type="string",
|
|
43
|
+
description="CSV delimiter character",
|
|
44
|
+
required=False,
|
|
45
|
+
default=",",
|
|
46
|
+
),
|
|
47
|
+
ToolParameter(
|
|
48
|
+
name="has_header",
|
|
49
|
+
type="boolean",
|
|
50
|
+
description="Whether CSV has header row",
|
|
51
|
+
required=False,
|
|
52
|
+
default=True,
|
|
53
|
+
),
|
|
54
|
+
ToolParameter(
|
|
55
|
+
name="filter_column",
|
|
56
|
+
type="string",
|
|
57
|
+
description="Column name to filter by (for filter operation)",
|
|
58
|
+
required=False,
|
|
59
|
+
),
|
|
60
|
+
ToolParameter(
|
|
61
|
+
name="filter_value",
|
|
62
|
+
type="string",
|
|
63
|
+
description="Value to filter for (for filter operation)",
|
|
64
|
+
required=False,
|
|
65
|
+
),
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
super().__init__(metadata, parameters)
|
|
69
|
+
|
|
70
|
+
async def _execute(
|
|
71
|
+
self,
|
|
72
|
+
data: str,
|
|
73
|
+
operation: str,
|
|
74
|
+
delimiter: str = ",",
|
|
75
|
+
has_header: bool = True,
|
|
76
|
+
filter_column: Optional[str] = None,
|
|
77
|
+
filter_value: Optional[str] = None,
|
|
78
|
+
) -> Dict[str, Any]:
|
|
79
|
+
"""Execute CSV processing.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
data: CSV string
|
|
83
|
+
operation: Operation to perform
|
|
84
|
+
delimiter: CSV delimiter
|
|
85
|
+
has_header: Has header flag
|
|
86
|
+
filter_column: Column to filter
|
|
87
|
+
filter_value: Value to filter
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
Dictionary containing processed data
|
|
91
|
+
"""
|
|
92
|
+
result: Dict[str, Any] = {
|
|
93
|
+
"operation": operation,
|
|
94
|
+
"success": False,
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
# Parse CSV
|
|
99
|
+
csv_file = io.StringIO(data)
|
|
100
|
+
reader = csv.reader(csv_file, delimiter=delimiter)
|
|
101
|
+
rows = list(reader)
|
|
102
|
+
|
|
103
|
+
if not rows:
|
|
104
|
+
raise ValueError("Empty CSV data")
|
|
105
|
+
|
|
106
|
+
headers = rows[0] if has_header else [f"col_{i}" for i in range(len(rows[0]))]
|
|
107
|
+
data_rows = rows[1:] if has_header else rows
|
|
108
|
+
|
|
109
|
+
if operation == "parse":
|
|
110
|
+
# Convert to list of dictionaries
|
|
111
|
+
parsed_data = []
|
|
112
|
+
for row in data_rows:
|
|
113
|
+
if len(row) == len(headers):
|
|
114
|
+
parsed_data.append(dict(zip(headers, row)))
|
|
115
|
+
|
|
116
|
+
result["data"] = parsed_data
|
|
117
|
+
result["headers"] = headers
|
|
118
|
+
result["row_count"] = len(parsed_data)
|
|
119
|
+
result["column_count"] = len(headers)
|
|
120
|
+
result["success"] = True
|
|
121
|
+
|
|
122
|
+
elif operation == "filter":
|
|
123
|
+
if not filter_column or filter_value is None:
|
|
124
|
+
raise ValueError("filter_column and filter_value required for filter operation")
|
|
125
|
+
|
|
126
|
+
if filter_column not in headers:
|
|
127
|
+
raise ValueError(f"Column '{filter_column}' not found in headers")
|
|
128
|
+
|
|
129
|
+
col_index = headers.index(filter_column)
|
|
130
|
+
filtered_rows = [
|
|
131
|
+
row for row in data_rows
|
|
132
|
+
if len(row) > col_index and row[col_index] == filter_value
|
|
133
|
+
]
|
|
134
|
+
|
|
135
|
+
filtered_data = [dict(zip(headers, row)) for row in filtered_rows]
|
|
136
|
+
result["data"] = filtered_data
|
|
137
|
+
result["filtered_count"] = len(filtered_data)
|
|
138
|
+
result["original_count"] = len(data_rows)
|
|
139
|
+
result["success"] = True
|
|
140
|
+
|
|
141
|
+
elif operation == "transform":
|
|
142
|
+
# Convert to structured format
|
|
143
|
+
transformed = {
|
|
144
|
+
"headers": headers,
|
|
145
|
+
"rows": data_rows,
|
|
146
|
+
"metadata": {
|
|
147
|
+
"row_count": len(data_rows),
|
|
148
|
+
"column_count": len(headers),
|
|
149
|
+
"delimiter": delimiter,
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
result["data"] = transformed
|
|
153
|
+
result["success"] = True
|
|
154
|
+
|
|
155
|
+
elif operation == "aggregate":
|
|
156
|
+
# Calculate basic statistics for numeric columns
|
|
157
|
+
aggregates = {}
|
|
158
|
+
|
|
159
|
+
for col_idx, header in enumerate(headers):
|
|
160
|
+
column_values = [row[col_idx] for row in data_rows if len(row) > col_idx]
|
|
161
|
+
|
|
162
|
+
# Try to convert to numbers
|
|
163
|
+
numeric_values = []
|
|
164
|
+
for val in column_values:
|
|
165
|
+
try:
|
|
166
|
+
numeric_values.append(float(val))
|
|
167
|
+
except (ValueError, TypeError):
|
|
168
|
+
pass
|
|
169
|
+
|
|
170
|
+
if numeric_values:
|
|
171
|
+
aggregates[header] = {
|
|
172
|
+
"count": len(numeric_values),
|
|
173
|
+
"sum": sum(numeric_values),
|
|
174
|
+
"mean": sum(numeric_values) / len(numeric_values),
|
|
175
|
+
"min": min(numeric_values),
|
|
176
|
+
"max": max(numeric_values),
|
|
177
|
+
}
|
|
178
|
+
else:
|
|
179
|
+
# For non-numeric columns, count unique values
|
|
180
|
+
unique_values = set(column_values)
|
|
181
|
+
aggregates[header] = {
|
|
182
|
+
"count": len(column_values),
|
|
183
|
+
"unique_count": len(unique_values),
|
|
184
|
+
"sample_values": list(unique_values)[:5],
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
result["data"] = aggregates
|
|
188
|
+
result["success"] = True
|
|
189
|
+
|
|
190
|
+
elif operation == "validate":
|
|
191
|
+
# Validate CSV structure
|
|
192
|
+
issues = []
|
|
193
|
+
|
|
194
|
+
# Check for consistent column count
|
|
195
|
+
expected_cols = len(headers)
|
|
196
|
+
for idx, row in enumerate(data_rows):
|
|
197
|
+
if len(row) != expected_cols:
|
|
198
|
+
issues.append({
|
|
199
|
+
"row": idx + (2 if has_header else 1),
|
|
200
|
+
"issue": f"Expected {expected_cols} columns, found {len(row)}",
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
result["valid"] = len(issues) == 0
|
|
204
|
+
result["issues"] = issues
|
|
205
|
+
result["row_count"] = len(data_rows)
|
|
206
|
+
result["column_count"] = len(headers)
|
|
207
|
+
result["success"] = True
|
|
208
|
+
|
|
209
|
+
except Exception as e:
|
|
210
|
+
result["error"] = str(e)
|
|
211
|
+
|
|
212
|
+
logger.info(f"CSV {operation} operation completed: success={result['success']}")
|
|
213
|
+
return result
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
"""Data transformer tool for converting between data formats."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, Optional
|
|
4
|
+
import logging
|
|
5
|
+
import json
|
|
6
|
+
import csv
|
|
7
|
+
import io
|
|
8
|
+
|
|
9
|
+
from genxai.tools.base import Tool, ToolMetadata, ToolParameter, ToolCategory
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class DataTransformerTool(Tool):
|
|
15
|
+
"""Transform data between different formats (JSON, CSV, XML, etc.)."""
|
|
16
|
+
|
|
17
|
+
def __init__(self) -> None:
|
|
18
|
+
"""Initialize data transformer tool."""
|
|
19
|
+
metadata = ToolMetadata(
|
|
20
|
+
name="data_transformer",
|
|
21
|
+
description="Convert data between different formats (JSON, CSV, XML, YAML)",
|
|
22
|
+
category=ToolCategory.DATA,
|
|
23
|
+
tags=["transformation", "conversion", "format", "data", "json", "csv", "xml"],
|
|
24
|
+
version="1.0.0",
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
parameters = [
|
|
28
|
+
ToolParameter(
|
|
29
|
+
name="data",
|
|
30
|
+
type="string",
|
|
31
|
+
description="Input data to transform",
|
|
32
|
+
required=True,
|
|
33
|
+
),
|
|
34
|
+
ToolParameter(
|
|
35
|
+
name="operation",
|
|
36
|
+
type="string",
|
|
37
|
+
description="Simple text transformation operation (used by unit tests)",
|
|
38
|
+
required=False,
|
|
39
|
+
default="uppercase",
|
|
40
|
+
enum=["uppercase", "lowercase", "trim", "convert"],
|
|
41
|
+
),
|
|
42
|
+
ToolParameter(
|
|
43
|
+
name="source_format",
|
|
44
|
+
type="string",
|
|
45
|
+
description="Source data format (for convert operation)",
|
|
46
|
+
required=False,
|
|
47
|
+
default="json",
|
|
48
|
+
enum=["json", "csv", "xml", "yaml"],
|
|
49
|
+
),
|
|
50
|
+
ToolParameter(
|
|
51
|
+
name="target_format",
|
|
52
|
+
type="string",
|
|
53
|
+
description="Target data format (for convert operation)",
|
|
54
|
+
required=False,
|
|
55
|
+
default="json",
|
|
56
|
+
enum=["json", "csv", "xml", "yaml"],
|
|
57
|
+
),
|
|
58
|
+
ToolParameter(
|
|
59
|
+
name="csv_delimiter",
|
|
60
|
+
type="string",
|
|
61
|
+
description="CSV delimiter (for CSV operations)",
|
|
62
|
+
required=False,
|
|
63
|
+
default=",",
|
|
64
|
+
),
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
super().__init__(metadata, parameters)
|
|
68
|
+
|
|
69
|
+
async def _execute(
|
|
70
|
+
self,
|
|
71
|
+
data: str,
|
|
72
|
+
operation: str = "convert",
|
|
73
|
+
source_format: str = "json",
|
|
74
|
+
target_format: str = "json",
|
|
75
|
+
csv_delimiter: str = ",",
|
|
76
|
+
) -> Dict[str, Any]:
|
|
77
|
+
"""Execute data transformation.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
data: Input data
|
|
81
|
+
source_format: Source format
|
|
82
|
+
target_format: Target format
|
|
83
|
+
csv_delimiter: CSV delimiter
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
Dictionary containing transformed data
|
|
87
|
+
"""
|
|
88
|
+
result: Dict[str, Any] = {
|
|
89
|
+
"operation": operation,
|
|
90
|
+
"success": False,
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
try:
|
|
94
|
+
if operation == "uppercase":
|
|
95
|
+
result["result"] = data.upper()
|
|
96
|
+
result["success"] = True
|
|
97
|
+
return result
|
|
98
|
+
if operation == "lowercase":
|
|
99
|
+
result["result"] = data.lower()
|
|
100
|
+
result["success"] = True
|
|
101
|
+
return result
|
|
102
|
+
if operation == "trim":
|
|
103
|
+
result["result"] = data.strip()
|
|
104
|
+
result["success"] = True
|
|
105
|
+
return result
|
|
106
|
+
|
|
107
|
+
if operation not in {"convert"}:
|
|
108
|
+
raise ValueError(f"Unsupported operation: {operation}")
|
|
109
|
+
|
|
110
|
+
# Parse source data
|
|
111
|
+
parsed_data = self._parse_data(data, source_format, csv_delimiter)
|
|
112
|
+
|
|
113
|
+
# Convert to target format
|
|
114
|
+
transformed_data = self._convert_data(parsed_data, target_format, csv_delimiter)
|
|
115
|
+
|
|
116
|
+
result["data"] = transformed_data
|
|
117
|
+
result["source_format"] = source_format
|
|
118
|
+
result["target_format"] = target_format
|
|
119
|
+
result["success"] = True
|
|
120
|
+
|
|
121
|
+
except Exception as e:
|
|
122
|
+
result["error"] = str(e)
|
|
123
|
+
|
|
124
|
+
logger.info(
|
|
125
|
+
f"Data transformer ({operation}) completed: success={result['success']}"
|
|
126
|
+
)
|
|
127
|
+
return result
|
|
128
|
+
|
|
129
|
+
def _parse_data(self, data: str, format: str, delimiter: str) -> Any:
|
|
130
|
+
"""Parse data from source format.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
data: Input data
|
|
134
|
+
format: Data format
|
|
135
|
+
delimiter: CSV delimiter
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
Parsed data
|
|
139
|
+
"""
|
|
140
|
+
if format == "json":
|
|
141
|
+
return json.loads(data)
|
|
142
|
+
|
|
143
|
+
elif format == "csv":
|
|
144
|
+
csv_file = io.StringIO(data)
|
|
145
|
+
reader = csv.DictReader(csv_file, delimiter=delimiter)
|
|
146
|
+
return list(reader)
|
|
147
|
+
|
|
148
|
+
elif format == "xml":
|
|
149
|
+
import xml.etree.ElementTree as ET
|
|
150
|
+
root = ET.fromstring(data)
|
|
151
|
+
return self._xml_to_dict(root)
|
|
152
|
+
|
|
153
|
+
elif format == "yaml":
|
|
154
|
+
try:
|
|
155
|
+
import yaml
|
|
156
|
+
return yaml.safe_load(data)
|
|
157
|
+
except ImportError:
|
|
158
|
+
raise ImportError(
|
|
159
|
+
"pyyaml package not installed. Install with: pip install pyyaml"
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
else:
|
|
163
|
+
raise ValueError(f"Unsupported source format: {format}")
|
|
164
|
+
|
|
165
|
+
def _convert_data(self, data: Any, format: str, delimiter: str) -> str:
|
|
166
|
+
"""Convert data to target format.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
data: Parsed data
|
|
170
|
+
format: Target format
|
|
171
|
+
delimiter: CSV delimiter
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
Converted data string
|
|
175
|
+
"""
|
|
176
|
+
if format == "json":
|
|
177
|
+
return json.dumps(data, indent=2)
|
|
178
|
+
|
|
179
|
+
elif format == "csv":
|
|
180
|
+
if not isinstance(data, list):
|
|
181
|
+
raise ValueError("CSV conversion requires list of dictionaries")
|
|
182
|
+
|
|
183
|
+
if not data:
|
|
184
|
+
return ""
|
|
185
|
+
|
|
186
|
+
output = io.StringIO()
|
|
187
|
+
if isinstance(data[0], dict):
|
|
188
|
+
writer = csv.DictWriter(output, fieldnames=data[0].keys(), delimiter=delimiter)
|
|
189
|
+
writer.writeheader()
|
|
190
|
+
writer.writerows(data)
|
|
191
|
+
else:
|
|
192
|
+
writer = csv.writer(output, delimiter=delimiter)
|
|
193
|
+
writer.writerows(data)
|
|
194
|
+
|
|
195
|
+
return output.getvalue()
|
|
196
|
+
|
|
197
|
+
elif format == "xml":
|
|
198
|
+
import xml.etree.ElementTree as ET
|
|
199
|
+
root = self._dict_to_xml(data, "root")
|
|
200
|
+
self._indent_xml(root)
|
|
201
|
+
return ET.tostring(root, encoding="unicode")
|
|
202
|
+
|
|
203
|
+
elif format == "yaml":
|
|
204
|
+
try:
|
|
205
|
+
import yaml
|
|
206
|
+
return yaml.dump(data, default_flow_style=False, sort_keys=False)
|
|
207
|
+
except ImportError:
|
|
208
|
+
raise ImportError(
|
|
209
|
+
"pyyaml package not installed. Install with: pip install pyyaml"
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
else:
|
|
213
|
+
raise ValueError(f"Unsupported target format: {format}")
|
|
214
|
+
|
|
215
|
+
def _xml_to_dict(self, element: Any) -> Dict[str, Any]:
|
|
216
|
+
"""Convert XML element to dictionary.
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
element: XML element
|
|
220
|
+
|
|
221
|
+
Returns:
|
|
222
|
+
Dictionary representation
|
|
223
|
+
"""
|
|
224
|
+
result: Dict[str, Any] = {}
|
|
225
|
+
|
|
226
|
+
# Add attributes
|
|
227
|
+
if element.attrib:
|
|
228
|
+
result["@attributes"] = element.attrib
|
|
229
|
+
|
|
230
|
+
# Add text content
|
|
231
|
+
if element.text and element.text.strip():
|
|
232
|
+
result["#text"] = element.text.strip()
|
|
233
|
+
|
|
234
|
+
# Add children
|
|
235
|
+
for child in element:
|
|
236
|
+
child_data = self._xml_to_dict(child)
|
|
237
|
+
if child.tag in result:
|
|
238
|
+
# Multiple children with same tag - convert to list
|
|
239
|
+
if not isinstance(result[child.tag], list):
|
|
240
|
+
result[child.tag] = [result[child.tag]]
|
|
241
|
+
result[child.tag].append(child_data)
|
|
242
|
+
else:
|
|
243
|
+
result[child.tag] = child_data
|
|
244
|
+
|
|
245
|
+
return {element.tag: result} if result else {element.tag: element.text}
|
|
246
|
+
|
|
247
|
+
def _dict_to_xml(self, data: Any, root_tag: str = "root") -> Any:
|
|
248
|
+
"""Convert dictionary to XML element.
|
|
249
|
+
|
|
250
|
+
Args:
|
|
251
|
+
data: Dictionary data
|
|
252
|
+
root_tag: Root element tag
|
|
253
|
+
|
|
254
|
+
Returns:
|
|
255
|
+
XML element
|
|
256
|
+
"""
|
|
257
|
+
import xml.etree.ElementTree as ET
|
|
258
|
+
|
|
259
|
+
root = ET.Element(root_tag)
|
|
260
|
+
|
|
261
|
+
if isinstance(data, dict):
|
|
262
|
+
for key, value in data.items():
|
|
263
|
+
if key == "@attributes":
|
|
264
|
+
root.attrib.update(value)
|
|
265
|
+
elif key == "#text":
|
|
266
|
+
root.text = str(value)
|
|
267
|
+
else:
|
|
268
|
+
if isinstance(value, list):
|
|
269
|
+
for item in value:
|
|
270
|
+
child = self._dict_to_xml(item, key)
|
|
271
|
+
root.append(child)
|
|
272
|
+
else:
|
|
273
|
+
child = self._dict_to_xml(value, key)
|
|
274
|
+
root.append(child)
|
|
275
|
+
else:
|
|
276
|
+
root.text = str(data)
|
|
277
|
+
|
|
278
|
+
return root
|
|
279
|
+
|
|
280
|
+
def _indent_xml(self, elem: Any, level: int = 0) -> None:
|
|
281
|
+
"""Add indentation to XML element.
|
|
282
|
+
|
|
283
|
+
Args:
|
|
284
|
+
elem: XML element
|
|
285
|
+
level: Indentation level
|
|
286
|
+
"""
|
|
287
|
+
indent = "\n" + " " * level
|
|
288
|
+
if len(elem):
|
|
289
|
+
if not elem.text or not elem.text.strip():
|
|
290
|
+
elem.text = indent + " "
|
|
291
|
+
if not elem.tail or not elem.tail.strip():
|
|
292
|
+
elem.tail = indent
|
|
293
|
+
for child in elem:
|
|
294
|
+
self._indent_xml(child, level + 1)
|
|
295
|
+
if not child.tail or not child.tail.strip():
|
|
296
|
+
child.tail = indent
|
|
297
|
+
else:
|
|
298
|
+
if level and (not elem.tail or not elem.tail.strip()):
|
|
299
|
+
elem.tail = indent
|