From fca91bdc54c38f9d4e1244b19fcdd638cd48e56e Mon Sep 17 00:00:00 2001 From: Yusur Princeps Date: Sat, 11 Oct 2025 10:22:49 +0200 Subject: [PATCH 1/2] improve decorator typing --- src/suou/dei.py | 7 +++++-- src/suou/functools.py | 8 ++++---- src/suou/luck.py | 4 ++-- src/suou/sqlalchemy/__init__.py | 20 +++++++++++--------- src/suou/sqlalchemy/asyncio.py | 7 +++++-- 5 files changed, 27 insertions(+), 19 deletions(-) diff --git a/src/suou/dei.py b/src/suou/dei.py index 0f7a7a0..d114289 100644 --- a/src/suou/dei.py +++ b/src/suou/dei.py @@ -19,7 +19,10 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. from __future__ import annotations from functools import wraps -from typing import Callable +from typing import Callable, TypeVar + +_T = TypeVar('_T') +_U = TypeVar('_U') BRICKS = '@abcdefghijklmnopqrstuvwxyz+?-\'/' @@ -122,7 +125,7 @@ def dei_args(**renames): Dear conservatives, this does not influence the ability to call the wrapped function with the original parameter names. """ - def decorator(func: Callable): + def decorator(func: Callable[_T, _U]) -> Callable[_T, _U]: @wraps(func) def wrapper(*args, **kwargs): for alias_name, actual_name in renames.items(): diff --git a/src/suou/functools.py b/src/suou/functools.py index b841a00..f07ea81 100644 --- a/src/suou/functools.py +++ b/src/suou/functools.py @@ -19,7 +19,7 @@ import math from threading import RLock import time from types import CoroutineType, NoneType -from typing import Callable, Iterable, Mapping, TypeVar +from typing import Any, Callable, Iterable, Mapping, Never, TypeVar import warnings from functools import update_wrapper, wraps, lru_cache @@ -70,7 +70,7 @@ def not_implemented(msg: Callable | str | None = None): """ A more elegant way to say a method is not implemented, but may get in the future. """ - def decorator(func: Callable) -> Callable: + def decorator(func: Callable[_T, Any]) -> Callable[_T, Never]: da_msg = msg if isinstance(msg, str) else 'method {name}() is not implemented'.format(name=func.__name__) @wraps(func) def wrapper(*a, **k): @@ -288,7 +288,7 @@ def timed_cache(ttl: int, maxsize: int = 128, typed: bool = False, *, async_: bo NEW 0.5.0 """ - def decorator(func): + def decorator(func: Callable[_T, _U]) -> Callable[_T, _U]: start_time = None if async_: @@ -318,7 +318,7 @@ def timed_cache(ttl: int, maxsize: int = 128, typed: bool = False, *, async_: bo return wrapper return decorator -def none_pass(func: Callable, *args, **kwargs) -> Callable: +def none_pass(func: Callable[_T, _U], *args, **kwargs) -> Callable[_T, _U]: """ Wrap callable so that gets called only on not None values. diff --git a/src/suou/luck.py b/src/suou/luck.py index 669025c..1ea9039 100644 --- a/src/suou/luck.py +++ b/src/suou/luck.py @@ -35,7 +35,7 @@ def lucky(validators: Iterable[Callable[[_U], bool]] = ()): NEW 0.7.0 """ - def decorator(func: Callable[..., _U]): + def decorator(func: Callable[_T, _U]) -> Callable[_T, _U]: @wraps(func) def wrapper(*args, **kwargs) -> _U: try: @@ -102,7 +102,7 @@ def rng_overload(prev_func: RngCallable[..., _U] | int | None, /, *, weight: int if isinstance(prev_func, int) and weight == 1: weight, prev_func = prev_func, None - def decorator(func: Callable[_T, _U]): + def decorator(func: Callable[_T, _U]) -> RngCallable[_T, _U]: nonlocal prev_func if prev_func is None: prev_func = RngCallable(func, weight=weight) diff --git a/src/suou/sqlalchemy/__init__.py b/src/suou/sqlalchemy/__init__.py index 63216b6..7794f39 100644 --- a/src/suou/sqlalchemy/__init__.py +++ b/src/suou/sqlalchemy/__init__.py @@ -1,5 +1,5 @@ """ -Utilities for SQLAlchemy +Utilities for SQLAlchemy. --- @@ -33,12 +33,16 @@ from ..iding import Siq, SiqGen, SiqType, SiqCache from ..classtools import Incomplete, Wanted - _T = TypeVar('_T') +_U = TypeVar('_U') -# SIQs are 14 bytes long. Storage is padded for alignment -# Not to be confused with SiqType. IdType: TypeEngine = LargeBinary(16) +""" +Database type for SIQ. + +SIQs are 14 bytes long. Storage is padded for alignment +Not to be confused with SiqType. +""" def create_session(url: str) -> Session: """ @@ -52,7 +56,6 @@ def create_session(url: str) -> Session: return Session(bind = engine) - def token_signer(id_attr: Column | str, secret_attr: Column | str) -> Incomplete[UserSigner]: """ Generate a user signing function. @@ -80,9 +83,6 @@ def token_signer(id_attr: Column | str, secret_attr: Column | str) -> Incomplete return Incomplete(Wanted(token_signer_factory)) - - - ## (in)Utilities for use in web apps below @deprecated('not part of the public API and not even working') @@ -93,6 +93,8 @@ class AuthSrc(metaclass=ABCMeta): This is an abstract class and is NOT usable directly. This is not part of the public API + + DEPRECATED ''' def required_exc(self) -> Never: raise ValueError('required field missing') @@ -140,7 +142,7 @@ def require_auth_base(cls: type[DeclarativeBase], *, src: AuthSrc, column: str | invalid_exc = src.invalid_exc or _default_invalid required_exc = src.required_exc or (lambda: _default_invalid('Login required')) - def decorator(func: Callable): + def decorator(func: Callable[_T, _U]) -> Callable[_T, _U]: @wraps(func) def wrapper(*a, **ka): ka[dest] = get_user(src.get_token()) diff --git a/src/suou/sqlalchemy/asyncio.py b/src/suou/sqlalchemy/asyncio.py index 02a8949..331407b 100644 --- a/src/suou/sqlalchemy/asyncio.py +++ b/src/suou/sqlalchemy/asyncio.py @@ -21,14 +21,17 @@ from __future__ import annotations from functools import wraps from contextvars import ContextVar, Token +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.classtools import MISSING from suou.exceptions import NotFoundError +_T = TypeVar('_T') +_U = TypeVar('_U') + class SQLAlchemy: """ Drop-in (in fact, almost) replacement for flask_sqlalchemy.SQLAlchemy() @@ -186,7 +189,7 @@ def async_query(db: SQLAlchemy, multi: False): The query function remains available as the .q or .query attribute. """ - def decorator(func): + def decorator(func: Callable[_T, _U]) -> Callable[_T, _U]: @wraps(func) async def executor(*args, **kwargs): async with db as session: From 6c00217095e0c73566f6c3a30fbdfb9e00a49c7a Mon Sep 17 00:00:00 2001 From: Yusur Princeps Date: Sat, 11 Oct 2025 10:35:40 +0200 Subject: [PATCH 2/2] update requirements for Sphinx --- requirements.txt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 1efa301..e818cd6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,6 @@ +# This file is only used for Sphinx. +# End users should use pyproject.toml instead + itsdangerous==2.2.0 libsass==0.23.0 peewee==3.18.1 @@ -5,5 +8,7 @@ pydantic==2.12.0 quart_schema==0.22.0 setuptools==80.9.0 starlette==0.48.0 +SQLAlchemy==2.0.40 toml==0.10.2 -sphinx_rtd_theme +sphinx_rtd_theme==3.0.2 +