如何在声明时自动验证 String/Unicode 列的最大长度?
How to validate automatically String/Unicode columns maximum length when specified on declaration?
SQLAlchemy 允许在声明 String
列时指定长度:
foo = Column(String(10))
如 SQL:
foo VARCHAR(10)
我知道某些 DBMS 在 table 中创建行时使用此长度值分配内存。但是一些 DBMS(比如 SQLite)并不关心它并且接受这种语法只是为了与 SQL 标准兼容。但是某些 DBMS(例如 MySQL)要求指定它。
就个人而言,我喜欢为某些文本数据指定最大长度,因为它有助于设计 UI,因为您知道显示它所需的区域。
此外,我认为这将使我的应用程序行为在不同的 DBMS 之间更加一致。
所以,我想通过检查其长度与声明的(当声明长度时)来验证插入时String/Unicode列的值。
检查约束
第一个解决方案是使用 check constraint:
from sqlalchemy import CheckConstraint, Column, Integer, String, create_engine
from sqlalchemy.exc import IntegrityError
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
engine = create_engine("sqlite:///:memory:", echo=True)
Base = declarative_base(bind=engine)
Session = sessionmaker(bind=engine)
class Foo(Base):
__tablename__ = "Foo"
id = Column(Integer, primary_key=True)
bar = Column(String(10), CheckConstraint("LENGTH(bar) < 10"))
Base.metadata.create_all()
if __name__ == "__main__":
session = Session()
session.add(Foo(bar="a" * 20))
try:
session.commit()
except IntegrityError as e:
print(f"Failed with: {e.orig}")
它有效,但 SQL 约束表达式不是由 SQLAlchemy 生成的。因此,如果 DBMS 需要不同的语法,则可能需要一些自定义生成。
验证者
我也尝试过使用 SQLAlchemy validator:
class Foo(Base):
__tablename__ = "Foo"
id = Column(Integer, primary_key=True)
bar = Column(String(10))
@validates("bar")
def check_bar_length(self, key, value):
column_type = getattr(type(self), key).expression.type
max_length = column_type.length
if len(value) > max_length:
raise ValueError(
f"Value '{value}' for column '{key}' "
f"exceed maximum length of '{max_length}'"
)
return value
try:
Foo(bar="a" * 20)
except ValueError as e:
print(f"Failed with: {e}")
现在,最大长度是从声明的长度推断出来的。
检查是在实体创建时完成的,而不是在提交时完成的。不知道会不会有问题
自定义类型
上面显示的两种解决方案都需要对每一列应用验证。我正在寻找一种解决方案来自动对具有已声明长度的 String/Unicode 列执行检查。
使用 custom type 可能是解决方案。但它看起来像一个丑陋的 hack,因为自定义类型不是用于数据验证而是用于数据转换。
那么,您是否考虑另一种解决方案,也许是我不知道的 SQLAlchemy 功能,这将帮助我自动将检查添加到所有 String
列,其中 length
指定 ?
另一种选择可能是显式定义 table 并分解出您的字符串列定义,以便为每个字符串列创建检查约束而无需重复它。
def string_column(name, length):
check_str = "LENGTH({}) < {}".format(name, length)
return Column(name, String(length), CheckConstraint(check_str))
class Foo(Base):
__table__ = Table("Foo", Base.metadata,
Column("id", Integer, primary_key=True),
string_column("bar", 10),
string_column("name", 15))
我找到了一个似乎适合我的解决方案needs.But我认为我添加约束的方式有点老套。
涉及到的用法:
实体声明
像往常一样声明实体,无需指定任何约束:
from sqlalchemy import Column, Integer, LargeBinary, String, Unicode,
class Foo(Entity):
__tablename__ = "Foo"
id = Column(Integer, primary_key=True)
string_without_length = Column(String())
string_with_length = Column(String(10))
unicode_with_length = Column(Unicode(20))
binary = Column(LargeBinary(256))
附加约束
约束在检测 class 之前附加到列:
from sqlalchemy import CheckConstraint, func, String
from sqlalchemy.event import listen_for
from sqlalchemy.orm import mapper
@listens_for(mapper, "instrument_class")
def add_string_length_constraint(mapper, cls):
table = cls.__table__
for column in table.columns:
if isinstance(column.type, String):
length = column.type.length
if length is not None:
CheckConstraint(
func.length(column) <= length,
table=column,
_autoattach=False,
)
生成的 DDL 语句 (SQLite)
CREATE TABLE "Foo" (
id INTEGER NOT NULL,
string_without_length VARCHAR,
string_with_length VARCHAR(10) CHECK (length(string_with_length) <= 10),
unicode_with_length VARCHAR(20) CHECK (length(unicode_with_length) <= 20),
binary BLOB,
PRIMARY KEY (id)
)
String
没有长度的列不受影响,
String
和 Unicode
长度的列添加了 CHECK 约束,
- 其他接受
length
参数的列(如 LargeBinary)不受影响。
实施细节
@listens_for(mapper, "instrument_class")
instrument_class
事件发生在已检测 class 的映射器已创建但未完全初始化时。它可以在您的基本声明 class(使用 declarative_base()
创建)或直接在 slqalchemy.orm.mapper
class.
上收听
if isinstance(column.type, String):
只有 String
(和子 class 像 Unicode
)列...
if length is not None:
...设置了length
的考虑
CheckConstraint(
func.length(column) <= length,
table=column,
_autoattach=False,
)
约束是使用 SQLAlchemy 表达式生成的。
最后,hacky 部分:
创建约束时,SQLAlchemy 会自动将其附加到 table(我认为它会检测约束所涉及的列)。
因为我希望它作为列定义的一部分生成,所以我使用 _autoattach=False
禁用此自动附加,然后我使用 table=column
.
指定列
如果您不关心它,请忽略这些参数:
CheckConstraint(func.length(column) <= length)
生成的 DDL 语句将是:
CREATE TABLE "Foo" (
id INTEGER NOT NULL,
string_without_length VARCHAR,
string_with_length VARCHAR(10),
unicode_with_length VARCHAR(20),
binary BLOB,
PRIMARY KEY (id),
CHECK (length(string_with_length) <= 10),
CHECK (length(unicode_with_length) <= 20)
)
SQLAlchemy 允许在声明 String
列时指定长度:
foo = Column(String(10))
如 SQL:
foo VARCHAR(10)
我知道某些 DBMS 在 table 中创建行时使用此长度值分配内存。但是一些 DBMS(比如 SQLite)并不关心它并且接受这种语法只是为了与 SQL 标准兼容。但是某些 DBMS(例如 MySQL)要求指定它。
就个人而言,我喜欢为某些文本数据指定最大长度,因为它有助于设计 UI,因为您知道显示它所需的区域。
此外,我认为这将使我的应用程序行为在不同的 DBMS 之间更加一致。
所以,我想通过检查其长度与声明的(当声明长度时)来验证插入时String/Unicode列的值。
检查约束
第一个解决方案是使用 check constraint:
from sqlalchemy import CheckConstraint, Column, Integer, String, create_engine
from sqlalchemy.exc import IntegrityError
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
engine = create_engine("sqlite:///:memory:", echo=True)
Base = declarative_base(bind=engine)
Session = sessionmaker(bind=engine)
class Foo(Base):
__tablename__ = "Foo"
id = Column(Integer, primary_key=True)
bar = Column(String(10), CheckConstraint("LENGTH(bar) < 10"))
Base.metadata.create_all()
if __name__ == "__main__":
session = Session()
session.add(Foo(bar="a" * 20))
try:
session.commit()
except IntegrityError as e:
print(f"Failed with: {e.orig}")
它有效,但 SQL 约束表达式不是由 SQLAlchemy 生成的。因此,如果 DBMS 需要不同的语法,则可能需要一些自定义生成。
验证者
我也尝试过使用 SQLAlchemy validator:
class Foo(Base):
__tablename__ = "Foo"
id = Column(Integer, primary_key=True)
bar = Column(String(10))
@validates("bar")
def check_bar_length(self, key, value):
column_type = getattr(type(self), key).expression.type
max_length = column_type.length
if len(value) > max_length:
raise ValueError(
f"Value '{value}' for column '{key}' "
f"exceed maximum length of '{max_length}'"
)
return value
try:
Foo(bar="a" * 20)
except ValueError as e:
print(f"Failed with: {e}")
现在,最大长度是从声明的长度推断出来的。
检查是在实体创建时完成的,而不是在提交时完成的。不知道会不会有问题
自定义类型
上面显示的两种解决方案都需要对每一列应用验证。我正在寻找一种解决方案来自动对具有已声明长度的 String/Unicode 列执行检查。
使用 custom type 可能是解决方案。但它看起来像一个丑陋的 hack,因为自定义类型不是用于数据验证而是用于数据转换。
那么,您是否考虑另一种解决方案,也许是我不知道的 SQLAlchemy 功能,这将帮助我自动将检查添加到所有 String
列,其中 length
指定 ?
另一种选择可能是显式定义 table 并分解出您的字符串列定义,以便为每个字符串列创建检查约束而无需重复它。
def string_column(name, length):
check_str = "LENGTH({}) < {}".format(name, length)
return Column(name, String(length), CheckConstraint(check_str))
class Foo(Base):
__table__ = Table("Foo", Base.metadata,
Column("id", Integer, primary_key=True),
string_column("bar", 10),
string_column("name", 15))
我找到了一个似乎适合我的解决方案needs.But我认为我添加约束的方式有点老套。
涉及到的用法:
实体声明
像往常一样声明实体,无需指定任何约束:
from sqlalchemy import Column, Integer, LargeBinary, String, Unicode,
class Foo(Entity):
__tablename__ = "Foo"
id = Column(Integer, primary_key=True)
string_without_length = Column(String())
string_with_length = Column(String(10))
unicode_with_length = Column(Unicode(20))
binary = Column(LargeBinary(256))
附加约束
约束在检测 class 之前附加到列:
from sqlalchemy import CheckConstraint, func, String
from sqlalchemy.event import listen_for
from sqlalchemy.orm import mapper
@listens_for(mapper, "instrument_class")
def add_string_length_constraint(mapper, cls):
table = cls.__table__
for column in table.columns:
if isinstance(column.type, String):
length = column.type.length
if length is not None:
CheckConstraint(
func.length(column) <= length,
table=column,
_autoattach=False,
)
生成的 DDL 语句 (SQLite)
CREATE TABLE "Foo" (
id INTEGER NOT NULL,
string_without_length VARCHAR,
string_with_length VARCHAR(10) CHECK (length(string_with_length) <= 10),
unicode_with_length VARCHAR(20) CHECK (length(unicode_with_length) <= 20),
binary BLOB,
PRIMARY KEY (id)
)
String
没有长度的列不受影响,String
和Unicode
长度的列添加了 CHECK 约束,- 其他接受
length
参数的列(如 LargeBinary)不受影响。
实施细节
@listens_for(mapper, "instrument_class")
instrument_class
事件发生在已检测 class 的映射器已创建但未完全初始化时。它可以在您的基本声明 class(使用 declarative_base()
创建)或直接在 slqalchemy.orm.mapper
class.
if isinstance(column.type, String):
只有 String
(和子 class 像 Unicode
)列...
if length is not None:
...设置了length
的考虑
CheckConstraint(
func.length(column) <= length,
table=column,
_autoattach=False,
)
约束是使用 SQLAlchemy 表达式生成的。
最后,hacky 部分:
创建约束时,SQLAlchemy 会自动将其附加到 table(我认为它会检测约束所涉及的列)。
因为我希望它作为列定义的一部分生成,所以我使用 _autoattach=False
禁用此自动附加,然后我使用 table=column
.
如果您不关心它,请忽略这些参数:
CheckConstraint(func.length(column) <= length)
生成的 DDL 语句将是:
CREATE TABLE "Foo" (
id INTEGER NOT NULL,
string_without_length VARCHAR,
string_with_length VARCHAR(10),
unicode_with_length VARCHAR(20),
binary BLOB,
PRIMARY KEY (id),
CHECK (length(string_with_length) <= 10),
CHECK (length(unicode_with_length) <= 20)
)