0.7.2 add version= to @future(), support Py3.14, mark .waiter as future

This commit is contained in:
Yusur 2025-10-11 18:39:06 +02:00
parent be4404c520
commit 7e80c84de6
6 changed files with 43 additions and 6 deletions

View file

@ -1,5 +1,12 @@
# Changelog # 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 ## 0.7.1
+ Add documentation ([Read The Docs](https://suou.readthedocs.io/)) + Add documentation ([Read The Docs](https://suou.readthedocs.io/))

View file

@ -27,7 +27,8 @@ classifiers = [
"Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13" "Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14"
] ]
[project.urls] [project.urls]

View file

@ -37,7 +37,7 @@ from .redact import redact_url_password
from .http import WantsContentType from .http import WantsContentType
from .color import chalk from .color import chalk
__version__ = "0.7.1" __version__ = "0.7.2"
__all__ = ( __all__ = (
'ConfigOptions', 'ConfigParserConfigSource', 'ConfigSource', 'ConfigValue', 'ConfigOptions', 'ConfigParserConfigSource', 'ConfigSource', 'ConfigValue',

View file

@ -80,17 +80,23 @@ def not_implemented(msg: Callable | str | None = None):
return decorator(msg) return decorator(msg)
return decorator 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) 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). but not yet intended for general use (mostly to keep semver consistent).
version= is the intended version release.
NEW 0.7.0 NEW 0.7.0
""" """
def decorator(func: Callable[_T, _U]) -> Callable[_T, _U]: def decorator(func: Callable[_T, _U]) -> Callable[_T, _U]:
@wraps(func) @wraps(func)
def wrapper(*a, **k) -> _U: 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 func(*a, **k)
return wrapper return wrapper
return decorator return decorator

View file

@ -1,7 +1,7 @@
""" """
Utilities for SQLAlchemy; ORM 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 from binascii import Incomplete
import os import os
import re
from typing import Any, Callable, TypeVar from typing import Any, Callable, TypeVar
import warnings import warnings
from sqlalchemy import BigInteger, Boolean, CheckConstraint, Column, Date, ForeignKey, LargeBinary, MetaData, SmallInteger, String, text 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.orm import DeclarativeBase, InstrumentedAttribute, Relationship, declarative_base as _declarative_base, relationship
from sqlalchemy.types import TypeEngine from sqlalchemy.types import TypeEngine
from sqlalchemy.ext.hybrid import Comparator from sqlalchemy.ext.hybrid import Comparator
from suou.functools import future
from suou.classtools import Wanted, Incomplete from suou.classtools import Wanted, Incomplete
from suou.codecs import StringCase from suou.codecs import StringCase
from suou.dei import dei_args from suou.dei import dei_args
@ -101,7 +103,7 @@ match_constraint.TEXT_DIALECTS = {
'mariadb': ':n RLIKE :re' '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. 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, 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) 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]: def bool_column(value: bool = False, nullable: bool = False, **kwargs) -> Column[bool]:
""" """
Column for a single boolean value. Column for a single boolean value.

View file

@ -21,6 +21,9 @@ from starlette.applications import Starlette
from starlette.responses import JSONResponse, PlainTextResponse, Response from starlette.responses import JSONResponse, PlainTextResponse, Response
from starlette.routing import Route from starlette.routing import Route
from suou.functools import future
@future()
class Waiter(): class Waiter():
def __init__(self): def __init__(self):
self.routes: list[Route] = [] self.routes: list[Route] = []