diff --git a/CHANGELOG.md b/CHANGELOG.md index f449db1..21a7882 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,9 @@ ## 0.12.0 +* 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. ## 0.11.2 diff --git a/src/suou/__init__.py b/src/suou/__init__.py index 2d79b4b..a0fbed1 100644 --- a/src/suou/__init__.py +++ b/src/suou/__init__.py @@ -36,12 +36,14 @@ from .validators import matches, not_less_than, not_greater_than, yesno from .redact import redact_url_password from .http import WantsContentType from .color import chalk, WebColor +from .mat import Matrix -__version__ = "0.12.0a1" +__version__ = "0.12.0a2" __all__ = ( 'ConfigOptions', 'ConfigParserConfigSource', 'ConfigSource', 'ConfigValue', 'DictConfigSource', 'EnvConfigSource', 'I18n', 'Incomplete', 'JsonI18n', + 'Matrix', 'MissingConfigError', 'MissingConfigWarning', 'PrefixIdentifier', 'Siq', 'SiqCache', 'SiqGen', 'SiqType', 'Snowflake', 'SnowflakeGen', 'StringCase', 'TimedDict', 'TomlI18n', 'UserSigner', 'Wanted', 'WantsContentType', diff --git a/src/suou/flask_sqlalchemy.py b/src/suou/flask_sqlalchemy.py index 88122d2..3f1caf0 100644 --- a/src/suou/flask_sqlalchemy.py +++ b/src/suou/flask_sqlalchemy.py @@ -1,7 +1,7 @@ """ Utilities for Flask-SQLAlchemy binding. -This module is deprecated and will be REMOVED in 0.14.0. +This module has been emptied in 0.12.0 following deprecation removals. --- @@ -16,50 +16,6 @@ This software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. """ -from functools import partial -from typing import Any, Callable, Never - -from flask import abort, request -from flask_sqlalchemy import SQLAlchemy -from sqlalchemy.orm import DeclarativeBase, Session -from .functools import deprecated - -from .codecs import want_bytes -from .sqlalchemy import AuthSrc, require_auth_base - -@deprecated('inherits from deprecated and unused class') -class FlaskAuthSrc(AuthSrc): - ''' - - ''' - db: SQLAlchemy - def __init__(self, db: SQLAlchemy): - super().__init__() - self.db = db - def get_session(self) -> Session: - return self.db.session - def get_token(self): - if request.authorization: - return request.authorization.token - def get_signature(self) -> bytes: - sig = request.headers.get('authorization-signature', None) - return want_bytes(sig) if sig else None - def invalid_exc(self, msg: str = 'validation failed') -> Never: - abort(400, msg) - def required_exc(self): - abort(401, 'Login required') - -@deprecated('not intuitive to use') -def require_auth(cls: type[DeclarativeBase], db: SQLAlchemy) -> Callable[Any, Callable]: - """ - - """ - def auth_required(**kwargs): - return require_auth_base(cls=cls, src=FlaskAuthSrc(db), **kwargs) - - auth_required.__doc__ = require_auth_base.__doc__ - - return auth_required # Optional dependency: do not import into __init__.py __all__ = () diff --git a/src/suou/iding.py b/src/suou/iding.py index a2e0c37..7997da3 100644 --- a/src/suou/iding.py +++ b/src/suou/iding.py @@ -249,13 +249,20 @@ class Siq(int): def to_base64(self, length: int = 15, *, strip: bool = True) -> str: return b64encode(self.to_bytes(length), strip=strip) + def to_cb32(self) -> str: return cb32encode(self.to_bytes(15, 'big')).lstrip('0') to_crockford = to_cb32 + @classmethod + def from_cb32(cls, val: str | bytes): + return cls.from_bytes(cb32decode(want_str(val).zfill(24))) + def to_hex(self) -> str: return f'{self:x}' + def to_oct(self) -> str: return f'{self:o}' + def to_b32l(self) -> str: """ This is NOT the URI serializer! @@ -305,12 +312,10 @@ class Siq(int): raise ValueError('checksum mismatch') return cls(int.from_bytes(b, 'big')) - @classmethod - def from_cb32(cls, val: str | bytes): - return cls.from_bytes(cb32decode(want_str(val).zfill(24))) def to_mastodon(self, /, domain: str | None = None): return f'@{self:u}{"@" if domain else ""}{domain}' + def to_matrix(self, /, domain: str): return f'@{self:u}:{domain}' diff --git a/src/suou/mat.py b/src/suou/mat.py index fa60f08..4f8f2b9 100644 --- a/src/suou/mat.py +++ b/src/suou/mat.py @@ -116,6 +116,7 @@ class Matrix(Collection[_T]): [self[j, i] for j in range(sx)] for i in range(sy) ) -## TODO write tests! + +__all__ = ('Matrix', ) diff --git a/src/suou/obsolete/configparsev0_3.py b/src/suou/obsolete/configparsev0_3.py deleted file mode 100644 index 1563813..0000000 --- a/src/suou/obsolete/configparsev0_3.py +++ /dev/null @@ -1,239 +0,0 @@ -""" -Utilities for parsing config variables. - -BREAKING older, non-generalized version, kept for backwards compability -in case 0.4+ version happens to break. - -WILL BE removed in 0.5.0. - ---- - -Copyright (c) 2025 Sakuragasaki46. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -See LICENSE for the specific language governing permissions and -limitations under the License. - -This software is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -""" - -from __future__ import annotations -from ast import TypeVar -from collections.abc import Mapping -from configparser import ConfigParser as _ConfigParser -import os -from typing import Any, Callable, Iterator -from collections import OrderedDict -import warnings - -from ..functools import deprecated -from ..exceptions import MissingConfigError, MissingConfigWarning - -warnings.warn('This module will be removed in 0.5.0 and is kept only in case new implementation breaks!\n'\ - 'Do not use unless you know what you are doing.', DeprecationWarning) - -MISSING = object() -_T = TypeVar('T') - - -@deprecated('use configparse') -class ConfigSource(Mapping): - ''' - Abstract config source. - ''' - __slots__ = () - -@deprecated('use configparse') -class EnvConfigSource(ConfigSource): - ''' - Config source from os.environ aka .env - ''' - def __getitem__(self, key: str, /) -> str: - return os.environ[key] - def get(self, key: str, fallback = None, /): - return os.getenv(key, fallback) - def __contains__(self, key: str, /) -> bool: - return key in os.environ - def __iter__(self) -> Iterator[str]: - yield from os.environ - def __len__(self) -> int: - return len(os.environ) - -@deprecated('use configparse') -class ConfigParserConfigSource(ConfigSource): - ''' - Config source from ConfigParser - ''' - __slots__ = ('_cfp', ) - _cfp: _ConfigParser - - def __init__(self, cfp: _ConfigParser): - if not isinstance(cfp, _ConfigParser): - raise TypeError(f'a ConfigParser object is required (got {cfp.__class__.__name__!r})') - self._cfp = cfp - def __getitem__(self, key: str, /) -> str: - k1, _, k2 = key.partition('.') - return self._cfp.get(k1, k2) - def get(self, key: str, fallback = None, /): - k1, _, k2 = key.partition('.') - return self._cfp.get(k1, k2, fallback=fallback) - def __contains__(self, key: str, /) -> bool: - k1, _, k2 = key.partition('.') - return self._cfp.has_option(k1, k2) - def __iter__(self) -> Iterator[str]: - for k1, v1 in self._cfp.items(): - for k2 in v1: - yield f'{k1}.{k2}' - def __len__(self) -> int: - ## XXX might be incorrect but who cares - return sum(len(x) for x in self._cfp) - -@deprecated('use configparse') -class DictConfigSource(ConfigSource): - ''' - Config source from Python mappings. Useful with JSON/TOML config - ''' - __slots__ = ('_d',) - - _d: dict[str, Any] - - def __init__(self, mapping: dict[str, Any]): - self._d = mapping - def __getitem__(self, key: str, /) -> str: - return self._d[key] - def get(self, key: str, fallback: _T = None, /): - return self._d.get(key, fallback) - def __contains__(self, key: str, /) -> bool: - return key in self._d - def __iter__(self) -> Iterator[str]: - yield from self._d - def __len__(self) -> int: - return len(self._d) - -@deprecated('use configparse') -class ConfigValue: - """ - A single config property. - - By default, it is sourced from os.environ — i.e. environment variables, - and property name is upper cased. - - You can specify further sources, if the parent ConfigOptions class - supports them. - - Arguments: - - public: mark value as public, making it available across the app (e.g. in Jinja2 templates). - - prefix: src but for the lazy - - preserve_case: if True, src is not CAPITALIZED. Useful for parsing from Python dictionaries or ConfigParser's - - required: throw an error if empty or not supplied - """ - # XXX disabled per https://stackoverflow.com/questions/45864273/slots-conflicts-with-a-class-variable-in-a-generic-class - #__slots__ = ('_srcs', '_val', '_default', '_cast', '_required', '_preserve_case') - - _srcs: dict[str, str] | None - _preserve_case: bool = False - _val: Any | MISSING = MISSING - _default: Any | None - _cast: Callable | None - _required: bool - _pub_name: str | bool = False - def __init__(self, /, - src: str | None = None, *, default = None, cast: Callable | None = None, - required: bool = False, preserve_case: bool = False, prefix: str | None = None, - public: str | bool = False, **kwargs): - self._srcs = dict() - self._preserve_case = preserve_case - if src: - self._srcs['default'] = src if preserve_case else src.upper() - elif prefix: - self._srcs['default'] = f'{prefix if preserve_case else prefix.upper}?' - self._default = default - self._cast = cast - self._required = required - self._pub_name = public - for k, v in kwargs.items(): - if k.endswith('_src'): - self._srcs[k[:-4]] = v - else: - raise TypeError(f'unknown keyword argument {k!r}') - def __set_name__(self, owner, name: str): - if 'default' not in self._srcs: - self._srcs['default'] = name if self._preserve_case else name.upper() - elif self._srcs['default'].endswith('?'): - self._srcs['default'] = self._srcs['default'].rstrip('?') + (name if self._preserve_case else name.upper() ) - - if self._pub_name is True: - self._pub_name = name - if self._pub_name and isinstance(owner, ConfigOptions): - owner.expose(self._pub_name, name) - def __get__(self, obj: ConfigOptions, owner=False): - if self._val is MISSING: - v = MISSING - for srckey, src in obj._srcs.items(): - if srckey in self._srcs: - v = src.get(self._srcs[srckey], v) - if self._required and (not v or v is MISSING): - raise MissingConfigError(f'required config {self._srcs['default']} not set!') - if v is MISSING: - v = self._default - if callable(self._cast): - v = self._cast(v) if v is not None else self._cast() - self._val = v - return self._val - - @property - def source(self, /): - return self._srcs['default'] - -@deprecated('use configparse') -class ConfigOptions: - """ - Base class for loading config values. - - It is intended to get subclassed; config values must be defined as - ConfigValue() properties. - - Further config sources can be added with .add_source() - """ - - __slots__ = ('_srcs', '_pub') - - _srcs: OrderedDict[str, ConfigSource] - _pub: dict[str, str] - - def __init__(self, /): - self._srcs = OrderedDict( - default = EnvConfigSource() - ) - self._pub = dict() - - def add_source(self, key: str, csrc: ConfigSource, /, *, first: bool = False): - self._srcs[key] = csrc - if first: - self._srcs.move_to_end(key, False) - - add_config_source = deprecated_alias(add_source) - - def expose(self, public_name: str, attr_name: str | None = None) -> None: - ''' - Mark a config value as public. - - Called automatically by ConfigValue.__set_name__(). - ''' - attr_name = attr_name or public_name - self._pub[public_name] = attr_name - - def to_dict(self, /): - d = dict() - for k, v in self._pub.items(): - d[k] = getattr(self, v) - return d - - -__all__ = ( - 'MissingConfigError', 'MissingConfigWarning', 'ConfigOptions', 'EnvConfigSource', 'ConfigParserConfigSource', 'DictConfigSource', 'ConfigSource', 'ConfigValue' -) - -