0.14.0a3 add ascii_column() and runtime check to match_column()
This commit is contained in:
parent
e2aec01d31
commit
b239d0eaca
3 changed files with 50 additions and 8 deletions
|
|
@ -4,7 +4,9 @@
|
|||
|
||||
* Added `ast` module
|
||||
* Deprecate `dei_args()` for problems with the typing system. The function is not going away tho
|
||||
* Module `sqlalchemy`: added `email_column()`
|
||||
* Module `sqlalchemy`:
|
||||
* added `email_column()`, `ascii_column()`
|
||||
* added application level checks for `match_column()`
|
||||
* Added common values for snowflake epoch in `SnowflakeEpoch` enum
|
||||
|
||||
## 0.13.1 and 0.12.7
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ from .color import OKLabColor, chalk, WebColor, RGBColor, LinearRGBColor, \
|
|||
from .mat import Matrix
|
||||
from .argparse import LetterSubparsers
|
||||
|
||||
__version__ = "0.14.0a2"
|
||||
__version__ = "0.14.0a3"
|
||||
|
||||
__all__ = (
|
||||
'ColorFormatter',
|
||||
|
|
|
|||
|
|
@ -20,9 +20,9 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
|
||||
import os
|
||||
import re
|
||||
from typing import Any, Callable, TypeVar
|
||||
from typing import Any, Callable, TypeAlias, TypeVar
|
||||
import warnings
|
||||
from sqlalchemy import BigInteger, Boolean, CheckConstraint, Column, Date, ForeignKey, LargeBinary, MetaData, SmallInteger, String, text
|
||||
from sqlalchemy import VARCHAR, BigInteger, Boolean, CheckConstraint, Column, Date, ForeignKey, 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
|
||||
|
|
@ -71,8 +71,6 @@ def id_column(typ: SiqType, *, primary_key: bool = True, **kwargs):
|
|||
def snowflake_column(*, primary_key: bool = True, **kwargs):
|
||||
"""
|
||||
Same as id_column() but with snowflakes.
|
||||
|
||||
XXX this is meant ONLY as means of transition; for new stuff, use id_column() and SIQ.
|
||||
"""
|
||||
def new_id_factory(owner: DeclarativeBase) -> Callable:
|
||||
epoch = owner.metadata.info['snowflake_epoch']
|
||||
|
|
@ -100,7 +98,39 @@ match_constraint.TEXT_DIALECTS = {
|
|||
'mariadb': ':n RLIKE :re'
|
||||
}
|
||||
|
||||
def match_column(length: int, regex: str | re.Pattern, /, case: StringCase = StringCase.AS_IS, *args, constraint_name: str | None = None, **kwargs) -> Incomplete[Column[str]]:
|
||||
## Type decorators for match_column and ascii_column follow
|
||||
|
||||
class MatchString(TypeDecorator):
|
||||
impl: TypeAlias = String
|
||||
|
||||
def __init__(self, length: int, regex: str | re.Pattern, case: StringCase = StringCase.AS_IS, *args, **kwargs):
|
||||
TypeDecorator.__init__(self, length=length, *args, **kwargs)
|
||||
self.regex = regex
|
||||
self.case = case
|
||||
|
||||
def process_bind_param(self, value, dialect):
|
||||
value = self.case.transform(value)
|
||||
if not re.fullmatch(self.regex, value):
|
||||
raise ValueError('value does not match regex')
|
||||
return value
|
||||
|
||||
|
||||
class AsciiString(TypeDecorator):
|
||||
impl: TypeAlias = String
|
||||
|
||||
def process_bind_param(self, value: str, dialect) :
|
||||
try:
|
||||
value.encode('ascii')
|
||||
except Exception:
|
||||
raise ValueError('only ASCII strings are allowed')
|
||||
return value
|
||||
|
||||
## END type decorators
|
||||
|
||||
|
||||
def match_column(
|
||||
length: int, regex: str | re.Pattern, /, case: StringCase = StringCase.AS_IS,
|
||||
*args, constraint_name: str | None = None, **kwargs) -> Incomplete[Column[str]]:
|
||||
"""
|
||||
Syntactic sugar to create a String() column with a check constraint matching the given regular expression.
|
||||
|
||||
|
|
@ -108,10 +138,20 @@ def match_column(length: int, regex: str | re.Pattern, /, case: StringCase = Str
|
|||
"""
|
||||
if case != StringCase.AS_IS: # TODO
|
||||
warnings.warn('case arg is currently not working', FutureWarning)
|
||||
return Incomplete(Column, String(length), Wanted(lambda x, n: match_constraint(n, regex, #dialect=x.metadata.engine.dialect.name,
|
||||
return Incomplete(Column, MatchString(length, regex, case), Wanted(lambda x, n: match_constraint(n, regex, #dialect=x.metadata.engine.dialect.name,
|
||||
constraint_name=constraint_name or f'{x.__tablename__}_{n}_valid')), *args, **kwargs)
|
||||
|
||||
|
||||
def ascii_column(length: int, /, *args, **kwargs):
|
||||
"""
|
||||
String() column that admits only ASCII characters.
|
||||
|
||||
*New in 0.14.0*
|
||||
"""
|
||||
|
||||
return Column(AsciiString(length), *args, **kwargs)
|
||||
|
||||
|
||||
def username_column(
|
||||
length: int = 32, regex: str | re.Pattern = '[a-z_][a-z0-9_-]+', *args, case: StringCase = StringCase.LOWER,
|
||||
nullable : bool = False, **kwargs) -> Incomplete[Column[str] | Column[str | None]]:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue