This is a Python package that can be used as a library to bootstrap REST API services, based on FastAPI. This page describes different concepts within the library and instructions on how to use it.
This package/library contains basic components, like Configuration helpers, Logging helpers, DB ORM helpers, Base Initializers, etc that are required to bootstrap a basic service built using Python and FastAPI. A detailed description is given below.
Name | Description |
---|---|
Component |
|
Service |
|
Controller |
|
Settings |
|
BaseORMModel |
|
BaseExceptionHandler |
|
Initializer |
|
This section describes instructions for installing the package. Primarily intended for developers using this module to build their own projects.
-
Install python3 for your environment.
-
Set up a virtualenv in your project directory. Using:
python3 -n venv .venv source .venv/bin/activate
-
Clone openg2p-fastapi-common.
-
Then Install the common package using pip:
pip install -e <path-to-cloned-common-repo>/openg2p-fastapi-common
- Follow the instructions here https://github.com/OpenG2P/openg2p-fastapi-template, to set up a repository with the given template and folder structure.
This section describes instructions for using the package/library. Primarily intended for developers using this module to build their own projects.
-
The
app.py
file in the project acts as the main file which initializes all the components of the project. It should contain anInitializer
. -
Initialize the Components (like Services, Controllers, etc.) inside the
initialize
method of theInitializer
. Example# ruff: noqa: E402 from .config import Settings _config = Settings.get_config() from openg2p_fastapi_common.app import Initializer from .services.ping_service import PingService from .controllers.ping_controller import PingController class PingInitializer(Initializer): def initialize(self): PingService() PingController().post_init()
-
Note: If the Initializer is only supposed to be used by external modules to inherit/extend/use. Then do not run
super().initialize()
inside theinitialize
method. If the Initializer is the main Initializer that sets up the FastAPI apps etc then runsuper().initialize()
inside theinitialize
method. -
Note: Due to a limitation in the way the config is set up, the
Settings.get_config()
needs to be put at the beginning of the app.py (only applies to app.py), before importing other Initializers. If your Linters/Code Formatters are throwing up an E402 error, ignore the error at the beginning of app.py. Check the above example.
-
If you are using the template given above, use the config file present in the
src
folder of your Python package. If not, create aconfig.py
file in your project that looks like this.from openg2p_fastapi_common.config import Settings from pydantic_settings import SettingsConfigDict class Settings(Settings): model_config = SettingsConfigDict( env_prefix="myproject_", env_file=".env", extra="allow" )
-
This
Settings
class derives frompydantic_settings
'sBaseSettings
. -
To define configuration parameters for your project, add the properties to the above Settings class defined in
config.py
. Exampleclass Settings(AuthSettings, Settings): ... m_param_a : str = "default_value" m_param_b : int = 12
-
The parameters defined here can be loaded through environment variables or
.env
file. The environment variables can be case insensitive. For examplemyproject_m_param_a="loaded_value" MYPROEJCT_M_PARAM_B="10456"
- The environment variable prefix,
myproject_
in the above example, can be configured undermodel_config
of Settings class, underenv_prefix
.
- The environment variable prefix,
-
To use this config in other components of your project like models/controllers/services, etc. use the
get_config
class method of the above Settings class. Example inside a controller file... from .config import Settings _config = Settings.get_config() class PingController(BaseController): ... def get_ping(self): ... print(_config.m_param_a) ...
-
Refer to Additional Configuration to see the configuration properties already available in the base
Settings
class.
-
To add more APIs to your project, create controllers in the
controllers
directory. -
Add each API route using the
add_api_route
method of the router. Exampleping_controller.py
.from openg2p_fastapi_common.controller import BaseController from .config import Settings _config = Settings.get_config() class PingController(BaseController): def __init__(self, name="", **kwargs): super().__init__(name, **kwargs) self.router.tags += ["ping"] self.router.add_api_route( "/ping", self.get_ping, methods=["GET"], ) async def get_ping(self): return "pong"
-
Initialize the Controller, preferably in an Initializer like given above. It is important to run the
post_init
method of the Controller after initializing it since that will add the API Router of the Controller to the FastAPI App. Example... from .controllers.ping_controller import PingController class PingInitializer(Initializer): def __init__(self): ... PingController().post_init()
-
A Controller will automatically initialize Response models for the APIs for the following HTTP Codes: 401, 403, 404, and 500 (with the
ErrorListResponse
response model defined in this module). These can be changed/updated accordingly.
-
Create Services in the
services
directory similar to a Controller. -
Initialize the Service in the Initializer like given above.
-
Example
from openg2p_fastapi_common.service import BaseService from .config import Settings _config = Settings.get_config() class PingService(BaseService): def ping(self, pong: str): return _config.m_param_a + " " + pong
-
To retrieve an instance of a Component (Service, Controller, etc) use
Component.get_component()
. -
Example in
PingController
if you want to retrieve thePingService
.... from .services.ping_service import PingService ... class PingController(BaseController): def __init__(self, name="", **kwargs): super().__init__(name, **kwargs) ... self.ping_service = PingService.get_component() async def get_ping(self): return self.ping_service.ping("pong")
- Get the logger using
logging.getLogger(__name__)
. This logger initializes JSON logging, using json-logging. - This can be modified using the
init_logger
method of an Initializer.
-
Define Pydantic Models and ORM Models (based on SQLAlcehmy 2.0 ORM) inside the
models
directory. -
Use
BaseORMModel
as the base class for your ORM Model.- Use
BaseORMModelWithID
as the base class, to automatically addid
andactive
fields to the ORM class, along with quick helper classmethods to get an object using id. Example<MyORMModel>.get_by_id(id)
. - Use
BaseORMModelWithTimes
as the base class, to automatically addcreated_at
andupdated_at
along with features ofBaseORMModelWithID
.
- Use
-
This module also uses the AsyncIO Extension of SQLAlchemy 2.0 ORM. Follow the link to understand how to use SQLAlchemy and async conventions to interact with the database. Example:
from openg2p_fastapi_common.context import dbengine from openg2p_fastapi_common.models import BaseORMModelWithTimes from sqlalchemy.ext.asyncio import async_sessionmaker from sqlalchemy.orm import Mapped, mapped_column from sqlalchemy import String, select class MyORMModel(BaseORMModelWithTimes): name: Mapped[str] = mapped_column(String()) @classmethod def get_by_name(cls, name: str): response = [] async_session_maker = async_sessionmaker(dbengine.get()) async with async_session_maker() as session: stmt = select(cls).where(cls.name==name).order_by(cls.id.asc()) result = await session.execute(stmt) response = list(result.scalars()) return response
The following Exceptions are defined by this module. When these exceptions are raised in code, they are caught and handled by BaseExceptionHandler.
The HTTP Response Payload looks like this when the following exceptions are raised. (The HTTP Response Status code is defined according to the Exception).
{
"errors": [
{
"code": "<error_code from Exception>",
"message": "<error_message from Exception>",
}
]
}
Exception | Description |
---|---|
UnauthorizedError | Raise this to return Default Default Default
|
ForbiddenError | Raise this to return Default Default Default
|
BadRequestError | Raise this to return Default Default Default
|
NotFoundError | Raise this to return Default Default Default
|
InternalServerError | Raise this to return Default Default Default
|
BaseAppException | BaseAppException is the parent for the custom exceptions. It takes an
|
The following configuration properties are already present in the base Settings
class mentioned in Configuration Guide.
The following properties can also be set through the environment variables, but the env_prefix
configured in your project's config Settings
will have to be used, as mentioned above. Example myproject_logging_level=DEBUG
.
Property | Description | Default Value |
---|---|---|
host | Host/IP to which the HTTP server should bind to. | 0.0.0.0 |
port | Port on which the server HTTP should run. | 8000 |
logging_level | Logging Level. Available values DEBUG , INFO , WARN , ERROR and CRITICAL . | INFO |
logging_file_name | Path to a file where the log should should be stored. If left empty, no file logging. Stdout logging is enabled by default. | |
openapi_title | Title in OpenAPI Definition. This is the title field in openapi.json generated by FastAPI. Hence also present on Swagger and API Docs. | Common |
openapi_description | Description in OpenAPI | |
openapi_version | Version in OpenAPI | 1.0.0 |
openapi_contact_url | Contact URL in OpenAPI | https://www.openg2p.org/ |
openapi_contact_email | Contact Email in OpenAPI | [email protected] |
openapi_license_name | License Name in OpenAPI | Mozilla Public License 2.0 |
openapi_license_url | License URL in OpenAPI | https://www.mozilla.org/en-US/MPL/2.0/ |
db_datasource | This is the property used by SQLALchemy for DB datasource. If left empty, this will be constructed, using the following db properties, like the following:
| |
db_driver | Driver to use while connecting to Database. Configure this based on the Database being used. If using PostgreSQL, leave it as default. | postgresql+asyncpg |
db_hostname | Database Host/IP | localhost |
db_port | Database Port | 5432 |
db_dbname | Database Name | |
db_username | Database Authentication Username | |
db_password | Database Authentication Password | |
db_logging | Database Logging. If true, all the database operations being made will be put out in the server logs. Useful while debugging. | false |
- OpenG2P FastAPI Common Module Source Code - https://github.com/OpenG2P/openg2p-fastapi-common/tree/develop/openg2p-fastapi-common