Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
21 changed files
with
614 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
.. _fastapi-sqlalchemy-example: | ||
|
||
FastAPI + SQLAlchemy example | ||
============================ | ||
|
||
.. meta:: | ||
:keywords: Python,Dependency Injection,FastAPI,SQLAlchemy,Example | ||
:description: This example demonstrates a usage of the FastAPI, SQLAlchemy, and Dependency Injector. | ||
|
||
This example shows how to use ``Dependency Injector`` with `FastAPI <https://fastapi.tiangolo.com/>`_ and | ||
`SQLAlchemy <https://www.sqlalchemy.org/>`_. | ||
|
||
The source code is available on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/fastapi-sqlalchemy>`_. | ||
|
||
Thanks to `@ShvetsovYura <https://github.com/ShvetsovYura>`_ for providing initial example: | ||
`FastAPI_DI_SqlAlchemy <https://github.com/ShvetsovYura/FastAPI_DI_SqlAlchemy>`_. | ||
|
||
Application structure | ||
--------------------- | ||
|
||
Application has next structure: | ||
|
||
.. code-block:: bash | ||
./ | ||
├── webapp/ | ||
│ ├── __init__.py | ||
│ ├── application.py | ||
│ ├── containers.py | ||
│ ├── database.py | ||
│ ├── endpoints.py | ||
│ ├── models.py | ||
│ ├── repositories.py | ||
│ ├── services.py | ||
│ └── tests.py | ||
├── config.yml | ||
├── docker-compose.yml | ||
├── Dockerfile | ||
└── requirements.txt | ||
Application factory | ||
------------------- | ||
|
||
Application factory creates container, wires it with the ``endpoints`` module, creates | ||
``FastAPI`` app, and setup routes. | ||
|
||
Application factory also creates database if it does not exist. | ||
|
||
Listing of ``webapp/application.py``: | ||
|
||
.. literalinclude:: ../../examples/miniapps/fastapi-sqlalchemy/webapp/application.py | ||
:language: python | ||
|
||
Endpoints | ||
--------- | ||
|
||
Module ``endpoints`` contains example endpoints. Endpoints have a dependency on user service. | ||
User service is injected using :ref:`wiring` feature. See ``webapp/endpoints.py``: | ||
|
||
.. literalinclude:: ../../examples/miniapps/fastapi-sqlalchemy/webapp/endpoints.py | ||
:language: python | ||
|
||
Container | ||
--------- | ||
|
||
Declarative container wires example user service, user repository, and utility database class. | ||
See ``webapp/containers.py``: | ||
|
||
.. literalinclude:: ../../examples/miniapps/fastapi-sqlalchemy/webapp/containers.py | ||
:language: python | ||
|
||
Services | ||
-------- | ||
|
||
Module ``services`` contains example user service. See ``webapp/services.py``: | ||
|
||
.. literalinclude:: ../../examples/miniapps/fastapi-sqlalchemy/webapp/services.py | ||
:language: python | ||
|
||
Repositories | ||
------------ | ||
|
||
Module ``repositories`` contains example user repository. See ``webapp/repositories.py``: | ||
|
||
.. literalinclude:: ../../examples/miniapps/fastapi-sqlalchemy/webapp/repositories.py | ||
:language: python | ||
|
||
Models | ||
------ | ||
|
||
Module ``models`` contains example SQLAlchemy user model. See ``webapp/models.py``: | ||
|
||
.. literalinclude:: ../../examples/miniapps/fastapi-sqlalchemy/webapp/models.py | ||
:language: python | ||
|
||
Database | ||
----- | ||
|
||
Module ``database`` defines declarative base and utility class with engine and session factory. | ||
See ``webapp/database.py``: | ||
|
||
.. literalinclude:: ../../examples/miniapps/fastapi-sqlalchemy/webapp/database.py | ||
:language: python | ||
|
||
Tests | ||
----- | ||
|
||
Tests use :ref:`provider-overriding` feature to replace repository with a mock. See ``webapp/tests.py``: | ||
|
||
.. literalinclude:: ../../examples/miniapps/fastapi-sqlalchemy/webapp/tests.py | ||
:language: python | ||
:emphasize-lines: 25, 45, 58, 74, 86, 97 | ||
|
||
Sources | ||
------- | ||
|
||
The source code is available on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/fastapi-sqlalchemy>`_. | ||
|
||
.. disqus:: |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
FROM python:3.9-buster | ||
|
||
ENV PYTHONUNBUFFERED=1 | ||
ENV HOST=0.0.0.0 | ||
ENV PORT=8000 | ||
|
||
WORKDIR /code | ||
COPY . /code/ | ||
|
||
RUN pip install --upgrade pip \ | ||
&& pip install -r requirements.txt | ||
|
||
CMD uvicorn webapp.application:app --host ${HOST} --port ${PORT} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
FastAPI + SQLAlchemy + Dependency Injector Example | ||
================================================== | ||
|
||
This is a `FastAPI <https://fastapi.tiangolo.com/>`_ + | ||
`SQLAlchemy <https://www.sqlalchemy.org/>`_ + | ||
`Dependency Injector <https://python-dependency-injector.ets-labs.org/>`_ example application. | ||
|
||
Thanks to `@ShvetsovYura <https://github.com/ShvetsovYura>`_ for providing initial example: | ||
`FastAPI_DI_SqlAlchemy <https://github.com/ShvetsovYura/FastAPI_DI_SqlAlchemy>`_. | ||
|
||
Run | ||
--- | ||
|
||
Build the Docker image: | ||
|
||
.. code-block:: bash | ||
docker-compose build | ||
Run the docker-compose environment: | ||
|
||
.. code-block:: bash | ||
docker-compose up | ||
The output should be something like: | ||
|
||
.. code-block:: | ||
Starting fastapi-sqlalchemy_webapp_1 ... done | ||
Attaching to fastapi-sqlalchemy_webapp_1 | ||
webapp_1 | 2021-02-04 22:07:19,804 INFO sqlalchemy.engine.base.Engine SELECT CAST('test plain returns' AS VARCHAR(60)) AS anon_1 | ||
webapp_1 | 2021-02-04 22:07:19,804 INFO sqlalchemy.engine.base.Engine () | ||
webapp_1 | 2021-02-04 22:07:19,804 INFO sqlalchemy.engine.base.Engine SELECT CAST('test unicode returns' AS VARCHAR(60)) AS anon_1 | ||
webapp_1 | 2021-02-04 22:07:19,804 INFO sqlalchemy.engine.base.Engine () | ||
webapp_1 | 2021-02-04 22:07:19,805 INFO sqlalchemy.engine.base.Engine PRAGMA main.table_info("users") | ||
webapp_1 | 2021-02-04 22:07:19,805 INFO sqlalchemy.engine.base.Engine () | ||
webapp_1 | 2021-02-04 22:07:19,808 INFO sqlalchemy.engine.base.Engine PRAGMA temp.table_info("users") | ||
webapp_1 | 2021-02-04 22:07:19,808 INFO sqlalchemy.engine.base.Engine () | ||
webapp_1 | 2021-02-04 22:07:19,809 INFO sqlalchemy.engine.base.Engine | ||
webapp_1 | CREATE TABLE users ( | ||
webapp_1 | id INTEGER NOT NULL, | ||
webapp_1 | email VARCHAR, | ||
webapp_1 | hashed_password VARCHAR, | ||
webapp_1 | is_active BOOLEAN, | ||
webapp_1 | PRIMARY KEY (id), | ||
webapp_1 | UNIQUE (email), | ||
webapp_1 | CHECK (is_active IN (0, 1)) | ||
webapp_1 | ) | ||
webapp_1 | | ||
webapp_1 | | ||
webapp_1 | 2021-02-04 22:07:19,810 INFO sqlalchemy.engine.base.Engine () | ||
webapp_1 | 2021-02-04 22:07:19,821 INFO sqlalchemy.engine.base.Engine COMMIT | ||
webapp_1 | INFO: Started server process [8] | ||
webapp_1 | INFO: Waiting for application startup. | ||
webapp_1 | INFO: Application startup complete. | ||
webapp_1 | INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit) | ||
After that visit http://127.0.0.1:8000/docs in your browser. | ||
|
||
Test | ||
---- | ||
|
||
This application comes with the unit tests. | ||
|
||
To run the tests do: | ||
|
||
.. code-block:: bash | ||
docker-compose run --rm webapp py.test webapp/tests.py --cov=webapp | ||
The output should be something like: | ||
|
||
.. code-block:: | ||
platform linux -- Python 3.9.1, pytest-6.2.2, py-1.10.0, pluggy-0.13.1 | ||
rootdir: /code | ||
plugins: cov-2.11.1 | ||
collected 7 items | ||
webapp/tests.py ....... [100%] | ||
----------- coverage: platform linux, python 3.9.1-final-0 ----------- | ||
Name Stmts Miss Cover | ||
-------------------------------------------- | ||
webapp/__init__.py 0 0 100% | ||
webapp/application.py 14 0 100% | ||
webapp/containers.py 9 0 100% | ||
webapp/database.py 24 8 67% | ||
webapp/endpoints.py 32 0 100% | ||
webapp/models.py 10 1 90% | ||
webapp/repositories.py 36 20 44% | ||
webapp/services.py 16 0 100% | ||
webapp/tests.py 59 0 100% | ||
-------------------------------------------- | ||
TOTAL 200 29 86% |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
db: | ||
url: "sqlite:///./webapp.db" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
version: "3.7" | ||
|
||
services: | ||
|
||
webapp: | ||
build: ./ | ||
image: webapp | ||
ports: | ||
- "8000:8000" | ||
volumes: | ||
- "./:/code" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
dependency-injector | ||
fastapi | ||
uvicorn | ||
pyyaml | ||
sqlalchemy | ||
pytest | ||
requests | ||
pytest-cov |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
"""Top-level package.""" |
23 changes: 23 additions & 0 deletions
23
examples/miniapps/fastapi-sqlalchemy/webapp/application.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
"""Application module.""" | ||
|
||
from fastapi import FastAPI | ||
|
||
from .containers import Container | ||
from . import endpoints | ||
|
||
|
||
def create_app() -> FastAPI: | ||
container = Container() | ||
container.config.from_yaml('config.yml') | ||
container.wire(modules=[endpoints]) | ||
|
||
db = container.db() | ||
db.create_database() | ||
|
||
app = FastAPI() | ||
app.container = container | ||
app.include_router(endpoints.router) | ||
return app | ||
|
||
|
||
app = create_app() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
"""Containers module.""" | ||
|
||
from dependency_injector import containers, providers | ||
|
||
from .database import Database | ||
from .repositories import UserRepository | ||
from .services import UserService | ||
|
||
|
||
class Container(containers.DeclarativeContainer): | ||
|
||
config = providers.Configuration() | ||
|
||
db = providers.Singleton(Database, db_url=config.db.url) | ||
|
||
user_repository = providers.Factory( | ||
UserRepository, | ||
session_factory=db.provided.session, | ||
) | ||
|
||
user_service = providers.Factory( | ||
UserService, | ||
user_repository=user_repository, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
"""Database module.""" | ||
|
||
from contextlib import contextmanager, AbstractContextManager | ||
from typing import Callable | ||
import logging | ||
|
||
from sqlalchemy import create_engine, orm | ||
from sqlalchemy.ext.declarative import declarative_base | ||
from sqlalchemy.orm import Session | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
Base = declarative_base() | ||
|
||
|
||
class Database: | ||
|
||
def __init__(self, db_url: str) -> None: | ||
self._engine = create_engine(db_url, echo=True) | ||
self._session_factory = orm.scoped_session( | ||
orm.sessionmaker( | ||
autocommit=False, | ||
autoflush=False, | ||
bind=self._engine, | ||
), | ||
) | ||
|
||
def create_database(self) -> None: | ||
Base.metadata.create_all(self._engine) | ||
|
||
@contextmanager | ||
def session(self) -> Callable[..., AbstractContextManager[Session]]: | ||
session: Session = self._session_factory() | ||
try: | ||
yield session | ||
except Exception: | ||
logger.exception('Session rollback because of exception') | ||
session.rollback() | ||
raise | ||
finally: | ||
session.close() |
Oops, something went wrong.