add PrefixIdentifier() and some tests
This commit is contained in:
parent
e5ca63953d
commit
8a16fe159f
6 changed files with 162 additions and 6 deletions
|
|
@ -6,7 +6,8 @@
|
|||
+ \[BREAKING] Changed the behavior of `makelist()`: now it's also a decorator, converting its return type to a list (revertable with `wrap=False`)
|
||||
+ New module `lex` with functions `symbol_table()` and `lex()` — make tokenization more affordable
|
||||
+ Add `dorks` module and `flask.harden()`
|
||||
+ Added `addattr()`
|
||||
+ Add `sqlalchemy.bool_column()`: make making flags painless
|
||||
+ Added `addattr()`, `PrefixIdentifier()`
|
||||
|
||||
## 0.3.6
|
||||
|
||||
|
|
|
|||
|
|
@ -37,9 +37,7 @@ sqlalchemy = [
|
|||
]
|
||||
flask = [
|
||||
"Flask>=2.0.0",
|
||||
"Flask-RestX",
|
||||
"Quart",
|
||||
"Quart-Schema"
|
||||
"Flask-RestX"
|
||||
]
|
||||
flask_sqlalchemy = [
|
||||
"Flask-SqlAlchemy",
|
||||
|
|
@ -50,6 +48,21 @@ peewee = [
|
|||
markdown = [
|
||||
"markdown>=3.0.0"
|
||||
]
|
||||
quart = [
|
||||
"Flask>=2.0.0",
|
||||
"Quart",
|
||||
"Quart-Schema",
|
||||
"uvloop; os_name=='posix'"
|
||||
]
|
||||
|
||||
full = [
|
||||
"sakuragasaki46-suou[sqlalchemy]",
|
||||
"sakuragasaki46-suou[flask]",
|
||||
"sakuragasaki46-suou[quart]",
|
||||
"sakuragasaki46-suou[peewee]",
|
||||
"sakuragasaki46-suou[markdown]"
|
||||
]
|
||||
|
||||
|
||||
[tool.setuptools.dynamic]
|
||||
version = { attr = "suou.__version__" }
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ from abc import ABCMeta, abstractmethod
|
|||
from functools import wraps
|
||||
from typing import Callable, Iterable, Never, TypeVar
|
||||
import warnings
|
||||
from sqlalchemy import BigInteger, CheckConstraint, Date, Dialect, ForeignKey, LargeBinary, Column, MetaData, SmallInteger, String, create_engine, select, text
|
||||
from sqlalchemy import BigInteger, Boolean, CheckConstraint, Date, Dialect, ForeignKey, LargeBinary, Column, MetaData, SmallInteger, String, create_engine, select, text
|
||||
from sqlalchemy.orm import DeclarativeBase, Session, declarative_base as _declarative_base, relationship
|
||||
|
||||
from .snowflake import SnowflakeGen
|
||||
|
|
@ -120,7 +120,17 @@ def match_column(length: int, regex: str, /, case: StringCase = StringCase.AS_IS
|
|||
constraint_name=constraint_name or f'{x.__tablename__}_{n}_valid')), *args, **kwargs)
|
||||
|
||||
|
||||
def declarative_base(domain_name: str, master_secret: bytes, metadata: dict | None = None, **kwargs):
|
||||
def bool_column(value: bool = False, nullable: bool = False, **kwargs):
|
||||
"""
|
||||
Column for a single boolean value.
|
||||
|
||||
NEW in 0.4.0
|
||||
"""
|
||||
def_val = text('true') if value else text('false')
|
||||
return Column(Boolean, server_default=def_val, nullable=nullable, **kwargs)
|
||||
|
||||
|
||||
def declarative_base(domain_name: str, master_secret: bytes, metadata: dict | None = None, **kwargs) -> DeclarativeBase:
|
||||
"""
|
||||
Drop-in replacement for sqlalchemy.orm.declarative_base()
|
||||
taking in account requirements for SIQ generation (i.e. domain name).
|
||||
|
|
|
|||
44
src/suou/strtools.py
Normal file
44
src/suou/strtools.py
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
"""
|
||||
Utilities for string manipulation.
|
||||
|
||||
Why `strtools`? Why not `string`? I just~ happen to not like it
|
||||
|
||||
---
|
||||
|
||||
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, Iterable
|
||||
|
||||
from .itertools import makelist
|
||||
|
||||
class PrefixIdentifier:
|
||||
_prefix: str
|
||||
|
||||
def __init__(self, prefix: str | None, validators: Iterable[Callable[[str], bool]] | Callable[[str], bool] | None = None):
|
||||
prefix = '' if prefix is None else prefix
|
||||
if not isinstance(prefix, str):
|
||||
raise TypeError
|
||||
validators = makelist(validators, wrap=False)
|
||||
for validator in validators:
|
||||
if not validator(prefix):
|
||||
raise ValueError('invalid prefix')
|
||||
self._prefix = prefix
|
||||
|
||||
def __getattr__(self, key: str):
|
||||
return f'{self._prefix}{key}'
|
||||
|
||||
def __getitem__(self, key: str) -> str:
|
||||
return f'{self._prefix}{key}'
|
||||
|
||||
__all__ = ('PrefixIdentifier',)
|
||||
|
||||
50
tests/test_codecs.py
Normal file
50
tests/test_codecs.py
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
|
||||
|
||||
import binascii
|
||||
import unittest
|
||||
from suou.codecs import b64encode, b64decode
|
||||
|
||||
B1 = b'N\xf0\xb4\xc3\x85\n\xf9\xb6\x9a\x0f\x82\xa6\x99G\x07#'
|
||||
B2 = b'\xbcXiF,@|{\xbe\xe3\x0cz\xa8\xcbQ\x82'
|
||||
B3 = b"\xe9\x18)\xcb'\xc2\x96\xae\xde\x86"
|
||||
B4 = B1[-2:] + B2[:-2]
|
||||
B5 = b'\xff\xf8\xa7\x8a\xdf\xff'
|
||||
|
||||
|
||||
class TestCodecs(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
...
|
||||
def tearDown(self) -> None:
|
||||
...
|
||||
|
||||
#def runTest(self):
|
||||
# self.test_b64encode()
|
||||
# self.test_b64decode()
|
||||
|
||||
def test_b64encode(self):
|
||||
self.assertEqual(b64encode(B1), 'TvC0w4UK-baaD4KmmUcHIw')
|
||||
self.assertEqual(b64encode(B2), 'vFhpRixAfHu-4wx6qMtRgg')
|
||||
self.assertEqual(b64encode(B3), '6RgpyyfClq7ehg')
|
||||
self.assertEqual(b64encode(B4), 'ByO8WGlGLEB8e77jDHqoyw')
|
||||
self.assertEqual(b64encode(B5), '__init__')
|
||||
self.assertEqual(b64encode(B1[:4]), 'TvC0ww')
|
||||
self.assertEqual(b64encode(b'\0' + B1[:4]), 'AE7wtMM')
|
||||
self.assertEqual(b64encode(b'\0\0\0\0\0' + B1[:4]), 'AAAAAABO8LTD')
|
||||
self.assertEqual(b64encode(b'\xff'), '_w')
|
||||
self.assertEqual(b64encode(b''), '')
|
||||
|
||||
def test_b64decode(self):
|
||||
self.assertEqual(b64decode('TvC0w4UK-baaD4KmmUcHIw'), B1)
|
||||
self.assertEqual(b64decode('vFhpRixAfHu-4wx6qMtRgg'), B2)
|
||||
self.assertEqual(b64decode('6RgpyyfClq7ehg'), B3)
|
||||
self.assertEqual(b64decode('ByO8WGlGLEB8e77jDHqoyw'), B4)
|
||||
self.assertEqual(b64decode('__init__'), B5)
|
||||
self.assertEqual(b64decode('TvC0ww'), B1[:4])
|
||||
self.assertEqual(b64decode('AE7wtMM'), b'\0' + B1[:4])
|
||||
self.assertEqual(b64decode('AAAAAABO8LTD'), b'\0\0\0\0\0' + B1[:4])
|
||||
self.assertEqual(b64decode('_w'), b'\xff')
|
||||
self.assertEqual(b64decode(''), b'')
|
||||
|
||||
self.assertRaises(binascii.Error, b64decode, 'C')
|
||||
|
||||
|
||||
38
tests/test_strtools.py
Normal file
38
tests/test_strtools.py
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
|
||||
|
||||
|
||||
import unittest
|
||||
|
||||
from suou.strtools import PrefixIdentifier
|
||||
|
||||
class TestStrtools(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
...
|
||||
|
||||
def tearDown(self) -> None:
|
||||
...
|
||||
|
||||
def test_PrefixIdentifier_empty(self):
|
||||
pi = PrefixIdentifier(None)
|
||||
self.assertEqual(pi.hello, 'hello')
|
||||
self.assertEqual(pi['with spaces'], 'with spaces')
|
||||
self.assertEqual(pi['\x1b\x00'], '\x1b\0')
|
||||
self.assertEqual(pi.same_thing, pi['same_thing'])
|
||||
|
||||
with self.assertRaises(TypeError):
|
||||
pi[0]
|
||||
|
||||
self.assertEqual(PrefixIdentifier(None), PrefixIdentifier(''))
|
||||
|
||||
def test_PrefixIdentifier_invalid(self):
|
||||
with self.assertRaises(TypeError):
|
||||
pi = PrefixIdentifier(1)
|
||||
pi.hello
|
||||
|
||||
with self.assertRaises(TypeError):
|
||||
PrefixIdentifier([99182])
|
||||
|
||||
with self.assertRaises(TypeError):
|
||||
PrefixIdentifier(b'alpha_')
|
||||
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue