0.12.0a9 new sqlalchemy.quart submodule

This commit is contained in:
Yusur 2026-01-01 11:41:06 +01:00
parent 09ac75f07e
commit fb245f7d12
5 changed files with 90 additions and 77 deletions

View file

@ -2,10 +2,11 @@
## 0.12.0 "The Color Update" ## 0.12.0 "The Color Update"
* Moved `AsyncSelectPagination` to submodule `sqlalchemy.quart`. If you need to use it, specify `suou[quart_sqlalchemy]` in requirements.
* All `AuthSrc()` derivatives, deprecated and never used, have been removed. * All `AuthSrc()` derivatives, deprecated and never used, have been removed.
* New module `mat` adds a shallow reimplementation of `Matrix()` in order to implement matrix multiplication * New module `mat` adds a shallow reimplementation of `Matrix()` in order to implement matrix multiplication
* Removed obsolete `configparse` implementation that has been around since 0.3 and shelved since 0.4. * Removed obsolete `configparse` implementation that has been around since 0.3 and shelved since 0.4.
* `color`: added support for conversion from RGB to sRGB, XYZ, OKLab and OKLCH. * `color`: added support for conversion from RGB to linear RGB, XYZ, OKLab and OKLCH.
* Added `user-loader` for Quart-Auth and SQLAlchemy * Added `user-loader` for Quart-Auth and SQLAlchemy
## 0.11.2 ## 0.11.2

View file

