0.7.x: @lucky, @rng_overload and more exceptions
This commit is contained in:
parent
3de5a3629d
commit
a2fdc9166f
5 changed files with 169 additions and 2 deletions
|
|
@ -1,5 +1,10 @@
|
|||
# Changelog
|
||||
|
||||
## 0.7.0 "The Lucky Update"
|
||||
|
||||
+ Add RNG/random selection overloads such as `luck()`, `rng_overload()`.
|
||||
+ Add 7 new throwable exceptions.
|
||||
|
||||
## 0.6.1
|
||||
|
||||
- First release on PyPI under the name `suou`.
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ from .validators import matches
|
|||
from .redact import redact_url_password
|
||||
from .http import WantsContentType
|
||||
|
||||
__version__ = "0.6.1"
|
||||
__version__ = "0.7.0-dev37"
|
||||
|
||||
__all__ = (
|
||||
'ConfigOptions', 'ConfigParserConfigSource', 'ConfigSource', 'ConfigValue',
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
"""
|
||||
Exceptions and throwables for various purposes
|
||||
Exceptions and throwables for all purposes!
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -14,6 +14,17 @@ This software is distributed on an "AS IS" BASIS,
|
|||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
"""
|
||||
|
||||
|
||||
class PoliticalError(Exception):
|
||||
"""
|
||||
Base class for anything that is refused to be executed for political reasons.
|
||||
"""
|
||||
|
||||
class PoliticalWarning(PoliticalError, Warning):
|
||||
"""
|
||||
Base class for politically suspicious behaviors.
|
||||
"""
|
||||
|
||||
class MissingConfigError(LookupError):
|
||||
"""
|
||||
Config variable not found.
|
||||
|
|
@ -53,6 +64,35 @@ class BabelTowerError(NotFoundError):
|
|||
The user requested a language that cannot be understood.
|
||||
"""
|
||||
|
||||
class BadLuckError(Exception):
|
||||
"""
|
||||
Stuff did not go as expected.
|
||||
|
||||
Raised by @lucky decorator.
|
||||
"""
|
||||
|
||||
class TerminalRequiredError(OSError):
|
||||
"""
|
||||
Raised by terminal_required() decorator when a function is called from a non-interactive environment.
|
||||
"""
|
||||
|
||||
class BrokenStringsError(OSError):
|
||||
"""
|
||||
Issues related to audio happened, i.e. appropriate executables/libraries/drivers are not installed.
|
||||
"""
|
||||
|
||||
class Fahrenheit451Error(PoliticalError):
|
||||
"""
|
||||
Base class for thought crimes related to arts (e.g. writing, visual arts, music)
|
||||
"""
|
||||
|
||||
class FuckAroundFindOutError(PoliticalError):
|
||||
"""
|
||||
Raised when there is no actual grounds to raise an exception, but you did something in the past to deserve this outcome.
|
||||
|
||||
Ideal for permanent service bans or something.
|
||||
"""
|
||||
|
||||
__all__ = (
|
||||
'MissingConfigError', 'MissingConfigWarning', 'LexError', 'InconsistencyError', 'NotFoundError'
|
||||
)
|
||||
|
|
@ -2,6 +2,16 @@
|
|||
Utilities for tokenization of text.
|
||||
|
||||
---
|
||||
|
||||
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 re import Match
|
||||
|
|
|
|||
112
src/suou/luck.py
Normal file
112
src/suou/luck.py
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
"""
|
||||
Fortune and esoterism helpers.
|
||||
|
||||
---
|
||||
|
||||
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 typing import Callable, Generic, Iterable, TypeVar
|
||||
import random
|
||||
from suou.exceptions import BadLuckError
|
||||
|
||||
_T = TypeVar('_T')
|
||||
_U = TypeVar('_U')
|
||||
|
||||
|
||||
def lucky(validators: Iterable[Callable[[_U], bool]] = ()):
|
||||
"""
|
||||
Add one or more constraint on a function's return value.
|
||||
Each validator must return a boolean. If false, the result is considered
|
||||
unlucky and BadLuckError() is raised.
|
||||
|
||||
UNTESTED
|
||||
|
||||
NEW 0.7.0
|
||||
"""
|
||||
def decorator(func: Callable[_T, _U]):
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs) -> _U:
|
||||
try:
|
||||
result = func(*args, **kwargs)
|
||||
except Exception as e:
|
||||
raise BadLuckError(f'exception happened: {e}') from e
|
||||
for v in validators:
|
||||
try:
|
||||
if not v(result):
|
||||
raise BadLuckError(f'result not expected: {result!r}')
|
||||
except BadLuckError:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise BadLuckError(f'cannot validate: {e}') from e
|
||||
return result
|
||||
return wrapper
|
||||
return decorator
|
||||
|
||||
class RngCallable(Callable, Generic[_T, _U]):
|
||||
"""
|
||||
Overloaded ... randomly chosen callable.
|
||||
|
||||
UNTESTED
|
||||
|
||||
NEW 0.7.0
|
||||
"""
|
||||
def __init__(self, /, func: Callable[_T, _U] | None = None, weight: int = 1):
|
||||
self._callables = []
|
||||
self._max_weight = 0
|
||||
if callable(func):
|
||||
self.add_callable(func, weight)
|
||||
def add_callable(self, func: Callable[_T, _U], weight: int = 1):
|
||||
"""
|
||||
"""
|
||||
weight = int(weight)
|
||||
if weight <= 0:
|
||||
return
|
||||
self._callables.append((func, weight))
|
||||
self._max_weight += weight
|
||||
def __call__(self, *a, **ka) -> _U:
|
||||
choice = random.randrange(self._max_weight)
|
||||
for w, c in self._callables:
|
||||
if choice < w:
|
||||
return c(*a, **ka)
|
||||
elif choice < 0:
|
||||
raise RuntimeError('inconsistent state')
|
||||
else:
|
||||
choice -= w
|
||||
|
||||
|
||||
def rng_overload(prev_func: RngCallable[_T, _U] | int | None, /, *, weight: int = 1) -> RngCallable[_T, _U]:
|
||||
"""
|
||||
Decorate the first function with @rng_overload and the weight= parameter
|
||||
(default 1, must be an integer) to create a "RNG" overloaded callable.
|
||||
|
||||
Each call chooses randomly one candidate (weight is taken in consideration)
|
||||
, calls it, and returns the result.
|
||||
|
||||
UNTESTED
|
||||
|
||||
NEW 0.7.0
|
||||
"""
|
||||
if isinstance(prev_func, int) and weight == 1:
|
||||
weight, prev_func = prev_func, None
|
||||
|
||||
def decorator(func: Callable[_T, _U]):
|
||||
nonlocal prev_func
|
||||
if prev_func is None:
|
||||
prev_func = RngCallable(func, weight=weight)
|
||||
else:
|
||||
prev_func.add_callable(func, weight=weight)
|
||||
return prev_func
|
||||
return decorator
|
||||
|
||||
|
||||
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue