Skip to content

Commit

Permalink
Merge branch 'release/0.17.4'
Browse files Browse the repository at this point in the history
  • Loading branch information
ri-gilfanov committed Jun 23, 2021
2 parents 4c989bf + e8c9843 commit fe0aaa3
Show file tree
Hide file tree
Showing 27 changed files with 327 additions and 343 deletions.
14 changes: 11 additions & 3 deletions .flake8
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
[flake8]
count = True
exclude = .venv
max-complexity = 8
max-line-length = 88
exclude =
__pycache__,
dist,
.git,
.github,
.mypy_cache,
.pytest_cache,
.venv,
.vscode
max-complexity = 10
max-line-length = 80
show_source = True
statistics = True
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: [3.9]
python-version: [3.7, 3.8, 3.9]

steps:
- uses: actions/checkout@v2
Expand Down
14 changes: 7 additions & 7 deletions README.rst
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
==================
aiohttp-sqlalchemy
==================
|Python versions| |PyPI release| |PyPI downloads| |License| |ReadTheDocs| |GitHub CI| |Codecov| |Codacy|
|ReadTheDocs| |PyPI release| |PyPI downloads| |License| |Python versions| |GitHub CI| |Codecov| |Codacy|

.. |Python versions| image:: https://img.shields.io/badge/Python-3.7%20%7C%203.8%20%7C%203.9-blue
:target: https://pypi.org/project/aiohttp-sqlalchemy/
:alt: Python version support
.. |ReadTheDocs| image:: https://readthedocs.org/projects/aiohttp-sqlalchemy/badge/?version=latest
:target: https://aiohttp-sqlalchemy.readthedocs.io/en/latest/?badge=latest
:alt: Read The Docs build

.. |PyPI release| image:: https://badge.fury.io/py/aiohttp-sqlalchemy.svg
:target: https://pypi.org/project/aiohttp-sqlalchemy/
Expand All @@ -19,9 +19,9 @@ aiohttp-sqlalchemy
:target: https://github.com/ri-gilfanov/aiohttp-sqlalchemy/blob/master/LICENSE
:alt: MIT License

.. |ReadTheDocs| image:: https://readthedocs.org/projects/aiohttp-sqlalchemy/badge/?version=latest
:target: https://aiohttp-sqlalchemy.readthedocs.io/en/latest/?badge=latest
:alt: Read The Docs build
.. |Python versions| image:: https://img.shields.io/badge/Python-3.7%20%7C%203.8%20%7C%203.9-blue
:target: https://pypi.org/project/aiohttp-sqlalchemy/
:alt: Python version support

.. |GitHub CI| image:: https://github.com/ri-gilfanov/aiohttp-sqlalchemy/actions/workflows/ci.yml/badge.svg?branch=master
:target: https://github.com/ri-gilfanov/aiohttp-sqlalchemy/actions/workflows/ci.yml
Expand Down
79 changes: 50 additions & 29 deletions aiohttp_sqlalchemy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,32 @@

from aiohttp.web import Application
from sqlalchemy.engine import Engine
from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, create_async_engine
from sqlalchemy.ext.asyncio import (
AsyncEngine,
AsyncSession,
create_async_engine,
)
from sqlalchemy.orm import Session, sessionmaker

from aiohttp_sqlalchemy.constants import DEFAULT_KEY, SA_DEFAULT_KEY
from aiohttp_sqlalchemy.decorators import sa_decorator
from aiohttp_sqlalchemy.exceptions import DuplicateAppKeyError, DuplicateRequestKeyError
from aiohttp_sqlalchemy.exceptions import (
DuplicateAppKeyError,
DuplicateRequestKeyError,
)
from aiohttp_sqlalchemy.middlewares import sa_middleware
from aiohttp_sqlalchemy.typedefs import TBinding, TBindings, TBindTo, TSessionFactory
from aiohttp_sqlalchemy.utils import init_db, sa_init_db, sa_session, sa_session_factory
from aiohttp_sqlalchemy.typedefs import (
TBinding,
TBindings,
TBindTo,
TSessionFactory,
)
from aiohttp_sqlalchemy.utils import (
init_db,
sa_init_db,
sa_session,
sa_session_factory,
)
from aiohttp_sqlalchemy.views import (
SAAbstractView,
SABaseView,
Expand All @@ -20,38 +37,42 @@
SAView,
)

__version__ = "0.17.3"
__version__ = '0.17.4'

