Skip to content

Commit

Permalink
Merge branch 'release/0.17.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
ri-gilfanov committed Jun 22, 2021
2 parents dc37330 + 99c629f commit 9e794d7
Show file tree
Hide file tree
Showing 14 changed files with 177 additions and 119 deletions.
37 changes: 18 additions & 19 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -66,20 +66,21 @@ Copy and paste this code in a file and run:

.. code-block:: python
from aiohttp import web
import aiohttp_sqlalchemy
from aiohttp_sqlalchemy import sa_session
from datetime import datetime
import sqlalchemy as sa
from aiohttp import web
from sqlalchemy import orm
import aiohttp_sqlalchemy
from aiohttp_sqlalchemy import sa_session
metadata = sa.MetaData()
Base = orm.declarative_base(metadata=metadata)
class MyModel(Base):
__tablename__ = 'my_table'
__tablename__ = "my_table"
pk = sa.Column(sa.Integer, primary_key=True)
timestamp = sa.Column(sa.DateTime(), default=datetime.now)
Expand All @@ -89,30 +90,28 @@ Copy and paste this code in a file and run:
db_session = sa_session(request)
async with db_session.begin():
db_session.add_all([MyModel()])
stmt = sa.select(MyModel)
result = await db_session.execute(stmt)
items = result.scalars()
data = {}
for item in items:
data[item.pk] = item.timestamp.isoformat()
db_session.add(MyModel())
result = await db_session.execute(sa.select(MyModel))
result = result.scalars()
data = {
instance.pk: instance.timestamp.isoformat()
for instance in result
}
return web.json_response(data)
async def app_factory():
app = web.Application()
aiohttp_sqlalchemy.setup(app, [
aiohttp_sqlalchemy.bind('sqlite+aiosqlite:///'),
])
bind = aiohttp_sqlalchemy.bind("sqlite+aiosqlite:///")
aiohttp_sqlalchemy.setup(app, [bind])
await aiohttp_sqlalchemy.init_db(app, metadata)
app.add_routes([web.get('/', main)])
app.add_routes([web.get("/", main)])
return app
if __name__ == '__main__':
web.run_app(app_factory())
if __name__ == "__main__":
web.run_app(app_factory())
43 changes: 26 additions & 17 deletions aiohttp_sqlalchemy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from aiohttp.web import Application
from sqlalchemy.engine import Engine
from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, create_async_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm import Session, sessionmaker

from aiohttp_sqlalchemy.constants import DEFAULT_KEY, SA_DEFAULT_KEY
from aiohttp_sqlalchemy.decorators import sa_decorator
Expand All @@ -14,7 +14,7 @@
from aiohttp_sqlalchemy.utils import init_db, sa_init_db, sa_session, sa_session_factory
from aiohttp_sqlalchemy.views import SAAbstractView, SABaseView, SAView

__version__ = "0.16.1"
__version__ = "0.17.0"

__all__ = [
"bind",
Expand All @@ -30,7 +30,7 @@
"sa_session_factory",
"SAView",
"setup",
# synonyms
# Synonyms
"DEFAULT_KEY",
"sa_bind",
"sa_init_db",
Expand All @@ -40,7 +40,14 @@
def bind(
bind_to: TBindTo, key: str = SA_DEFAULT_KEY, *, middleware: bool = True
) -> "TBinding":
"""Session factory wrapper for binding in setup function."""
"""Session factory wrapper for binding in setup function.
:param bind_to: argument can be string with database connection url, instance of
``sqlalchemy.ext.asyncio.AsyncEngine`` or callable object which
returns ``sqlalchemy.ext.asyncio.AsyncSession`` instance.
:param key: key of SQLAlchemy binding. Has default.
:param middleware: ``bool`` for enable middleware. True by default.
"""
if isinstance(bind_to, str):
bind_to = cast(AsyncEngine, create_async_engine(bind_to))

Expand All @@ -54,26 +61,24 @@ def bind(
),
)

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

if not callable(bind_to):
msg = "Session factory must be callable."
raise ValueError(msg)

if not isinstance(bind_to(), AsyncSession):
msg = "Session factory must returning `AsyncSession` instance."
raise ValueError(msg)
msg = f"{bind_to} is unsupported type of argument `bind_to`."
raise TypeError(msg)

return bind_to, key, middleware


sa_bind = bind # sa_bind is synonym for bind


def setup(app: Application, bindings: "TBindings") -> None:
"""Setup function for binding SQLAlchemy engines."""
"""Setup function for binding SQLAlchemy engines.
:param app: instance of ``aiohttp.web_app.Application``.
:param bindings: iterable of ``aiohttp_sqlalchemy.bind()`` calls.
"""
for factory, key, middleware in bindings:
if key in app:
raise DuplicateAppKeyError(key)
Expand All @@ -82,3 +87,7 @@ def setup(app: Application, bindings: "TBindings") -> None:

if middleware:
app.middlewares.append(sa_middleware(key))


