From f08af888217bc3d1749c2f70943b27863669ecdd Mon Sep 17 00:00:00 2001 From: Yusur Princeps Date: Sat, 11 Jul 2026 22:43:00 +0200 Subject: [PATCH] 0.14.0a5 add i4_column() --- CHANGELOG.md | 3 ++- src/suou/__init__.py | 6 +++--- src/suou/bits.py | 28 +++++++++++++++++++++++++++- src/suou/sqlalchemy/__init__.py | 4 ++-- src/suou/sqlalchemy/orm.py | 19 ++++++++++++++++++- 5 files changed, 52 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ecbd66..26f2e35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,9 +5,10 @@ * Added `ast` module * Deprecate `dei_args()` for problems with the typing system. The function is not going away tho * Module `sqlalchemy`: - * added `email_column()`, `ascii_column()` + * added `email_column()`, `ascii_column()`, `i4_column()` * added application level checks for `match_column()` * Added common values for snowflake epoch in `SnowflakeEpoch` enum +* Module `bitÅ›`: added `i4_to_int()`, `int_to_i4()` ## 0.13.1 and 0.12.7 diff --git a/src/suou/__init__.py b/src/suou/__init__.py index c24b2ec..351ff60 100644 --- a/src/suou/__init__.py +++ b/src/suou/__init__.py @@ -21,7 +21,7 @@ from .iding import Siq, SiqCache, SiqType, SiqGen from .codecs import (StringCase, cb32encode, cb32decode, b32lencode, b32ldecode, b64encode, b64decode, b2048encode, b2048decode, jsonencode, twocolon_list, want_bytes, want_str, ssv_list, want_urlsafe, want_urlsafe_bytes, z85encode, z85decode) -from .bits import count_ones, mask_shift, split_bits, join_bits, mod_ceil, mod_floor +from .bits import count_ones, i4_to_int, int_to_i4, mask_shift, split_bits, join_bits, mod_ceil, mod_floor 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 @@ -41,7 +41,7 @@ from .color import OKLabColor, chalk, WebColor, RGBColor, LinearRGBColor, \ from .mat import Matrix from .argparse import LetterSubparsers -__version__ = "0.14.0a4" +__version__ = "0.14.0a5" __all__ = ( 'ColorFormatter', @@ -56,7 +56,7 @@ __all__ = ( 'addattr', 'additem', 'age_and_days', 'alru_cache', 'b2048decode', 'b2048encode', 'b32ldecode', 'b32lencode', 'b64encode', 'b64decode', 'cb32encode', 'cb32decode', 'chalk', 'cooldown', 'count_ones', 'dei_args', 'deprecated', 'do_not_flood', - 'future', 'ilex', 'join_bits', 'jsonencode', 'kwargs_prefix', + 'future', 'i4_to_int', 'int_to_i4', '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', diff --git a/src/suou/bits.py b/src/suou/bits.py index 86f55ba..70f390e 100644 --- a/src/suou/bits.py +++ b/src/suou/bits.py @@ -15,6 +15,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ''' import math +import re def mask_shift(n: int, mask: int) -> int: ''' @@ -108,4 +109,29 @@ def mod_ceil(x: int, y: int) -> int: return x + (y - x % y) % y -__all__ = ('count_ones', 'mask_shift', 'split_bits', 'join_bits', 'mod_floor', 'mod_ceil') +def i4_to_int(value): + if not (mo := re.fullmatch(r"([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})", value)): + raise ValueError + parts = [int(m) for m in mo.groups()] + for p in parts: + if p > 255 or p < 0: + raise ValueError + return ( + (parts[0] << 24) | + (parts[1] << 16) | + (parts[2] << 8) | + parts[3] + ) + + +def int_to_i4(value): + parts = ( + (value >> 24) % 256, + (value >> 16) % 256, + (value >> 8) % 256, + value % 256 + ) + return '.'.join(f'{x}' for x in parts) + + +__all__ = ('count_ones', 'mask_shift', 'split_bits', 'join_bits', 'mod_floor', 'mod_ceil', 'i4_to_int', 'int_to_i4') diff --git a/src/suou/sqlalchemy/__init__.py b/src/suou/sqlalchemy/__init__.py index f7adca5..31260a2 100644 --- a/src/suou/sqlalchemy/__init__.py +++ b/src/suou/sqlalchemy/__init__.py @@ -158,7 +158,7 @@ from .asyncio import SQLAlchemy, async_query, SessionWrapper, AsyncSelectPaginat 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, - ascii_column + ascii_column, i4_column ) try: @@ -173,7 +173,7 @@ __all__ = ( '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', - 'ascii_column', + 'ascii_column', 'i4_column', # .asyncio 'SQLAlchemy', 'AsyncSelectPagination', 'async_query', 'SessionWrapper' ) \ No newline at end of file diff --git a/src/suou/sqlalchemy/orm.py b/src/suou/sqlalchemy/orm.py index 04a3053..3164a78 100644 --- a/src/suou/sqlalchemy/orm.py +++ b/src/suou/sqlalchemy/orm.py @@ -22,10 +22,11 @@ import os import re from typing import Any, Callable, TypeAlias, TypeVar import warnings -from sqlalchemy import VARCHAR, BigInteger, Boolean, CheckConstraint, Column, Date, ForeignKey, LargeBinary, MetaData, SmallInteger, String, TypeDecorator, text +from sqlalchemy import BigInteger, Boolean, CheckConstraint, Column, Date, ForeignKey, Integer, LargeBinary, MetaData, SmallInteger, String, TypeDecorator, text from sqlalchemy.orm import DeclarativeBase, InstrumentedAttribute, Relationship, declarative_base as _declarative_base, relationship from sqlalchemy.types import TypeEngine from sqlalchemy.ext.hybrid import Comparator +from suou.bits import i4_to_int, int_to_i4 from suou.classtools import Wanted, Incomplete from suou.codecs import StringCase from suou.iding import Siq, SiqCache, SiqGen, SiqType @@ -125,6 +126,15 @@ class AsciiString(TypeDecorator): raise ValueError('only ASCII strings are allowed') return value +class I4Addr(TypeDecorator): + impl: TypeAlias = Integer + + def process_bind_param(self, value, dialect): + return i4_to_int(value) + + def process_result_value(self, value, dialect): + return int_to_i4(value) + ## END type decorators @@ -185,6 +195,13 @@ def email_column( unique = unique, nullable = nullable, *args, **kwargs) +def i4_column(*args, nullable : bool = False, **kwargs): + """ + *New in 0.14.0* + """ + return Column(I4Addr, nullable=nullable, *args, **kwargs) + + def bool_column(value: bool = False, nullable: bool = False, **kwargs) -> Column[bool]: """ Column for a single boolean value.