Dealing with errors¶
Note
This chapter describe common error that may happen at runtime.
Some exception can happen during the registration phase, using
the function blacksmith.scan()
which are not par not
runtime desigated runtime errors here.
HTTP Errors¶
Changed in version 2.0: the HTTP Errors are not throws anymore.
When a resource is consuming a get, post, put, patch, delete, collection_get, and so on, the function return a Result object that have method to unwrap the resource or the associated error.
An error is a class blacksmith.HTTPError
and get the status_code
of the errors with a JSON payload.
Async example¶
from result import Result
from blacksmith import (
AsyncClientFactory,
AsyncStaticDiscovery,
CollectionIterator,
HTTPError,
ResponseBox,
)
from .resources import Item, PartialItem
async def main():
sd = AsyncStaticDiscovery({("api", None): "http://srv:8000/"})
cli = AsyncClientFactory(sd)
api = await cli("api")
items: Result[CollectionIterator[PartialItem], HTTPError] = (
await api.item.collection_get()
)
if items.is_ok():
for item in items.unwrap():
rfull_item: ResponseBox[Item, HTTPError] = await api.item.get(
{"name": item.name}
)
if rfull_item.is_err():
print(f"Unexpected error: {rfull_item.json}")
continue
full_item = rfull_item.unwrap()
print(full_item)
else:
err = items.unwrap_err()
print(f"Unexpected error: {err.json}")
Sync example¶
from result import Result
from blacksmith import (
CollectionIterator,
HTTPError,
ResponseBox,
SyncClientFactory,
SyncStaticDiscovery,
)
from .resources import Item, PartialItem
def main():
sd = SyncStaticDiscovery({("api", None): "http://srv:8000/"})
cli = SyncClientFactory(sd)
api = cli("api")
items: Result[CollectionIterator[PartialItem], HTTPError] = (
api.item.collection_get()
)
if items.is_ok():
for item in items.unwrap():
rfull_item: ResponseBox[Item, HTTPError] = api.item.get({"name": item.name})
if rfull_item.is_err():
print(f"Unexpected error: {rfull_item.json}")
continue
full_item = rfull_item.unwrap()
print(full_item)
else:
err = items.unwrap_err()
print(f"Unexpected error: {err.json}")
Note
The error is supposed to be a json document, under attribute json
.
If it is not the case, the content of the document will be in plain text
under the key detail
.
HTTP Errors Parser¶
To get better error handling, a parser can be passed to the Client Factory to replace the raw HTTPError received by a parsed version.
Usually, API have a consistent way to represent error in the set of route.
Async example¶
from typing import Optional
from pydantic import BaseModel, Field
from result import Result
from blacksmith import (
AsyncClientFactory,
AsyncStaticDiscovery,
CollectionIterator,
ResponseBox,
)
from blacksmith.domain.exceptions import HTTPError
from blacksmith.domain.model.http import HTTPRequest
from .resources import Item, PartialItem
class APIError(BaseModel):
request: HTTPRequest = Field(...)
message: str = Field(...)
detail: Optional[str] = Field(None)
def error_parser(error: HTTPError) -> APIError:
return APIError(request=error.request, **error.json)
async def main():
sd = AsyncStaticDiscovery({("api", None): "http://srv:8000/"})
cli = AsyncClientFactory(sd, error_parser=error_parser)
api = await cli("api")
items: Result[CollectionIterator[PartialItem], APIError] = (
await api.item.collection_get()
)
if items.is_ok():
for item in items.unwrap():
rfull_item: ResponseBox[Item, APIError] = await api.item.get(
{"name": item.name}
)
if rfull_item.is_err():
print(f"Unexpected error: {rfull_item.json}")
continue
full_item = rfull_item.unwrap()
print(full_item)
else:
# In this case, the error is not an APIError instance
# build using the error_parser.
err = items.unwrap_err()
print(f"Unexpected error: {err}")
Sync example¶
from typing import Optional
from pydantic import BaseModel, Field
from result import Result
from blacksmith import (
CollectionIterator,
ResponseBox,
SyncClientFactory,
SyncStaticDiscovery,
)
from blacksmith.domain.exceptions import HTTPError
from blacksmith.domain.model.http import HTTPRequest
from .resources import Item, PartialItem
class APIError(BaseModel):
request: HTTPRequest = Field(...)
message: str = Field(...)
detail: Optional[str] = Field(None)
def error_parser(error: HTTPError) -> APIError:
return APIError(request=error.request, **error.json)
def main():
sd = SyncStaticDiscovery({("api", None): "http://srv:8000/"})
cli = SyncClientFactory(sd, error_parser=error_parser)
api = cli("api")
items: Result[CollectionIterator[PartialItem], APIError] = api.item.collection_get()
if items.is_ok():
for item in items.unwrap():
rfull_item: ResponseBox[Item, APIError] = api.item.get({"name": item.name})
if rfull_item.is_err():
print(f"Unexpected error: {rfull_item.json}")
continue
full_item = rfull_item.unwrap()
print(full_item)
else:
# In this case, the error is not an APIError instance
# build using the error_parser.
err = items.unwrap_err()
print(f"Unexpected error: {err.json}")
Timeout¶
If a service is too slow, a blacksmith.HTTPTimeoutError
exception
will be raised to avoid a process to be locked.
The default timeout is at 30 seconds but it can be configured on the client
factory, and can be overriden on every http call.
The default connect timeout is at 15 seconds.
from blacksmith import AsyncClientFactory, AsyncStaticDiscovery, HTTPTimeout
sd = AsyncStaticDiscovery({})
# read timeout at 5 seconds
# and connect timeout at 5 seconds
cli = AsyncClientFactory(sd, timeout=(10.0, 5.0))
# Or
cli = AsyncClientFactory(sd, timeout=HTTPTimeout(10.0, 5.0))
# All timeout at 10 seconds
cli = AsyncClientFactory(sd, timeout=10.0)
# Or
cli = AsyncClientFactory(sd, timeout=HTTPTimeout(10.0))
async def main():
api = await cli("api")
# user the default timeout
resp = await api.resource.collection_get()
# force the timeout
resp = await api.resource.collection_get(timeout=42.0)
# Or even with a connect timeout using the HTTPTimeout class
resp = await api.resource.collection_get(timeout=HTTPTimeout(42.0, 7.0)) # noqa
Opened Circuit Breaker¶
While using the Circuit Breaker Middleware, the OpenedState exception is raised from the Circuit Breaker library, when a service is detected down, and then, that circuit has been opened.
Note
While writing your own Middleware, Exceptions, such as the HTTPError must be raise
to let the Circuit Breaker track them.
The Result[T, E]
exposed on client resources is a layer that consume the whole
middleware stack response, and is not used internaly in middlewares.
Runtime Errors¶
During the development, blacksmith may raises different RuntimeError or TypeError while consuming unregistered resources, or typo in consumers. Those exception are sanity check with an explicit message to get the source of the error.