# Synonyms
sa_bind = bind
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"

DEFAULT_KEY = SA_DEFAULT_KEY # synonym for SA_DEFAULT_KEY
DEFAULT_KEY = SA_DEFAULT_KEY # synonym
6 changes: 5 additions & 1 deletion aiohttp_sqlalchemy/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@


def sa_decorator(key: str = SA_DEFAULT_KEY) -> THandlerWrapper:
"""SQLAlchemy asynchronous handler decorator."""
"""SQLAlchemy asynchronous handler decorator.
:param key: key of SQLAlchemy binding. Has default.
"""

def wrapper(handler: THandler) -> THandler:
@wraps(handler)
async def wrapped(*args: Any, **kwargs: Any) -> StreamResponse:
Expand Down
6 changes: 5 additions & 1 deletion aiohttp_sqlalchemy/middlewares.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@


def sa_middleware(key: str = SA_DEFAULT_KEY) -> THandler:
"""SQLAlchemy asynchronous middleware factory."""
"""SQLAlchemy asynchronous middleware factory.
:param key: key of SQLAlchemy binding. Has default.
"""

@middleware
async def sa_middleware_(request: Request, handler: THandler) -> StreamResponse:
if key in request:
Expand Down
34 changes: 28 additions & 6 deletions aiohttp_sqlalchemy/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,27 +13,49 @@ async def init_db(
metadata: MetaData,
key: str = SA_DEFAULT_KEY,
) -> None:
"""Create all tables, indexes and etc.
:param app: instance of ``aiohttp.web_app.Application``.
:param metadata: instance of ``sqlalchemy.scql.schema.MetaData``.
:param key: key of SQLAlchemy binding. Has default.
"""
session_factory = sa_session_factory(app, key)
async with session_factory() as session:
async with session.bind.begin() as connection:
await connection.run_sync(metadata.create_all)


sa_init_db = init_db # synonym for init_db


def sa_session(
request: Request,
key: str = SA_DEFAULT_KEY,
) -> AsyncSession:
"""Return ``AsyncSession`` instance.
:param request: instance of ``aiohttp.web_request.Request``.
:param key: key of SQLAlchemy binding. Has default.
"""
if not isinstance(request, Request):
raise TypeError(f"{request} is not {Request}.")

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

return session


def sa_session_factory(
source: Union[Request, Application],
key: str = SA_DEFAULT_KEY,
) -> TSessionFactory:
"""Return callable object which returns an ``AsyncSession`` instance.
:param sorce: instance of ``aiohttp.web_request.Request`` or
``aiohttp.web_app.Application``.
:param key: key of SQLAlchemy binding. Has default.
"""
return cast(TSessionFactory, getattr(source, "app", source).get(key))


# Synonyms
sa_init_db = init_db
30 changes: 18 additions & 12 deletions aiohttp_sqlalchemy/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,33 @@
from sqlalchemy.ext.asyncio import AsyncSession

from aiohttp_sqlalchemy.constants import SA_DEFAULT_KEY
from aiohttp_sqlalchemy.utils import sa_session


class SAAbstractView(AbstractView, metaclass=ABCMeta):
"""Simple SQLAlchemy view based on aiohttp.abc.AbstractView."""
class SAMixin(AbstractView, metaclass=ABCMeta):
"""SQLAlchemy view mixin based ``aiohttp.abc.AbstractView``."""

sa_session_key: str = SA_DEFAULT_KEY

def sa_session(self, key: Optional[str] = None) -> AsyncSession:
session = self.request.get(key or self.sa_session_key)
if isinstance(session, AsyncSession):
return session
raise TypeError(f"{session} is not {AsyncSession}")
"""Return ``AsyncSession`` instance."""
return sa_session(self.request, key or self.sa_session_key)


class SAModelMixin(SAMixin, metaclass=ABCMeta):
"""SQLAlchemy single model view mixin based ``aiohttp.abc.AbstractView``."""

class SAOneModelMixin(SAAbstractView, metaclass=ABCMeta):
"""One model SQLAlchemy view based on aiohttp.abc.AbstractView."""
sa_model: Any # Not all developers use declarative mapping


class SABaseView(View, SAAbstractView):
"""Simple SQLAlchemy view based on aiohttp.web.View."""
class SABaseView(View, SAMixin):
"""SQLAlchemy class based view."""


class SAView(View, SAModelMixin):
"""SQLAlchemy single model class based view."""


class SAView(View, SAOneModelMixin):
"""One model SQLAlchemy view based on aiohttp.web.View."""
# Synonyms
SAAbstractView = SAMixin
SAOneModelMixin = SAModelMixin
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
author = "Ruslan Ilyasovich Gilfanov"

# The full version, including alpha/beta/rc tags
release = "0.16.1"
release = "0.17.0"


# -- General configuration ---------------------------------------------------
Expand Down

0 comments on commit 9e794d7

Please sign in to comment.