Compare commits
3 commits
da0caadf08
...
83ab616e13
| Author | SHA1 | Date | |
|---|---|---|---|
| 83ab616e13 | |||
| a2fdc9166f | |||
| 3de5a3629d |
11 changed files with 275 additions and 4 deletions
|
|
@ -1,5 +1,11 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.7.0 "The Lucky Update"
|
||||||
|
|
||||||
|
+ Add RNG/random selection overloads such as `luck()`, `rng_overload()`
|
||||||
|
+ Add 7 new throwable exceptions
|
||||||
|
+ Add color utilities: `chalk` module
|
||||||
|
|
||||||
## 0.6.1
|
## 0.6.1
|
||||||
|
|
||||||
- First release on PyPI under the name `suou`.
|
- First release on PyPI under the name `suou`.
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
|
|
||||||
1
aliases/sakuragasaki46_suou/README.md
Normal file
1
aliases/sakuragasaki46_suou/README.md
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
moved to [suou](https://pypi.org/project/suou)
|
||||||
8
aliases/sakuragasaki46_suou/pyproject.toml
Normal file
8
aliases/sakuragasaki46_suou/pyproject.toml
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
[project]
|
||||||
|
name = "sakuragasaki46_suou"
|
||||||
|
authors = [ { name = "Sakuragasaki46" } ]
|
||||||
|
version = "0.6.1"
|
||||||
|
requires-python = ">=3.10"
|
||||||
|
dependencies = [ "suou==0.6.1" ]
|
||||||
|
readme = "README.md"
|
||||||
|
|
||||||
|
|
@ -35,8 +35,9 @@ from .strtools import PrefixIdentifier
|
||||||
from .validators import matches
|
from .validators import matches
|
||||||
from .redact import redact_url_password
|
from .redact import redact_url_password
|
||||||
from .http import WantsContentType
|
from .http import WantsContentType
|
||||||
|
from .color import chalk
|
||||||
|
|
||||||
__version__ = "0.6.1"
|
__version__ = "0.7.0-dev37"
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'ConfigOptions', 'ConfigParserConfigSource', 'ConfigSource', 'ConfigValue',
|
'ConfigOptions', 'ConfigParserConfigSource', 'ConfigSource', 'ConfigValue',
|
||||||
|
|
@ -46,7 +47,7 @@ __all__ = (
|
||||||
'StringCase', 'TimedDict', 'TomlI18n', 'UserSigner', 'Wanted', 'WantsContentType',
|
'StringCase', 'TimedDict', 'TomlI18n', 'UserSigner', 'Wanted', 'WantsContentType',
|
||||||
'addattr', 'additem', 'age_and_days', 'alru_cache', 'b2048decode', 'b2048encode',
|
'addattr', 'additem', 'age_and_days', 'alru_cache', 'b2048decode', 'b2048encode',
|
||||||
'b32ldecode', 'b32lencode', 'b64encode', 'b64decode', 'cb32encode',
|
'b32ldecode', 'b32lencode', 'b64encode', 'b64decode', 'cb32encode',
|
||||||
'cb32decode', 'count_ones', 'dei_args', 'deprecated', 'ilex', 'join_bits',
|
'cb32decode', 'chalk', 'count_ones', 'dei_args', 'deprecated', 'ilex', 'join_bits',
|
||||||
'jsonencode', 'kwargs_prefix', 'lex', 'ltuple', 'makelist', 'mask_shift',
|
'jsonencode', 'kwargs_prefix', 'lex', 'ltuple', 'makelist', 'mask_shift',
|
||||||
'matches', 'mod_ceil', 'mod_floor', 'none_pass', 'not_implemented',
|
'matches', 'mod_ceil', 'mod_floor', 'none_pass', 'not_implemented',
|
||||||
'redact_url_password', 'rtuple', 'split_bits', 'ssv_list', 'symbol_table',
|
'redact_url_password', 'rtuple', 'split_bits', 'ssv_list', 'symbol_table',
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,17 @@
|
||||||
"""
|
"""
|
||||||
|
ASGI stuff
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
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 Any, Awaitable, Callable, MutableMapping, ParamSpec, Protocol
|
from typing import Any, Awaitable, Callable, MutableMapping, ParamSpec, Protocol
|
||||||
|
|
|
||||||
79
src/suou/color.py
Normal file
79
src/suou/color.py
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
"""
|
||||||
|
Colors for coding artists
|
||||||
|
|
||||||
|
NEW 0.7.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 functools import lru_cache
|
||||||
|
|
||||||
|
|
||||||
|
class Chalk:
|
||||||
|
"""
|
||||||
|
ANSI escape codes for terminal colors, similar to JavaScript's `chalk` library.
|
||||||
|
|
||||||
|
Best used with Python 3.12+ that allows arbitrary nesting of f-strings.
|
||||||
|
|
||||||
|
Yes, I am aware colorama exists.
|
||||||
|
|
||||||
|
UNTESTED
|
||||||
|
|
||||||
|
NEW 0.7.0
|
||||||
|
"""
|
||||||
|
CSI = '\x1b['
|
||||||
|
RED = CSI + "31m"
|
||||||
|
GREEN = CSI + "32m"
|
||||||
|
YELLOW = CSI + "33m"
|
||||||
|
BLUE = CSI + "34m"
|
||||||
|
CYAN = CSI + "36m"
|
||||||
|
PURPLE = CSI + "35m"
|
||||||
|
GREY = CSI + "90m"
|
||||||
|
END_COLOR = CSI + "39m"
|
||||||
|
BOLD = CSI + "1m"
|
||||||
|
END_BOLD = CSI + "22m"
|
||||||
|
FAINT = CSI + "2m"
|
||||||
|
def __init__(self, flags = (), ends = ()):
|
||||||
|
self._flags = tuple(flags)
|
||||||
|
self._ends = tuple(ends)
|
||||||
|
@lru_cache()
|
||||||
|
def _wrap(self, beg, end):
|
||||||
|
return Chalk(self._flags + (beg,), self._ends + (end,))
|
||||||
|
def __call__(self, s: str) -> str:
|
||||||
|
return ''.join(self._flags) + s + ''.join(reversed(self._ends))
|
||||||
|
def red(self):
|
||||||
|
return self._wrap(self.RED, self.END_COLOR)
|
||||||
|
def green(self):
|
||||||
|
return self._wrap(self.GREEN, self.END_COLOR)
|
||||||
|
def blue(self):
|
||||||
|
return self._wrap(self.BLUE, self.END_COLOR)
|
||||||
|
def yellow(self):
|
||||||
|
return self._wrap(self.YELLOW, self.END_COLOR)
|
||||||
|
def cyan(self):
|
||||||
|
return self._wrap(self.CYAN, self.END_COLOR)
|
||||||
|
def purple(self):
|
||||||
|
return self._wrap(self.PURPLE, self.END_COLOR)
|
||||||
|
def grey(self):
|
||||||
|
return self._wrap(self.GREY, self.END_COLOR)
|
||||||
|
gray = grey
|
||||||
|
marine = blue
|
||||||
|
def bold(self):
|
||||||
|
return self._wrap(self.BOLD, self.END_BOLD)
|
||||||
|
def faint(self):
|
||||||
|
return self._wrap(self.FAINT, self.END_BOLD)
|
||||||
|
|
||||||
|
|
||||||
|
## TODO make it lazy?
|
||||||
|
chalk = Chalk()
|
||||||
|
|
@ -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.
|
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):
|
class MissingConfigError(LookupError):
|
||||||
"""
|
"""
|
||||||
Config variable not found.
|
Config variable not found.
|
||||||
|
|
@ -53,6 +64,35 @@ class BabelTowerError(NotFoundError):
|
||||||
The user requested a language that cannot be understood.
|
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__ = (
|
__all__ = (
|
||||||
'MissingConfigError', 'MissingConfigWarning', 'LexError', 'InconsistencyError', 'NotFoundError'
|
'MissingConfigError', 'MissingConfigWarning', 'LexError', 'InconsistencyError', 'NotFoundError'
|
||||||
)
|
)
|
||||||
|
|
@ -2,6 +2,16 @@
|
||||||
Utilities for tokenization of text.
|
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
|
from re import Match
|
||||||
|
|
|
||||||
114
src/suou/luck.py
Normal file
114
src/suou/luck.py
Normal file
|
|
@ -0,0 +1,114 @@
|
||||||
|
"""
|
||||||
|
Fortune' RNG and esoterism.
|
||||||
|
|
||||||
|
NEW 0.7.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 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
|
||||||
|
|
||||||
|
|
||||||
|
# This module is experimental and therefore not re-exported into __init__
|
||||||
|
__all__ = ('lucky', 'rng_overload')
|
||||||
|
|
@ -54,4 +54,5 @@ def ko(status: int, /, content = None, **ka):
|
||||||
return PlainTextResponse(content, status_code=status, **ka)
|
return PlainTextResponse(content, status_code=status, **ka)
|
||||||
return content
|
return content
|
||||||
|
|
||||||
|
# This module is experimental and therefore not re-exported into __init__
|
||||||
__all__ = ('ko', 'ok', 'Waiter')
|
__all__ = ('ko', 'ok', 'Waiter')
|
||||||
Loading…
Add table
Add a link
Reference in a new issue