0
# Error Handling
1
2
Comprehensive error handling classes for HubSpot API-specific errors including authentication, rate limiting, timeouts, and permission issues. The connector provides detailed error information and appropriate retry strategies.
3
4
## Capabilities
5
6
### Base Error Class
7
8
Foundation error class for all HubSpot-specific exceptions.
9
10
```python { .api }
11
class HubspotError(AirbyteTracedException):
12
"""
13
Base error class for HubSpot connector exceptions.
14
15
Subclasses HTTPError to maintain compatibility with existing
16
error handling code while providing enhanced error context.
17
"""
18
19
def __init__(
20
self,
21
internal_message: str = None,
22
message: str = None,
23
failure_type: FailureType = FailureType.system_error,
24
exception: BaseException = None,
25
response: requests.Response = None
26
):
27
"""
28
Initialize HubSpot error with detailed context.
29
30
Parameters:
31
- internal_message: Internal error message for debugging
32
- message: User-facing error message
33
- failure_type: Classification of failure type
34
- exception: Original exception that caused this error
35
- response: HTTP response object if applicable
36
"""
37
```
38
39
### Authentication Errors
40
41
Errors related to authentication and authorization failures.
42
43
```python { .api }
44
class HubspotInvalidAuth(HubspotError):
45
"""
46
401 Unauthorized error.
47
48
Raised when:
49
- Invalid API credentials provided
50
- OAuth token has expired or been revoked
51
- Private app token is invalid or malformed
52
- Cloudflare DNS error (530) indicating token format issues
53
54
This error indicates the user needs to check and refresh
55
their authentication credentials.
56
"""
57
58
class HubspotAccessDenied(HubspotError):
59
"""
60
403 Forbidden error.
61
62
Raised when:
63
- User lacks permissions for requested resource
64
- OAuth scopes are insufficient for the operation
65
- Private app permissions don't include required access
66
67
This error indicates the user needs additional permissions
68
or OAuth scopes to access the requested resource.
69
"""
70
```
71
72
### Rate Limiting & Throttling
73
74
Errors related to API rate limits and request throttling.
75
76
```python { .api }
77
class HubspotRateLimited(HTTPError):
78
"""
79
429 Too Many Requests error.
80
81
Raised when HubSpot API rate limits are exceeded.
82
83
The connector automatically handles retry logic based on
84
the 'Retry-After' header provided by HubSpot.
85
86
Rate limits vary by:
87
- API endpoint type
88
- HubSpot subscription level
89
- Authentication method (OAuth vs Private App)
90
"""
91
```
92
93
### Server & Timeout Errors
94
95
Errors related to server issues and request timeouts.
96
97
```python { .api }
98
class HubspotTimeout(HTTPError):
99
"""
100
502/503/504 Server timeout errors.
101
102
HubSpot has processing limits to prevent performance degradation.
103
These responses indicate those limits have been hit.
104
105
Recommended action:
106
- Pause requests for a few seconds
107
- Retry with exponential backoff
108
- Consider reducing request frequency
109
"""
110
```
111
112
### Request Errors
113
114
Errors related to malformed or invalid requests.
115
116
```python { .api }
117
class HubspotBadRequest(HubspotError):
118
"""
119
400 Bad Request error.
120
121
Raised when:
122
- Request parameters are invalid or malformed
123
- Required fields are missing
124
- Data validation fails
125
- API endpoint doesn't exist
126
127
This error indicates the request needs to be corrected
128
before retrying.
129
"""
130
```
131
132
### Configuration Errors
133
134
Errors related to connector configuration and setup.
135
136
```python { .api }
137
class InvalidStartDateConfigError(Exception):
138
"""
139
Raised when invalid start_date is provided in configuration.
140
141
The start_date must be a valid ISO 8601 datetime string.
142
Common issues:
143
- Incorrect date format
144
- Invalid timezone specification
145
- Future dates that don't make sense for historical sync
146
"""
147
148
def __init__(self, actual_value: Any, message: str):
149
"""
150
Initialize with details about the invalid start_date.
151
152
Parameters:
153
- actual_value: The invalid value that was provided
154
- message: Detailed error message explaining the issue
155
"""
156
```
157
158
## Usage Examples
159
160
### Basic Error Handling
161
162
```python
163
from source_hubspot.streams import API
164
from source_hubspot.errors import (
165
HubspotInvalidAuth, HubspotRateLimited, HubspotTimeout,
166
HubspotAccessDenied, HubspotBadRequest
167
)
168
import time
169
170
api = API(credentials)
171
172
try:
173
data, response = api.get("/crm/v3/objects/contacts")
174
print(f"Retrieved {len(data.get('results', []))} contacts")
175
176
except HubspotInvalidAuth as e:
177
print(f"Authentication failed: {e}")
178
print("Please check your credentials and try again")
179
180
except HubspotAccessDenied as e:
181
print(f"Access denied: {e}")
182
print("Check your OAuth scopes or Private App permissions")
183
184
except HubspotRateLimited as e:
185
retry_after = e.response.headers.get("Retry-After", "60")
186
print(f"Rate limited. Retrying after {retry_after} seconds...")
187
time.sleep(int(retry_after))
188
# Retry the request
189
190
except HubspotTimeout as e:
191
print(f"Request timeout: {e}")
192
print("Pausing before retry...")
193
time.sleep(5)
194
# Retry with backoff
195
196
except HubspotBadRequest as e:
197
print(f"Bad request: {e}")
198
print("Check your request parameters and try again")
199
```
200
201
### Advanced Error Handling with Retry Logic
202
203
```python
204
import time
205
import random
206
from typing import Tuple, Any
207
208
def api_call_with_retry(
209
api: API,
210
endpoint: str,
211
params: dict = None,
212
max_retries: int = 3
213
) -> Tuple[Any, bool]:
214
"""
215
Make API call with comprehensive error handling and retry logic.
216
217
Returns:
218
- Tuple of (data, success)
219
"""
220
221
for attempt in range(max_retries + 1):
222
try:
223
data, response = api.get(endpoint, params)
224
return data, True
225
226
except HubspotInvalidAuth as e:
227
print(f"Authentication error: {e}")
228
return None, False # Don't retry auth errors
229
230
except HubspotAccessDenied as e:
231
print(f"Access denied: {e}")
232
return None, False # Don't retry permission errors
233
234
except HubspotRateLimited as e:
235
if attempt < max_retries:
236
retry_after = int(e.response.headers.get("Retry-After", "60"))
237
print(f"Rate limited. Waiting {retry_after} seconds before retry {attempt + 1}")
238
time.sleep(retry_after)
239
continue
240
else:
241
print("Max retries exceeded for rate limiting")
242
return None, False
243
244
except HubspotTimeout as e:
245
if attempt < max_retries:
246
# Exponential backoff with jitter
247
backoff_time = (2 ** attempt) + random.uniform(0, 1)
248
print(f"Timeout error. Retrying in {backoff_time:.1f} seconds")
249
time.sleep(backoff_time)
250
continue
251
else:
252
print("Max retries exceeded for timeout")
253
return None, False
254
255
except HubspotBadRequest as e:
256
print(f"Bad request error: {e}")
257
return None, False # Don't retry bad requests
258
259
except Exception as e:
260
print(f"Unexpected error: {e}")
261
if attempt < max_retries:
262
time.sleep(2 ** attempt)
263
continue
264
return None, False
265
266
return None, False
267
268
# Usage
269
data, success = api_call_with_retry(api, "/crm/v3/objects/contacts", {"limit": 100})
270
if success:
271
print(f"Successfully retrieved data: {len(data.get('results', []))} records")
272
else:
273
print("Failed to retrieve data after retries")
274
```
275
276
### Connection Testing with Error Handling
277
278
```python
279
from source_hubspot import SourceHubspot
280
from source_hubspot.errors import HubspotInvalidAuth, HubspotAccessDenied
281
import logging
282
283
def test_hubspot_connection(config: dict) -> Tuple[bool, str]:
284
"""
285
Test HubSpot connection with detailed error reporting.
286
287
Returns:
288
- Tuple of (is_healthy, error_message)
289
"""
290
291
try:
292
source = SourceHubspot(catalog=None, config=config, state=None)
293
logger = logging.getLogger("connection_test")
294
295
is_healthy, error = source.check_connection(logger, config)
296
297
if is_healthy:
298
return True, "Connection successful"
299
else:
300
return False, f"Connection failed: {error}"
301
302
except HubspotInvalidAuth:
303
return False, "Invalid authentication credentials. Please verify your client ID, client secret, and refresh token."
304
305
except HubspotAccessDenied:
306
return False, "Access denied. Please check your OAuth scopes or Private App permissions."
307
308
except InvalidStartDateConfigError as e:
309
return False, f"Invalid start_date configuration: {e}"
310
311
except Exception as e:
312
return False, f"Unexpected error during connection test: {e}"
313
314
# Test different credential configurations
315
oauth_config = {
316
"credentials": {
317
"credentials_title": "OAuth Credentials",
318
"client_id": "test_client_id",
319
"client_secret": "test_secret",
320
"refresh_token": "test_token"
321
},
322
"start_date": "2023-01-01T00:00:00Z"
323
}
324
325
is_healthy, message = test_hubspot_connection(oauth_config)
326
print(f"OAuth connection test: {'PASS' if is_healthy else 'FAIL'} - {message}")
327
```
328
329
### Stream-Level Error Handling
330
331
```python
332
from source_hubspot.streams import Contacts
333
334
def safe_stream_read(stream, sync_mode="full_refresh", max_errors=5):
335
"""
336
Read from stream with error handling and error counting.
337
"""
338
339
error_count = 0
340
records_processed = 0
341
342
try:
343
for record in stream.read_records(sync_mode=sync_mode):
344
try:
345
# Process record
346
records_processed += 1
347
yield record
348
349
except Exception as e:
350
error_count += 1
351
print(f"Error processing record {records_processed}: {e}")
352
353
if error_count >= max_errors:
354
print(f"Max errors ({max_errors}) exceeded. Stopping stream read.")
355
break
356
357
except HubspotRateLimited as e:
358
print(f"Stream read rate limited: {e}")
359
retry_after = e.response.headers.get("Retry-After", "60")
360
print(f"Consider pausing for {retry_after} seconds before continuing")
361
362
except HubspotInvalidAuth as e:
363
print(f"Authentication error during stream read: {e}")
364
print("Stream read cannot continue without valid authentication")
365
366
finally:
367
print(f"Stream read completed. Processed: {records_processed}, Errors: {error_count}")
368
369
# Usage
370
contacts = Contacts(api=api, start_date="2023-01-01T00:00:00Z", credentials=credentials)
371
372
for record in safe_stream_read(contacts):
373
# Process each record safely
374
contact_email = record['properties'].get('email', 'No email')
375
print(f"Contact: {contact_email}")
376
```
377
378
## Error Response Details
379
380
The HubSpot API provides detailed error information in response bodies:
381
382
```python { .api }
383
# Example error response structure
384
{
385
"status": "error",
386
"message": "This request is not allowed for this app",
387
"category": "PERMISSION_ERROR",
388
"subCategory": "SCOPE_MISSING",
389
"context": {
390
"missingScopes": ["contacts"],
391
"requiredScopes": ["contacts", "crm.objects.contacts.read"]
392
}
393
}
394
```
395
396
The connector parses these responses and includes relevant details in exception messages to help with troubleshooting.
397
398
## Best Practices
399
400
1. **Always handle authentication errors** - These require user intervention
401
2. **Implement exponential backoff** - For timeout and server errors
402
3. **Respect rate limit headers** - Use Retry-After values when available
403
4. **Log error context** - Include response details for debugging
404
5. **Differentiate retry vs non-retry errors** - Don't retry permission or configuration errors
405
6. **Monitor error patterns** - Track error rates to identify systemic issues