|
3 | 3 | from __future__ import annotations
|
4 | 4 |
|
5 | 5 | import json
|
| 6 | +import logging |
6 | 7 | import sys
|
7 | 8 | import typing as t
|
| 9 | +from http import HTTPStatus |
8 | 10 | from pathlib import Path
|
9 | 11 | from typing import Any, Callable, Generator, Iterable
|
10 | 12 |
|
11 | 13 | import requests
|
12 | 14 | from singer_sdk import metrics
|
| 15 | +from singer_sdk.exceptions import FatalAPIError, RetriableAPIError |
13 | 16 | from singer_sdk.helpers.jsonpath import extract_jsonpath
|
14 | 17 | from singer_sdk.streams import RESTStream
|
15 | 18 |
|
@@ -161,3 +164,36 @@ def request_records(self, context: dict | None) -> t.Iterable[dict]:
|
161 | 164 | else:
|
162 | 165 | yield from self.parse_response(resp)
|
163 | 166 | paginator.advance(resp)
|
| 167 | + |
| 168 | + def validate_response(self, response: requests.Response) -> None: |
| 169 | + """Validate HTTP response. |
| 170 | +
|
| 171 | + Args: |
| 172 | + response: A `requests.Response`_ object. |
| 173 | +
|
| 174 | + Raises: |
| 175 | + FatalAPIError: If the request is not retriable. |
| 176 | + RetriableAPIError: If the request is retriable. |
| 177 | + """ |
| 178 | + if response.status_code in [503]: |
| 179 | + msg = self.response_error_message(response) |
| 180 | + logging.info( |
| 181 | + f"Skipping request due to {response.status_code} error: {msg} " |
| 182 | + f"| body: {response.text}" |
| 183 | + ) |
| 184 | + return |
| 185 | + |
| 186 | + if ( |
| 187 | + response.status_code in self.extra_retry_statuses |
| 188 | + or response.status_code >= HTTPStatus.INTERNAL_SERVER_ERROR |
| 189 | + ): |
| 190 | + msg = self.response_error_message(response) |
| 191 | + raise RetriableAPIError(msg, response) |
| 192 | + |
| 193 | + if ( |
| 194 | + HTTPStatus.BAD_REQUEST |
| 195 | + <= response.status_code |
| 196 | + < HTTPStatus.INTERNAL_SERVER_ERROR |
| 197 | + ): |
| 198 | + msg = self.response_error_message(response) |
| 199 | + raise FatalAPIError(msg) |
0 commit comments