@ -39,7 +39,6 @@ Documentation = "https://suou.readthedocs.io"
# the below are all dev dependencies (and probably already installed) # the below are all dev dependencies (and probably already installed)
sqlalchemy = [ sqlalchemy = [
"SQLAlchemy[asyncio]>=2.0.0", "SQLAlchemy[asyncio]>=2.0.0",
"flask-sqlalchemy" # glue code
] ]
flask = [ flask = [
"Flask>=2.0.0", "Flask>=2.0.0",
@ -62,8 +61,13 @@ quart = [
"starlette>=0.47.2" "starlette>=0.47.2"
] ]
quart_auth = [ quart_auth = [
"Quart-Auth", "suou[quart_sqlalchemy]", # glue code
"suou[sqlalchemy]" # glue code "Quart-Auth"
]
quart_sqlalchemy = [
"suou[quart]",
"suou[sqlalchemy]",
"flask-sqlalchemy"
] ]
sass = [ sass = [
## HEADS UP!! libsass carries a C extension + uses setup.py ## HEADS UP!! libsass carries a C extension + uses setup.py
@ -71,10 +75,8 @@ sass = [
] ]
full = [ full = [
"suou[sqlalchemy]",
"suou[flask]", "suou[flask]",
"suou[quart]", "suou[quart_auth]", # includes quart, sqlalchemy and quart_sqlalchemy
"suou[quart_auth]",
"suou[peewee]", "suou[peewee]",
"suou[markdown]", "suou[markdown]",
"suou[sass]" "suou[sass]"

View file

@ -38,7 +38,7 @@ from .http import WantsContentType
from .color import OKLabColor, chalk, WebColor, RGBColor, LinearRGBColor, XYZColor, OKLabColor from .color import OKLabColor, chalk, WebColor, RGBColor, LinearRGBColor, XYZColor, OKLabColor
from .mat import Matrix from .mat import Matrix
__version__ = "0.12.0a8" __version__ = "0.12.0a9"
__all__ = ( __all__ = (
'ConfigOptions', 'ConfigParserConfigSource', 'ConfigSource', 'ConfigValue', 'ConfigOptions', 'ConfigParserConfigSource', 'ConfigSource', 'ConfigValue',

View file

@ -25,7 +25,7 @@ from typing import Callable, TypeVar
from sqlalchemy import Select, Table, func, select from sqlalchemy import Select, Table, func, select
from sqlalchemy.orm import DeclarativeBase, lazyload from sqlalchemy.orm import DeclarativeBase, lazyload
from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, create_async_engine from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, create_async_engine
from flask_sqlalchemy.pagination import Pagination
from suou.exceptions import NotFoundError from suou.exceptions import NotFoundError
from suou.glue import glue from suou.glue import glue
@ -123,73 +123,6 @@ class SQLAlchemy:
current_session: ContextVar[AsyncSession] = ContextVar('current_session') current_session: ContextVar[AsyncSession] = ContextVar('current_session')
class AsyncSelectPagination(Pagination):
"""
flask_sqlalchemy.SelectPagination but asynchronous.
Pagination is not part of the public API, therefore expect that it may break
"""
async def _query_items(self) -> list:
select_q: Select = self._query_args["select"]
select = select_q.limit(self.per_page).offset(self._query_offset)
session: AsyncSession = self._query_args["session"]
out = (await session.execute(select)).scalars()
return out
async def _query_count(self) -> int:
select_q: Select = self._query_args["select"]
sub = select_q.options(lazyload("*")).order_by(None).subquery()
session: AsyncSession = self._query_args["session"]
out = (await session.execute(select(func.count()).select_from(sub))).scalar()
return out
def __init__(self,
page: int | None = None,
per_page: int | None = None,
max_per_page: int | None = 100,
error_out: Exception | None = NotFoundError,
count: bool = True,
**kwargs):
## XXX flask-sqlalchemy says Pagination() is not public API.
## Things may break; beware.
self._query_args = kwargs
page, per_page = self._prepare_page_args(
page=page,
per_page=per_page,
max_per_page=max_per_page,
error_out=error_out,
)
self.page: int = page
"""The current page."""
self.per_page: int = per_page
"""The maximum number of items on a page."""
self.max_per_page: int | None = max_per_page
"""The maximum allowed value for ``per_page``."""
self.items = None
self.total = None
self.error_out = error_out
self.has_count = count
async def __aiter__(self):
self.items = await self._query_items()
if self.items is None:
raise RuntimeError('query returned None')
if not self.items and self.page != 1 and self.error_out:
raise self.error_out
if self.has_count:
self.total = await self._query_count()
for i in self.items:
yield i
def async_query(db: SQLAlchemy, multi: False): def async_query(db: SQLAlchemy, multi: False):
""" """
Wraps a query returning function into an executor coroutine. Wraps a query returning function into an executor coroutine.
@ -206,6 +139,7 @@ def async_query(db: SQLAlchemy, multi: False):
return executor return executor
return decorator return decorator
class SessionWrapper: class SessionWrapper:
""" """
Wrap a SQLAlchemy() session (context manager) adding several QoL utilitites. Wrap a SQLAlchemy() session (context manager) adding several QoL utilitites.
@ -257,5 +191,7 @@ class SessionWrapper:
def __del__(self): def __del__(self):
self._session.close() self._session.close()
# Optional dependency: do not import into __init__.py # Optional dependency: do not import into __init__.py
__all__ = ('SQLAlchemy', 'AsyncSelectPagination', 'async_query', 'SessionWrapper') __all__ = ('SQLAlchemy', 'AsyncSelectPagination', 'async_query', 'SessionWrapper')

View file

@ -0,0 +1,74 @@
"""
SQLAlchemy-Quart bindings
"""
from flask_sqlalchemy.pagination import Pagination
class AsyncSelectPagination(Pagination):
"""
flask_sqlalchemy.SelectPagination but asynchronous.
Pagination is not part of the public API, therefore expect that it may break
"""
async def _query_items(self) -> list:
select_q: Select = self._query_args["select"]
select = select_q.limit(self.per_page).offset(self._query_offset)
session: AsyncSession = self._query_args["session"]
out = (await session.execute(select)).scalars()
return out
async def _query_count(self) -> int:
select_q: Select = self._query_args["select"]
sub = select_q.options(lazyload("*")).order_by(None).subquery()
session: AsyncSession = self._query_args["session"]
out = (await session.execute(select(func.count()).select_from(sub))).scalar()
return out
def __init__(self,
page: int | None = None,
per_page: int | None = None,
max_per_page: int | None = 100,
error_out: Exception | None = NotFoundError,
count: bool = True,
**kwargs):
## XXX flask-sqlalchemy says Pagination() is not public API.
## Things may break; beware.
self._query_args = kwargs
page, per_page = self._prepare_page_args(
page=page,
per_page=per_page,
max_per_page=max_per_page,
error_out=error_out,
)
self.page: int = page
"""The current page."""
self.per_page: int = per_page
"""The maximum number of items on a page."""
self.max_per_page: int | None = max_per_page
"""The maximum allowed value for ``per_page``."""
self.items = None
self.total = None
self.error_out = error_out
self.has_count = count
async def __aiter__(self):
self.items = await self._query_items()
if self.items is None:
raise RuntimeError('query returned None')
if not self.items and self.page != 1 and self.error_out:
raise self.error_out
if self.has_count:
self.total = await self._query_count()
for i in self.items:
yield i