diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e92377..23c6186 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,9 +5,10 @@ + Add RNG/random selection overloads such as `luck()`, `rng_overload()` + Add 7 new throwable exceptions + Add color utilities: `chalk` module -+ Add `.terminal` module, to ease TUI development. -+ `calendar`: add `parse_time()`. -+ Add validator `not_greater_than()`. ++ Add `.terminal` module, to ease TUI development ++ `calendar`: add `parse_time()` ++ Add validator `not_greater_than()` ++ Add `@future()` decorator ## 0.6.1 diff --git a/src/suou/__init__.py b/src/suou/__init__.py index a33a879..7abb543 100644 --- a/src/suou/__init__.py +++ b/src/suou/__init__.py @@ -24,7 +24,7 @@ from .calendar import want_datetime, want_isodate, want_timestamp, age_and_days from .configparse import MissingConfigError, MissingConfigWarning, ConfigOptions, ConfigParserConfigSource, ConfigSource, DictConfigSource, ConfigValue, EnvConfigSource from .collections import TimedDict from .dei import dei_args -from .functools import deprecated, not_implemented, timed_cache, none_pass, alru_cache +from .functools import deprecated, not_implemented, timed_cache, none_pass, alru_cache, future from .classtools import Wanted, Incomplete from .itertools import makelist, kwargs_prefix, ltuple, rtuple, additem, addattr from .i18n import I18n, JsonI18n, TomlI18n @@ -47,7 +47,8 @@ __all__ = ( 'StringCase', 'TimedDict', 'TomlI18n', 'UserSigner', 'Wanted', 'WantsContentType', 'addattr', 'additem', 'age_and_days', 'alru_cache', 'b2048decode', 'b2048encode', 'b32ldecode', 'b32lencode', 'b64encode', 'b64decode', 'cb32encode', - 'cb32decode', 'chalk', 'count_ones', 'dei_args', 'deprecated', 'ilex', 'join_bits', + 'cb32decode', 'chalk', 'count_ones', 'dei_args', 'deprecated', + 'future', 'ilex', 'join_bits', 'jsonencode', 'kwargs_prefix', 'lex', 'ltuple', 'makelist', 'mask_shift', 'matches', 'mod_ceil', 'mod_floor', 'none_pass', 'not_implemented', 'redact_url_password', 'rtuple', 'split_bits', 'ssv_list', 'symbol_table', diff --git a/src/suou/functools.py b/src/suou/functools.py index b702fe7..b841a00 100644 --- a/src/suou/functools.py +++ b/src/suou/functools.py @@ -28,37 +28,43 @@ from suou.itertools import hashed_list _T = TypeVar('_T') _U = TypeVar('_U') + +def _suou_deprecated(message: str, /, *, category=DeprecationWarning, stacklevel: int = 1) -> Callable[[Callable[_T, _U]], Callable[_T, _U]]: + """ + Backport of PEP 702 for Python <=3.12. + The stack_level stuff is used by warnings.warn() btw + """ + def decorator(func: Callable[_T, _U]) -> Callable[_T, _U]: + @wraps(func) + def wrapper(*a, **ka): + if category is not None: + warnings.warn(message, category, stacklevel=stacklevel) + return func(*a, **ka) + func.__deprecated__ = True + wrapper.__deprecated__ = True + return wrapper + return decorator + try: from warnings import deprecated except ImportError: # Python <=3.12 does not implement warnings.deprecated - def deprecated(message: str, /, *, category=DeprecationWarning, stacklevel: int = 1) -> Callable[[Callable[_T, _U]], Callable[_T, _U]]: - """ - Backport of PEP 702 for Python <=3.12. - The stack_level stuff is not reimplemented on purpose because - too obscure for the average programmer. - """ - def decorator(func: Callable[_T, _U]) -> Callable[_T, _U]: - @wraps(func) - def wrapper(*a, **ka): - if category is not None: - warnings.warn(message, category, stacklevel=stacklevel) - return func(*a, **ka) - func.__deprecated__ = True - wrapper.__deprecated__ = True - return wrapper - return decorator + deprecated = _suou_deprecated ## this syntactic sugar for deprecated() is ... deprecated, which is ironic. ## Needed move because VSCode seems to not sense deprecated_alias()es as deprecated. @deprecated('use deprecated(message)(func) instead') -def deprecated_alias(func: Callable, /, message='use .{name}() instead', *, category=DeprecationWarning) -> Callable: +def deprecated_alias(func: Callable[_T, _U], /, message='use .{name}() instead', *, category=DeprecationWarning) -> Callable[_T, _U]: """ Syntactic sugar helper for renaming functions. DEPRECATED use deprecated(message)(func) instead """ - return deprecated(message.format(name=func.__name__), category=category)(func) + @deprecated(message.format(name=func.__name__), category=category) + @wraps(func) + def deprecated_wrapper(*a, **k) -> _U: + return func(*a, **k) + return deprecated_wrapper def not_implemented(msg: Callable | str | None = None): """ @@ -74,6 +80,20 @@ def not_implemented(msg: Callable | str | None = None): return decorator(msg) return decorator +def future(message: str | None = 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). + + 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) + return func(*a, **k) + return wrapper + return decorator def flat_args(args: Iterable, kwds: Mapping, typed, kwd_mark = (object(),),