__all__ = [
"SA_DEFAULT_KEY",
"DuplicateAppKeyError",
"DuplicateRequestKeyError",
"SABaseView",
"SAMixin",
"SAModelMixin",
"SAView",
"bind",
"init_db",
"sa_decorator",
"sa_middleware",
"sa_session",
"sa_session_factory",
"setup",
'SA_DEFAULT_KEY',
'DuplicateAppKeyError',
'DuplicateRequestKeyError',
'SABaseView',
'SAMixin',
'SAModelMixin',
'SAView',
'bind',
'init_db',
'sa_decorator',
'sa_middleware',
'sa_session',
'sa_session_factory',
'setup',
# Synonyms
"DEFAULT_KEY",
"SAAbstractView",
"sa_bind",
"sa_init_db",
'DEFAULT_KEY',
'SAAbstractView',
'sa_bind',
'sa_init_db',
]


def bind(
bind_to: TBindTo, key: str = SA_DEFAULT_KEY, *, middleware: bool = True
) -> "TBinding":
bind_to: TBindTo,
key: str = SA_DEFAULT_KEY,
*,
middleware: bool = True,
) -> 'TBinding':
"""Function wrapper for binding.
:param bind_to: target for SQLAlchemy binding. Argument can be database connection
url, asynchronous engine or asynchronous session factory.
:param bind_to: target for SQLAlchemy binding. Argument can be database
connection url, asynchronous engine or asynchronous session
factory.
:param key: key of SQLAlchemy binding.
:param middleware: `bool` for enable middleware. True by default.
"""
Expand All @@ -70,11 +91,11 @@ def bind(

for type_ in (AsyncSession, Engine, Session):
if isinstance(bind_to, type_):
msg = f"{type_} is unsupported type of argument `bind_to`."
msg = f'{type_} is unsupported type of argument `bind_to`.'
raise TypeError(msg)

if not callable(bind_to):
msg = f"{bind_to} is unsupported type of argument `bind_to`."
msg = f'{bind_to} is unsupported type of argument `bind_to`.'
raise TypeError(msg)

return bind_to, key, middleware
Expand Down
2 changes: 1 addition & 1 deletion aiohttp_sqlalchemy/constants.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
SA_DEFAULT_KEY = "sa_main"
SA_DEFAULT_KEY = 'sa_main'

DEFAULT_KEY = SA_DEFAULT_KEY # synonym
4 changes: 3 additions & 1 deletion aiohttp_sqlalchemy/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ def sa_decorator(key: str = SA_DEFAULT_KEY) -> THandlerWrapper:
def wrapper(handler: THandler) -> THandler:
@wraps(handler)
async def wrapped(*args: Any, **kwargs: Any) -> StreamResponse:
request = args[0].request if isinstance(args[0], AbstractView) else args[-1]
request = args[0].request \
if isinstance(args[0], AbstractView) \
else args[-1]

if key in request:
raise DuplicateRequestKeyError(key)
Expand Down
8 changes: 4 additions & 4 deletions aiohttp_sqlalchemy/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@ class AbstractDuplicateKeyError(ValueError):
class DuplicateAppKeyError(AbstractDuplicateKeyError):
def __init__(self, key: str):
msg = (
f"Duplicated app key `{key}`. Check `bindings` argument "
f"in `aiohttp_sqlalchemy.setup()` call."
f'Duplicated app key `{key}`. Check `bindings` argument '
f'in `aiohttp_sqlalchemy.setup()` call.'
)
super().__init__(msg)


class DuplicateRequestKeyError(AbstractDuplicateKeyError):
def __init__(self, key: str):
msg = (
f"Duplicated request key `{key}`. Check middlewares and "
f"decorators from `aiohttp_sqlalchemy`."
f'Duplicated request key `{key}`. Check middlewares and '
f'decorators from `aiohttp_sqlalchemy`.'
)
super().__init__(msg)
5 changes: 4 additions & 1 deletion aiohttp_sqlalchemy/middlewares.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ def sa_middleware(key: str = SA_DEFAULT_KEY) -> THandler:
"""

@middleware
async def sa_middleware_(request: Request, handler: THandler) -> StreamResponse:
async def sa_middleware_(
request: Request,
handler: THandler,
) -> StreamResponse:
if key in request:
raise DuplicateRequestKeyError(key)

Expand Down
7 changes: 4 additions & 3 deletions aiohttp_sqlalchemy/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,13 @@ def sa_session(
:param key: key of SQLAlchemy binding.
"""
if not isinstance(request, Request):
raise TypeError(f"{request} is not {Request}.")
raise TypeError(f'{request} is not {Request}.')

session = request.get(key)
if not isinstance(session, AsyncSession):
raise TypeError(f"{session} returned by {key} is not {AsyncSession} instance.")

raise TypeError(
f'{session} returned by {key} is not {AsyncSession} instance.'
)
return session


Expand Down
105 changes: 81 additions & 24 deletions docs/advansed.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
==============
Advansed usage
==============
Multiple database backends per session
--------------------------------------
See `Partitioning Strategies (e.g. multiple database backends per Session)
<https://docs.sqlalchemy.org/en/14/orm/persistence_techniques.html#partitioning-strategies-e-g-multiple-database-backends-per-session>`_
section in SQLAlchemy 1.4 documentation.

Multiple session factories in application
-----------------------------------------
.. code-block:: python
Expand All @@ -17,46 +23,97 @@ Multiple session factories in application
aiohttp_sqlalchemy.bind(sqlite_url, 'sa_third'),
])
Multiple database backends per session
--------------------------------------
See `Partitioning Strategies (e.g. multiple database backends per Session)
<https://docs.sqlalchemy.org/en/14/orm/persistence_techniques.html#partitioning-strategies-e-g-multiple-database-backends-per-session>`_
section in SQLAlchemy 1.4 documentation.
Class based views
-----------------
Decorating handlers
-------------------
.. warning::

