diff --git a/src/suou/__init__.py b/src/suou/__init__.py index 59b3aa0..ac4ddaa 100644 --- a/src/suou/__init__.py +++ b/src/suou/__init__.py @@ -34,7 +34,7 @@ from .validators import matches from .redact import redact_url_password from .http import WantsContentType -__version__ = "0.5.2" +__version__ = "0.5.3-dev34" __all__ = ( 'ConfigOptions', 'ConfigParserConfigSource', 'ConfigSource', 'ConfigValue', diff --git a/src/suou/classtools.py b/src/suou/classtools.py index c27fa61..c58a123 100644 --- a/src/suou/classtools.py +++ b/src/suou/classtools.py @@ -17,6 +17,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. from __future__ import annotations from abc import abstractmethod +from types import EllipsisType from typing import Any, Callable, Generic, Iterable, Mapping, TypeVar import logging @@ -24,7 +25,10 @@ _T = TypeVar('_T') logger = logging.getLogger(__name__) -MISSING = object() +class MissingType(object): + __slots__ = () + +MISSING = MissingType() def _not_missing(v) -> bool: return v and v is not MISSING @@ -43,10 +47,10 @@ class Wanted(Generic[_T]): Owner class will call .__set_name__() on the parent Incomplete instance; the __set_name__ parameters (owner class and name) will be passed down here. """ - _target: Callable | str | None | Ellipsis - def __init__(self, getter: Callable | str | None | Ellipsis): + _target: Callable | str | None | EllipsisType + def __init__(self, getter: Callable | str | None | EllipsisType): self._target = getter - def __call__(self, owner: type, name: str | None = None) -> _T: + def __call__(self, owner: type, name: str | None = None) -> _T | str | None: if self._target is None or self._target is Ellipsis: return name elif isinstance(self._target, str): @@ -67,10 +71,10 @@ class Incomplete(Generic[_T]): Missing arguments must be passed in the appropriate positions (positional or keyword) as a Wanted() object. """ - _obj = Callable[Any, _T] + _obj: Callable[..., _T] _args: Iterable _kwargs: dict - def __init__(self, obj: Callable[Any, _T] | Wanted, *args, **kwargs): + def __init__(self, obj: Callable[..., _T] | Wanted, *args, **kwargs): if isinstance(obj, Wanted): self._obj = lambda x: x self._args = (obj, ) @@ -120,7 +124,7 @@ class ValueSource(Mapping): class ValueProperty(Generic[_T]): _name: str | None _srcs: dict[str, str] - _val: Any | MISSING + _val: Any | MissingType _default: Any | None _cast: Callable | None _required: bool diff --git a/src/suou/sqlalchemy.py b/src/suou/sqlalchemy.py index 2f434e2..4e27f7a 100644 --- a/src/suou/sqlalchemy.py +++ b/src/suou/sqlalchemy.py @@ -18,10 +18,11 @@ from __future__ import annotations from abc import ABCMeta, abstractmethod from functools import wraps -from typing import Callable, Iterable, Never, TypeVar +from typing import Any, Callable, Iterable, Never, TypeVar import warnings from sqlalchemy import BigInteger, Boolean, CheckConstraint, Date, Dialect, ForeignKey, LargeBinary, Column, MetaData, SmallInteger, String, create_engine, select, text from sqlalchemy.orm import DeclarativeBase, InstrumentedAttribute, Relationship, Session, declarative_base as _declarative_base, relationship +from sqlalchemy.types import TypeEngine from .snowflake import SnowflakeGen from .itertools import kwargs_prefix, makelist @@ -35,7 +36,7 @@ _T = TypeVar('_T') # SIQs are 14 bytes long. Storage is padded for alignment # Not to be confused with SiqType. -IdType: type[LargeBinary] = LargeBinary(16) +IdType: TypeEngine = LargeBinary(16) @not_implemented def sql_escape(s: str, /, dialect: Dialect) -> str: @@ -158,6 +159,7 @@ def token_signer(id_attr: Column | str, secret_attr: Column | str) -> Incomplete Requires a master secret (taken from Base.metadata), a user id (visible in the token) and a user secret. """ + id_val: Column | Wanted[Column] if isinstance(id_attr, Column): id_val = id_attr elif isinstance(id_attr, str): @@ -168,13 +170,16 @@ def token_signer(id_attr: Column | str, secret_attr: Column | str) -> Incomplete secret_val = Wanted(secret_attr) def token_signer_factory(owner: DeclarativeBase, name: str): def my_signer(self): - return UserSigner(owner.metadata.info['secret_key'], id_val.__get__(self, owner), secret_val.__get__(self, owner)) + return UserSigner( + owner.metadata.info['secret_key'], + id_val.__get__(self, owner), secret_val.__get__(self, owner) # pyright: ignore[reportAttributeAccessIssue] + ) my_signer.__name__ = name return my_signer return Incomplete(Wanted(token_signer_factory)) -def author_pair(fk_name: str, *, id_type: type = IdType, sig_type: type | None = None, nullable: bool = False, sig_length: int | None = 2048, **ka) -> tuple[Column, Column]: +def author_pair(fk_name: str, *, id_type: type | TypeEngine = IdType, sig_type: type | None = None, nullable: bool = False, sig_length: int | None = 2048, **ka) -> tuple[Column, Column]: """ Return an owner ID/signature column pair, for authenticated values. """ @@ -203,7 +208,7 @@ def age_pair(*, nullable: bool = False, **ka) -> tuple[Column, Column]: return (date_col, acc_col) -def parent_children(keyword: str, /, *, lazy='selectin', **kwargs) -> tuple[Incomplete[Relationship], Incomplete[Relationship]]: +def parent_children(keyword: str, /, *, lazy='selectin', **kwargs) -> tuple[Incomplete[Relationship[Any]], Incomplete[Relationship[Any]]]: """ Self-referential one-to-many relationship pair. Parent comes first, children come later. @@ -220,8 +225,8 @@ def parent_children(keyword: str, /, *, lazy='selectin', **kwargs) -> tuple[Inco parent_kwargs = kwargs_prefix(kwargs, 'parent_') child_kwargs = kwargs_prefix(kwargs, 'child_') - parent = Incomplete(relationship, Wanted(lambda o, n: o.__name__), back_populates=f'child_{keyword}s', lazy=lazy, **parent_kwargs) - child = Incomplete(relationship, Wanted(lambda o, n: o.__name__), back_populates=f'parent_{keyword}', lazy=lazy, **child_kwargs) + parent: Incomplete[Relationship[Any]] = Incomplete(relationship, Wanted(lambda o, n: o.__name__), back_populates=f'child_{keyword}s', lazy=lazy, **parent_kwargs) + child: Incomplete[Relationship[Any]] = Incomplete(relationship, Wanted(lambda o, n: o.__name__), back_populates=f'parent_{keyword}', lazy=lazy, **child_kwargs) return parent, child