From 7e80c84de695473e969bcd55466cbca8f506d4df Mon Sep 17 00:00:00 2001 From: Yusur Princeps Date: Sat, 11 Oct 2025 18:39:06 +0200 Subject: [PATCH] 0.7.2 add version= to @future(), support Py3.14, mark .waiter as future --- CHANGELOG.md | 7 +++++++ pyproject.toml | 3 ++- src/suou/__init__.py | 2 +- src/suou/functools.py | 10 ++++++++-- src/suou/sqlalchemy/orm.py | 24 ++++++++++++++++++++++-- src/suou/waiter.py | 3 +++ 6 files changed, 43 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 79cce18..1e68baf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## 0.7.2 + ++ `@future()` now can take a `version=` argument ++ `Waiter()` got marked `@future` indefinitely ++ Stage `username_column()` for release in 0.8.0 ++ Explicit support for Python 3.14 (aka python pi) + ## 0.7.1 + Add documentation ([Read The Docs](https://suou.readthedocs.io/)) diff --git a/pyproject.toml b/pyproject.toml index e9b7c97..d29c3df 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,7 +27,8 @@ classifiers = [ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13" + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14" ] [project.urls] diff --git a/src/suou/__init__.py b/src/suou/__init__.py index 6eb7546..60eff4b 100644 --- a/src/suou/__init__.py +++ b/src/suou/__init__.py @@ -37,7 +37,7 @@ from .redact import redact_url_password from .http import WantsContentType from .color import chalk -__version__ = "0.7.1" +__version__ = "0.7.2" __all__ = ( 'ConfigOptions', 'ConfigParserConfigSource', 'ConfigSource', 'ConfigValue', diff --git a/src/suou/functools.py b/src/suou/functools.py index f07ea81..91eb916 100644 --- a/src/suou/functools.py +++ b/src/suou/functools.py @@ -80,17 +80,23 @@ def not_implemented(msg: Callable | str | None = None): return decorator(msg) return decorator -def future(message: str | None = None): +def future(message: str | None = None, *, version: str = None): """ Describes experimental or future API's introduced as bug fixes (including as backports) but not yet intended for general use (mostly to keep semver consistent). + version= is the intended version release. + NEW 0.7.0 """ def decorator(func: Callable[_T, _U]) -> Callable[_T, _U]: @wraps(func) def wrapper(*a, **k) -> _U: - warnings.warn(message or f'{func.__name__}() is intended for a future release and not intended for use right now', FutureWarning) + warnings.warn(message or ( + f'{func.__name__}() is intended for release on {version} and not ready for use right now' + if version else + f'{func.__name__}() is intended for a future release and not ready for use right now' + ), FutureWarning) return func(*a, **k) return wrapper return decorator diff --git a/src/suou/sqlalchemy/orm.py b/src/suou/sqlalchemy/orm.py index 403e762..37b4def 100644 --- a/src/suou/sqlalchemy/orm.py +++ b/src/suou/sqlalchemy/orm.py @@ -1,7 +1,7 @@ """ Utilities for SQLAlchemy; ORM -NEW 0.6.0 +NEW 0.6.0 (moved) --- @@ -20,12 +20,14 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. from binascii import Incomplete import os +import re from typing import Any, Callable, TypeVar import warnings from sqlalchemy import BigInteger, Boolean, CheckConstraint, Column, Date, ForeignKey, LargeBinary, MetaData, SmallInteger, String, text from sqlalchemy.orm import DeclarativeBase, InstrumentedAttribute, Relationship, declarative_base as _declarative_base, relationship from sqlalchemy.types import TypeEngine from sqlalchemy.ext.hybrid import Comparator +from suou.functools import future from suou.classtools import Wanted, Incomplete from suou.codecs import StringCase from suou.dei import dei_args @@ -101,7 +103,7 @@ match_constraint.TEXT_DIALECTS = { 'mariadb': ':n RLIKE :re' } -def match_column(length: int, regex: str, /, case: StringCase = StringCase.AS_IS, *args, constraint_name: str | None = None, **kwargs) -> Incomplete[Column[str]]: +def match_column(length: int, regex: str | re.Pattern, /, case: StringCase = StringCase.AS_IS, *args, constraint_name: str | None = None, **kwargs) -> Incomplete[Column[str]]: """ Syntactic sugar to create a String() column with a check constraint matching the given regular expression. @@ -112,6 +114,24 @@ def match_column(length: int, regex: str, /, case: StringCase = StringCase.AS_IS return Incomplete(Column, String(length), Wanted(lambda x, n: match_constraint(n, regex, #dialect=x.metadata.engine.dialect.name, constraint_name=constraint_name or f'{x.__tablename__}_{n}_valid')), *args, **kwargs) + +@future(version='0.8.0') +def username_column( + length: int = 32, regex: str | re.Pattern = '[a-z_][a-z0-9_-]+', *args, case: StringCase = StringCase.LOWER, + nullable : bool = False, **kwargs) -> Incomplete[Column[str] | Column[str | None]]: + """ + Construct a column containing a unique handle / username. + + Username must match the given `regex` and be at most `length` characters long. + + NEW 0.8.0 + """ + if case is StringCase.AS_IS: + warnings.warn('case sensitive usernames may lead to impersonation and unexpected behavior', UserWarning) + + return match_column(length, regex, case=case, nullable=nullable, unique=True, *args, **kwargs) + + def bool_column(value: bool = False, nullable: bool = False, **kwargs) -> Column[bool]: """ Column for a single boolean value. diff --git a/src/suou/waiter.py b/src/suou/waiter.py index a210f45..897062f 100644 --- a/src/suou/waiter.py +++ b/src/suou/waiter.py @@ -21,6 +21,9 @@ from starlette.applications import Starlette from starlette.responses import JSONResponse, PlainTextResponse, Response from starlette.routing import Route +from suou.functools import future + +@future() class Waiter(): def __init__(self): self.routes: list[Route] = []