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,110 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Main FastAPI application
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import uvicorn
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from fastapi import FastAPI
|
|
9
|
+
from fastapi.staticfiles import StaticFiles
|
|
10
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
11
|
+
from fastapi.responses import FileResponse
|
|
12
|
+
|
|
13
|
+
from .api.projects import router as projects_router
|
|
14
|
+
from .api.database import router as database_router
|
|
15
|
+
from .api.google import router as google_router
|
|
16
|
+
|
|
17
|
+
# Create FastAPI app
|
|
18
|
+
app = FastAPI(
|
|
19
|
+
title="GraphXR Database Proxy",
|
|
20
|
+
description="Secure middleware for connecting GraphXR to databases",
|
|
21
|
+
version="1.0.0",
|
|
22
|
+
docs_url="/docs",
|
|
23
|
+
redoc_url="/redoc"
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
# Add CORS middleware
|
|
27
|
+
app.add_middleware(
|
|
28
|
+
CORSMiddleware,
|
|
29
|
+
allow_origins=["*"], # Configure this for production
|
|
30
|
+
allow_credentials=True,
|
|
31
|
+
allow_methods=["*"],
|
|
32
|
+
allow_headers=["*"],
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
# Include API routers
|
|
36
|
+
app.include_router(projects_router)
|
|
37
|
+
app.include_router(database_router)
|
|
38
|
+
app.include_router(google_router)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@app.get("/health")
|
|
42
|
+
async def health_check():
|
|
43
|
+
"""Health check endpoint"""
|
|
44
|
+
return {
|
|
45
|
+
"status": "healthy",
|
|
46
|
+
"message": "GraphXR Database Proxy is running",
|
|
47
|
+
"version": "1.0.0",
|
|
48
|
+
"environment": "development",
|
|
49
|
+
"auto_reload": "enabled"
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
# Mount static files from package
|
|
53
|
+
static_dir = Path(__file__).parent / "static"
|
|
54
|
+
frontend_dist = Path(__file__).parent.parent.parent / "frontend" / "dist"
|
|
55
|
+
|
|
56
|
+
if static_dir.exists() or frontend_dist.exists():
|
|
57
|
+
static_dir = frontend_dist if frontend_dist.exists() else static_dir
|
|
58
|
+
print(f"🔧 Serving static files from: {static_dir}")
|
|
59
|
+
# Development: Use frontend dist directory
|
|
60
|
+
app.mount("/static", StaticFiles(directory=str(static_dir)), name="static")
|
|
61
|
+
|
|
62
|
+
@app.get("/{full_path:path}")
|
|
63
|
+
async def serve_frontend(full_path: str):
|
|
64
|
+
"""Serve the frontend application for all unmatched paths"""
|
|
65
|
+
index_file = static_dir / "index.html"
|
|
66
|
+
if index_file.exists():
|
|
67
|
+
return FileResponse(str(index_file))
|
|
68
|
+
return {"message": "Frontend not available"}
|
|
69
|
+
|
|
70
|
+
else:
|
|
71
|
+
@app.get("/")
|
|
72
|
+
async def serve_fallback():
|
|
73
|
+
"""Fallback when frontend is not built"""
|
|
74
|
+
return {
|
|
75
|
+
"message": "Frontend not available",
|
|
76
|
+
"hint": "Run 'python scripts/build_frontend.py' to build and package frontend"
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def main():
|
|
81
|
+
"""Main entry point"""
|
|
82
|
+
import argparse
|
|
83
|
+
|
|
84
|
+
parser = argparse.ArgumentParser(description="GraphXR Database Proxy")
|
|
85
|
+
parser.add_argument("--host", default="0.0.0.0", help="Host to bind to")
|
|
86
|
+
parser.add_argument("--port", type=int, default=9080, help="Port to bind to")
|
|
87
|
+
parser.add_argument("--ui", action="store_true", help="Enable UI mode")
|
|
88
|
+
parser.add_argument("--dev", action="store_true", help="Development mode with hot reload")
|
|
89
|
+
|
|
90
|
+
args = parser.parse_args()
|
|
91
|
+
|
|
92
|
+
# Configure logging
|
|
93
|
+
import logging
|
|
94
|
+
logging.basicConfig(
|
|
95
|
+
level=logging.INFO,
|
|
96
|
+
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
# Run the server
|
|
100
|
+
uvicorn.run(
|
|
101
|
+
"src.graphxr_database_proxy.main:app",
|
|
102
|
+
host=args.host,
|
|
103
|
+
port=args.port,
|
|
104
|
+
reload=args.dev,
|
|
105
|
+
log_level="info" if not args.dev else "debug"
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
if __name__ == "__main__":
|
|
110
|
+
main()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Models package
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Google Cloud API models
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Dict, Any, List, Optional
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class GoogleAuthInfo(BaseModel):
|
|
10
|
+
"""Google authentication information"""
|
|
11
|
+
client_id: Optional[str] = None
|
|
12
|
+
client_secret: Optional[str] = None
|
|
13
|
+
token: Optional[str] = None
|
|
14
|
+
refresh_token: Optional[str] = None
|
|
15
|
+
project_id: Optional[str] = None
|
|
16
|
+
instance_id: Optional[str] = None
|
|
17
|
+
database_id: Optional[str] = None
|
|
18
|
+
graph_name: Optional[str] = None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class GoogleProject(BaseModel):
|
|
22
|
+
"""Google Cloud project information"""
|
|
23
|
+
name: str
|
|
24
|
+
id: str
|
|
25
|
+
instances: List[Any] = []
|
|
26
|
+
|
|
27
|
+
class GraphDatabase(BaseModel):
|
|
28
|
+
"""Graph database information"""
|
|
29
|
+
id: str
|
|
30
|
+
name: str
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class SpannerDatabase(BaseModel):
|
|
34
|
+
"""Spanner database information"""
|
|
35
|
+
id: str
|
|
36
|
+
name: str
|
|
37
|
+
graphDBs: List[GraphDatabase] = []
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class SpannerInstance(BaseModel):
|
|
41
|
+
"""Spanner instance information"""
|
|
42
|
+
id: str
|
|
43
|
+
name: str
|
|
44
|
+
databases: List[SpannerDatabase] = []
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class ProjectDetails(BaseModel):
|
|
48
|
+
"""Project details with instances and databases"""
|
|
49
|
+
# Key is instance_id, value is instance details
|
|
50
|
+
instances: List[SpannerInstance] = []
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Data models for the GraphXR Database Proxy
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from typing import Any, Dict, List, Optional, Literal
|
|
7
|
+
from pydantic import BaseModel, Field
|
|
8
|
+
from enum import Enum
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class DatabaseType(str, Enum):
|
|
12
|
+
"""Supported database types"""
|
|
13
|
+
SPANNER = "spanner"
|
|
14
|
+
POSTGRESQL = "postgresql"
|
|
15
|
+
MYSQL = "mysql"
|
|
16
|
+
MONGODB = "mongodb"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class AuthType(str, Enum):
|
|
20
|
+
"""Authentication types"""
|
|
21
|
+
OAUTH2 = "oauth2"
|
|
22
|
+
SERVICE_ACCOUNT = "service_account"
|
|
23
|
+
USERNAME_PASSWORD = "username_password"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class OAuthConfig(BaseModel):
|
|
27
|
+
"""OAuth2 configuration"""
|
|
28
|
+
client_id: Optional[str] = None
|
|
29
|
+
client_secret: Optional[str] = None
|
|
30
|
+
redirect_uri: str = "http://localhost:9080/google/spanner/callback"
|
|
31
|
+
scopes: List[str] = Field(default_factory=list)
|
|
32
|
+
|
|
33
|
+
# For OAuth2 token-based auth
|
|
34
|
+
token: Optional[str] = None
|
|
35
|
+
refresh_token: Optional[str] = None
|
|
36
|
+
expires_in: Optional[int] = None
|
|
37
|
+
last_refreshed: Optional[float] = None # Unix timestamp of last token refresh
|
|
38
|
+
token_uri: Optional[str] = None
|
|
39
|
+
|
|
40
|
+
# For Service Account JSON key
|
|
41
|
+
type: Optional[str] = None
|
|
42
|
+
project_id: Optional[str] = None
|
|
43
|
+
private_key_id: Optional[str] = None
|
|
44
|
+
private_key: Optional[str] = None
|
|
45
|
+
client_email: Optional[str] = None
|
|
46
|
+
client_x509_cert_url: Optional[str] = None
|
|
47
|
+
auth_uri: Optional[str] = None
|
|
48
|
+
auth_provider_x509_cert_url: Optional[str] = None
|
|
49
|
+
|
|
50
|
+
# Allow additional fields for flexibility
|
|
51
|
+
class Config:
|
|
52
|
+
extra = "allow"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class DatabaseConfig(BaseModel):
|
|
56
|
+
"""Database connection configuration"""
|
|
57
|
+
# Common fields
|
|
58
|
+
type: DatabaseType
|
|
59
|
+
host: Optional[str] = None
|
|
60
|
+
port: Optional[int] = None
|
|
61
|
+
|
|
62
|
+
# Spanner specific
|
|
63
|
+
project_id: Optional[str] = None
|
|
64
|
+
instance_id: Optional[str] = None
|
|
65
|
+
database_id: Optional[str] = None
|
|
66
|
+
graph_name: Optional[str] = None
|
|
67
|
+
|
|
68
|
+
# Authentication
|
|
69
|
+
auth_type: AuthType = AuthType.USERNAME_PASSWORD
|
|
70
|
+
username: Optional[str] = None
|
|
71
|
+
password: Optional[str] = None
|
|
72
|
+
oauth_config: Optional[OAuthConfig] = None
|
|
73
|
+
service_account_path: Optional[str] = None # For backward compatibility
|
|
74
|
+
# Additional options
|
|
75
|
+
options: Dict[str, Any] = Field(default_factory=dict)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class Project(BaseModel):
|
|
79
|
+
"""Project model"""
|
|
80
|
+
id: str = Field(..., description="Unique project identifier")
|
|
81
|
+
name: str = Field(..., description="Project name (should be unique)")
|
|
82
|
+
database_type: DatabaseType = Field(..., description="Database type")
|
|
83
|
+
database_config: DatabaseConfig = Field(..., description="Database configuration")
|
|
84
|
+
create_time: datetime = Field(default_factory=datetime.utcnow)
|
|
85
|
+
update_time: datetime = Field(default_factory=datetime.utcnow)
|
|
86
|
+
|
|
87
|
+
class Config:
|
|
88
|
+
json_encoders = {
|
|
89
|
+
datetime: lambda v: v.isoformat()
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class ProjectCreate(BaseModel):
|
|
94
|
+
"""Project creation request"""
|
|
95
|
+
name: str = Field(..., description="Project name")
|
|
96
|
+
database_type: DatabaseType = Field(..., description="Database type")
|
|
97
|
+
database_config: DatabaseConfig = Field(..., description="Database configuration")
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class ProjectUpdate(BaseModel):
|
|
101
|
+
"""Project update request"""
|
|
102
|
+
name: Optional[str] = None
|
|
103
|
+
database_config: Optional[DatabaseConfig] = None
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class APIInfo(BaseModel):
|
|
107
|
+
"""Database API information"""
|
|
108
|
+
type: DatabaseType
|
|
109
|
+
api_urls: Dict[str, str]
|
|
110
|
+
version: Optional[str] = None
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class QueryRequest(BaseModel):
|
|
114
|
+
"""Database query request"""
|
|
115
|
+
query: str = Field(..., description="Query string")
|
|
116
|
+
parameters: Dict[str, Any] = Field(default_factory=dict, description="Query parameters")
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class QueryResponse(BaseModel):
|
|
120
|
+
"""Database query response"""
|
|
121
|
+
success: bool
|
|
122
|
+
data: Optional[Any] = None
|
|
123
|
+
error: Optional[str] = None
|
|
124
|
+
execution_time: Optional[float] = None
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class SchemaResponse(BaseModel):
|
|
128
|
+
"""Database schema response"""
|
|
129
|
+
success: bool
|
|
130
|
+
data: Optional[Dict[str, Dict[str, str]]] = None
|
|
131
|
+
error: Optional[str] = None
|
|
132
|
+
|
|
133
|
+
class Category(BaseModel):
|
|
134
|
+
name: str
|
|
135
|
+
props: Optional[List[str]] = None
|
|
136
|
+
keys: Optional[List[str]] = None
|
|
137
|
+
propsTypes: Optional[Dict[str, str]] = None
|
|
138
|
+
|
|
139
|
+
class Relationship(BaseModel):
|
|
140
|
+
name: str
|
|
141
|
+
props: Optional[List[str]] = None
|
|
142
|
+
keys: Optional[List[str]] = None
|
|
143
|
+
propsTypes: Optional[Dict[str, str]] = None
|
|
144
|
+
startCategory: str
|
|
145
|
+
endCategory: str
|
|
146
|
+
|
|
147
|
+
class GraphSchema(BaseModel):
|
|
148
|
+
categories: List[Category]
|
|
149
|
+
relationships: List[Relationship]
|
|
150
|
+
|
|
151
|
+
class GraphSchemaResponse(BaseModel):
|
|
152
|
+
"""Graph database schema response"""
|
|
153
|
+
success: bool
|
|
154
|
+
data: GraphSchema = Field(default_factory=lambda: GraphSchema(categories=[], relationships=[]))
|
|
155
|
+
error: Optional[str] = None
|
|
156
|
+
|
|
157
|
+
class SampleDataResponse(BaseModel):
|
|
158
|
+
"""Sample data response"""
|
|
159
|
+
success: bool
|
|
160
|
+
data: Optional[Dict[str, Any]] = None
|
|
161
|
+
error: Optional[str] = None
|
|
162
|
+
|
|
163
|
+
class GraphData(BaseModel):
|
|
164
|
+
nodes: List[Dict[str, Any]] = Field(default_factory=list)
|
|
165
|
+
relationships: List[Dict[str, Any]] = Field(default_factory=list)
|
|
166
|
+
|
|
167
|
+
class QueryData(BaseModel):
|
|
168
|
+
type: Literal["TABLE", "GRAPH"]
|
|
169
|
+
data: List[Dict[str, Any]] | GraphData | None = None
|
|
170
|
+
summary: Dict[str, str] = Field(default_factory=lambda: {"version": "4.0.1"})
|