0
# Error Handling
1
2
Geopy provides a comprehensive exception hierarchy covering authentication failures, quota exceeded, rate limiting, service unavailability, and parsing errors with detailed error information for robust error handling.
3
4
## Capabilities
5
6
### Base Exception
7
8
All geopy exceptions inherit from the base GeopyError class.
9
10
```python { .api }
11
from geopy.exc import GeopyError
12
13
class GeopyError(Exception):
14
"""
15
Base exception for all geopy-specific errors.
16
All other geopy exceptions inherit from this class.
17
"""
18
```
19
20
### Configuration Errors
21
22
Errors related to geocoder initialization and configuration.
23
24
```python { .api }
25
from geopy.exc import ConfigurationError
26
27
class ConfigurationError(GeopyError):
28
"""
29
Exception raised when geocoder is initialized with invalid parameters.
30
31
Common causes:
32
- Missing required API keys
33
- Invalid domain or scheme settings
34
- Conflicting authentication parameters
35
"""
36
```
37
38
### Service Errors
39
40
Errors returned by geocoding services during API requests.
41
42
```python { .api }
43
from geopy.exc import (
44
GeocoderServiceError, GeocoderQueryError, GeocoderQuotaExceeded,
45
GeocoderRateLimited, GeocoderAuthenticationFailure,
46
GeocoderInsufficientPrivileges, GeocoderTimedOut,
47
GeocoderUnavailable, GeocoderParseError
48
)
49
50
class GeocoderServiceError(GeopyError):
51
"""
52
Base class for all geocoding service errors.
53
Represents generic service-side issues.
54
"""
55
56
class GeocoderQueryError(GeocoderServiceError):
57
"""
58
Exception raised when geocoding service cannot process the query.
59
60
Common causes:
61
- Malformed query string
62
- Invalid coordinates for reverse geocoding
63
- Query contains unsupported characters
64
- Query exceeds maximum length limits
65
"""
66
67
class GeocoderQuotaExceeded(GeocoderServiceError):
68
"""
69
Exception raised when API quota or usage limits are exceeded.
70
71
Common causes:
72
- Daily/monthly request limit reached
73
- Rate limits exceeded
74
- Billing account suspended
75
"""
76
77
class GeocoderRateLimited(GeocoderServiceError):
78
"""
79
Exception raised when requests are being rate limited.
80
81
Attributes:
82
- retry_after (int): Seconds to wait before retrying (if provided by service)
83
"""
84
85
def __init__(self, message, retry_after=None):
86
"""
87
Initialize rate limiting exception.
88
89
Parameters:
90
- message (str): Error message
91
- retry_after (int): Seconds to wait before retry
92
"""
93
super().__init__(message)
94
self.retry_after = retry_after
95
96
class GeocoderAuthenticationFailure(GeocoderServiceError):
97
"""
98
Exception raised when API authentication fails.
99
100
Common causes:
101
- Invalid API key
102
- Expired API key
103
- API key not authorized for service
104
- Missing authentication credentials
105
"""
106
107
class GeocoderInsufficientPrivileges(GeocoderServiceError):
108
"""
109
Exception raised when API key lacks required permissions.
110
111
Common causes:
112
- API key restricted to specific domains/IPs
113
- Service not enabled for API key
114
- Premium features require upgraded account
115
"""
116
117
class GeocoderTimedOut(GeocoderServiceError):
118
"""
119
Exception raised when geocoding request times out.
120
121
Common causes:
122
- Network connectivity issues
123
- Service experiencing high load
124
- Timeout value set too low
125
"""
126
127
class GeocoderUnavailable(GeocoderServiceError):
128
"""
129
Exception raised when geocoding service is unavailable.
130
131
Common causes:
132
- Service maintenance
133
- Server downtime
134
- Network infrastructure issues
135
"""
136
137
class GeocoderParseError(GeocoderServiceError):
138
"""
139
Exception raised when geocoder response cannot be parsed.
140
141
Common causes:
142
- Unexpected response format
143
- Malformed JSON/XML response
144
- Service API changes
145
"""
146
```
147
148
### Utility Errors
149
150
Errors related to geopy utilities and helper functions.
151
152
```python { .api }
153
from geopy.exc import GeocoderNotFound
154
155
class GeocoderNotFound(GeopyError):
156
"""
157
Exception raised when requesting unknown geocoder service.
158
159
Raised by get_geocoder_for_service() when service name
160
is not recognized.
161
"""
162
```
163
164
## Usage Examples
165
166
### Basic Error Handling
167
168
```python
169
from geopy.geocoders import Nominatim
170
from geopy.exc import (
171
GeopyError, GeocoderServiceError, GeocoderTimedOut,
172
GeocoderAuthenticationFailure, GeocoderQuotaExceeded
173
)
174
175
def safe_geocode(query, geolocator):
176
"""Safely geocode with comprehensive error handling"""
177
try:
178
location = geolocator.geocode(query, timeout=10)
179
return location
180
181
except GeocoderAuthenticationFailure as e:
182
print(f"Authentication failed: {e}")
183
print("Check your API key and permissions")
184
return None
185
186
except GeocoderQuotaExceeded as e:
187
print(f"Quota exceeded: {e}")
188
print("Consider upgrading your plan or waiting until quota resets")
189
return None
190
191
except GeocoderTimedOut as e:
192
print(f"Request timed out: {e}")
193
print("Try again with a longer timeout or check network connectivity")
194
return None
195
196
except GeocoderServiceError as e:
197
print(f"Service error: {e}")
198
print("The geocoding service encountered an error")
199
return None
200
201
except GeopyError as e:
202
print(f"Geopy error: {e}")
203
return None
204
205
except Exception as e:
206
print(f"Unexpected error: {e}")
207
return None
208
209
# Usage
210
geolocator = Nominatim(user_agent="my_app")
211
result = safe_geocode("New York City", geolocator)
212
if result:
213
print(f"Found: {result.address}")
214
else:
215
print("Geocoding failed")
216
```
217
218
### Rate Limiting Error Handling
219
220
```python
221
from geopy.geocoders import GoogleV3
222
from geopy.exc import GeocoderRateLimited, GeocoderQuotaExceeded
223
import time
224
225
def geocode_with_retry(query, geolocator, max_retries=3):
226
"""Geocode with automatic retry on rate limiting"""
227
228
for attempt in range(max_retries + 1):
229
try:
230
return geolocator.geocode(query)
231
232
except GeocoderRateLimited as e:
233
if attempt == max_retries:
234
raise # Re-raise on final attempt
235
236
# Wait before retry
237
wait_time = e.retry_after if e.retry_after else 2 ** attempt
238
print(f"Rate limited. Waiting {wait_time} seconds...")
239
time.sleep(wait_time)
240
241
except GeocoderQuotaExceeded:
242
print("Quota exceeded. Cannot retry.")
243
raise
244
245
return None
246
247
# Usage
248
try:
249
geolocator = GoogleV3(api_key="your_api_key")
250
location = geocode_with_retry("San Francisco", geolocator)
251
print(f"Result: {location.address if location else 'Not found'}")
252
253
except GeocoderRateLimited:
254
print("Still rate limited after retries")
255
except GeocoderQuotaExceeded:
256
print("API quota exceeded")
257
```
258
259
### Service Fallback Strategy
260
261
```python
262
from geopy.geocoders import Nominatim, Photon, OpenCage
263
from geopy.exc import (
264
GeopyError, GeocoderServiceError, GeocoderTimedOut,
265
GeocoderUnavailable, GeocoderAuthenticationFailure
266
)
267
268
class FallbackGeocoder:
269
"""Geocoder with automatic fallback to alternative services"""
270
271
def __init__(self):
272
self.geocoders = [
273
("Primary", Nominatim(user_agent="fallback_app")),
274
("Fallback1", Photon(user_agent="fallback_app")),
275
# Add more geocoders as needed
276
]
277
278
def geocode(self, query, exactly_one=True):
279
"""Try geocoding with fallback services"""
280
last_error = None
281
282
for name, geocoder in self.geocoders:
283
try:
284
print(f"Trying {name}...")
285
result = geocoder.geocode(query, exactly_one=exactly_one, timeout=10)
286
if result:
287
print(f"Success with {name}")
288
return result
289
else:
290
print(f"No results from {name}")
291
292
except (GeocoderUnavailable, GeocoderTimedOut) as e:
293
print(f"{name} unavailable: {e}")
294
last_error = e
295
continue
296
297
except GeocoderAuthenticationFailure as e:
298
print(f"{name} authentication failed: {e}")
299
last_error = e
300
continue
301
302
except GeocoderServiceError as e:
303
print(f"{name} service error: {e}")
304
last_error = e
305
continue
306
307
except GeopyError as e:
308
print(f"{name} geopy error: {e}")
309
last_error = e
310
continue
311
312
# All services failed
313
if last_error:
314
raise last_error
315
else:
316
return None
317
318
# Usage
319
fallback_geocoder = FallbackGeocoder()
320
try:
321
location = fallback_geocoder.geocode("Paris, France")
322
if location:
323
print(f"Found: {location.address}")
324
else:
325
print("No results from any service")
326
except Exception as e:
327
print(f"All services failed: {e}")
328
```
329
330
### Error Logging and Monitoring
331
332
```python
333
from geopy.geocoders import Bing
334
from geopy.exc import *
335
import logging
336
from datetime import datetime
337
from collections import defaultdict
338
339
# Configure logging
340
logging.basicConfig(level=logging.INFO)
341
logger = logging.getLogger(__name__)
342
343
class MonitoredGeocoder:
344
"""Geocoder wrapper with error monitoring and logging"""
345
346
def __init__(self, geocoder):
347
self.geocoder = geocoder
348
self.error_counts = defaultdict(int)
349
self.total_requests = 0
350
self.successful_requests = 0
351
352
def geocode(self, query, **kwargs):
353
"""Geocode with monitoring"""
354
self.total_requests += 1
355
start_time = datetime.now()
356
357
try:
358
result = self.geocoder.geocode(query, **kwargs)
359
360
if result:
361
self.successful_requests += 1
362
duration = (datetime.now() - start_time).total_seconds()
363
logger.info(f"Geocoding success: '{query}' -> {result.address} ({duration:.2f}s)")
364
else:
365
logger.warning(f"Geocoding returned no results: '{query}'")
366
367
return result
368
369
except GeocoderAuthenticationFailure as e:
370
self.error_counts['auth_failure'] += 1
371
logger.error(f"Authentication failure for '{query}': {e}")
372
raise
373
374
except GeocoderQuotaExceeded as e:
375
self.error_counts['quota_exceeded'] += 1
376
logger.error(f"Quota exceeded for '{query}': {e}")
377
raise
378
379
except GeocoderRateLimited as e:
380
self.error_counts['rate_limited'] += 1
381
logger.warning(f"Rate limited for '{query}': {e} (retry_after={e.retry_after})")
382
raise
383
384
except GeocoderTimedOut as e:
385
self.error_counts['timeout'] += 1
386
logger.warning(f"Timeout for '{query}': {e}")
387
raise
388
389
except GeocoderUnavailable as e:
390
self.error_counts['unavailable'] += 1
391
logger.error(f"Service unavailable for '{query}': {e}")
392
raise
393
394
except GeocoderServiceError as e:
395
self.error_counts['service_error'] += 1
396
logger.error(f"Service error for '{query}': {e}")
397
raise
398
399
except GeopyError as e:
400
self.error_counts['geopy_error'] += 1
401
logger.error(f"Geopy error for '{query}': {e}")
402
raise
403
404
def get_stats(self):
405
"""Get monitoring statistics"""
406
success_rate = (self.successful_requests / self.total_requests * 100
407
if self.total_requests > 0 else 0)
408
409
return {
410
'total_requests': self.total_requests,
411
'successful_requests': self.successful_requests,
412
'success_rate': success_rate,
413
'error_counts': dict(self.error_counts)
414
}
415
416
def print_stats(self):
417
"""Print monitoring statistics"""
418
stats = self.get_stats()
419
print(f"\nGeocoding Statistics:")
420
print(f"Total requests: {stats['total_requests']}")
421
print(f"Successful requests: {stats['successful_requests']}")
422
print(f"Success rate: {stats['success_rate']:.2f}%")
423
print(f"Error breakdown:")
424
for error_type, count in stats['error_counts'].items():
425
print(f" {error_type}: {count}")
426
427
# Usage
428
base_geocoder = Bing(api_key="your_api_key")
429
monitored_geocoder = MonitoredGeocoder(base_geocoder)
430
431
# Test queries
432
test_queries = [
433
"New York City",
434
"Invalid address 123456",
435
"London, UK",
436
"Tokyo, Japan"
437
]
438
439
for query in test_queries:
440
try:
441
result = monitored_geocoder.geocode(query)
442
except Exception as e:
443
print(f"Error processing '{query}': {e}")
444
445
# Print statistics
446
monitored_geocoder.print_stats()
447
```
448
449
### Custom Error Handling Context Manager
450
451
```python
452
from geopy.exc import *
453
from contextlib import contextmanager
454
import time
455
456
@contextmanager
457
def geocoding_context(max_retries=3, retry_delay=1):
458
"""Context manager for geocoding with automatic error handling"""
459
460
retry_count = 0
461
while retry_count <= max_retries:
462
try:
463
yield
464
break # Success, exit retry loop
465
466
except GeocoderRateLimited as e:
467
if retry_count == max_retries:
468
print(f"Rate limited after {max_retries} retries")
469
raise
470
471
wait_time = e.retry_after if e.retry_after else retry_delay * (2 ** retry_count)
472
print(f"Rate limited, waiting {wait_time} seconds...")
473
time.sleep(wait_time)
474
retry_count += 1
475
476
except (GeocoderTimedOut, GeocoderUnavailable) as e:
477
if retry_count == max_retries:
478
print(f"Service issue after {max_retries} retries: {e}")
479
raise
480
481
print(f"Service issue, retrying in {retry_delay} seconds...")
482
time.sleep(retry_delay)
483
retry_count += 1
484
485
except (GeocoderAuthenticationFailure, GeocoderQuotaExceeded) as e:
486
print(f"Non-retryable error: {e}")
487
raise
488
489
except Exception as e:
490
print(f"Unexpected error: {e}")
491
raise
492
493
# Usage
494
from geopy.geocoders import Nominatim
495
496
geolocator = Nominatim(user_agent="context_app")
497
498
with geocoding_context(max_retries=3, retry_delay=2):
499
location = geolocator.geocode("San Francisco, CA")
500
print(f"Result: {location.address if location else 'Not found'}")
501
```