Skip to content

Commit 46eb240

Browse files
Phase 8 Batch 6: Performance Optimization + API v2/GraphQL (820 lines, 19 endpoints, 2 features)
1 parent b78b2fd commit 46eb240

5 files changed

Lines changed: 920 additions & 0 deletions

File tree

app/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,8 @@ def _register_blueprints(app):
234234
from app.routes.customer_portal_routes import portal_bp
235235
from app.routes.video_conferencing_routes import video_bp
236236
from app.routes.resource_planning_routes import resource_bp
237+
from app.routes.performance_routes import perf_bp
238+
from app.routes.api_versioning_routes import api_mgmt_bp
237239
from app.admin_secure.routes import create_secure_admin_blueprint
238240
import secrets
239241

@@ -258,6 +260,8 @@ def _register_blueprints(app):
258260
app.register_blueprint(portal_bp)
259261
app.register_blueprint(video_bp)
260262
app.register_blueprint(resource_bp)
263+
app.register_blueprint(perf_bp)
264+
app.register_blueprint(api_mgmt_bp)
261265
app.register_blueprint(phase6_bp)
262266
app.register_blueprint(projects_bp, url_prefix='/project')
263267

app/api/api_versioning.py

Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
# app/api/api_versioning.py
2+
"""
3+
API Versioning and GraphQL Support
4+
Support multiple API versions and GraphQL endpoints.
5+
"""
6+
7+
from dataclasses import dataclass, field
8+
from datetime import datetime
9+
from typing import Dict, List, Optional
10+
from enum import Enum
11+
import uuid
12+
13+
14+
class APIVersion(Enum):
15+
"""API versions."""
16+
V1 = "v1"
17+
V2 = "v2"
18+
19+
20+
@dataclass
21+
class APIEndpoint:
22+
"""API endpoint definition."""
23+
endpoint_id: str = field(default_factory=lambda: str(uuid.uuid4()))
24+
path: str = ""
25+
method: str = "GET"
26+
version: APIVersion = APIVersion.V2
27+
description: str = ""
28+
deprecated: bool = False
29+
replacement_path: Optional[str] = None
30+
request_schema: Dict = field(default_factory=dict)
31+
response_schema: Dict = field(default_factory=dict)
32+
rate_limit_per_hour: int = 1000
33+
authentication_required: bool = True
34+
35+
def to_dict(self) -> Dict:
36+
return {
37+
'path': self.path,
38+
'method': self.method,
39+
'version': self.version.value,
40+
'description': self.description,
41+
'deprecated': self.deprecated,
42+
'rate_limit_per_hour': self.rate_limit_per_hour,
43+
'authentication_required': self.authentication_required
44+
}
45+
46+
47+
@dataclass
48+
class GraphQLQuery:
49+
"""GraphQL query."""
50+
query_id: str = field(default_factory=lambda: str(uuid.uuid4()))
51+
query: str = ""
52+
variables: Dict = field(default_factory=dict)
53+
user_id: str = ""
54+
executed_at: datetime = field(default_factory=datetime.utcnow)
55+
execution_time_ms: float = 0.0
56+
57+
def to_dict(self) -> Dict:
58+
return {
59+
'query_id': self.query_id,
60+
'execution_time_ms': round(self.execution_time_ms, 2),
61+
'executed_at': self.executed_at.isoformat()
62+
}
63+
64+
65+
@dataclass
66+
class APIUsage:
67+
"""API usage tracking."""
68+
usage_id: str = field(default_factory=lambda: str(uuid.uuid4()))
69+
user_id: str = ""
70+
endpoint_path: str = ""
71+
method: str = ""
72+
version: str = "v1"
73+
status_code: int = 200
74+
response_time_ms: float = 0.0
75+
timestamp: datetime = field(default_factory=datetime.utcnow)
76+
77+
def to_dict(self) -> Dict:
78+
return {
79+
'endpoint': self.endpoint_path,
80+
'method': self.method,
81+
'version': self.version,
82+
'status_code': self.status_code,
83+
'response_time_ms': round(self.response_time_ms, 2),
84+
'timestamp': self.timestamp.isoformat()
85+
}
86+
87+
88+
class APIVersioningManager:
89+
"""
90+
Manages API versions, GraphQL, and endpoint definitions.
91+
"""
92+
93+
def __init__(self):
94+
"""Initialize API versioning manager."""
95+
self.endpoints: Dict[str, APIEndpoint] = {}
96+
self.graphql_queries: Dict[str, GraphQLQuery] = {}
97+
self.api_usage: Dict[str, APIUsage] = {}
98+
self.stats = {
99+
'total_endpoints': 0,
100+
'v1_endpoints': 0,
101+
'v2_endpoints': 0,
102+
'total_graphql_queries': 0,
103+
'total_api_calls': 0
104+
}
105+
106+
def register_endpoint(self, path: str, method: str, version: str,
107+
description: str = "", auth_required: bool = True) -> APIEndpoint:
108+
"""Register API endpoint."""
109+
try:
110+
version_enum = APIVersion[version.upper()]
111+
except KeyError:
112+
version_enum = APIVersion.V2
113+
114+
endpoint = APIEndpoint(
115+
path=path,
116+
method=method,
117+
version=version_enum,
118+
description=description,
119+
authentication_required=auth_required
120+
)
121+
122+
key = f"{method}:{path}:{version}"
123+
self.endpoints[key] = endpoint
124+
125+
self.stats['total_endpoints'] += 1
126+
if version == 'v1':
127+
self.stats['v1_endpoints'] += 1
128+
else:
129+
self.stats['v2_endpoints'] += 1
130+
131+
return endpoint
132+
133+
def get_endpoint(self, method: str, path: str, version: str) -> Optional[APIEndpoint]:
134+
"""Get endpoint definition."""
135+
key = f"{method}:{path}:{version}"
136+
return self.endpoints.get(key)
137+
138+
def deprecate_endpoint(self, method: str, path: str, version: str,
139+
replacement_path: str = None) -> bool:
140+
"""Deprecate API endpoint."""
141+
key = f"{method}:{path}:{version}"
142+
143+
if key not in self.endpoints:
144+
return False
145+
146+
endpoint = self.endpoints[key]
147+
endpoint.deprecated = True
148+
endpoint.replacement_path = replacement_path
149+
150+
return True
151+
152+
def execute_graphql_query(self, user_id: str, query: str,
153+
variables: Dict = None) -> GraphQLQuery:
154+
"""Execute GraphQL query."""
155+
graphql_query = GraphQLQuery(
156+
query=query,
157+
variables=variables or {},
158+
user_id=user_id,
159+
execution_time_ms=50.0 # Simulated
160+
)
161+
162+
self.graphql_queries[graphql_query.query_id] = graphql_query
163+
self.stats['total_graphql_queries'] += 1
164+
165+
return graphql_query
166+
167+
def get_graphql_introspection(self) -> Dict:
168+
"""Get GraphQL schema introspection."""
169+
return {
170+
'types': [
171+
{'name': 'Query', 'kind': 'OBJECT'},
172+
{'name': 'Project', 'kind': 'OBJECT'},
173+
{'name': 'Task', 'kind': 'OBJECT'},
174+
{'name': 'User', 'kind': 'OBJECT'},
175+
{'name': 'Team', 'kind': 'OBJECT'},
176+
{'name': 'String', 'kind': 'SCALAR'},
177+
{'name': 'Int', 'kind': 'SCALAR'},
178+
{'name': 'Boolean', 'kind': 'SCALAR'},
179+
{'name': 'DateTime', 'kind': 'SCALAR'}
180+
],
181+
'queryType': 'Query',
182+
'mutationType': 'Mutation'
183+
}
184+
185+
def record_api_call(self, user_id: str, endpoint_path: str, method: str,
186+
version: str, status_code: int, response_time_ms: float) -> APIUsage:
187+
"""Record API call for analytics."""
188+
usage = APIUsage(
189+
user_id=user_id,
190+
endpoint_path=endpoint_path,
191+
method=method,
192+
version=version,
193+
status_code=status_code,
194+
response_time_ms=response_time_ms
195+
)
196+
197+
self.api_usage[usage.usage_id] = usage
198+
self.stats['total_api_calls'] += 1
199+
200+
return usage
201+
202+
def get_api_usage_report(self, version: str = None, limit: int = 100) -> List[Dict]:
203+
"""Get API usage report."""
204+
usage_list = list(self.api_usage.values())
205+
206+
if version:
207+
usage_list = [u for u in usage_list if u.version == version]
208+
209+
# Sort by timestamp (most recent first)
210+
usage_list.sort(key=lambda x: x.timestamp, reverse=True)
211+
212+
return [u.to_dict() for u in usage_list[:limit]]
213+
214+
def get_endpoint_coverage(self) -> Dict:
215+
"""Get API endpoint coverage metrics."""
216+
total = self.stats['total_endpoints']
217+
deprecated = sum(1 for e in self.endpoints.values() if e.deprecated)
218+
auth_required = sum(1 for e in self.endpoints.values() if e.authentication_required)
219+
220+
return {
221+
'total_endpoints': total,
222+
'v1_endpoints': self.stats['v1_endpoints'],
223+
'v2_endpoints': self.stats['v2_endpoints'],
224+
'deprecated_endpoints': deprecated,
225+
'auth_required_endpoints': auth_required,
226+
'auth_coverage_percent': round((auth_required / total * 100) if total > 0 else 0, 1)
227+
}
228+
229+
def get_stats(self) -> Dict:
230+
"""Get API versioning statistics."""
231+
return {
232+
'total_endpoints': self.stats['total_endpoints'],
233+
'v1_endpoints': self.stats['v1_endpoints'],
234+
'v2_endpoints': self.stats['v2_endpoints'],
235+
'total_graphql_queries': self.stats['total_graphql_queries'],
236+
'total_api_calls': self.stats['total_api_calls'],
237+
'api_versions': ['v1', 'v2'],
238+
'graphql_enabled': True
239+
}
240+
241+
242+
# Global API versioning manager
243+
api_versioning_manager = APIVersioningManager()

0 commit comments

Comments
 (0)