diff --git a/.fern/metadata.json b/.fern/metadata.json index 3c97467..da6d4c0 100644 --- a/.fern/metadata.json +++ b/.fern/metadata.json @@ -1,9 +1,9 @@ { - "cliVersion": "4.3.3", + "cliVersion": "4.17.0", "generatorName": "fernapi/fern-python-sdk", - "generatorVersion": "4.61.4", + "generatorVersion": "4.61.5", "generatorConfig": { "client_class_name": "PhenomlClient" }, - "sdkVersion": "8.0.1" + "sdkVersion": "8.1.0" } \ No newline at end of file diff --git a/changelog.md b/changelog.md index b0f791e..e33096f 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,13 @@ +## 8.1.0 - 2026-03-09 +* feat: add ServiceUnavailableError and TooManyRequestsError handling +* Add comprehensive error handling for HTTP 429 (Too Many Requests) and HTTP 503 (Service Unavailable) status codes across all FHIR client methods. This improves the client's robustness by properly handling rate limiting and temporary service unavailability scenarios. +* Key changes: +* Add ServiceUnavailableError class for HTTP 503 responses +* Add TooManyRequestsError class for HTTP 429 responses with structured ErrorResponse body +* Update all sync and async client methods to handle these new error types +* Add proper imports and exports in module initialization files +* 🌿 Generated with Fern + ## 8.0.0 - 2026-03-04 ### Breaking Changes diff --git a/pyproject.toml b/pyproject.toml index 1311071..73586ff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ dynamic = ["version"] [tool.poetry] name = "phenoml" -version = "8.0.1" +version = "8.1.0" description = "" readme = "README.md" authors = [] diff --git a/src/phenoml/core/client_wrapper.py b/src/phenoml/core/client_wrapper.py index ca2bdb3..150c900 100644 --- a/src/phenoml/core/client_wrapper.py +++ b/src/phenoml/core/client_wrapper.py @@ -27,12 +27,12 @@ def get_headers(self) -> typing.Dict[str, str]: import platform headers: typing.Dict[str, str] = { - "User-Agent": "phenoml/8.0.1", + "User-Agent": "phenoml/8.1.0", "X-Fern-Language": "Python", "X-Fern-Runtime": f"python/{platform.python_version()}", "X-Fern-Platform": f"{platform.system().lower()}/{platform.release()}", "X-Fern-SDK-Name": "phenoml", - "X-Fern-SDK-Version": "8.0.1", + "X-Fern-SDK-Version": "8.1.0", **(self.get_custom_headers() or {}), } token = self._get_token() diff --git a/src/phenoml/fhir/__init__.py b/src/phenoml/fhir/__init__.py index 3c4f33d..fca9ec2 100644 --- a/src/phenoml/fhir/__init__.py +++ b/src/phenoml/fhir/__init__.py @@ -19,7 +19,15 @@ FhirResourceMeta, FhirSearchResponse, ) - from .errors import BadGatewayError, BadRequestError, InternalServerError, NotFoundError, UnauthorizedError + from .errors import ( + BadGatewayError, + BadRequestError, + InternalServerError, + NotFoundError, + ServiceUnavailableError, + TooManyRequestsError, + UnauthorizedError, + ) _dynamic_imports: typing.Dict[str, str] = { "BadGatewayError": ".errors", "BadRequestError": ".errors", @@ -36,6 +44,8 @@ "FhirSearchResponse": ".types", "InternalServerError": ".errors", "NotFoundError": ".errors", + "ServiceUnavailableError": ".errors", + "TooManyRequestsError": ".errors", "UnauthorizedError": ".errors", } @@ -77,5 +87,7 @@ def __dir__(): "FhirSearchResponse", "InternalServerError", "NotFoundError", + "ServiceUnavailableError", + "TooManyRequestsError", "UnauthorizedError", ] diff --git a/src/phenoml/fhir/errors/__init__.py b/src/phenoml/fhir/errors/__init__.py index 888dea5..1425bf1 100644 --- a/src/phenoml/fhir/errors/__init__.py +++ b/src/phenoml/fhir/errors/__init__.py @@ -10,12 +10,16 @@ from .bad_request_error import BadRequestError from .internal_server_error import InternalServerError from .not_found_error import NotFoundError + from .service_unavailable_error import ServiceUnavailableError + from .too_many_requests_error import TooManyRequestsError from .unauthorized_error import UnauthorizedError _dynamic_imports: typing.Dict[str, str] = { "BadGatewayError": ".bad_gateway_error", "BadRequestError": ".bad_request_error", "InternalServerError": ".internal_server_error", "NotFoundError": ".not_found_error", + "ServiceUnavailableError": ".service_unavailable_error", + "TooManyRequestsError": ".too_many_requests_error", "UnauthorizedError": ".unauthorized_error", } @@ -41,4 +45,12 @@ def __dir__(): return sorted(lazy_attrs) -__all__ = ["BadGatewayError", "BadRequestError", "InternalServerError", "NotFoundError", "UnauthorizedError"] +__all__ = [ + "BadGatewayError", + "BadRequestError", + "InternalServerError", + "NotFoundError", + "ServiceUnavailableError", + "TooManyRequestsError", + "UnauthorizedError", +] diff --git a/src/phenoml/fhir/errors/service_unavailable_error.py b/src/phenoml/fhir/errors/service_unavailable_error.py new file mode 100644 index 0000000..1e7c99e --- /dev/null +++ b/src/phenoml/fhir/errors/service_unavailable_error.py @@ -0,0 +1,10 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core.api_error import ApiError + + +class ServiceUnavailableError(ApiError): + def __init__(self, body: typing.Any, headers: typing.Optional[typing.Dict[str, str]] = None): + super().__init__(status_code=503, headers=headers, body=body) diff --git a/src/phenoml/fhir/errors/too_many_requests_error.py b/src/phenoml/fhir/errors/too_many_requests_error.py new file mode 100644 index 0000000..b2bf7ae --- /dev/null +++ b/src/phenoml/fhir/errors/too_many_requests_error.py @@ -0,0 +1,11 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core.api_error import ApiError +from ..types.error_response import ErrorResponse + + +class TooManyRequestsError(ApiError): + def __init__(self, body: ErrorResponse, headers: typing.Optional[typing.Dict[str, str]] = None): + super().__init__(status_code=429, headers=headers, body=body) diff --git a/src/phenoml/fhir/raw_client.py b/src/phenoml/fhir/raw_client.py index 400152d..5c60a10 100644 --- a/src/phenoml/fhir/raw_client.py +++ b/src/phenoml/fhir/raw_client.py @@ -14,6 +14,8 @@ from .errors.bad_request_error import BadRequestError from .errors.internal_server_error import InternalServerError from .errors.not_found_error import NotFoundError +from .errors.service_unavailable_error import ServiceUnavailableError +from .errors.too_many_requests_error import TooManyRequestsError from .errors.unauthorized_error import UnauthorizedError from .types.error_response import ErrorResponse from .types.fhir_bundle import FhirBundle @@ -138,6 +140,17 @@ def search( ), ), ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + ErrorResponse, + parse_obj_as( + type_=ErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) if _response.status_code == 500: raise InternalServerError( headers=dict(_response.headers), @@ -160,6 +173,17 @@ def search( ), ), ) + if _response.status_code == 503: + raise ServiceUnavailableError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) @@ -271,6 +295,17 @@ def create( ), ), ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + ErrorResponse, + parse_obj_as( + type_=ErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) if _response.status_code == 500: raise InternalServerError( headers=dict(_response.headers), @@ -293,6 +328,17 @@ def create( ), ), ) + if _response.status_code == 503: + raise ServiceUnavailableError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) @@ -404,6 +450,17 @@ def upsert( ), ), ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + ErrorResponse, + parse_obj_as( + type_=ErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) if _response.status_code == 500: raise InternalServerError( headers=dict(_response.headers), @@ -426,6 +483,17 @@ def upsert( ), ), ) + if _response.status_code == 503: + raise ServiceUnavailableError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) @@ -527,6 +595,17 @@ def delete( ), ), ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + ErrorResponse, + parse_obj_as( + type_=ErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) if _response.status_code == 500: raise InternalServerError( headers=dict(_response.headers), @@ -549,6 +628,17 @@ def delete( ), ), ) + if _response.status_code == 503: + raise ServiceUnavailableError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) @@ -663,6 +753,17 @@ def patch( ), ), ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + ErrorResponse, + parse_obj_as( + type_=ErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) if _response.status_code == 500: raise InternalServerError( headers=dict(_response.headers), @@ -685,6 +786,17 @@ def patch( ), ), ) + if _response.status_code == 503: + raise ServiceUnavailableError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) @@ -787,6 +899,17 @@ def execute_bundle( ), ), ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + ErrorResponse, + parse_obj_as( + type_=ErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) if _response.status_code == 500: raise InternalServerError( headers=dict(_response.headers), @@ -809,6 +932,17 @@ def execute_bundle( ), ), ) + if _response.status_code == 503: + raise ServiceUnavailableError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) @@ -926,6 +1060,17 @@ async def search( ), ), ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + ErrorResponse, + parse_obj_as( + type_=ErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) if _response.status_code == 500: raise InternalServerError( headers=dict(_response.headers), @@ -948,6 +1093,17 @@ async def search( ), ), ) + if _response.status_code == 503: + raise ServiceUnavailableError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) @@ -1059,6 +1215,17 @@ async def create( ), ), ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + ErrorResponse, + parse_obj_as( + type_=ErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) if _response.status_code == 500: raise InternalServerError( headers=dict(_response.headers), @@ -1081,6 +1248,17 @@ async def create( ), ), ) + if _response.status_code == 503: + raise ServiceUnavailableError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) @@ -1192,6 +1370,17 @@ async def upsert( ), ), ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + ErrorResponse, + parse_obj_as( + type_=ErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) if _response.status_code == 500: raise InternalServerError( headers=dict(_response.headers), @@ -1214,6 +1403,17 @@ async def upsert( ), ), ) + if _response.status_code == 503: + raise ServiceUnavailableError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) @@ -1315,6 +1515,17 @@ async def delete( ), ), ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + ErrorResponse, + parse_obj_as( + type_=ErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) if _response.status_code == 500: raise InternalServerError( headers=dict(_response.headers), @@ -1337,6 +1548,17 @@ async def delete( ), ), ) + if _response.status_code == 503: + raise ServiceUnavailableError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) @@ -1451,6 +1673,17 @@ async def patch( ), ), ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + ErrorResponse, + parse_obj_as( + type_=ErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) if _response.status_code == 500: raise InternalServerError( headers=dict(_response.headers), @@ -1473,6 +1706,17 @@ async def patch( ), ), ) + if _response.status_code == 503: + raise ServiceUnavailableError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) @@ -1575,6 +1819,17 @@ async def execute_bundle( ), ), ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + ErrorResponse, + parse_obj_as( + type_=ErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) if _response.status_code == 500: raise InternalServerError( headers=dict(_response.headers), @@ -1597,6 +1852,17 @@ async def execute_bundle( ), ), ) + if _response.status_code == 503: + raise ServiceUnavailableError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) _response_json = _response.json() except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)