A template to consume rest-api in python code
.
├── client.py contains a client that exposes a generic request method and accepts `session` as instantiation parameter and api base-url and so on.
├── .env.example contains envrionment variable declarations.
├── environment.py contains environment variables load as language (Python|JS) objects
├── exceptions.py contains custom exceptions related to the target api.
├── __init__.py exports the client, the services and the exceptions.
├── README.md
├── services.py contains queries and commands related to the target api.
└── session.py contains transport layer configuration struff (max-retries, rate-limits, exponential-backoff, ..)
└── types.py contains request and response declarations.
- session --use--> environment
- client --use--> session
- client --[use]--> exceptions
- services --use--> client, exceptions, types, environment
-
Always define custom exceptions for the target Api.
-
Always define a base exception for the target Api.
-
Always seperate exceptions into two groups
techinalandbusinessthat will inherit from the base exception. -
Always translate
technicalexceptions intobusinessexceptions (when the client is business oriented, in this case use are less technical person, so they don't need to know about technical information) -
Always translate
transportlayer exceptions intotechnical, but when doing so make sure not to loose the exception context, you have to chain the translated exception and it's cause. -
Always throw an exception when the
queryor thecommandcannot be fullill, avoid returning error codes or boolean, you can't the task in your hand you just fail and fail fast. -
Never handle an exception (you can translate but don't catch and log, cause you don't know how the user of library will want to handle exception).
-
Always write the happy part of the code, if you cannot do something, just throw an exception, avoid guessing.
-
Try not do validate input parameters inside the
queryor acommanditself, define a wrapper that will do it before passing parameters to the wrappee. -
Always translate(map) Http error-code to exception.
-
All Custom exception must have the shape below:
from __future__ import annotations
# definition
class APIException(Exception):
def __init__(
self,
message: str = None,
code: str = None,
errors: dict = None,
response: Response = None
):
self.message = message
self.code = code
self.errors = errors or {}
self.response = response
@property
def cause(self) -> Exception | None:
return self.__cause__
# Usage
raise APIException(
message="message",
code="validation_error",
errors={},
response=response,
) from causeExceptionInstance # Chain the cause- The client must throw only throw technical errors.
- The service must throw only throw business errors.