For use a some session factory in decorators, you must set a ``middleware``
argument to ``False`` in ``bind()`` call. Else will raise an exception
``DuplicateRequestKeyError``.

If access to one or more databases is needed only in some request handlers, then you can
use a ``sa_decorator(key)``. For example:

.. code-block:: python
class Handler(aiohttp_sqlalchemy.SAView):
async def get(self):
db_session = self.sa_session()
from aiohttp_sqlalchemy import sa_decorator
@sa_decorator('sa_specific')
async def specific_handler(request):
specific_db_session = sa_session(request, 'sa_specific')
async with specific_db_session.begin():
# some your code
aiohttp_sqlalchemy.setup(app, [
aiohttp_sqlalchemy.bind(MainSession),
aiohttp_sqlalchemy.bind(specific_db_url, 'sa_specific', middleware=False),
])
You can combine the use of decorators with the use of middlewares. For example:

Decorating handlers
-------------------
.. warning::
.. code-block:: python
For use a some session factory in decorators, you must set a ``middleware``
argument to ``False`` in ``bind()`` call. Else will raise an exception
``DuplicateRequestKeyError``.
from aiohttp_sqlalchemy import sa_decorator
async def simple_handler(request):
main_db_session = sa_session(request)
async with main_db_session.begin():
# some your code
@sa_decorator('sa_specific')
async def specific_handler(request):
main_db_session = sa_session(request)
specific_db_session = sa_session(request, 'sa_specific')
async with main_db_session.begin():
# some your code
async with specific_db_session.begin():
# some your code
aiohttp_sqlalchemy.setup(app, [
aiohttp_sqlalchemy.bind(main_db_url),
aiohttp_sqlalchemy.bind(specific_db_url, 'sa_specific', middleware=False),
])
app.add_routes([
web.get('/simple', simple_handler),
web.get('/specific', specific_handler),
])
You can apply ``sa_decorator(key)`` with class based views. For example:

.. code-block:: python
@sa_decorator('sa_optional')
async def handler(request):
# some your code
from aiohttp import web
from aiohttp_sqlalchemy import SAView, sa_decorator
class Handler(SAView):
@sa_decorator('sa_optional')
SPECIFIC_DB_KEY = 'sa_specific'
SPECIFIC_DB_URL = 'sqlite+aiosqlite:///'
class SpecificHandler(SAView):
@property
def specific_session(self):
return self.sa_session(SPECIFIC_DB_KEY)
@sa_decorator(SPECIFIC_DB_KEY)
async def get(self):
# some your code
async with self.specific_session.begin():
# some your code
@sa_decorator(SPECIFIC_DB_KEY)
async def post(self):
async with self.specific_session.begin():
# some your code
aiohttp_sqlalchemy.setup(app, [
aiohttp_sqlalchemy.bind(Session, 'sa_optional', middleware=False),
aiohttp_sqlalchemy.bind(SPECIFIC_DB_URL, SPECIFIC_DB_KEY, middleware=False),
])
app.add_routes([web.view('/', SpecificHandler)])

0 comments on commit fe0aaa3

Please sign in to comment.