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.
Files changed (62) hide show
  1. memra/cli.py +322 -51
  2. {memra-0.2.13.dist-info → memra-0.2.15.dist-info}/METADATA +1 -1
  3. {memra-0.2.13.dist-info → memra-0.2.15.dist-info}/RECORD +7 -61
  4. memra-0.2.15.dist-info/top_level.txt +1 -0
  5. memra-0.2.13.dist-info/top_level.txt +0 -4
  6. memra-ops/app.py +0 -808
  7. memra-ops/config/config.py +0 -25
  8. memra-ops/config.py +0 -34
  9. memra-ops/logic/__init__.py +0 -1
  10. memra-ops/logic/file_tools.py +0 -43
  11. memra-ops/logic/invoice_tools.py +0 -668
  12. memra-ops/logic/invoice_tools_fix.py +0 -66
  13. memra-ops/mcp_bridge_server.py +0 -1178
  14. memra-ops/scripts/check_database.py +0 -37
  15. memra-ops/scripts/clear_database.py +0 -48
  16. memra-ops/scripts/monitor_database.py +0 -67
  17. memra-ops/scripts/release.py +0 -133
  18. memra-ops/scripts/reset_database.py +0 -65
  19. memra-ops/scripts/start_memra.py +0 -334
  20. memra-ops/scripts/stop_memra.py +0 -132
  21. memra-ops/server_tool_registry.py +0 -190
  22. memra-ops/tests/test_llm_text_to_sql.py +0 -115
  23. memra-ops/tests/test_llm_vs_pattern.py +0 -130
  24. memra-ops/tests/test_mcp_schema_aware.py +0 -124
  25. memra-ops/tests/test_schema_aware_sql.py +0 -139
  26. memra-ops/tests/test_schema_aware_sql_simple.py +0 -66
  27. memra-ops/tests/test_text_to_sql_demo.py +0 -140
  28. memra-ops/tools/mcp_bridge_server.py +0 -851
  29. memra-sdk/examples/accounts_payable.py +0 -215
  30. memra-sdk/examples/accounts_payable_client.py +0 -217
  31. memra-sdk/examples/accounts_payable_mcp.py +0 -200
  32. memra-sdk/examples/ask_questions.py +0 -123
  33. memra-sdk/examples/invoice_processing.py +0 -116
  34. memra-sdk/examples/propane_delivery.py +0 -87
  35. memra-sdk/examples/simple_text_to_sql.py +0 -158
  36. memra-sdk/memra/__init__.py +0 -31
  37. memra-sdk/memra/discovery.py +0 -15
  38. memra-sdk/memra/discovery_client.py +0 -49
  39. memra-sdk/memra/execution.py +0 -481
  40. memra-sdk/memra/models.py +0 -99
  41. memra-sdk/memra/tool_registry.py +0 -343
  42. memra-sdk/memra/tool_registry_client.py +0 -106
  43. memra-sdk/scripts/release.py +0 -133
  44. memra-sdk/setup.py +0 -52
  45. memra-workflows/accounts_payable/accounts_payable.py +0 -215
  46. memra-workflows/accounts_payable/accounts_payable_client.py +0 -216
  47. memra-workflows/accounts_payable/accounts_payable_mcp.py +0 -200
  48. memra-workflows/accounts_payable/accounts_payable_smart.py +0 -221
  49. memra-workflows/invoice_processing/invoice_processing.py +0 -116
  50. memra-workflows/invoice_processing/smart_invoice_processor.py +0 -220
  51. memra-workflows/logic/__init__.py +0 -1
  52. memra-workflows/logic/file_tools.py +0 -50
  53. memra-workflows/logic/invoice_tools.py +0 -501
  54. memra-workflows/logic/propane_agents.py +0 -52
  55. memra-workflows/mcp_bridge_server.py +0 -230
  56. memra-workflows/propane_delivery/propane_delivery.py +0 -87
  57. memra-workflows/text_to_sql/complete_invoice_workflow_with_queries.py +0 -208
  58. memra-workflows/text_to_sql/complete_text_to_sql_system.py +0 -266
  59. memra-workflows/text_to_sql/file_discovery_demo.py +0 -156
  60. {memra-0.2.13.dist-info → memra-0.2.15.dist-info}/LICENSE +0 -0
  61. {memra-0.2.13.dist-info → memra-0.2.15.dist-info}/WHEEL +0 -0
  62. {memra-0.2.13.dist-info → memra-0.2.15.dist-info}/entry_points.txt +0 -0
@@ -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
@@ -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
-