Skip to content

Commit

Permalink
Merge a5b895d into a1f779a
Browse files Browse the repository at this point in the history
  • Loading branch information
rmk135 committed Feb 4, 2021
2 parents a1f779a + a5b895d commit f9fa379
Show file tree
Hide file tree
Showing 21 changed files with 614 additions and 1 deletion.
1 change: 1 addition & 0 deletions README.rst
Expand Up @@ -161,6 +161,7 @@ Choose one of the following:
- `Sanic example <https://python-dependency-injector.ets-labs.org/examples/sanic.html>`_
- `FastAPI example <https://python-dependency-injector.ets-labs.org/examples/fastapi.html>`_
- `FastAPI + Redis example <https://python-dependency-injector.ets-labs.org/examples/fastapi-redis.html>`_
- `FastAPI + SQLAlchemy example <https://python-dependency-injector.ets-labs.org/examples/fastapi-sqlalchemy.html>`_

Tutorials
---------
Expand Down
119 changes: 119 additions & 0 deletions docs/examples/fastapi-sqlalchemy.rst
@@ -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::
1 change: 1 addition & 0 deletions docs/examples/index.rst
Expand Up @@ -20,5 +20,6 @@ Explore the examples to see the ``Dependency Injector`` in action.
sanic
fastapi
fastapi-redis
fastapi-sqlalchemy

.. disqus::
1 change: 1 addition & 0 deletions docs/introduction/di_in_python.rst
Expand Up @@ -288,6 +288,7 @@ Choose one of the following as a next step:
- :ref:`sanic-example`
- :ref:`fastapi-example`
- :ref:`fastapi-redis-example`
- :ref:`fastapi-sqlalchemy-example`
- Pass the tutorials:
- :ref:`flask-tutorial`
- :ref:`aiohttp-tutorial`
Expand Down
6 changes: 6 additions & 0 deletions docs/main/changelog.rst
Expand Up @@ -7,6 +7,12 @@ that were made in every particular version.
From version 0.7.6 *Dependency Injector* framework strictly
follows `Semantic versioning`_

Development version
-------------------
- Add ``FastAPI`` + ``SQLAlchemy`` example.
Thanks to `@ShvetsovYura <https://github.com/ShvetsovYura>`_ for providing initial example:
`FastAPI_DI_SqlAlchemy <https://github.com/ShvetsovYura/FastAPI_DI_SqlAlchemy>`_.

4.16.0
------
- Add container base class ``containers.Container``. ``DynamicContainer``
Expand Down
1 change: 1 addition & 0 deletions docs/wiring.rst
Expand Up @@ -336,5 +336,6 @@ Take a look at other application examples:
- :ref:`sanic-example`
- :ref:`fastapi-example`
- :ref:`fastapi-redis-example`
- :ref:`fastapi-sqlalchemy-example`

.. disqus::
13 changes: 13 additions & 0 deletions examples/miniapps/fastapi-sqlalchemy/Dockerfile
@@ -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}
96 changes: 96 additions & 0 deletions examples/miniapps/fastapi-sqlalchemy/README.rst
@@ -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%
2 changes: 2 additions & 0 deletions examples/miniapps/fastapi-sqlalchemy/config.yml
@@ -0,0 +1,2 @@
db:
url: "sqlite:///./webapp.db"
11 changes: 11 additions & 0 deletions examples/miniapps/fastapi-sqlalchemy/docker-compose.yml
@@ -0,0 +1,11 @@
version: "3.7"

services:

webapp:
build: ./
image: webapp
ports:
- "8000:8000"
volumes:
- "./:/code"
8 changes: 8 additions & 0 deletions examples/miniapps/fastapi-sqlalchemy/requirements.txt
@@ -0,0 +1,8 @@
dependency-injector
fastapi
uvicorn
pyyaml
sqlalchemy
pytest
requests
pytest-cov
1 change: 1 addition & 0 deletions examples/miniapps/fastapi-sqlalchemy/webapp/__init__.py
@@ -0,0 +1 @@
"""Top-level package."""
23 changes: 23 additions & 0 deletions examples/miniapps/fastapi-sqlalchemy/webapp/application.py
@@ -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()
24 changes: 24 additions & 0 deletions examples/miniapps/fastapi-sqlalchemy/webapp/containers.py
@@ -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,
)
41 changes: 41 additions & 0 deletions examples/miniapps/fastapi-sqlalchemy/webapp/database.py
@@ -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()

0 comments on commit f9fa379

Please sign in to comment.