diff --git a/CHANGELOG.md b/CHANGELOG.md index 0734c65..a30c9ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## 0.13.0 + ++ Added module `argparse` with class `LetterSubparsers()` ++ module `sqlalchemy`: + * removed deprecated alias `entity_base()`. use `declarative_base()` instead. + * fix imports. + ## 0.12.6 + Added unittests to `dei_args()` diff --git a/README.md b/README.md index 447a9b7..27dabec 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # SIS Unified Object Underarmor -Good morning, my brother! Welcome **SUOU** (**S**IS **U**nified **O**bject **U**nderarmor), the Python library which speeds up and makes it pleasing to develop API, database schemas and stuff in Python. +Good morning, my brother! Welcome **SUOU** (**S**IS **U**nified **O**bject **U**nderarmor), the Python library which (maybe) speeds up and makes it pleasing to develop API, database schemas and stuff in Python. It provides utilities such as: * SIQ ([specification](https://yusur.moe/protocols/siq.html) \[LINK BROKEN - WON'T FIX\] - [copy](https://suou.readthedocs.io/en/latest/iding.html)) @@ -15,13 +15,13 @@ It provides utilities such as: **Python 3.10**+ with Pip is required. ```bash -$ pip install sakuragasaki46-suou +$ pip install suou ``` To install optional dependencies (i.e. `sqlalchemy`) for development use: ```bash -$ pip install sakuragasaki46-suou[sqlalchemy] +$ pip install suou[sqlalchemy] ``` Please note that you probably already have those dependencies, if you just use the library. @@ -34,7 +34,7 @@ Read the [documentation](https://suou.readthedocs.io/). ### Disclaimer -Just a heads up: SUOU was made to support Sakuragasaki46 (me)'s own selfish, egoistic needs. Not certainly to provide a service to the public. +Just a heads up: SUOU was made to support yusurko (me)'s own selfish, egoistic needs. Not certainly to provide a service to the public. As a consequence, 'add this add that' stuff is best-effort. @@ -42,8 +42,6 @@ Expect breaking changes, disruptive renames in bugfix releases, sudden deprecati Don't want to depend on my codebase for moral reasons (albeit unrelated)? It's fine. I did not ask you. -**DO NOT ASK TO MAKE SUOU SAFE FOR CHILDREN**. Enjoy having your fingers cut. - ### "LTS" The following versions are supported: the latest, the second-to-latest, 0.12.x and 0.7.x. @@ -54,7 +52,7 @@ Licensed under the [Apache License, Version 2.0](LICENSE), a non-copyleft free a This is a hobby project, made available “AS IS”, with __no warranty__ express or implied. -I (sakuragasaki46) may NOT be held accountable for Your use of my code. +I (yusurko) may NOT be held accountable for Your use of my code. > It's pointless to file a lawsuit because you feel damaged, and it's only going to turn against you. What a waste of money you could have spent on a vacation or charity, or invested in stocks. diff --git a/src/suou/__init__.py b/src/suou/__init__.py index 6a434e4..d28099f 100644 --- a/src/suou/__init__.py +++ b/src/suou/__init__.py @@ -37,13 +37,14 @@ from .redact import redact_url_password from .http import WantsContentType from .color import OKLabColor, chalk, WebColor, RGBColor, LinearRGBColor, XYZColor, OKLCHColor from .mat import Matrix +from .argparse import LetterSubparsers -__version__ = "0.12.6" +__version__ = "0.13.0a1" __all__ = ( 'ConfigOptions', 'ConfigParserConfigSource', 'ConfigSource', 'ConfigValue', 'DictConfigSource', 'EnvConfigSource', 'I18n', 'Incomplete', 'JsonI18n', - 'LinearRGBColor', + 'LetterSubparsers', 'LinearRGBColor', 'Matrix', 'MissingConfigError', 'MissingConfigWarning', 'OKLabColor', 'OKLCHColor', 'PrefixIdentifier', 'RGBColor', 'Siq', 'SiqCache', 'SiqGen', 'SiqType', 'Snowflake', 'SnowflakeGen', @@ -52,8 +53,8 @@ __all__ = ( 'addattr', 'additem', 'age_and_days', 'alru_cache', 'b2048decode', 'b2048encode', 'b32ldecode', 'b32lencode', 'b64encode', 'b64decode', 'cb32encode', 'cb32decode', 'chalk', 'count_ones', 'dei_args', 'deprecated', - 'future', 'ilex', 'join_bits', - 'jsonencode', 'kwargs_prefix', 'lex', 'ltuple', 'makelist', 'mask_shift', + 'future', 'ilex', 'join_bits', 'jsonencode', 'kwargs_prefix', + 'lex', 'ltuple', 'makelist', 'mask_shift', 'matches', 'mod_ceil', 'mod_floor', 'must_be', 'none_pass', 'not_implemented', 'not_less_than', 'not_greater_than', 'redact_url_password', 'rtuple', 'split_bits', 'ssv_list', 'symbol_table', diff --git a/src/suou/argparse.py b/src/suou/argparse.py new file mode 100644 index 0000000..c235a30 --- /dev/null +++ b/src/suou/argparse.py @@ -0,0 +1,105 @@ +""" +Utilities for parsing arguments. Based on argparse. + +--- + +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 + +import argparse +import sys +from typing import Callable, Mapping + +class LetterSubparsers(object): + """ + Subparsers in pacman style, where action name can be shortened to a single letter, prefixed by a hyphen + + (i.e. "-S" is expanded to "sync") + + *New in 0.13.0* + """ + + _parser: argparse.ArgumentParser + _letters: dict[str, str] + _subparsers: argparse._SubParsersAction[argparse.ArgumentParser] + + def __init__(self, parser : argparse.ArgumentParser, *, dest: str = 'action', **kwargs): + self._parser = parser + self._letters = {} + self._subparsers = parser.add_subparsers(dest = dest, **kwargs) + + def action(self, /, letter: str, name: str | None = None, **kwargs): + """ + Decorator which adds a subparser of an argument parser's subparsers, and specifies a letter to make a shorthand in pacman style. + + For example, assuming name="sync" and letter="S", if the first argument is "-S", it will be turned to "sync", . + + The first argument is always the object returned by ArgumentParser.add_subparsers(). + + Additional kwargs are passed to the add_parser constructor. + """ + + if len(letter) != 1: + raise ValueError('letter must be one character') + + o_name = name + + def decorator(func: Callable[argparse.ArgumentParser, ...]): + name = o_name or func.__name__ + parser = self._subparsers.add_parser(name, **kwargs) + func(parser) + + self._letters[letter] = name + + return func + return decorator + + def parse_args(self, argv = None, system_exit: bool = True): + """ + Variation of ArgumentParser.parse_args() that takes shortcut letters into account. + + Best used together with letter_action(). + """ + + if argv is None: + argv = sys.argv[1:] + else: + argv = list(argv) + + if len(argv) > 0: + first_arg = argv.pop(0) + + if first_arg.startswith('-') and len(first_arg) >= 2: + letter, rest = first_arg[1], first_arg[2:] + if letter in self._letters: + argv.insert(0, self._letters[letter]) + if rest: + argv.insert(1, "-" + rest) + else: + # put it back + argv.insert(0, first_arg) + else: + # put it back + argv.insert(0, first_arg) + + try: + return self._parser.parse_args(argv) + except SystemExit: + # prevent SystemExit at parse fail + if system_exit: + raise + + +__all__ = ('LetterSubparsers',) + diff --git a/src/suou/sqlalchemy/__init__.py b/src/suou/sqlalchemy/__init__.py index d46ca8f..fb06720 100644 --- a/src/suou/sqlalchemy/__init__.py +++ b/src/suou/sqlalchemy/__init__.py @@ -154,7 +154,7 @@ def require_auth_base(cls: type[DeclarativeBase], *, src: AuthSrc, column: str | return decorator -from .asyncio import SQLAlchemy, async_query +from .asyncio import SQLAlchemy, async_query, SessionWrapper, AsyncSelectPagination from .orm import ( id_column, snowflake_column, match_column, match_constraint, bool_column, declarative_base, parent_children, author_pair, age_pair, bound_fk, unbound_fk, want_column, a_relationship, BitSelector, secret_column, username_column @@ -168,7 +168,7 @@ except ImportError: # Optional dependency: do not import into __init__.py __all__ = ( - 'IdType', 'id_column', 'snowflake_column', 'entity_base', 'declarative_base', 'token_signer', + 'IdType', 'id_column', 'snowflake_column', 'declarative_base', 'token_signer', 'match_column', 'match_constraint', 'bool_column', 'parent_children', 'author_pair', 'age_pair', 'bound_fk', 'unbound_fk', 'want_column', 'a_relationship', 'BitSelector', 'secret_column', 'username_column', diff --git a/src/suou/sqlalchemy/orm.py b/src/suou/sqlalchemy/orm.py index ada5e94..ba022e7 100644 --- a/src/suou/sqlalchemy/orm.py +++ b/src/suou/sqlalchemy/orm.py @@ -146,6 +146,8 @@ def declarative_base(domain_name: str, master_secret: bytes, metadata: dict | No """ Drop-in replacement for sqlalchemy.orm.declarative_base() taking in account requirements for SIQ generation (i.e. domain name). + + Also supports snowflake generation parameters such as epoch. """ if not isinstance(metadata, dict): metadata = dict() @@ -160,7 +162,6 @@ def declarative_base(domain_name: str, master_secret: bytes, metadata: dict | No ) Base = _declarative_base(metadata=MetaData(**metadata), **kwargs) return Base -entity_base = warnings.deprecated('use declarative_base() instead')(declarative_base)