Skip to main content

Make application with Fastapi and Sqlalchemy with ease

Project description

Welcome to fast-sqlalchemy

This project was first made to provide some tools to use Fastapi with SQLAlchemy with ease.

Contents

Installation

Installation using pip:

pip install fast_sqlalchemy

Or with poetry:

poetry add fast_sqlalchemy

The database middlewares

Fast-sqlalchemy provide multiple middlewares to use SQLAlchemy with Fastapi easily

The DatabaseMiddleware

The main middleware is the database middleware which is made to provide a sqlalchemy session accessible throughout your application. We use the ContextVar api of python to have unique session in the context of each request.

To use this middleware you must at first create a Database object where you must pass the url of your database and the engine options of SQLAlchemy:

db = Database(
    URL.create(
        drivername="mariadb+pymysql", username = config["database"]["user"].get(str),
        password=config["database"]["password"].get(str),
        host =config["database"]["host"].get(str), database = config["database"]["name"].get(str)),
    autoflush=False
)

And then register the database middleware:

fastapi = FastAPI()
fastapi.add_middleware(DatabaseMiddleware, db=db)

After that you can have access to the sqlalchemy session of the current request, through the property session of the Database object in the entire application:

db.session.query(User).first()

Note that if you want to have access to a sqlalchemy session outside a request context, you must create a session by using the session contextmanager of the Database object:

with db.session_ctx():
    db.session.query(User).all()

The middleware is actually using this contextmanager for each request.

The AutocommitMiddleware

The auto commit middleware as its name suggest is a middleware which automatically commit at the end of each request. It must be used with the database middleware and must be registered before otherwise it won't work:

fastapi = Fastapi()
fastapi.add_middleware(AutocommitMiddleware, db=db)
fastapi.add_middleware(DatabaseMiddleware, db=db)

The event bus

The event bus provide you a way to emit event in your application and register handlers to handle them. This allows you to create an event-driven architecture for your application.

To use the event bus within your application, you must create at least one event bus and register the event bus middleware to the fastapi middlewares stack

fastapi = FastAPI()
event_bus = LocalEventBus()
fastapi.add_middleware(EventBusMiddleware, buses=[event_bus])

Once the middleware is registered with an event bus you can start creating events and event handlers. Event can be any python object, the most practical way is to create dataclass object:

@dataclass
class EmailChanged:
    email: str

Then you can add an event handler for this event:

@local_event_bus.handler(EmailChanged)
def check_email_uniqueness(e: EmailChanged):
    # some logic
    pass

There are two kinds of handler sync and async handler. Sync handlers are called once the event is emitted, whereas async handlers are called at the end of the current request. To register an async handler is nearly the same as above

@local_event_bus.async_handler(EmailChanged, OtherEvent)
def check_email_uniqueness(e: EmailChanged | OtherEvent):
    # some logic
    pass

Note that a handler can handle multiple types of event

After that you can emit events wherever you want in your Fastapi application:

emit(EmailChanged(email=email))

The database testing class

Fast-sqlalchemy provide a utility class named TestDatabase which can be used to test your Fastapi application with SQLAlchemy with ease. This class allow you to have isolated test by having each test wrapped in a transaction that is rollback at the end of each test, so that each test have a fresh database.

To use it with pytest, you can simply create two fixtures. A primary fixture with a scope of 'session' which will create a connection to the database and create the database if it doesn't exist (A testing database is created with the same name of your application's database prefixed with 'test_'). The testing database is then dropped at the end (You can optionally turn if off).

from my_app import factories

@pytest.fixture(scope="session")
def db_client():
    db_client = TestDatabase(db=app_db, factories_modules=[factories])
    with db_client.start_connection(metadata=metadata):
        yield db_client

Note that this class is compatible with the library factory_boy, you can register as shown in the example above a list of modules which contains your factory classes so that each factory wil be bound to the session provided by the TestDatabase object.

After that you can create a second fixture:

@pytest.fixture()
def sqla_session(db_client):
    with db_client.start_session() as session:
        yield session

This fixture will provide a sqlalchemy session to your tests:

