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
|
* Added `ast` module
|
||||||
* Deprecate `dei_args()` for problems with the typing system. The function is not going away tho
|
* 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
|
* Added common values for snowflake epoch in `SnowflakeEpoch` enum
|
||||||
|
|
||||||
## 0.13.1 and 0.12.7
|
## 0.13.1 and 0.12.7
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ from .color import OKLabColor, chalk, WebColor, RGBColor, LinearRGBColor, \
|
||||||
from .mat import Matrix
|
from .mat import Matrix
|
||||||
from .argparse import LetterSubparsers
|
from .argparse import LetterSubparsers
|
||||||
|
|
||||||
__version__ = "0.14.0a2"
|
__version__ = "0.14.0a3"
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'ColorFormatter',
|
'ColorFormatter',
|
||||||
|
|
|
||||||
|
|
@ -20,9 +20,9 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
from typing import Any, Callable, TypeVar
|
from typing import Any, Callable, TypeAlias, TypeVar
|
||||||
import warnings
|
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.orm import DeclarativeBase, InstrumentedAttribute, Relationship, declarative_base as _declarative_base, relationship
|
||||||
from sqlalchemy.types import TypeEngine
|
from sqlalchemy.types import TypeEngine
|
||||||
from sqlalchemy.ext.hybrid import Comparator
|
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):
|
def snowflake_column(*, primary_key: bool = True, **kwargs):
|
||||||
"""
|
"""
|
||||||
Same as id_column() but with snowflakes.
|
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:
|
def new_id_factory(owner: DeclarativeBase) -> Callable:
|
||||||
epoch = owner.metadata.info['snowflake_epoch']
|
epoch = owner.metadata.info['snowflake_epoch']
|
||||||
|
|
@ -100,7 +98,39 @@ match_constraint.TEXT_DIALECTS = {
|
||||||
'mariadb': ':n RLIKE :re'
|
'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.
|
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
|
if case != StringCase.AS_IS: # TODO
|
||||||
warnings.warn('case arg is currently not working', FutureWarning)
|
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)
|
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(
|
def username_column(
|
||||||
length: int = 32, regex: str | re.Pattern = '[a-z_][a-z0-9_-]+', *args, case: StringCase = StringCase.LOWER,
|
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]]:
|
nullable : bool = False, **kwargs) -> Incomplete[Column[str] | Column[str | None]]:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue