Skip to content

Commit

Permalink
Release 1.0.0
Browse files Browse the repository at this point in the history
* Added support for SQLAlchemy 2.0
* Dropped support for SQLAlchemy 1.4
  • Loading branch information
ri-gilfanov committed Sep 15, 2024
1 parent 26737bd commit 2b7a4aa
Show file tree
Hide file tree
Showing 25 changed files with 206 additions and 155 deletions.
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ aiohttp-sqlalchemy
:target: https://www.codacy.com/gh/ri-gilfanov/aiohttp-sqlalchemy/dashboard?utm_source=github.com&utm_medium=referral&utm_content=ri-gilfanov/aiohttp-sqlalchemy&utm_campaign=Badge_Grade
:alt: Codacy code quality

`SQLAlchemy 1.4 / 2.0 <https://www.sqlalchemy.org/>`_ support for `AIOHTTP
`SQLAlchemy 2.0 <https://www.sqlalchemy.org/>`_ support for `AIOHTTP
<https://docs.aiohttp.org/>`_.

The library provides the next features:
Expand Down
2 changes: 1 addition & 1 deletion docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ aiohttp-sqlalchemy's documentation
:target: https://www.codacy.com/gh/ri-gilfanov/aiohttp-sqlalchemy/dashboard?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=ri-gilfanov/aiohttp-sqlalchemy&amp;utm_campaign=Badge_Grade
:alt: Codacy code quality

`SQLAlchemy 1.4 / 2.0 <https://www.sqlalchemy.org/>`_ support for `AIOHTTP
`SQLAlchemy 2.0 <https://www.sqlalchemy.org/>`_ support for `AIOHTTP
<https://docs.aiohttp.org/>`_.

