graphxr-database-proxy 1.0.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.
- graphxr_database_proxy/__init__.py +16 -0
- graphxr_database_proxy/api/__init__.py +1 -0
- graphxr_database_proxy/api/database.py +266 -0
- graphxr_database_proxy/api/google.py +437 -0
- graphxr_database_proxy/api/projects.py +99 -0
- graphxr_database_proxy/common/util.py +38 -0
- graphxr_database_proxy/drivers/__init__.py +1 -0
- graphxr_database_proxy/drivers/base.py +56 -0
- graphxr_database_proxy/drivers/factory.py +36 -0
- graphxr_database_proxy/drivers/spanner.py +814 -0
- graphxr_database_proxy/main.py +110 -0
- graphxr_database_proxy/models/__init__.py +1 -0
- graphxr_database_proxy/models/google.py +50 -0
- graphxr_database_proxy/models/project.py +170 -0
- graphxr_database_proxy/proxy.py +495 -0
- graphxr_database_proxy/proxyForDev.py +290 -0
- graphxr_database_proxy/services/__init__.py +1 -0
- graphxr_database_proxy/services/project_service.py +150 -0
- graphxr_database_proxy/static/favicon.ico +0 -0
- graphxr_database_proxy/static/index.html +1 -0
- graphxr_database_proxy/static/main.7391ee9773c403483393.css +175 -0
- graphxr_database_proxy/static/main.7391ee9773c403483393.css.map +1 -0
- graphxr_database_proxy/static/main.ce3fbb85a7bc9452edb9.js +2 -0
- graphxr_database_proxy/static/main.ce3fbb85a7bc9452edb9.js.map +1 -0
- graphxr_database_proxy/static/vendors.70542a99f336a8021013.js +3 -0
- graphxr_database_proxy/static/vendors.70542a99f336a8021013.js.LICENSE.txt +95 -0
- graphxr_database_proxy/static/vendors.70542a99f336a8021013.js.map +1 -0
- graphxr_database_proxy-1.0.0.dist-info/METADATA +180 -0
- graphxr_database_proxy-1.0.0.dist-info/RECORD +33 -0
- graphxr_database_proxy-1.0.0.dist-info/WHEEL +5 -0
- graphxr_database_proxy-1.0.0.dist-info/entry_points.txt +2 -0
- graphxr_database_proxy-1.0.0.dist-info/licenses/LICENSE +21 -0
- graphxr_database_proxy-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
"""
|
|
2
|
+
DatabaseProxy - High-level interface for GraphXR Database Proxy
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import uvicorn
|
|
7
|
+
import json
|
|
8
|
+
import uuid
|
|
9
|
+
import os
|
|
10
|
+
from typing import Dict, Any, Optional, List
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
from .main import app
|
|
15
|
+
from .models.project import Project, DatabaseConfig, DatabaseType, AuthType, OAuthConfig
|
|
16
|
+
from .services.project_service import ProjectService
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class DatabaseProxy:
|
|
20
|
+
"""
|
|
21
|
+
High-level interface for GraphXR Database Proxy
|
|
22
|
+
|
|
23
|
+
This class provides a simplified Python API for configuring and running
|
|
24
|
+
the GraphXR Database Proxy with Service Account authentication.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self, config_dir: str = "config"):
|
|
28
|
+
"""
|
|
29
|
+
Initialize DatabaseProxy
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
config_dir: Directory to store configuration files
|
|
33
|
+
"""
|
|
34
|
+
self.project_service = ProjectService(config_dir)
|
|
35
|
+
self.projects: Dict[str, Project] = {}
|
|
36
|
+
self._app = app
|
|
37
|
+
self._load_existing_projects()
|
|
38
|
+
|
|
39
|
+
def _load_existing_projects(self) -> None:
|
|
40
|
+
"""Load existing projects from storage"""
|
|
41
|
+
try:
|
|
42
|
+
# This would load from the project service if needed
|
|
43
|
+
pass
|
|
44
|
+
except Exception as e:
|
|
45
|
+
print(f"Warning: Could not load existing projects: {e}")
|
|
46
|
+
|
|
47
|
+
def add_project(
|
|
48
|
+
self,
|
|
49
|
+
project_name: Optional[str] = None,
|
|
50
|
+
database_type: str = "spanner",
|
|
51
|
+
project_id: Optional[str] = None,
|
|
52
|
+
instance_id: Optional[str] = None,
|
|
53
|
+
database_id: Optional[str] = None,
|
|
54
|
+
credentials: Optional[str] = None,
|
|
55
|
+
graph_name: Optional[str] = None,
|
|
56
|
+
**kwargs
|
|
57
|
+
) -> str:
|
|
58
|
+
"""
|
|
59
|
+
Add a database project configuration
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
project_name: Name of the project (uses env var PROJECT_NAME if not provided)
|
|
63
|
+
database_type: Database type ("spanner", "neo4j", "postgresql", etc.)
|
|
64
|
+
project_id: Database project ID (for Spanner: GCP Project ID, uses env var SPANNER_PROJECT_ID if not provided)
|
|
65
|
+
instance_id: Database instance ID (for Spanner: uses env var SPANNER_INSTANCE_ID if not provided)
|
|
66
|
+
database_id: Database ID (for Spanner: uses env var SPANNER_DATABASE_ID if not provided)
|
|
67
|
+
credentials: Authentication credentials - either:
|
|
68
|
+
- Path to credential file (e.g., "/path/to/service-account.json")
|
|
69
|
+
- Credential JSON string (e.g., '{"type": "service_account", ...}')
|
|
70
|
+
Uses env var SPANNER_CREDENTIALS_PATH if not provided
|
|
71
|
+
graph_name: Optional graph name (uses env var SPANNER_GRAPH_NAME if not provided)
|
|
72
|
+
**kwargs: Additional database-specific configuration parameters
|
|
73
|
+
|
|
74
|
+
Environment Variables:
|
|
75
|
+
PROJECT_NAME: Default project name
|
|
76
|
+
SPANNER_PROJECT_ID: Default GCP project ID
|
|
77
|
+
SPANNER_INSTANCE_ID: Default Spanner instance ID
|
|
78
|
+
SPANNER_DATABASE_ID: Default Spanner database ID
|
|
79
|
+
SPANNER_CREDENTIALS_PATH: Default path to service account JSON file
|
|
80
|
+
SPANNER_GRAPH_NAME: Default graph name
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
Internal Project ID
|
|
84
|
+
|
|
85
|
+
Example 1 (Spanner with file path):
|
|
86
|
+
proxy.add_project(
|
|
87
|
+
project_name="MySpannerProject",
|
|
88
|
+
database_type="spanner",
|
|
89
|
+
project_id="your-gcp-project-id",
|
|
90
|
+
instance_id="your-spanner-instance-id",
|
|
91
|
+
database_id="your-database-id",
|
|
92
|
+
credentials="/path/to/your/service-account.json"
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
Example 2 (Spanner with JSON string):
|
|
96
|
+
proxy.add_project(
|
|
97
|
+
project_name="MySpannerProject",
|
|
98
|
+
database_type="spanner",
|
|
99
|
+
project_id="your-gcp-project-id",
|
|
100
|
+
instance_id="your-spanner-instance-id",
|
|
101
|
+
database_id="your-database-id",
|
|
102
|
+
credentials='{"type": "service_account"}'
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
Example 3 (Using environment variables):
|
|
106
|
+
# Set environment variables first
|
|
107
|
+
# os.environ['PROJECT_NAME'] = 'MySpannerProject'
|
|
108
|
+
# os.environ['SPANNER_PROJECT_ID'] = 'your-gcp-project-id'
|
|
109
|
+
# os.environ['SPANNER_INSTANCE_ID'] = 'your-spanner-instance-id'
|
|
110
|
+
# os.environ['SPANNER_DATABASE_ID'] = 'your-database-id'
|
|
111
|
+
# os.environ['SPANNER_CREDENTIALS_PATH'] = '/path/to/service-account.json'
|
|
112
|
+
# os.environ['SPANNER_GRAPH_NAME'] = 'graph'
|
|
113
|
+
proxy.add_project() # No parameters needed, all from environment variables
|
|
114
|
+
|
|
115
|
+
Example 4 (Future: Neo4j):
|
|
116
|
+
proxy.add_project(
|
|
117
|
+
project_name="MyNeo4jProject",
|
|
118
|
+
database_type="neo4j",
|
|
119
|
+
credentials="neo4j://username:password@localhost:7687",
|
|
120
|
+
graph_name="graph"
|
|
121
|
+
)
|
|
122
|
+
"""
|
|
123
|
+
# Use environment variables as defaults
|
|
124
|
+
project_name = project_name or os.getenv('PROJECT_NAME')
|
|
125
|
+
project_id = project_id or os.getenv('SPANNER_PROJECT_ID')
|
|
126
|
+
instance_id = instance_id or os.getenv('SPANNER_INSTANCE_ID')
|
|
127
|
+
database_id = database_id or os.getenv('SPANNER_DATABASE_ID')
|
|
128
|
+
credentials = credentials or os.getenv('SPANNER_CREDENTIALS_PATH')
|
|
129
|
+
graph_name = graph_name or os.getenv('SPANNER_GRAPH_NAME')
|
|
130
|
+
|
|
131
|
+
# Validate required parameters
|
|
132
|
+
if not project_name:
|
|
133
|
+
raise ValueError("project_name is required (either as parameter or PROJECT_NAME environment variable)")
|
|
134
|
+
if not project_id:
|
|
135
|
+
raise ValueError("project_id is required (either as parameter or SPANNER_PROJECT_ID environment variable)")
|
|
136
|
+
|
|
137
|
+
# Handle different database types
|
|
138
|
+
if database_type.lower() == "spanner":
|
|
139
|
+
return self._add_spanner_project(
|
|
140
|
+
project_name=project_name,
|
|
141
|
+
project_id=project_id,
|
|
142
|
+
instance_id=instance_id,
|
|
143
|
+
database_id=database_id,
|
|
144
|
+
credentials=credentials,
|
|
145
|
+
graph_name=graph_name,
|
|
146
|
+
**kwargs
|
|
147
|
+
)
|
|
148
|
+
else:
|
|
149
|
+
# Future database types can be added here
|
|
150
|
+
raise NotImplementedError(f"Database type '{database_type}' is not yet supported. Currently supported: spanner")
|
|
151
|
+
|
|
152
|
+
def _add_spanner_project(
|
|
153
|
+
self,
|
|
154
|
+
project_name: str,
|
|
155
|
+
project_id: Optional[str],
|
|
156
|
+
instance_id: Optional[str],
|
|
157
|
+
database_id: Optional[str],
|
|
158
|
+
credentials: Optional[str],
|
|
159
|
+
graph_name: Optional[str] = None,
|
|
160
|
+
**kwargs
|
|
161
|
+
) -> str:
|
|
162
|
+
"""Internal method to add Spanner project configuration"""
|
|
163
|
+
|
|
164
|
+
# Validate required Spanner parameters
|
|
165
|
+
if not project_id:
|
|
166
|
+
raise ValueError("project_id is required for Spanner projects")
|
|
167
|
+
if not instance_id:
|
|
168
|
+
raise ValueError("instance_id is required for Spanner projects (use SPANNER_INSTANCE_ID environment variable or parameter)")
|
|
169
|
+
if not database_id:
|
|
170
|
+
raise ValueError("database_id is required for Spanner projects (use SPANNER_DATABASE_ID environment variable or parameter)")
|
|
171
|
+
if not credentials:
|
|
172
|
+
raise ValueError("credentials is required for Spanner projects (use SPANNER_CREDENTIALS_PATH environment variable or parameter)")
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
# Validate required parameters for Spanner
|
|
176
|
+
if not all([project_id, instance_id, database_id, credentials]):
|
|
177
|
+
raise ValueError("For Spanner projects, project_id, instance_id, database_id, and credentials are required")
|
|
178
|
+
|
|
179
|
+
# Determine if credentials is a file path or JSON string
|
|
180
|
+
service_account_data = None
|
|
181
|
+
credentials_source = None
|
|
182
|
+
|
|
183
|
+
# Try to parse as JSON first
|
|
184
|
+
try:
|
|
185
|
+
service_account_data = json.loads(credentials)
|
|
186
|
+
credentials_source = "json_string"
|
|
187
|
+
print(f"✓ Using Service Account JSON string")
|
|
188
|
+
except json.JSONDecodeError:
|
|
189
|
+
# Not JSON, treat as file path
|
|
190
|
+
creds_path = Path(credentials)
|
|
191
|
+
if not creds_path.exists():
|
|
192
|
+
raise FileNotFoundError(f"Service account file not found: {credentials}")
|
|
193
|
+
|
|
194
|
+
try:
|
|
195
|
+
with open(creds_path, 'r') as f:
|
|
196
|
+
service_account_data = json.load(f)
|
|
197
|
+
credentials_source = "file_path"
|
|
198
|
+
print(f"✓ Using Service Account file: {creds_path.absolute()}")
|
|
199
|
+
except json.JSONDecodeError:
|
|
200
|
+
raise ValueError(f"Invalid JSON in service account file: {credentials}")
|
|
201
|
+
|
|
202
|
+
# Validate required fields
|
|
203
|
+
required_fields = ['type', 'project_id', 'private_key', 'client_email']
|
|
204
|
+
for field in required_fields:
|
|
205
|
+
if field not in service_account_data:
|
|
206
|
+
raise ValueError(f"Missing required field in service account JSON: {field}")
|
|
207
|
+
|
|
208
|
+
# Create OAuth config with service account data
|
|
209
|
+
oauth_config = OAuthConfig(**service_account_data)
|
|
210
|
+
|
|
211
|
+
# Create database configuration
|
|
212
|
+
database_config = DatabaseConfig(
|
|
213
|
+
type=DatabaseType.SPANNER,
|
|
214
|
+
project_id=project_id,
|
|
215
|
+
instance_id=instance_id,
|
|
216
|
+
database_id=database_id,
|
|
217
|
+
graph_name=graph_name,
|
|
218
|
+
auth_type=AuthType.SERVICE_ACCOUNT,
|
|
219
|
+
oauth_config=oauth_config,
|
|
220
|
+
service_account_path=credentials if credentials_source == "file_path" else None
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
# Generate unique project ID
|
|
224
|
+
internal_project_id = str(uuid.uuid4())
|
|
225
|
+
|
|
226
|
+
# Create project
|
|
227
|
+
project = Project(
|
|
228
|
+
id=internal_project_id,
|
|
229
|
+
name=project_name,
|
|
230
|
+
database_type=DatabaseType.SPANNER,
|
|
231
|
+
database_config=database_config
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
# Store project
|
|
235
|
+
self.projects[internal_project_id] = project
|
|
236
|
+
|
|
237
|
+
# Save to persistent storage
|
|
238
|
+
try:
|
|
239
|
+
# Try to create task if there's a running event loop
|
|
240
|
+
loop = asyncio.get_running_loop()
|
|
241
|
+
loop.create_task(self._save_project_async(project))
|
|
242
|
+
except RuntimeError:
|
|
243
|
+
# No running event loop, run synchronously
|
|
244
|
+
asyncio.run(self._save_project_async(project))
|
|
245
|
+
|
|
246
|
+
print(f"✅ Added Spanner project: {project_name}")
|
|
247
|
+
print(f" GCP Project ID: {project_id}")
|
|
248
|
+
print(f" Instance ID: {instance_id}")
|
|
249
|
+
print(f" Database ID: {database_id}")
|
|
250
|
+
print(f" Graph Name: {graph_name or 'default'}")
|
|
251
|
+
print(f" Internal ID: {internal_project_id}")
|
|
252
|
+
|
|
253
|
+
return internal_project_id
|
|
254
|
+
|
|
255
|
+
def add_database(
|
|
256
|
+
self,
|
|
257
|
+
project_name: str,
|
|
258
|
+
project_id: str,
|
|
259
|
+
instance_id: str,
|
|
260
|
+
database_id: str,
|
|
261
|
+
credentials: str,
|
|
262
|
+
graph_name: Optional[str] = None
|
|
263
|
+
) -> str:
|
|
264
|
+
"""
|
|
265
|
+
Add a Spanner database configuration (deprecated, use add_project instead)
|
|
266
|
+
|
|
267
|
+
This method is kept for backward compatibility. Please use add_project() instead.
|
|
268
|
+
|
|
269
|
+
Args:
|
|
270
|
+
project_name: Name of the project
|
|
271
|
+
project_id: Google Cloud Project ID
|
|
272
|
+
instance_id: Spanner Instance ID
|
|
273
|
+
database_id: Spanner Database ID
|
|
274
|
+
credentials: Service Account credentials
|
|
275
|
+
graph_name: Optional graph name
|
|
276
|
+
|
|
277
|
+
Returns:
|
|
278
|
+
Internal Project ID
|
|
279
|
+
"""
|
|
280
|
+
import warnings
|
|
281
|
+
warnings.warn(
|
|
282
|
+
"add_database() is deprecated and will be removed in a future version. "
|
|
283
|
+
"Please use add_project() instead.",
|
|
284
|
+
DeprecationWarning,
|
|
285
|
+
stacklevel=2
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
return self.add_project(
|
|
289
|
+
project_name=project_name,
|
|
290
|
+
database_type="spanner",
|
|
291
|
+
project_id=project_id,
|
|
292
|
+
instance_id=instance_id,
|
|
293
|
+
database_id=database_id,
|
|
294
|
+
credentials=credentials,
|
|
295
|
+
graph_name=graph_name
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
async def _save_project_async(self, project: Project) -> None:
|
|
299
|
+
"""Save project to persistent storage asynchronously"""
|
|
300
|
+
try:
|
|
301
|
+
from .models.project import ProjectCreate
|
|
302
|
+
project_create = ProjectCreate(
|
|
303
|
+
name=project.name,
|
|
304
|
+
database_type=project.database_type,
|
|
305
|
+
database_config=project.database_config
|
|
306
|
+
)
|
|
307
|
+
await self.project_service.create_project(project_create)
|
|
308
|
+
except Exception as e:
|
|
309
|
+
print(f"Warning: Could not save project to persistent storage: {e}")
|
|
310
|
+
|
|
311
|
+
def remove_project(self, project_id: str) -> bool:
|
|
312
|
+
"""
|
|
313
|
+
Remove a project configuration
|
|
314
|
+
|
|
315
|
+
Args:
|
|
316
|
+
project_id: ID of the project to remove
|
|
317
|
+
|
|
318
|
+
Returns:
|
|
319
|
+
True if removed successfully, False if not found
|
|
320
|
+
"""
|
|
321
|
+
if project_id in self.projects:
|
|
322
|
+
project = self.projects[project_id]
|
|
323
|
+
del self.projects[project_id]
|
|
324
|
+
print(f"✅ Removed project: {project.name}")
|
|
325
|
+
return True
|
|
326
|
+
print(f"❌ Project not found: {project_id}")
|
|
327
|
+
return False
|
|
328
|
+
|
|
329
|
+
def remove_database(self, project_id: str) -> bool:
|
|
330
|
+
"""
|
|
331
|
+
Remove a database configuration (deprecated, use remove_project instead)
|
|
332
|
+
|
|
333
|
+
This method is kept for backward compatibility. Please use remove_project() instead.
|
|
334
|
+
"""
|
|
335
|
+
import warnings
|
|
336
|
+
warnings.warn(
|
|
337
|
+
"remove_database() is deprecated and will be removed in a future version. "
|
|
338
|
+
"Please use remove_project() instead.",
|
|
339
|
+
DeprecationWarning,
|
|
340
|
+
stacklevel=2
|
|
341
|
+
)
|
|
342
|
+
return self.remove_project(project_id)
|
|
343
|
+
|
|
344
|
+
def list_projects(self) -> Dict[str, Dict[str, Any]]:
|
|
345
|
+
"""
|
|
346
|
+
List all configured projects
|
|
347
|
+
|
|
348
|
+
Returns:
|
|
349
|
+
Dictionary mapping project IDs to project information
|
|
350
|
+
"""
|
|
351
|
+
return {
|
|
352
|
+
project_id: {
|
|
353
|
+
"name": project.name,
|
|
354
|
+
"database_type": project.database_type.value,
|
|
355
|
+
"project_id": project.database_config.project_id,
|
|
356
|
+
"instance_id": project.database_config.instance_id,
|
|
357
|
+
"database_id": project.database_config.database_id,
|
|
358
|
+
"graph_name": project.database_config.graph_name,
|
|
359
|
+
"auth_type": project.database_config.auth_type.value,
|
|
360
|
+
"created_at": project.create_time.isoformat(),
|
|
361
|
+
"api_endpoint": f"/api/projects/{project_id}"
|
|
362
|
+
}
|
|
363
|
+
for project_id, project in self.projects.items()
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
def list_databases(self) -> Dict[str, Dict[str, Any]]:
|
|
367
|
+
"""
|
|
368
|
+
List all configured databases (deprecated, use list_projects instead)
|
|
369
|
+
|
|
370
|
+
This method is kept for backward compatibility. Please use list_projects() instead.
|
|
371
|
+
"""
|
|
372
|
+
import warnings
|
|
373
|
+
warnings.warn(
|
|
374
|
+
"list_databases() is deprecated and will be removed in a future version. "
|
|
375
|
+
"Please use list_projects() instead.",
|
|
376
|
+
DeprecationWarning,
|
|
377
|
+
stacklevel=2
|
|
378
|
+
)
|
|
379
|
+
return self.list_projects()
|
|
380
|
+
|
|
381
|
+
def _find_project_by_name(self, project_name: str) -> Optional[str]:
|
|
382
|
+
"""
|
|
383
|
+
Find project ID by project name
|
|
384
|
+
|
|
385
|
+
Args:
|
|
386
|
+
project_name: Name of the project to find
|
|
387
|
+
|
|
388
|
+
Returns:
|
|
389
|
+
Project ID if found, None otherwise
|
|
390
|
+
"""
|
|
391
|
+
for project_id, project in self.projects.items():
|
|
392
|
+
if project.name == project_name:
|
|
393
|
+
return project_id
|
|
394
|
+
return None
|
|
395
|
+
|
|
396
|
+
def get_project_apis(self, project_identifier: Optional[str] = None) -> Dict[str, Any]:
|
|
397
|
+
"""
|
|
398
|
+
Get API endpoints for projects
|
|
399
|
+
|
|
400
|
+
Args:
|
|
401
|
+
project_identifier: Project ID or project name, or None for all projects
|
|
402
|
+
|
|
403
|
+
Returns:
|
|
404
|
+
Dictionary with API endpoint information
|
|
405
|
+
"""
|
|
406
|
+
if project_identifier:
|
|
407
|
+
# First try to find by project_id
|
|
408
|
+
project_id = project_identifier
|
|
409
|
+
|
|
410
|
+
# If not found by ID, try to find by name
|
|
411
|
+
if project_id not in self.projects:
|
|
412
|
+
found_id = self._find_project_by_name(project_identifier)
|
|
413
|
+
if found_id:
|
|
414
|
+
project_id = found_id
|
|
415
|
+
else:
|
|
416
|
+
return {"error": f"Project not found by ID or name: {project_identifier}"}
|
|
417
|
+
|
|
418
|
+
project = self.projects[project_id]
|
|
419
|
+
return {
|
|
420
|
+
"project_id": project_id,
|
|
421
|
+
"name": project.name,
|
|
422
|
+
"endpoints": {
|
|
423
|
+
"base": f"/api/projects/{project_id}",
|
|
424
|
+
"query": f"/api/projects/{project_id}/query",
|
|
425
|
+
"schema": f"/api/projects/{project_id}/schema",
|
|
426
|
+
"health": f"/api/projects/{project_id}/health"
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
else:
|
|
430
|
+
# Return all projects
|
|
431
|
+
return {
|
|
432
|
+
"projects": {
|
|
433
|
+
pid: {
|
|
434
|
+
"name": project.name,
|
|
435
|
+
"endpoints": {
|
|
436
|
+
"base": f"/api/projects/{pid}",
|
|
437
|
+
"query": f"/api/projects/{pid}/query",
|
|
438
|
+
"schema": f"/api/projects/{pid}/schema",
|
|
439
|
+
"health": f"/api/projects/{pid}/health"
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
for pid, project in self.projects.items()
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
def start(
|
|
447
|
+
self,
|
|
448
|
+
host: str = "0.0.0.0",
|
|
449
|
+
port: int = 9080,
|
|
450
|
+
dev: bool = False,
|
|
451
|
+
show_apis: bool = True
|
|
452
|
+
) -> None:
|
|
453
|
+
"""
|
|
454
|
+
Start the GraphXR Database Proxy server
|
|
455
|
+
Args:
|
|
456
|
+
host: Host to bind to (default: "0.0.0.0")
|
|
457
|
+
port: Port to bind to (default: 9080)
|
|
458
|
+
show_apis: Show API endpoints information (default: True)
|
|
459
|
+
"""
|
|
460
|
+
print("\n🚀 Starting GraphXR Database Proxy...")
|
|
461
|
+
print(f" Web UI: http://{host if host != '0.0.0.0' else 'localhost'}:{port}")
|
|
462
|
+
|
|
463
|
+
if show_apis and self.projects:
|
|
464
|
+
print("\n📡 Available API Endpoints:")
|
|
465
|
+
for project_id, project in self.projects.items():
|
|
466
|
+
databaseType = project.database_type._value_ if hasattr(project.database_type, '_value_') else project.database_type
|
|
467
|
+
print(f" Project: {project.name}")
|
|
468
|
+
base_url = f"http://{host if host != '0.0.0.0' else 'localhost'}:{port}/api/{databaseType}/{project.name}"
|
|
469
|
+
print(f" - API URL(GraphXR): {base_url}")
|
|
470
|
+
print(f" - Query API : {base_url}/query")
|
|
471
|
+
print(f" - Schema API : {base_url}/schema")
|
|
472
|
+
|
|
473
|
+
# Configure logging
|
|
474
|
+
import logging
|
|
475
|
+
logging.basicConfig(
|
|
476
|
+
level=logging.INFO if not dev else logging.DEBUG,
|
|
477
|
+
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
478
|
+
)
|
|
479
|
+
|
|
480
|
+
# Run the server
|
|
481
|
+
try:
|
|
482
|
+
uvicorn.run(
|
|
483
|
+
"graphxr_database_proxy.main:app",
|
|
484
|
+
host=host,
|
|
485
|
+
port=port,
|
|
486
|
+
reload=dev,
|
|
487
|
+
log_level="info" if not dev else "debug"
|
|
488
|
+
)
|
|
489
|
+
except KeyboardInterrupt:
|
|
490
|
+
print("\n⏹️ Stopping GraphXR Database Proxy...")
|
|
491
|
+
self.stop()
|
|
492
|
+
|
|
493
|
+
def stop(self) -> None:
|
|
494
|
+
"""Stop the server"""
|
|
495
|
+
print("✅ GraphXR Database Proxy stopped")
|