-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapi_authentication_handler.py
More file actions
311 lines (267 loc) · 11.3 KB
/
api_authentication_handler.py
File metadata and controls
311 lines (267 loc) · 11.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
#!/usr/bin/env python3
"""
API Authentication Handler for Materials Project API
This module provides robust handling of API authentication issues,
including validation, fallback mechanisms, and clear error messages.
"""
import os
import requests
import json
import logging
from typing import Optional, Dict, Any
from datetime import datetime
# Set up logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class APIAuthenticationHandler:
"""Handles Materials Project API authentication with robust error handling."""
def __init__(self, api_key: Optional[str] = None):
"""
Initialize the API authentication handler.
Args:
api_key: Materials Project API key (optional)
"""
self.api_key = api_key or os.getenv('MP_API_KEY')
self.base_url = "https://materialsproject.org/rest/v2"
self.headers = {}
if self.api_key:
self.headers = {
'X-API-KEY': self.api_key,
'Content-Type': 'application/json'
}
def validate_api_key(self) -> Dict[str, Any]:
"""
Validate the API key by making a simple test request.
Returns:
Dict containing validation result and details
"""
if not self.api_key:
return {
'valid': False,
'error': 'No API key provided',
'message': 'Please provide a valid Materials Project API key',
'suggestions': [
'Get a free API key at: https://materialsproject.org/api',
'Set the MP_API_KEY environment variable',
'Or provide the API key in the application settings'
]
}
try:
# Make a simple test request to validate the API key
test_url = f"{self.base_url}/materials/summary"
params = {
'_limit': 1,
'_fields': 'material_id'
}
response = requests.get(test_url, headers=self.headers, params=params, timeout=10)
if response.status_code == 200:
return {
'valid': True,
'message': 'API key is valid',
'status_code': response.status_code
}
elif response.status_code == 401:
return {
'valid': False,
'error': 'Invalid API key',
'message': 'The provided API key is not valid or has expired',
'status_code': response.status_code,
'suggestions': [
'Verify your API key is correct',
'Check if your API key has expired',
'Regenerate your API key at: https://materialsproject.org/api',
'Ensure you are using the correct API endpoint'
]
}
elif response.status_code == 429:
return {
'valid': False,
'error': 'Rate limit exceeded',
'message': 'API rate limit exceeded. Please try again later.',
'status_code': response.status_code,
'suggestions': [
'Wait a few minutes and try again',
'Check your API usage limits',
'Consider using synthetic data as fallback'
]
}
elif response.status_code == 500:
return {
'valid': False,
'error': 'Server error',
'message': 'Materials Project server error. Please try again later.',
'status_code': response.status_code,
'suggestions': [
'Try again in a few minutes',
'Check Materials Project status at: https://status.materialsproject.org',
'Use synthetic data as fallback'
]
}
else:
return {
'valid': False,
'error': f'Unexpected status code: {response.status_code}',
'message': f'API returned unexpected status: {response.status_code}',
'status_code': response.status_code,
'suggestions': [
'Check your internet connection',
'Verify the API endpoint is correct',
'Use synthetic data as fallback'
]
}
except requests.exceptions.Timeout:
return {
'valid': False,
'error': 'Request timeout',
'message': 'API request timed out. Please check your internet connection.',
'suggestions': [
'Check your internet connection',
'Try again with a longer timeout',
'Use synthetic data as fallback'
]
}
except requests.exceptions.ConnectionError:
return {
'valid': False,
'error': 'Connection error',
'message': 'Unable to connect to Materials Project API.',
'suggestions': [
'Check your internet connection',
'Verify the API endpoint is accessible',
'Use synthetic data as fallback'
]
}
except requests.exceptions.RequestException as e:
return {
'valid': False,
'error': 'Request error',
'message': f'Error making API request: {str(e)}',
'suggestions': [
'Check your internet connection',
'Verify the API endpoint is correct',
'Use synthetic data as fallback'
]
}
except Exception as e:
return {
'valid': False,
'error': 'Unknown error',
'message': f'Unexpected error: {str(e)}',
'suggestions': [
'Check your internet connection',
'Verify the API endpoint is correct',
'Use synthetic data as fallback'
]
}
def get_api_status(self) -> Dict[str, Any]:
"""
Get the current API status and authentication information.
Returns:
Dict containing API status information
"""
validation_result = self.validate_api_key()
return {
'timestamp': datetime.now().isoformat(),
'api_key_provided': bool(self.api_key),
'api_key_valid': validation_result['valid'],
'api_key_status': validation_result.get('message', 'Unknown'),
'base_url': self.base_url,
'validation_details': validation_result
}
def log_api_status(self):
"""Log the current API status with appropriate level."""
status = self.get_api_status()
if status['api_key_valid']:
logger.info(f"✅ API Authentication: {status['api_key_status']}")
else:
logger.warning(f"⚠️ API Authentication: {status['api_key_status']}")
if 'suggestions' in status['validation_details']:
for suggestion in status['validation_details']['suggestions']:
logger.info(f" → {suggestion}")
def get_fallback_message(self) -> str:
"""
Get a user-friendly message about using fallback data.
Returns:
String message about fallback options
"""
status = self.get_api_status()
if not status['api_key_valid']:
message = f"""
**API Authentication Issue Detected**
Status: {status['api_key_status']}
**Recommended Actions:**
"""
if 'suggestions' in status['validation_details']:
for i, suggestion in enumerate(status['validation_details']['suggestions'], 1):
message += f"\n{i}. {suggestion}"
message += f"""
**Using Synthetic Data:**
The application will continue using synthetic data for materials discovery.
While not as accurate as real Materials Project data, synthetic data still
provides valuable insights for materials exploration.
**Data Quality Comparison:**
- Real Materials Project data: High accuracy, experimentally validated
- Synthetic data: Good for exploration, may have higher uncertainty
**To Enable Real Data:**
1. Get a free API key: https://materialsproject.org/api
2. Set MP_API_KEY environment variable
3. Restart the application
"""
return message
else:
return "✅ API authentication successful - using real Materials Project data"
def handle_api_authentication(api_key: Optional[str] = None) -> APIAuthenticationHandler:
"""
Create and validate an API authentication handler.
Args:
api_key: Optional API key
Returns:
Configured APIAuthenticationHandler instance
"""
handler = APIAuthenticationHandler(api_key)
handler.log_api_status()
return handler
def validate_and_use_api_key(api_key: Optional[str] = None) -> bool:
"""
Validate API key and return whether to use real API or fallback.
Args:
api_key: Optional API key
Returns:
Boolean indicating whether to use real API (True) or fallback (False)
"""
handler = handle_api_authentication(api_key)
status = handler.get_api_status()
if status['api_key_valid']:
logger.info("✅ Using real Materials Project API")
return True
else:
logger.warning("⚠️ Using synthetic data fallback")
logger.info(handler.get_fallback_message())
return False
def validate_api_key(api_key: Optional[str] = None) -> bool:
"""
Standalone function to validate API key (for backward compatibility).
Args:
api_key: Optional API key
Returns:
Boolean indicating whether the API key is valid
"""
handler = APIAuthenticationHandler(api_key)
validation_result = handler.validate_api_key()
return validation_result['valid']
# Example usage and testing
if __name__ == "__main__":
print("=== API Authentication Handler Test ===")
# Test with no API key
print("\n1. Testing with no API key:")
handler1 = handle_api_authentication(None)
print(handler1.get_api_status())
# Test with environment variable
print("\n2. Testing with environment variable:")
os.environ['MP_API_KEY'] = 'test_key'
handler2 = handle_api_authentication()
print(handler2.get_api_status())
# Test validation function
print("\n3. Testing validation function:")
use_real_api = validate_and_use_api_key('test_key')
print(f"Should use real API: {use_real_api}")