From fb245f7d128a6cc46ad7832f31e7f2b3abe7c740 Mon Sep 17 00:00:00 2001 From: Yusur Princeps Date: Thu, 1 Jan 2026 11:41:06 +0100 Subject: [PATCH] 0.12.0a9 new sqlalchemy.quart submodule --- CHANGELOG.md | 3 +- pyproject.toml | 14 ++++--- src/suou/__init__.py | 2 +- src/suou/sqlalchemy/asyncio.py | 74 +++------------------------------- src/suou/sqlalchemy/quart.py | 74 ++++++++++++++++++++++++++++++++++ 5 files changed, 90 insertions(+), 77 deletions(-) create mode 100644 src/suou/sqlalchemy/quart.py diff --git a/CHANGELOG.md b/CHANGELOG.md index d13957e..7a26905 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,11 @@ ## 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. * 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. -* `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 ## 0.11.2 diff --git a/pyproject.toml b/pyproject.toml index c020308..03b7166 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,7 +39,6 @@ Documentation = "https://suou.readthedocs.io" # the below are all dev dependencies (and probably already installed) sqlalchemy = [ "SQLAlchemy[asyncio]>=2.0.0", - "flask-sqlalchemy" # glue code ] flask = [ "Flask>=2.0.0", @@ -62,8 +61,13 @@ quart = [ "starlette>=0.47.2" ] quart_auth = [ - "Quart-Auth", - "suou[sqlalchemy]" # glue code + "suou[quart_sqlalchemy]", # glue code + "Quart-Auth" +] +quart_sqlalchemy = [ + "suou[quart]", + "suou[sqlalchemy]", + "flask-sqlalchemy" ] sass = [ ## HEADS UP!! libsass carries a C extension + uses setup.py @@ -71,10 +75,8 @@ sass = [ ] full = [ - "suou[sqlalchemy]", "suou[flask]", - "suou[quart]", - "suou[quart_auth]", + "suou[quart_auth]", # includes quart, sqlalchemy and quart_sqlalchemy "suou[peewee]", "suou[markdown]", "suou[sass]" diff --git a/src/suou/__init__.py b/src/suou/__init__.py index fc84022..c97f8fb 100644 --- a/src/suou/__init__.py +++ b/src/suou/__init__.py @@ -38,7 +38,7 @@ from .http import WantsContentType from .color import OKLabColor, chalk, WebColor, RGBColor, LinearRGBColor, XYZColor, OKLabColor from .mat import Matrix -__version__ = "0.12.0a8" +__version__ = "0.12.0a9" __all__ = ( 'ConfigOptions', 'ConfigParserConfigSource', 'ConfigSource', 'ConfigValue', diff --git a/src/suou/sqlalchemy/asyncio.py b/src/suou/sqlalchemy/asyncio.py index 72578bd..e2bab47 100644 --- a/src/suou/sqlalchemy/asyncio.py +++ b/src/suou/sqlalchemy/asyncio.py @@ -25,7 +25,7 @@ from typing import Callable, TypeVar from sqlalchemy import Select, Table, func, select from sqlalchemy.orm import DeclarativeBase, lazyload from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, create_async_engine -from flask_sqlalchemy.pagination import Pagination + from suou.exceptions import NotFoundError from suou.glue import glue @@ -123,73 +123,6 @@ class SQLAlchemy: 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): """ Wraps a query returning function into an executor coroutine. @@ -205,7 +138,8 @@ def async_query(db: SQLAlchemy, multi: False): executor.query = executor.q = func return executor return decorator - + + class SessionWrapper: """ Wrap a SQLAlchemy() session (context manager) adding several QoL utilitites. @@ -257,5 +191,7 @@ class SessionWrapper: def __del__(self): self._session.close() + + # Optional dependency: do not import into __init__.py __all__ = ('SQLAlchemy', 'AsyncSelectPagination', 'async_query', 'SessionWrapper') diff --git a/src/suou/sqlalchemy/quart.py b/src/suou/sqlalchemy/quart.py new file mode 100644 index 0000000..e314f06 --- /dev/null +++ b/src/suou/sqlalchemy/quart.py @@ -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 + + + +