def test_create_user(sqla_session):
    user = UserFactory()
    assert sqla_session.query(User).first().id == user.id

The yaml configuration loader

Fast-sqlalchemy provide a class named Configuration which allow you to have your application's configuration store in yaml files:

ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
config = Configuration(os.path.join(ROOT_DIR, "config"), env_path=os.path.join(ROOT_DIR, ".env"))
config.load_config(config="test")

When you're creating the object you must specify the path of your configuration directory, this directory will contain all of your yaml files. You can also specify a .env file which will be read thanks to the dotenv library. Then you can load these configurations file by calling load_config, you can specify a config name, this config name must match a subdirectory within the configuration directory. This subdirectory should contain yaml files that will be merged with the yaml files present at the root of the configuration directory. This way you can have multiple configurations witch will share the same base configuration. The configuration folder may look like this:

+-- config
|   +-- base.yaml
|   +-- other.yaml
|   +-- test
|    |   +-- base.yaml
|   +-- prod
|    |   +-- base.yaml

Note that you can use your environment variables within your yaml files, these variables will be parsed.

project_name: ${PROJECT_NAME}
secret_key: ${SECRET_KEY}
local: fr_FR

Then you can have access to your configuration within your application like this:

config["general"]["project_name"]

or with the get method witch accept dot-separated notation and a default value as second parameter.

config.get("general.project_name", "default_name")

Note that, if a key is not found in yaml files, as fallback we'll try to find the key in environment or raise a KeyError exception if not present.

Pydantic i18n

This utility class allow you to translate all error messages of pydantic. You can specify a translation for a specific pydantic's error code. for instance:

translations = {
  "fr_FR": {
    "value_error.any_str.max_length": "Ce champs doit faire {0} caractères",
    "value_error.any_str.min_length": "Ce {1} doit faire plus de {0} caractères",
  }
}

You can even organize it with a nested structure:

translations = {
"fr_FR": {
    "value_error": {
      "any_str": {
        "max_length": "Ce champs doit faire {0} caractères",
      }
    } 
  }
}

The error's context can be accessible through the placeholders '{\d}' like the format python's function Then you can use these translations this way:

tr = PydanticI18n(translations, local="fr_FR")

And create a middleware in Fastapi

async def request_validation_handler(request: Request, exc: RequestValidationError):
    return JSONResponse(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, content=tr.translate(exc.errors()))

Licence

This project is licensed under the terms of the MIT license.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

fast-sqlalchemy-0.12.2.tar.gz (16.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

fast_sqlalchemy-0.12.2-py3-none-any.whl (19.0 kB view details)

Uploaded Python 3

File details

Details for the file fast-sqlalchemy-0.12.2.tar.gz.

File metadata

  • Download URL: fast-sqlalchemy-0.12.2.tar.gz
  • Upload date:
  • Size: 16.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.1.14 CPython/3.10.6 Linux/5.4.0-126-generic

File hashes

Hashes for fast-sqlalchemy-0.12.2.tar.gz
Algorithm Hash digest
SHA256 f4109c21c5db79a9e6ad3ee6ee5baa6640cebac718427c082694d0f657d799ad
MD5 ac2ac94fca52f05752cb9bb9010cf3b0
BLAKE2b-256 df10cf30f5f74550164511698c55721bd4bdb6d017a3ca21f1b4005b6b61860a

See more details on using hashes here.

File details

Details for the file fast_sqlalchemy-0.12.2-py3-none-any.whl.

File metadata

  • Download URL: fast_sqlalchemy-0.12.2-py3-none-any.whl
  • Upload date:
  • Size: 19.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.1.14 CPython/3.10.6 Linux/5.4.0-126-generic

File hashes

Hashes for fast_sqlalchemy-0.12.2-py3-none-any.whl
Algorithm Hash digest
SHA256 44d449b77277b50eaf5fd986ab9cc2798062fa635a1cc068fd3065f0f25eac60
MD5 61ec80b0925b2e6882d459d1c8bf4744
BLAKE2b-256 ffca3c7ea3c2a667bac415c8df57e98cb0b7fb262771d614aad553831217a826

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page