memra 0.2.13__py3-none-any.whl → 0.2.15__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.
- memra/cli.py +322 -51
- {memra-0.2.13.dist-info → memra-0.2.15.dist-info}/METADATA +1 -1
- {memra-0.2.13.dist-info → memra-0.2.15.dist-info}/RECORD +7 -61
- memra-0.2.15.dist-info/top_level.txt +1 -0
- memra-0.2.13.dist-info/top_level.txt +0 -4
- memra-ops/app.py +0 -808
- memra-ops/config/config.py +0 -25
- memra-ops/config.py +0 -34
- memra-ops/logic/__init__.py +0 -1
- memra-ops/logic/file_tools.py +0 -43
- memra-ops/logic/invoice_tools.py +0 -668
- memra-ops/logic/invoice_tools_fix.py +0 -66
- memra-ops/mcp_bridge_server.py +0 -1178
- memra-ops/scripts/check_database.py +0 -37
- memra-ops/scripts/clear_database.py +0 -48
- memra-ops/scripts/monitor_database.py +0 -67
- memra-ops/scripts/release.py +0 -133
- memra-ops/scripts/reset_database.py +0 -65
- memra-ops/scripts/start_memra.py +0 -334
- memra-ops/scripts/stop_memra.py +0 -132
- memra-ops/server_tool_registry.py +0 -190
- memra-ops/tests/test_llm_text_to_sql.py +0 -115
- memra-ops/tests/test_llm_vs_pattern.py +0 -130
- memra-ops/tests/test_mcp_schema_aware.py +0 -124
- memra-ops/tests/test_schema_aware_sql.py +0 -139
- memra-ops/tests/test_schema_aware_sql_simple.py +0 -66
- memra-ops/tests/test_text_to_sql_demo.py +0 -140
- memra-ops/tools/mcp_bridge_server.py +0 -851
- memra-sdk/examples/accounts_payable.py +0 -215
- memra-sdk/examples/accounts_payable_client.py +0 -217
- memra-sdk/examples/accounts_payable_mcp.py +0 -200
- memra-sdk/examples/ask_questions.py +0 -123
- memra-sdk/examples/invoice_processing.py +0 -116
- memra-sdk/examples/propane_delivery.py +0 -87
- memra-sdk/examples/simple_text_to_sql.py +0 -158
- memra-sdk/memra/__init__.py +0 -31
- memra-sdk/memra/discovery.py +0 -15
- memra-sdk/memra/discovery_client.py +0 -49
- memra-sdk/memra/execution.py +0 -481
- memra-sdk/memra/models.py +0 -99
- memra-sdk/memra/tool_registry.py +0 -343
- memra-sdk/memra/tool_registry_client.py +0 -106
- memra-sdk/scripts/release.py +0 -133
- memra-sdk/setup.py +0 -52
- memra-workflows/accounts_payable/accounts_payable.py +0 -215
- memra-workflows/accounts_payable/accounts_payable_client.py +0 -216
- memra-workflows/accounts_payable/accounts_payable_mcp.py +0 -200
- memra-workflows/accounts_payable/accounts_payable_smart.py +0 -221
- memra-workflows/invoice_processing/invoice_processing.py +0 -116
- memra-workflows/invoice_processing/smart_invoice_processor.py +0 -220
- memra-workflows/logic/__init__.py +0 -1
- memra-workflows/logic/file_tools.py +0 -50
- memra-workflows/logic/invoice_tools.py +0 -501
- memra-workflows/logic/propane_agents.py +0 -52
- memra-workflows/mcp_bridge_server.py +0 -230
- memra-workflows/propane_delivery/propane_delivery.py +0 -87
- memra-workflows/text_to_sql/complete_invoice_workflow_with_queries.py +0 -208
- memra-workflows/text_to_sql/complete_text_to_sql_system.py +0 -266
- memra-workflows/text_to_sql/file_discovery_demo.py +0 -156
- {memra-0.2.13.dist-info → memra-0.2.15.dist-info}/LICENSE +0 -0
- {memra-0.2.13.dist-info → memra-0.2.15.dist-info}/WHEEL +0 -0
- {memra-0.2.13.dist-info → memra-0.2.15.dist-info}/entry_points.txt +0 -0
memra-sdk/memra/tool_registry.py
DELETED
@@ -1,343 +0,0 @@
|
|
1
|
-
import importlib
|
2
|
-
import logging
|
3
|
-
import sys
|
4
|
-
import os
|
5
|
-
import httpx
|
6
|
-
from typing import Dict, Any, List, Optional, Callable
|
7
|
-
from pathlib import Path
|
8
|
-
|
9
|
-
logger = logging.getLogger(__name__)
|
10
|
-
|
11
|
-
class ToolRegistry:
|
12
|
-
"""Registry for managing and executing tools via API calls only"""
|
13
|
-
|
14
|
-
def __init__(self):
|
15
|
-
self.tools: Dict[str, Dict[str, Any]] = {}
|
16
|
-
self._register_known_tools()
|
17
|
-
|
18
|
-
def _register_known_tools(self):
|
19
|
-
"""Register known tools with their metadata (no actual implementations)"""
|
20
|
-
# Server-hosted tools (executed via Memra API)
|
21
|
-
server_tools = [
|
22
|
-
("DatabaseQueryTool", "Query database schemas and data"),
|
23
|
-
("PDFProcessor", "Process PDF files and extract content"),
|
24
|
-
("OCRTool", "Perform OCR on images and documents"),
|
25
|
-
("InvoiceExtractionWorkflow", "Extract structured data from invoices"),
|
26
|
-
("FileReader", "Read files from the filesystem"),
|
27
|
-
("FileDiscovery", "Discover and list files in directories"),
|
28
|
-
("FileCopy", "Copy files to standard processing directories"),
|
29
|
-
]
|
30
|
-
|
31
|
-
for tool_name, description in server_tools:
|
32
|
-
self.register_tool(tool_name, None, "memra", description)
|
33
|
-
|
34
|
-
# MCP-hosted tools (executed via MCP bridge)
|
35
|
-
mcp_tools = [
|
36
|
-
("DataValidator", "Validate data against schemas"),
|
37
|
-
("PostgresInsert", "Insert data into PostgreSQL database"),
|
38
|
-
("TextToSQL", "Convert natural language questions to SQL queries and execute them"),
|
39
|
-
("SQLExecutor", "Execute SQL queries against PostgreSQL database"),
|
40
|
-
("TextToSQLGenerator", "Generate SQL from natural language questions"),
|
41
|
-
]
|
42
|
-
|
43
|
-
for tool_name, description in mcp_tools:
|
44
|
-
self.register_tool(tool_name, None, "mcp", description)
|
45
|
-
|
46
|
-
logger.info(f"Registered {len(self.tools)} tool definitions")
|
47
|
-
|
48
|
-
def register_tool(self, name: str, tool_class: Optional[type], hosted_by: str, description: str):
|
49
|
-
"""Register a tool in the registry (metadata only)"""
|
50
|
-
self.tools[name] = {
|
51
|
-
"class": tool_class, # Will be None for API-based tools
|
52
|
-
"hosted_by": hosted_by,
|
53
|
-
"description": description
|
54
|
-
}
|
55
|
-
logger.debug(f"Registered tool: {name} (hosted by {hosted_by})")
|
56
|
-
|
57
|
-
def discover_tools(self, hosted_by: Optional[str] = None) -> List[Dict[str, Any]]:
|
58
|
-
"""Discover available tools, optionally filtered by host"""
|
59
|
-
tools = []
|
60
|
-
for name, info in self.tools.items():
|
61
|
-
if hosted_by is None or info["hosted_by"] == hosted_by:
|
62
|
-
tools.append({
|
63
|
-
"name": name,
|
64
|
-
"hosted_by": info["hosted_by"],
|
65
|
-
"description": info["description"]
|
66
|
-
})
|
67
|
-
return tools
|
68
|
-
|
69
|
-
def execute_tool(self, tool_name: str, hosted_by: str, input_data: Dict[str, Any],
|
70
|
-
config: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
71
|
-
"""Execute a tool - handles MCP tools via bridge, rejects direct server tool execution"""
|
72
|
-
if hosted_by == "mcp":
|
73
|
-
return self._execute_mcp_tool(tool_name, input_data, config)
|
74
|
-
else:
|
75
|
-
logger.warning(f"Direct tool execution attempted for {tool_name}. Use API client instead.")
|
76
|
-
return {
|
77
|
-
"success": False,
|
78
|
-
"error": "Direct tool execution not supported. Use API client for tool execution."
|
79
|
-
}
|
80
|
-
|
81
|
-
def _execute_mcp_tool(self, tool_name: str, input_data: Dict[str, Any],
|
82
|
-
config: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
83
|
-
"""Execute an MCP tool via the bridge"""
|
84
|
-
try:
|
85
|
-
# Debug logging
|
86
|
-
logger.info(f"Executing MCP tool {tool_name} with config: {config}")
|
87
|
-
|
88
|
-
# Get bridge configuration
|
89
|
-
if not config:
|
90
|
-
logger.error(f"MCP tool {tool_name} requires bridge configuration")
|
91
|
-
return {
|
92
|
-
"success": False,
|
93
|
-
"error": "MCP bridge configuration required"
|
94
|
-
}
|
95
|
-
|
96
|
-
bridge_url = config.get("bridge_url", "http://localhost:8081")
|
97
|
-
bridge_secret = config.get("bridge_secret")
|
98
|
-
|
99
|
-
if not bridge_secret:
|
100
|
-
logger.error(f"MCP tool {tool_name} requires bridge_secret in config")
|
101
|
-
return {
|
102
|
-
"success": False,
|
103
|
-
"error": "MCP bridge secret required"
|
104
|
-
}
|
105
|
-
|
106
|
-
# Try different endpoint patterns that might exist
|
107
|
-
endpoints_to_try = [
|
108
|
-
f"{bridge_url}/execute_tool",
|
109
|
-
f"{bridge_url}/tool/{tool_name}",
|
110
|
-
f"{bridge_url}/mcp/execute",
|
111
|
-
f"{bridge_url}/api/execute"
|
112
|
-
]
|
113
|
-
|
114
|
-
# Prepare request
|
115
|
-
payload = {
|
116
|
-
"tool_name": tool_name,
|
117
|
-
"input_data": input_data
|
118
|
-
}
|
119
|
-
|
120
|
-
headers = {
|
121
|
-
"Content-Type": "application/json",
|
122
|
-
"X-Bridge-Secret": bridge_secret
|
123
|
-
}
|
124
|
-
|
125
|
-
# Try each endpoint
|
126
|
-
logger.info(f"Executing MCP tool {tool_name} via bridge at {bridge_url}")
|
127
|
-
|
128
|
-
last_error = None
|
129
|
-
for endpoint in endpoints_to_try:
|
130
|
-
try:
|
131
|
-
with httpx.Client(timeout=60.0) as client:
|
132
|
-
response = client.post(endpoint, json=payload, headers=headers)
|
133
|
-
|
134
|
-
if response.status_code == 200:
|
135
|
-
result = response.json()
|
136
|
-
logger.info(f"MCP tool {tool_name} executed successfully via {endpoint}")
|
137
|
-
return result
|
138
|
-
elif response.status_code == 404:
|
139
|
-
continue # Try next endpoint
|
140
|
-
else:
|
141
|
-
response.raise_for_status()
|
142
|
-
|
143
|
-
except httpx.HTTPStatusError as e:
|
144
|
-
if e.response.status_code == 404:
|
145
|
-
continue # Try next endpoint
|
146
|
-
last_error = e
|
147
|
-
continue
|
148
|
-
except Exception as e:
|
149
|
-
last_error = e
|
150
|
-
continue
|
151
|
-
|
152
|
-
# If we get here, none of the endpoints worked
|
153
|
-
# For now, return mock data to keep the workflow working
|
154
|
-
logger.warning(f"MCP bridge endpoints not available, returning mock data for {tool_name}")
|
155
|
-
|
156
|
-
if tool_name == "DataValidator":
|
157
|
-
return {
|
158
|
-
"success": True,
|
159
|
-
"data": {
|
160
|
-
"is_valid": True,
|
161
|
-
"validation_errors": [],
|
162
|
-
"validated_data": input_data.get("invoice_data", {}),
|
163
|
-
"_mock": True
|
164
|
-
}
|
165
|
-
}
|
166
|
-
elif tool_name == "PostgresInsert":
|
167
|
-
return {
|
168
|
-
"success": True,
|
169
|
-
"data": {
|
170
|
-
"success": True,
|
171
|
-
"record_id": 999, # Mock ID
|
172
|
-
"database_table": "invoices",
|
173
|
-
"inserted_data": input_data.get("invoice_data", {}),
|
174
|
-
"_mock": True
|
175
|
-
}
|
176
|
-
}
|
177
|
-
elif tool_name == "FileDiscovery":
|
178
|
-
# Mock file discovery - in real implementation, would scan directories
|
179
|
-
directory = input_data.get("directory", "invoices")
|
180
|
-
file_pattern = input_data.get("pattern", "*.pdf")
|
181
|
-
|
182
|
-
# Simulate finding files in the directory
|
183
|
-
mock_files = [
|
184
|
-
{
|
185
|
-
"filename": "10352259310.PDF",
|
186
|
-
"path": f"{directory}/10352259310.PDF",
|
187
|
-
"size": "542KB",
|
188
|
-
"modified": "2024-05-28",
|
189
|
-
"type": "PDF"
|
190
|
-
}
|
191
|
-
]
|
192
|
-
|
193
|
-
return {
|
194
|
-
"success": True,
|
195
|
-
"data": {
|
196
|
-
"directory": directory,
|
197
|
-
"pattern": file_pattern,
|
198
|
-
"files_found": len(mock_files),
|
199
|
-
"files": mock_files,
|
200
|
-
"message": f"Found {len(mock_files)} files in {directory}/ directory"
|
201
|
-
}
|
202
|
-
}
|
203
|
-
|
204
|
-
elif tool_name == "FileCopy":
|
205
|
-
# Mock file copy - in real implementation, would copy files
|
206
|
-
source_path = input_data.get("source_path", "")
|
207
|
-
destination_dir = input_data.get("destination_dir", "invoices")
|
208
|
-
|
209
|
-
if not source_path:
|
210
|
-
return {
|
211
|
-
"success": False,
|
212
|
-
"error": "Source path is required"
|
213
|
-
}
|
214
|
-
|
215
|
-
# Extract filename from path
|
216
|
-
import os
|
217
|
-
filename = os.path.basename(source_path)
|
218
|
-
destination_path = f"{destination_dir}/{filename}"
|
219
|
-
|
220
|
-
return {
|
221
|
-
"success": True,
|
222
|
-
"data": {
|
223
|
-
"source_path": source_path,
|
224
|
-
"destination_path": destination_path,
|
225
|
-
"message": f"File copied from {source_path} to {destination_path}",
|
226
|
-
"file_size": "245KB",
|
227
|
-
"operation": "copy_completed"
|
228
|
-
}
|
229
|
-
}
|
230
|
-
elif tool_name == "TextToSQL":
|
231
|
-
# Mock text-to-SQL - in real implementation, would use LLM to generate SQL
|
232
|
-
question = input_data.get("question", "")
|
233
|
-
schema = input_data.get("schema", {})
|
234
|
-
|
235
|
-
if not question:
|
236
|
-
return {
|
237
|
-
"success": False,
|
238
|
-
"error": "Question is required for text-to-SQL conversion"
|
239
|
-
}
|
240
|
-
|
241
|
-
# Simulate SQL generation and execution
|
242
|
-
mock_sql = "SELECT vendor_name, invoice_number, total_amount FROM invoices WHERE vendor_name ILIKE '%air liquide%' ORDER BY invoice_date DESC LIMIT 5;"
|
243
|
-
mock_results = [
|
244
|
-
{
|
245
|
-
"vendor_name": "Air Liquide Canada Inc.",
|
246
|
-
"invoice_number": "INV-12345",
|
247
|
-
"total_amount": 1234.56
|
248
|
-
},
|
249
|
-
{
|
250
|
-
"vendor_name": "Air Liquide Canada Inc.",
|
251
|
-
"invoice_number": "INV-67890",
|
252
|
-
"total_amount": 2345.67
|
253
|
-
}
|
254
|
-
]
|
255
|
-
|
256
|
-
return {
|
257
|
-
"success": True,
|
258
|
-
"data": {
|
259
|
-
"question": question,
|
260
|
-
"generated_sql": mock_sql,
|
261
|
-
"results": mock_results,
|
262
|
-
"row_count": len(mock_results),
|
263
|
-
"message": f"Found {len(mock_results)} results for: {question}",
|
264
|
-
"_mock": True
|
265
|
-
}
|
266
|
-
}
|
267
|
-
elif tool_name == "SQLExecutor":
|
268
|
-
# Mock SQL execution
|
269
|
-
sql_query = input_data.get("sql_query", "")
|
270
|
-
|
271
|
-
if not sql_query:
|
272
|
-
return {
|
273
|
-
"success": False,
|
274
|
-
"error": "SQL query is required"
|
275
|
-
}
|
276
|
-
|
277
|
-
# Mock results based on query type
|
278
|
-
if sql_query.upper().startswith("SELECT"):
|
279
|
-
mock_results = [
|
280
|
-
{"vendor_name": "Air Liquide Canada Inc.", "invoice_number": "INV-12345", "total_amount": 1234.56},
|
281
|
-
{"vendor_name": "Air Liquide Canada Inc.", "invoice_number": "INV-67890", "total_amount": 2345.67}
|
282
|
-
]
|
283
|
-
return {
|
284
|
-
"success": True,
|
285
|
-
"data": {
|
286
|
-
"query": sql_query,
|
287
|
-
"results": mock_results,
|
288
|
-
"row_count": len(mock_results),
|
289
|
-
"columns": ["vendor_name", "invoice_number", "total_amount"],
|
290
|
-
"_mock": True
|
291
|
-
}
|
292
|
-
}
|
293
|
-
else:
|
294
|
-
return {
|
295
|
-
"success": True,
|
296
|
-
"data": {
|
297
|
-
"query": sql_query,
|
298
|
-
"affected_rows": 1,
|
299
|
-
"message": "Query executed successfully",
|
300
|
-
"_mock": True
|
301
|
-
}
|
302
|
-
}
|
303
|
-
elif tool_name == "TextToSQLGenerator":
|
304
|
-
# Mock SQL generation
|
305
|
-
question = input_data.get("question", "")
|
306
|
-
|
307
|
-
if not question:
|
308
|
-
return {
|
309
|
-
"success": False,
|
310
|
-
"error": "Question is required for SQL generation"
|
311
|
-
}
|
312
|
-
|
313
|
-
# Generate mock SQL based on question
|
314
|
-
mock_sql = "SELECT * FROM invoices WHERE vendor_name ILIKE '%air liquide%'"
|
315
|
-
|
316
|
-
return {
|
317
|
-
"success": True,
|
318
|
-
"data": {
|
319
|
-
"question": question,
|
320
|
-
"generated_sql": mock_sql,
|
321
|
-
"explanation": "Generated SQL query based on natural language question",
|
322
|
-
"confidence": "medium",
|
323
|
-
"_mock": True
|
324
|
-
}
|
325
|
-
}
|
326
|
-
else:
|
327
|
-
return {
|
328
|
-
"success": False,
|
329
|
-
"error": f"MCP bridge not available and no mock data for {tool_name}"
|
330
|
-
}
|
331
|
-
|
332
|
-
except httpx.TimeoutException:
|
333
|
-
logger.error(f"MCP tool {tool_name} execution timed out")
|
334
|
-
return {
|
335
|
-
"success": False,
|
336
|
-
"error": f"MCP tool execution timed out after 60 seconds"
|
337
|
-
}
|
338
|
-
except Exception as e:
|
339
|
-
logger.error(f"MCP tool execution failed for {tool_name}: {str(e)}")
|
340
|
-
return {
|
341
|
-
"success": False,
|
342
|
-
"error": str(e)
|
343
|
-
}
|
@@ -1,106 +0,0 @@
|
|
1
|
-
import httpx
|
2
|
-
import logging
|
3
|
-
import os
|
4
|
-
from typing import Dict, Any, List, Optional
|
5
|
-
import asyncio
|
6
|
-
|
7
|
-
logger = logging.getLogger(__name__)
|
8
|
-
|
9
|
-
class ToolRegistryClient:
|
10
|
-
"""Client-side registry that calls Memra API for tool execution"""
|
11
|
-
|
12
|
-
def __init__(self):
|
13
|
-
self.api_base = os.getenv("MEMRA_API_URL", "https://api.memra.co")
|
14
|
-
self.api_key = os.getenv("MEMRA_API_KEY")
|
15
|
-
self.tools_cache = None
|
16
|
-
|
17
|
-
if not self.api_key:
|
18
|
-
raise ValueError(
|
19
|
-
"MEMRA_API_KEY environment variable is required. "
|
20
|
-
"Please contact info@memra.co for an API key."
|
21
|
-
)
|
22
|
-
|
23
|
-
def discover_tools(self, hosted_by: Optional[str] = None) -> List[Dict[str, Any]]:
|
24
|
-
"""Discover available tools from the API"""
|
25
|
-
try:
|
26
|
-
# Use sync httpx for compatibility with existing sync code
|
27
|
-
with httpx.Client(timeout=30.0) as client:
|
28
|
-
response = client.get(
|
29
|
-
f"{self.api_base}/tools/discover",
|
30
|
-
headers={"X-API-Key": self.api_key}
|
31
|
-
)
|
32
|
-
response.raise_for_status()
|
33
|
-
|
34
|
-
data = response.json()
|
35
|
-
tools = data.get("tools", [])
|
36
|
-
|
37
|
-
# Filter by hosted_by if specified
|
38
|
-
if hosted_by:
|
39
|
-
tools = [t for t in tools if t.get("hosted_by") == hosted_by]
|
40
|
-
|
41
|
-
self.tools_cache = tools
|
42
|
-
logger.info(f"Discovered {len(tools)} tools from API")
|
43
|
-
return tools
|
44
|
-
|
45
|
-
except Exception as e:
|
46
|
-
logger.error(f"Failed to discover tools from API: {e}")
|
47
|
-
# Return empty list if API is unavailable
|
48
|
-
return []
|
49
|
-
|
50
|
-
def execute_tool(self, tool_name: str, hosted_by: str, input_data: Dict[str, Any],
|
51
|
-
config: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
52
|
-
"""Execute a tool via the API"""
|
53
|
-
try:
|
54
|
-
logger.info(f"Executing tool {tool_name} via API")
|
55
|
-
|
56
|
-
# Prepare request payload
|
57
|
-
payload = {
|
58
|
-
"tool_name": tool_name,
|
59
|
-
"hosted_by": hosted_by,
|
60
|
-
"input_data": input_data,
|
61
|
-
"config": config
|
62
|
-
}
|
63
|
-
|
64
|
-
# Make API call
|
65
|
-
with httpx.Client(timeout=120.0) as client: # Longer timeout for tool execution
|
66
|
-
response = client.post(
|
67
|
-
f"{self.api_base}/tools/execute",
|
68
|
-
headers={
|
69
|
-
"X-API-Key": self.api_key,
|
70
|
-
"Content-Type": "application/json"
|
71
|
-
},
|
72
|
-
json=payload
|
73
|
-
)
|
74
|
-
response.raise_for_status()
|
75
|
-
|
76
|
-
result = response.json()
|
77
|
-
logger.info(f"Tool {tool_name} executed successfully via API")
|
78
|
-
return result
|
79
|
-
|
80
|
-
except httpx.TimeoutException:
|
81
|
-
logger.error(f"Tool {tool_name} execution timed out")
|
82
|
-
return {
|
83
|
-
"success": False,
|
84
|
-
"error": f"Tool execution timed out after 120 seconds"
|
85
|
-
}
|
86
|
-
except httpx.HTTPStatusError as e:
|
87
|
-
logger.error(f"API error for tool {tool_name}: {e.response.status_code}")
|
88
|
-
return {
|
89
|
-
"success": False,
|
90
|
-
"error": f"API error: {e.response.status_code} - {e.response.text}"
|
91
|
-
}
|
92
|
-
except Exception as e:
|
93
|
-
logger.error(f"Tool execution failed for {tool_name}: {str(e)}")
|
94
|
-
return {
|
95
|
-
"success": False,
|
96
|
-
"error": str(e)
|
97
|
-
}
|
98
|
-
|
99
|
-
def health_check(self) -> bool:
|
100
|
-
"""Check if the API is available"""
|
101
|
-
try:
|
102
|
-
with httpx.Client(timeout=10.0) as client:
|
103
|
-
response = client.get(f"{self.api_base}/health")
|
104
|
-
return response.status_code == 200
|
105
|
-
except:
|
106
|
-
return False
|
memra-sdk/scripts/release.py
DELETED
@@ -1,133 +0,0 @@
|
|
1
|
-
#!/usr/bin/env python3
|
2
|
-
"""
|
3
|
-
Release script for Memra SDK
|
4
|
-
Builds and uploads the package to PyPI
|
5
|
-
"""
|
6
|
-
|
7
|
-
import os
|
8
|
-
import sys
|
9
|
-
import subprocess
|
10
|
-
import shutil
|
11
|
-
from pathlib import Path
|
12
|
-
|
13
|
-
def run_command(cmd, description):
|
14
|
-
"""Run a command and handle errors"""
|
15
|
-
print(f"🔄 {description}...")
|
16
|
-
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
|
17
|
-
if result.returncode != 0:
|
18
|
-
print(f"❌ {description} failed:")
|
19
|
-
print(result.stderr)
|
20
|
-
sys.exit(1)
|
21
|
-
print(f"✅ {description} completed")
|
22
|
-
return result.stdout
|
23
|
-
|
24
|
-
def clean_build_artifacts():
|
25
|
-
"""Clean up build artifacts"""
|
26
|
-
print("🧹 Cleaning build artifacts...")
|
27
|
-
|
28
|
-
# Remove build directories
|
29
|
-
for dir_name in ['build', 'dist', 'memra.egg-info']:
|
30
|
-
if os.path.exists(dir_name):
|
31
|
-
shutil.rmtree(dir_name)
|
32
|
-
print(f" Removed {dir_name}/")
|
33
|
-
|
34
|
-
# Remove __pycache__ directories
|
35
|
-
for root, dirs, files in os.walk('.'):
|
36
|
-
for dir_name in dirs:
|
37
|
-
if dir_name == '__pycache__':
|
38
|
-
pycache_path = os.path.join(root, dir_name)
|
39
|
-
shutil.rmtree(pycache_path)
|
40
|
-
print(f" Removed {pycache_path}")
|
41
|
-
|
42
|
-
print("✅ Build artifacts cleaned")
|
43
|
-
|
44
|
-
def run_tests():
|
45
|
-
"""Run tests before release"""
|
46
|
-
print("🧪 Running tests...")
|
47
|
-
|
48
|
-
# Check if pytest is available
|
49
|
-
try:
|
50
|
-
subprocess.run(['pytest', '--version'], check=True, capture_output=True)
|
51
|
-
run_command('pytest tests/', 'Running pytest')
|
52
|
-
except (subprocess.CalledProcessError, FileNotFoundError):
|
53
|
-
print("⚠️ pytest not found, skipping tests")
|
54
|
-
|
55
|
-
# Run basic import test
|
56
|
-
run_command('python -c "import memra; print(f\'Memra SDK version: {memra.__version__ if hasattr(memra, \"__version__\") else \"unknown\"}\')"', 'Testing basic import')
|
57
|
-
|
58
|
-
def build_package():
|
59
|
-
"""Build the package"""
|
60
|
-
print("📦 Building package...")
|
61
|
-
|
62
|
-
# Install build dependencies
|
63
|
-
run_command('pip install build twine', 'Installing build tools')
|
64
|
-
|
65
|
-
# Build the package
|
66
|
-
run_command('python -m build', 'Building wheel and source distribution')
|
67
|
-
|
68
|
-
# Check the package
|
69
|
-
run_command('twine check dist/*', 'Checking package')
|
70
|
-
|
71
|
-
def upload_package(test=False):
|
72
|
-
"""Upload package to PyPI"""
|
73
|
-
if test:
|
74
|
-
print("🚀 Uploading to Test PyPI...")
|
75
|
-
run_command('twine upload --repository testpypi dist/*', 'Uploading to Test PyPI')
|
76
|
-
print("📍 Package uploaded to Test PyPI: https://test.pypi.org/project/memra/")
|
77
|
-
else:
|
78
|
-
print("🚀 Uploading to PyPI...")
|
79
|
-
run_command('twine upload dist/*', 'Uploading to PyPI')
|
80
|
-
print("📍 Package uploaded to PyPI: https://pypi.org/project/memra/")
|
81
|
-
|
82
|
-
def main():
|
83
|
-
"""Main release process"""
|
84
|
-
print("🎯 Memra SDK Release Process")
|
85
|
-
print("=" * 40)
|
86
|
-
|
87
|
-
# Parse arguments
|
88
|
-
test_release = '--test' in sys.argv
|
89
|
-
skip_tests = '--skip-tests' in sys.argv
|
90
|
-
|
91
|
-
if test_release:
|
92
|
-
print("🧪 Test release mode enabled")
|
93
|
-
|
94
|
-
# Ensure we're in the right directory
|
95
|
-
if not os.path.exists('setup.py'):
|
96
|
-
print("❌ setup.py not found. Please run from the project root.")
|
97
|
-
sys.exit(1)
|
98
|
-
|
99
|
-
try:
|
100
|
-
# Clean up
|
101
|
-
clean_build_artifacts()
|
102
|
-
|
103
|
-
# Run tests
|
104
|
-
if not skip_tests:
|
105
|
-
run_tests()
|
106
|
-
else:
|
107
|
-
print("⚠️ Skipping tests")
|
108
|
-
|
109
|
-
# Build package
|
110
|
-
build_package()
|
111
|
-
|
112
|
-
# Upload package
|
113
|
-
upload_package(test=test_release)
|
114
|
-
|
115
|
-
print("\n🎉 Release completed successfully!")
|
116
|
-
|
117
|
-
if test_release:
|
118
|
-
print("\n📋 Next steps:")
|
119
|
-
print("1. Test the package: pip install -i https://test.pypi.org/simple/ memra")
|
120
|
-
print("2. If everything works, run: python scripts/release.py")
|
121
|
-
else:
|
122
|
-
print("\n📋 Package is now available on PyPI!")
|
123
|
-
print("Install with: pip install memra")
|
124
|
-
|
125
|
-
except KeyboardInterrupt:
|
126
|
-
print("\n❌ Release cancelled by user")
|
127
|
-
sys.exit(1)
|
128
|
-
except Exception as e:
|
129
|
-
print(f"\n❌ Release failed: {e}")
|
130
|
-
sys.exit(1)
|
131
|
-
|
132
|
-
if __name__ == '__main__':
|
133
|
-
main()
|
memra-sdk/setup.py
DELETED
@@ -1,52 +0,0 @@
|
|
1
|
-
from setuptools import setup, find_packages
|
2
|
-
|
3
|
-
with open("README.md", "r", encoding="utf-8") as fh:
|
4
|
-
long_description = fh.read()
|
5
|
-
|
6
|
-
setup(
|
7
|
-
name="memra",
|
8
|
-
version="0.2.4",
|
9
|
-
author="Memra",
|
10
|
-
author_email="support@memra.com",
|
11
|
-
description="Declarative framework for enterprise workflows with MCP integration - Client SDK",
|
12
|
-
long_description=long_description,
|
13
|
-
long_description_content_type="text/markdown",
|
14
|
-
url="https://github.com/memra/memra-sdk",
|
15
|
-
packages=find_packages(include=['memra', 'memra.*']),
|
16
|
-
classifiers=[
|
17
|
-
"Development Status :: 3 - Alpha",
|
18
|
-
"Intended Audience :: Developers",
|
19
|
-
"License :: OSI Approved :: MIT License",
|
20
|
-
"Operating System :: OS Independent",
|
21
|
-
"Programming Language :: Python :: 3",
|
22
|
-
"Programming Language :: Python :: 3.8",
|
23
|
-
"Programming Language :: Python :: 3.9",
|
24
|
-
"Programming Language :: Python :: 3.10",
|
25
|
-
"Programming Language :: Python :: 3.11",
|
26
|
-
],
|
27
|
-
python_requires=">=3.8",
|
28
|
-
install_requires=[
|
29
|
-
"pydantic>=1.8.0",
|
30
|
-
"httpx>=0.24.0",
|
31
|
-
"typing-extensions>=4.0.0",
|
32
|
-
"aiohttp>=3.8.0",
|
33
|
-
"aiohttp-cors>=0.7.0",
|
34
|
-
],
|
35
|
-
extras_require={
|
36
|
-
"dev": [
|
37
|
-
"pytest>=6.0",
|
38
|
-
"pytest-asyncio",
|
39
|
-
"black",
|
40
|
-
"flake8",
|
41
|
-
],
|
42
|
-
"mcp": [
|
43
|
-
"psycopg2-binary>=2.9.0",
|
44
|
-
],
|
45
|
-
},
|
46
|
-
entry_points={
|
47
|
-
"console_scripts": [
|
48
|
-
"memra=memra.cli:main",
|
49
|
-
],
|
50
|
-
},
|
51
|
-
)
|
52
|
-
|