The library provides the next features:
Expand Down
2 changes: 1 addition & 1 deletion docs/quickstart.rst
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ More control in configuration
url = 'sqlite+aiosqlite:///'
engine = create_async_engine(url, echo=True)
Session = orm.sessionmaker(main_engine, AsyncSession, expire_on_commit=False)
Session = orm.sessionmaker(main_engine, class_=AsyncSession, expire_on_commit=False)
ahsa.setup(app, [
ahsa.bind(Session),
Expand Down
14 changes: 12 additions & 2 deletions docs/releases.rst
Original file line number Diff line number Diff line change
@@ -1,19 +1,29 @@
========
Releases
========
Version 1.0.0
-------------
**Added**

* Added support for SQLAlchemy 2.0.

**Removed**

* Dropped support for SQLAlchemy 1.4.

Version 0.35
------------
**Added**

* Python 3.11 and 3.12 support.
* Added support for Python 3.11 and 3.12.

**Changed**

* Hold aiohttp-things and sqlalchemy-things versions.

**Removed**

* Python 3.7 support.
* Dropped support for Python 3.7.

Version 0.34
------------
Expand Down
20 changes: 4 additions & 16 deletions examples/test_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,21 +43,15 @@ async def select_instances(session):
stmt = sa.select(MyModel)
result = await session.execute(stmt)
instances = result.scalars()
return {
instance.pk: instance.timestamp.isoformat()
for instance in instances
}
return {instance.pk: instance.timestamp.isoformat() for instance in instances}


@sa_decorator(THIRD_KEY)
@sa_decorator(FOURTH_KEY)
async def function_handler(request):
await add_instance(sa_session(request, choice(KEY_LIST)))
return web.json_response(
{
key: await select_instances(sa_session(request, key))
for key in KEY_LIST
}
{key: await select_instances(sa_session(request, key)) for key in KEY_LIST}
)


Expand All @@ -67,10 +61,7 @@ class ClassOrganizedHandler:
async def get(self, request):
await add_instance(sa_session(request, choice(KEY_LIST)))
return web.json_response(
{
key: await select_instances(sa_session(request, key))
for key in KEY_LIST
}
{key: await select_instances(sa_session(request, key)) for key in KEY_LIST}
)


Expand All @@ -80,10 +71,7 @@ class ClassBasedView(web.View, SAMixin):
async def get(self):
await add_instance(self.get_sa_session(choice(KEY_LIST)))
return web.json_response(
{
key: await select_instances(self.get_sa_session(key))
for key in KEY_LIST
}
{key: await select_instances(self.get_sa_session(key)) for key in KEY_LIST}
)


Expand Down
154 changes: 82 additions & 72 deletions poetry.lock

Large diffs are not rendered by default.

20 changes: 9 additions & 11 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
[tool.poetry]
name = "aiohttp-sqlalchemy"
version = "0.35.0"
description = "SQLAlchemy 1.4 / 2.0 support for aiohttp."
packages = [{include = "aiohttp_sqlalchemy", from = "src" }]
version = "1.0.0"
description = "SQLAlchemy 2.0 support for aiohttp."
license = "MIT"

authors = [
Expand All @@ -28,6 +29,8 @@ classifiers = [
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Database",
"Topic :: Database :: Front-Ends",
"Topic :: Internet",
Expand All @@ -41,8 +44,8 @@ classifiers = [
[tool.poetry.dependencies]
python = "^3.8"
aiohttp = "^3.10.5"
aiohttp-things = "==0.14.0"
sqlalchemy-things = "==0.10.2"
aiohttp-things = "^1.0.0"
sqlalchemy-things = "^1.0.0"
aiomysql = { version = ">=0.2.0", optional = true }
aiosqlite = { version = ">=0.20.0", optional = true }
asyncpg = { version = ">=0.29.0", optional = true }
Expand All @@ -69,15 +72,10 @@ requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"

[tool.mypy]
files = ["aiohttp_sqlalchemy", "tests"]
plugins = "sqlalchemy.ext.mypy.plugin"

[[tool.mypy.overrides]]
module = ['sqlalchemy.*']
ignore_missing_imports = true
files = ["src", "tests"]

[tool.ruff]
line-length = 79
line-length = 88

[tool.ruff.lint]
select = ["ALL"]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""AIOHTTP-SQLAlchemy. SQLAlchemy 1.4 / 2.0 support for aiohttp."""
"""AIOHTTP-SQLAlchemy. SQLAlchemy 2.0 support for aiohttp."""

from __future__ import annotations

Expand Down Expand Up @@ -110,7 +110,7 @@ def bind(
target = create_async_engine(target)

if isinstance(target, AsyncEngine):
target = sessionmaker(
target = sessionmaker( # type: ignore
bind=target,
class_=AsyncSession,
expire_on_commit=False,
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,7 @@ 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
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

from aiohttp.web import StreamResponse
from sqlalchemy.ext.asyncio import AsyncEngine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm import Session, sessionmaker

THandler = Callable[..., Awaitable[StreamResponse]]
THandlerWrapper = Callable[..., THandler]

TTarget = Union[str, AsyncEngine, sessionmaker]
TBind = Tuple[sessionmaker, str, bool]
TTarget = Union[str, AsyncEngine, sessionmaker[Session]]
TBind = Tuple[sessionmaker[Session], str, bool]
TBinds = Iterable[TBind]
13 changes: 6 additions & 7 deletions aiohttp_sqlalchemy/utils.py → src/aiohttp_sqlalchemy/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

if TYPE_CHECKING: # pragma: no cover
from sqlalchemy import MetaData
from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm import Session, sessionmaker


async def init_db(
Expand Down Expand Up @@ -38,7 +38,7 @@ async def get_engine(
:param key: key of SQLAlchemy binding.
"""
session_factory = get_session_factory(app, key)
return session_factory.kw.get("bind")
return session_factory.kw.get("bind") # type: ignore


def get_session(
Expand All @@ -64,21 +64,20 @@ def get_session(
def get_session_factory(
source: Request | Application,
key: str = SA_DEFAULT_KEY,
) -> sessionmaker:
) -> sessionmaker[Session]:
"""Return callable object which returns an `AsyncSession` instance.
:param source: AIOHTTP request object or your AIOHTTP application.
:param key: key of SQLAlchemy binding.
"""
if not isinstance(source, (Request, Application)):
msg = (
"Arg `source` must be `aiohttp.web.Application`"
"or `aiohttp.web.Request`."
"Arg `source` must be `aiohttp.web.Application`" "or `aiohttp.web.Request`."
)
raise TypeError(msg)
if isinstance(source, Request):
return source.config_dict.get(key)
return source.get(key)
return source.config_dict.get(key) # type: ignore
return source.get(key) # type: ignore


# Synonyms
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def get_update_stmt(self, model: Any = None) -> Update:


class SelectStatementMixin(SAModelMixin):
def get_select_stmt(self, model: Any = None) -> Select:
def get_select_stmt(self, model: Any = None) -> Select[Any]:
return select(model or self.sa_model)


Expand Down Expand Up @@ -115,7 +115,7 @@ class UnitViewMixin(
PrimaryKeyMixin,
metaclass=ABCMeta,
):
def get_select_stmt(self, model: Any = None) -> Select:
def get_select_stmt(self, model: Any = None) -> Select[Any]:
return super().get_select_stmt(model).where(self.sa_pk_attr == self.pk)


Expand Down
31 changes: 21 additions & 10 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,29 @@
from __future__ import annotations

from typing import TYPE_CHECKING, Any

import pytest
import sqlalchemy as sa
from aiohttp import web
from aiohttp.hdrs import METH_GET
from aiohttp.test_utils import make_mocked_request
from aiohttp.web import Request, Response
from aiohttp.web_app import Application
from sqlalchemy import orm
from sqlalchemy.ext.asyncio import (
AsyncEngine,
AsyncSession,
create_async_engine,
)
from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm import Session, sessionmaker

import aiohttp_sqlalchemy
from aiohttp_sqlalchemy import SA_DEFAULT_KEY, sa_bind, sa_middleware
from aiohttp_sqlalchemy.typedefs import THandler

if TYPE_CHECKING: # pragma: no cover
from aiohttp.web_app import Application

from aiohttp_sqlalchemy.typedefs import THandler


pytest_plugins = "aiohttp.pytest_plugin"

Expand All @@ -26,9 +34,9 @@ def wrong_key() -> str:


@pytest.fixture
def base_model() -> orm.Mapper:
def base_model() -> orm.Mapper[Any]:
metadata = sa.MetaData()
return orm.declarative_base(metadata=metadata)
return orm.declarative_base(metadata=metadata) # type: ignore


@pytest.fixture
Expand All @@ -37,12 +45,15 @@ def orm_async_engine() -> AsyncEngine:


@pytest.fixture
def session_factory(orm_async_engine: AsyncEngine) -> sessionmaker:
return sessionmaker(orm_async_engine, AsyncSession)
def session_factory(orm_async_engine: AsyncEngine) -> sessionmaker[Session]:
return sessionmaker(orm_async_engine, class_=AsyncSession) # type: ignore


@pytest.fixture
def session(session_factory: sessionmaker) -> AsyncSession:
def session(session_factory: sessionmaker[Session]) -> AsyncSession:
session = session_factory()
if not isinstance(session, AsyncSession):
raise TypeError
return session_factory()


Expand All @@ -52,14 +63,14 @@ def main_middleware() -> THandler:


@pytest.fixture
def middlewared_app(session_factory: sessionmaker) -> Application:
def middlewared_app(session_factory: sessionmaker[Session]) -> Application:
app = web.Application()
aiohttp_sqlalchemy.setup(app, [sa_bind(session_factory)])
return app


@pytest.fixture
def mocked_request(middlewared_app: Application) -> "Request":
def mocked_request(middlewared_app: Application) -> Request:
return make_mocked_request(METH_GET, "/", app=middlewared_app)


Expand Down
2 changes: 2 additions & 0 deletions tests/test_aiohttp_sqlalchemy.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

from pathlib import Path

import tomli
Expand Down
12 changes: 7 additions & 5 deletions tests/test_bind.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

import pytest
from sqlalchemy import create_engine
from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession
Expand All @@ -23,25 +25,25 @@ def test_bind_to_async_engine(orm_async_engine: AsyncEngine) -> None:
def test_bind_to_sync_engine() -> None:
engine = create_engine("sqlite+aiosqlite:///")
with pytest.raises(TypeError):
aiohttp_sqlalchemy.bind(engine)
aiohttp_sqlalchemy.bind(engine) # type: ignore


def test_bind_with_ready_session(orm_async_engine: AsyncEngine) -> None:
session = AsyncSession(orm_async_engine)
with pytest.raises(TypeError):
aiohttp_sqlalchemy.bind(session)
aiohttp_sqlalchemy.bind(session) # type: ignore


def test_bind_with_sync_session() -> None:
engine = create_engine("sqlite+aiosqlite:///")
Session = sessionmaker(engine)
session = Session()
with pytest.raises(TypeError):
aiohttp_sqlalchemy.bind(session)
aiohttp_sqlalchemy.bind(session) # type: ignore


def test_bind_to_async_session_maker(orm_async_engine: AsyncEngine) -> None:
Session = sessionmaker(orm_async_engine, AsyncSession)
Session = sessionmaker(orm_async_engine, class_=AsyncSession) # type: ignore
binding = aiohttp_sqlalchemy.bind(Session)
Session = binding[0]
session = Session()
Expand All @@ -50,4 +52,4 @@ def test_bind_to_async_session_maker(orm_async_engine: AsyncEngine) -> None:

def test_bind_to_none() -> None:
with pytest.raises(TypeError):
aiohttp_sqlalchemy.bind(None)
aiohttp_sqlalchemy.bind(None) # type: ignore
2 changes: 2 additions & 0 deletions tests/test_constants.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

import aiohttp_sqlalchemy
from aiohttp_sqlalchemy import constants

Expand Down
Loading

0 comments on commit 2b7a4aa

Please sign in to comment.