自定义类型
存在各种方法来重新定义现有类型的行为以及提供新类型。
覆盖类型编译
经常需要强制类型的“字符串”版本,即在 CREATE TABLE 语句或其他 SQL 函数(如 CAST)中呈现的版本进行更改。例如,应用程序可能希望强制在除一个平台外的所有平台上呈现BINARY
,在该平台上希望呈现BLOB
。对于大多数用例,首选使用现有的通用类型,例如LargeBinary
。但为了更准确地控制类型,可以将每个方言的编译指令与任何类型关联起来:
from sqlalchemy.ext.compiler import compiles from sqlalchemy.types import BINARY @compiles(BINARY, "sqlite") def compile_binary_sqlite(type_, compiler, **kw): return "BLOB"
上述代码允许使用BINARY
,它将针对除 SQLite 外的所有后端生成字符串BINARY
,在 SQLite 的情况下,它将生成BLOB
。
请参阅更改类型编译部分,这是自定义 SQL 构造和编译扩展的一个子部分,其中包含额外的示例。
增强现有类型
TypeDecorator
允许创建自定义类型,为现有类型对象添加绑定参数和结果处理行为。当需要对数据进行额外的 Python 内部编组以及/或从数据库中进行时使用。
注意
TypeDecorator
的绑定和结果处理是额外的,除了由托管类型已执行的处理外,SQLAlchemy 还会根据每个 DBAPI 定制来执行特定于该 DBAPI 的处理。虽然可以通过直接子类化来替换给定类型的处理,但在实践中从不需要,并且 SQLAlchemy 不再支持这作为公共用例。
对象名称 | 描述 |
TypeDecorator | 允许创建类型,为现有类型添加额外功能。 |
class sqlalchemy.types.TypeDecorator
允许创建类型,为现有类型添加额外功能。
此方法优于直接子类化 SQLAlchemy 内置类型,因为它确保保留底层类型的所有必需功能。
典型用法:
import sqlalchemy.types as types class MyType(types.TypeDecorator): '''Prefixes Unicode values with "PREFIX:" on the way in and strips it off on the way out. ''' impl = types.Unicode cache_ok = True def process_bind_param(self, value, dialect): return "PREFIX:" + value def process_result_value(self, value, dialect): return value[7:] def copy(self, **kw): return MyType(self.impl.length)
类级别的impl
属性是必需的,并且可以引用任何TypeEngine
类。或者,可以使用load_dialect_impl()
方法根据给定的方言提供不同的类型类;在这种情况下,impl
变量可以引用TypeEngine
作为占位符。
TypeDecorator.cache_ok
类级别标志指示此自定义TypeDecorator
是否可以安全地用作缓存键的一部分。此标志默认为None
,当 SQL 编译器尝试为使用此类型的语句生成缓存键时,将最初生成警告。如果TypeDecorator
不能保证每次都产生相同的绑定/结果行为和 SQL 生成,则应将此标志设置为False
;否则,如果该类每次都产生相同的行为,则可以设置为True
。有关此工作原理的更多说明,请参见TypeDecorator.cache_ok
。
接收不类似于最终使用的类型的 Python 类型的类型可能希望定义TypeDecorator.coerce_compared_value()
方法。这用于在表达式中将 Python 对象强制转换为绑定参数时给表达式系统一个提示。考虑这个表达式:
mytable.c.somecol + datetime.date(2009, 5, 15)
在上面,如果“somecol”是一个Integer
变体,我们做日期算术操作是有意义的,其中上面通常被数据库解释为将一些天数加到给定日期上。表达式系统通过不试图将“date()”值强制转换为面向整数的绑定参数来做正确的事情。
但是,在TypeDecorator
的情况下,我们通常会将一个传入的 Python 类型更改为新的东西 - 默认情况下,TypeDecorator
会将非类型化的一侧“强制”成与自身相同的类型。例如下面,我们定义了一个将日期值存储为整数的“epoch”类型:
class MyEpochType(types.TypeDecorator): impl = types.Integer cache_ok = True epoch = datetime.date(1970, 1, 1) def process_bind_param(self, value, dialect): return (value - self.epoch).days def process_result_value(self, value, dialect): return self.epoch + timedelta(days=value)
使用上述类型的somecol + date
表达式将会强制右侧的“date”也被视为MyEpochType
。
通过TypeDecorator.coerce_compared_value()
方法可以覆盖此行为,该方法返回一个应用于表达式值的类型。在下面的示例中,我们设置了一个整数值将被视为Integer
,而任何其他值都被假定为日期并将被视为MyEpochType
:
def coerce_compared_value(self, op, value): if isinstance(value, int): return Integer() else: return self
警告
注意,coerce_compared_value 的行为不会默认从基本类型那里继承。如果 TypeDecorator
是增强某种类型需要特殊逻辑的装饰器,这个方法 必须 被重写。一个关键的例子是当装饰 JSON
和 JSONB
类型时;应该使用 TypeEngine.coerce_compared_value()
的默认规则来处理像索引操作这样的操作符:
from sqlalchemy import JSON from sqlalchemy import TypeDecorator class MyJsonType(TypeDecorator): impl = JSON cache_ok = True def coerce_compared_value(self, op, value): return self.impl.coerce_compared_value(op, value)
没有上述步骤,索引操作,比如mycol['foo']
会导致索引值'foo'
被 JSON 编码。
类似地,当使用 ARRAY
数据类型时,索引操作的类型强制转换(例如 mycol[5]
)也由 TypeDecorator.coerce_compared_value()
处理,再次简单的重写就足够了,除非对特定操作符需要特殊规则:
from sqlalchemy import ARRAY from sqlalchemy import TypeDecorator class MyArrayType(TypeDecorator): impl = ARRAY cache_ok = True def coerce_compared_value(self, op, value): return self.impl.coerce_compared_value(op, value)
成员
cache_ok, operate(), reverse_operate(), init(), bind_expression(), bind_processor(), coerce_compared_value(), coerce_to_is_types, column_expression(), comparator_factory, compare_values(), copy(), get_dbapi_type(), literal_processor(), load_dialect_impl(), process_bind_param(), process_literal_param(), process_result_value(), result_processor(), sort_key_function, type_engine()
类签名
类 sqlalchemy.types.TypeDecorator
(sqlalchemy.sql.expression.SchemaEventTarget
, sqlalchemy.types.ExternalType
, sqlalchemy.types.TypeEngine
)
attribute cache_ok: bool | None = None
继承自 ExternalType.cache_ok
属性 的 ExternalType
使用此 ExternalType
表示的 if 语句是否“可以缓存”。
默认值 None
会发出警告,然后不允许缓存包含此类型的语句。将其设置为 False
可以禁用使用此类型的语句的缓存,而不发出警告。当设置为 True
时,对象的类和其状态的选定元素将用作缓存键的一部分。例如,使用 TypeDecorator
:
class MyType(TypeDecorator): impl = String cache_ok = True def __init__(self, choices): self.choices = tuple(choices) self.internal_only = True
上述类型的缓存键将等同于:
>>> MyType(["a", "b", "c"])._static_cache_key (<class '__main__.MyType'>, ('choices', ('a', 'b', 'c')))
缓存方案将从类型中提取与 __init__()
方法中参数名称相对应的属性。在上面的例子中,“choices” 属性成为缓存键的一部分,但“internal_only” 不会,因为没有名为 “internal_only” 的参数。
可缓存元素的要求是它们是可哈希的,并且还要求对于给定缓存值,它们每次都指示使用此类型的表达式的相同 SQL 渲染。
为了适应引用不可哈希结构(如字典、集合和列表)的数据类型,可以通过将可哈希结构分配给其名称与参数名称对应的属性来使这些对象“可缓存”。例如,一个接受查找值字典的数据类型可以将其公布为一系列已排序的元组。给定一个先前不可缓存的类型如下:
class LookupType(UserDefinedType): '''a custom type that accepts a dictionary as a parameter. this is the non-cacheable version, as "self.lookup" is not hashable. ''' def __init__(self, lookup): self.lookup = lookup def get_col_spec(self, **kw): return "VARCHAR(255)" def bind_processor(self, dialect): # ... works with "self.lookup" ...
其中“lookup”是一个字典。该类型将无法生成缓存键:
>>> type_ = LookupType({"a": 10, "b": 20}) >>> type_._static_cache_key <stdin>:1: SAWarning: UserDefinedType LookupType({'a': 10, 'b': 20}) will not produce a cache key because the ``cache_ok`` flag is not set to True. Set this flag to True if this type object's state is safe to use in a cache key, or False to disable this warning. symbol('no_cache')
如果我们确实设置了这样的缓存键,它将无法使用。我们将得到一个包含字典的元组结构,该字典本身无法作为“缓存字典”中的键使用,例如 SQLAlchemy 的语句缓存,因为 Python 字典不可哈希:
>>> # set cache_ok = True >>> type_.cache_ok = True >>> # this is the cache key it would generate >>> key = type_._static_cache_key >>> key (<class '__main__.LookupType'>, ('lookup', {'a': 10, 'b': 20})) >>> # however this key is not hashable, will fail when used with >>> # SQLAlchemy statement cache >>> some_cache = {key: "some sql value"} Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unhashable type: 'dict'
通过将排序后的元组元组分配给“.lookup”属性,可以使该类型可缓存:
class LookupType(UserDefinedType): '''a custom type that accepts a dictionary as a parameter. The dictionary is stored both as itself in a private variable, and published in a public variable as a sorted tuple of tuples, which is hashable and will also return the same value for any two equivalent dictionaries. Note it assumes the keys and values of the dictionary are themselves hashable. ''' cache_ok = True def __init__(self, lookup): self._lookup = lookup # assume keys/values of "lookup" are hashable; otherwise # they would also need to be converted in some way here self.lookup = tuple( (key, lookup[key]) for key in sorted(lookup) ) def get_col_spec(self, **kw): return "VARCHAR(255)" def bind_processor(self, dialect): # ... works with "self._lookup" ...
在上面,LookupType({"a": 10, "b": 20})
的缓存键将是:
>>> LookupType({"a": 10, "b": 20})._static_cache_key (<class '__main__.LookupType'>, ('lookup', (('a', 10), ('b', 20))))
新功能,在版本 1.4.14 中:- 为 TypeDecorator
类添加了 cache_ok
标志,以允许对缓存进行一些可配置性。
新版本 1.4.28 中增加了ExternalType
mixin,它将cache_ok
标志推广到TypeDecorator
和UserDefinedType
类。
另请参阅
SQL 编译缓存
class Comparator
一个特定于TypeDecorator
的Comparator
。
用户定义的TypeDecorator
类通常不需要修改此内容。
类签名
类sqlalchemy.types.TypeDecorator.Comparator
(sqlalchemy.types.Comparator
)
method operate(op: OperatorType, *other: Any, **kwargs: Any) → ColumnElement[_CT]
对参数进行操作。
这是最低级的操作,默认情况下引发NotImplementedError
。
在子类中覆盖此内容可以允许将通用行为应用于所有操作。例如,覆盖ColumnOperators
以将func.lower()
应用于左右两侧:
class MyComparator(ColumnOperators): def operate(self, op, other, **kwargs): return op(func.lower(self), func.lower(other), **kwargs)
参数:
op
– 运算符可调用。*other
– 操作的‘其他’一侧。对于大多数操作,将是单个标量。**kwargs
– 修饰符。这些可以通过特殊的运算符传递,例如ColumnOperators.contains()
。
method reverse_operate(op: OperatorType, other: Any, **kwargs: Any) → ColumnElement[_CT]
对参数进行反向操作。
使用方式与operate()
相同。
method __init__(*args: Any, **kwargs: Any)
构造一个TypeDecorator
。
发送到这里的参数将传递给分配给impl
类级属性的类的构造函数,假设impl
是可调用的,并且将生成的对象分配给self.impl
实例属性(从而覆盖同名的类属性)。
如果类级impl
不是可调用的(不寻常的情况),它将被分配给相同的实例属性,忽略传递给构造函数的参数。
子类可以覆盖此内容以完全自定义self.impl
的生成。
method bind_expression(bindparam: BindParameter[_T]) → ColumnElement[_T] | None
给定一个绑定值(即一个BindParameter
实例),返回一个 SQL 表达式,该表达式通常将给定参数包装起来。
注意
此方法在语句的SQL 编译阶段调用,当渲染 SQL 字符串时。它不一定针对特定值调用,并且不应与TypeDecorator.process_bind_param()
方法混淆,后者是处理语句执行时传递给特定参数的实际值的更典型方法。
TypeDecorator
的子类可以重写此方法,以提供类型的自定义绑定表达式行为。此实现将替换基础实现类型的实现。
method bind_processor(dialect: Dialect) → _BindProcessorType[_T] | None
为给定的Dialect
提供一个绑定值处理函数。
这是通过TypeEngine.bind_processor()
方法通常发生的绑定值转换的方法,它履行了TypeEngine
合同。
注意
TypeDecorator
的用户定义的子类不应该实现这个方法,而应该实现TypeDecorator.process_bind_param()
,以便保持实现类型提供的“内部”处理。
参数:
dialect – 正在使用的方言实例。
method coerce_compared_value(op: OperatorType | None, value: Any) → Any
在表达式中建议为“强制转换”的 Python 值提供一种类型。
默认情况下,返回 self。当使用此类型的对象在表达式左侧或右侧与尚未分配 SQLAlchemy 类型的普通 Python 对象相比时,表达式系统将调用此方法:
expr = table.c.somecolumn + 35
在上述情况下,如果somecolumn
使用此类型,则将使用值operator.add
和35
调用此方法。返回值是为这个特定操作应该使用的 SQLAlchemy 类型。
attribute coerce_to_is_types: Sequence[Type[Any]] = (<class 'NoneType'>,)
指定那些应该在表达式级别强制转换为“IS ”的 Python 类型,当使用==
进行比较时(对于!=
结合IS NOT
也是如此)。
对于大多数 SQLAlchemy 类型,这包括NoneType
,以及bool
。
TypeDecorator
修改此列表,只包括NoneType
,因为处理布尔类型的 typedecorator 实现是常见的。
自定义TypeDecorator
类可以重写此属性以返回一个空元组,在这种情况下,不会将任何值强制转换为常量。
method column_expression(column: ColumnElement[_T]) → ColumnElement[_T] | None
给定一个 SELECT 列表达式,返回一个包装的 SQL 表达式。
注意
这个方法在语句的SQL 编译阶段调用,当渲染 SQL 字符串时。它不会针对特定值进行调用,并且不应将其与TypeDecorator.process_result_value()
方法混淆,后者是处理语句执行后返回的实际值的更典型的方法。
TypeDecorator
的子类可以重写此方法,以为类型提供自定义列表达式行为。此实现将替换底层实现类型的实现。
有关方法用途的完整描述,请参阅TypeEngine.column_expression()
的描述。
attribute comparator_factory: _ComparatorFactory[Any]
一个Comparator
类,将应用于由拥有的ColumnElement
对象执行的操作。
当执行列和 SQL 表达式操作时,核心表达式系统会查找comparator_factory
属性。当与此属性相关联的是一个Comparator
类时,它允许自定义重新定义所有现有运算符,以及定义新的运算符。现有运算符包括通过 Python 运算符重载提供的运算符,如ColumnOperators.__add__()
和ColumnOperators.__eq__()
,以及作为ColumnOperators
的标准属性提供的运算符,如ColumnOperators.like()
和ColumnOperators.in_()
。
通过简单地对现有类型进行子类化或者使用TypeDecorator
,可以允许对这个钩子进行基本的使用。有关示例,请参阅文档中的 Redefining and Creating New Operators 部分。
method compare_values(x: Any, y: Any) → bool
给定两个值,比较它们是否相等。
默认情况下,这将调用底层“impl”的TypeEngine.compare_values()
,这通常使用 Python 相等运算符==
。
此函数由 ORM 用于将原始加载的值与拦截的“更改”值进行比较,以确定是否发生了净变化。
method copy(**kw: Any) → Self
生产这个TypeDecorator
实例的副本。
这是一个浅拷贝,并提供了部分TypeEngine
合约的实现。通常不需要重写,除非用户定义的TypeDecorator
具有应该深拷贝的本地状态。
method get_dbapi_type(dbapi: module) → Any | None
返回由此TypeDecorator
表示的 DBAPI 类型对象。
默认情况下,这将调用底层“impl”的TypeEngine.get_dbapi_type()
。
method literal_processor(dialect: Dialect) → _LiteralProcessorType[_T] | None
为给定的Dialect
提供一个字面处理函数。
这是履行通过TypeEngine.literal_processor()
方法正常发生的字面值转换的TypeEngine
合约的方法。
注意
用户定义的TypeDecorator
子类不应该实现此方法,而应该实现TypeDecorator.process_literal_param()
,以便维护实现类型提供的“内部”处理。
method load_dialect_impl(dialect: Dialect) → TypeEngine[Any]
返回与方言对应的TypeEngine
对象。
这是一个最终用户的覆盖钩子,可用于根据给定的方言提供不同的类型。它被TypeDecorator
的实现在帮助确定对于给定的TypeDecorator
应最终返回什么类型时使用。
默认情况下返回self.impl
。
method process_bind_param(value: _T | None, dialect: Dialect) → Any
接收要转换的绑定参数值。
自定义的TypeDecorator
子类应该重写此方法,以提供传入数据值的自定义行为。此方法在语句执行时间被调用,并传递要与语句中的绑定参数关联的字面 Python 数据值。
操作可以是任何所需的自定义行为,例如转换或序列化数据。这也可以用作验证逻辑的钩子。
参数:
value
– 要操作的数据,应为子类中此方法预期的任何类型。可以是None
。dialect
– 使用的Dialect
。
另请参阅
增强现有类型
TypeDecorator.process_result_value()
method process_literal_param(value: _T | None, dialect: Dialect) → str
接收要在语句中内联呈现的文字参数值。
注意
这个方法在SQL 编译阶段的语句执行时被调用,用于渲染 SQL 字符串。与其他 SQL 编译方法不同,它接收一个特定的 Python 值作为字符串进行渲染。但是不要将其与TypeDecorator.process_bind_param()
方法混淆,后者是在语句执行时处理传递给特定参数的实际值的更典型的方法。
TypeDecorator
的自定义子类应重写此方法,以提供对特殊情况下作为文字呈现的传入数据值的自定义行为。
返回的字符串将被渲染到输出字符串中。
method process_result_value(value: Any | None, dialect: Dialect) → _T | None
接收要转换的结果行列值。
TypeDecorator
的自定义子类应重写此方法,以提供从数据库结果行中接收到的数据值的自定义行为。此方法在结果提取时被调用,并传递从数据库结果行中提取的字面 Python 数据值。
操作可以是任何希望执行自定义行为的内容,例如转换或反序列化数据。
参数:
value
– 要操作的数据,其类型由该子类中的此方法期望的类型决定。可以是None
。dialect
– 使用的Dialect
。
另请参阅
增强现有类型
TypeDecorator.process_bind_param()
method result_processor(dialect: Dialect, coltype: Any) → _ResultProcessorType[_T] | None
为给定的Dialect
提供结果值处理函数。
这是满足TypeEngine
约定的方法,用于绑定值转换,通常通过TypeEngine.result_processor()
方法进行。
注意
用户定义的 TypeDecorator
的子类不应实现这个方法,而应该实现 TypeDecorator.process_result_value()
,以便保持实现类型提供的“内部”处理。
参数:
dialect
– 正在使用的方言实例。coltype
– 一个 SQLAlchemy 数据类型。
attribute sort_key_function: Callable[[Any], Any] | None
一个可以作为 sorted 的键传递的排序函数。
None
的默认值表示此类型存储的值是自排序的。
版本 1.3.8 中的新功能。
method type_engine(dialect: Dialect) → TypeEngine[Any]
为这个 TypeDecorator
返回一个特定方言的 TypeEngine
实例。
在大多数情况下,这将返回一个由 self.impl
表示的 TypeEngine
类型的方言适配形式。使用 dialect_impl()
。通过覆盖 load_dialect_impl()
可在此处自定义行为。
TypeDecorator 配方
以下是一些关键的 TypeDecorator
配方。
将编码字符串强制转换为 Unicode
关于 Unicode
类型的一个常见困惑是,它仅用于处理 Python 端的 unicode
对象,这意味着作为绑定参数传递给它的值必须是 u'some string'
的形式,如果使用的是 Python 2 而不是 3。它执行的编码/解码函数仅适应所使用的 DBAPI 需要的内容,并且主要是一个私有实现细节。
可以通过使用需要时强制转换的 TypeDecorator
来实现安全接收 Python 字节串的用例:
from sqlalchemy.types import TypeDecorator, Unicode class CoerceUTF8(TypeDecorator): """Safely coerce Python bytestrings to Unicode before passing off to the database.""" impl = Unicode def process_bind_param(self, value, dialect): if isinstance(value, str): value = value.decode("utf-8") return value
四舍五入数值
一些数据库连接器(如 SQL Server 的连接器)如果传递带有太多小数位的 Decimal 会出错。以下是一个将其四舍五入的配方:
from sqlalchemy.types import TypeDecorator, Numeric from decimal import Decimal class SafeNumeric(TypeDecorator): """Adds quantization to Numeric.""" impl = Numeric def __init__(self, *arg, **kw): TypeDecorator.__init__(self, *arg, **kw) self.quantize_int = -self.impl.scale self.quantize = Decimal(10) ** self.quantize_int def process_bind_param(self, value, dialect): if isinstance(value, Decimal) and value.as_tuple()[2] < self.quantize_int: value = value.quantize(self.quantize) return value
将时区感知时间戳存储为时区无关的 UTC 时间
数据库中的时间戳应始终以不考虑时区的方式存储。对于大多数数据库,这意味着首先将时间戳设置为 UTC 时区,然后将其存储为无时区(即,没有与之关联的任何时区;假定 UTC 为“隐式”时区)。或者,通常更喜欢使用数据库特定类型,如 PostgreSQL 的“带时区的时间戳”,因为它们具有更丰富的功能;但是,以纯 UTC 存储将在所有数据库和驱动程序上运行。当智能时区的数据库类型不可用或不受欢迎时,可以使用 TypeDecorator
创建一种将时区感知时间戳转换为时区不敏感时间戳的数据类型。下面,使用 Python 内置的 datetime.timezone.utc
时区来归一化和反归一化:
import datetime class TZDateTime(TypeDecorator): impl = DateTime cache_ok = True def process_bind_param(self, value, dialect): if value is not None: if not value.tzinfo or value.tzinfo.utcoffset(value) is None: raise TypeError("tzinfo is required") value = value.astimezone(datetime.timezone.utc).replace(tzinfo=None) return value def process_result_value(self, value, dialect): if value is not None: value = value.replace(tzinfo=datetime.timezone.utc) return value
与后端无关的 GUID 类型
注意
自 2.0 版本起,应优先使用内置的 Uuid
类型,其行为类似。此示例仅作为接收和返回 Python 对象的类型装饰器的示例。
接收并返回 Python uuid() 对象。在使用 PostgreSQL 时使用 PG UUID 类型,在使用 MSSQL 时使用 UNIQUEIDENTIFIER,在其他后端上使用 CHAR(32),以字符串格式存储它们。GUIDHyphens
版本使用带连字符的值而不仅仅是十六进制字符串,使用 CHAR(36) 类型存储:
from operator import attrgetter from sqlalchemy.types import TypeDecorator, CHAR from sqlalchemy.dialects.mssql import UNIQUEIDENTIFIER from sqlalchemy.dialects.postgresql import UUID import uuid class GUID(TypeDecorator): """Platform-independent GUID type. Uses PostgreSQL's UUID type or MSSQL's UNIQUEIDENTIFIER, otherwise uses CHAR(32), storing as stringified hex values. """ impl = CHAR cache_ok = True _default_type = CHAR(32) _uuid_as_str = attrgetter("hex") def load_dialect_impl(self, dialect): if dialect.name == "postgresql": return dialect.type_descriptor(UUID()) elif dialect.name == "mssql": return dialect.type_descriptor(UNIQUEIDENTIFIER()) else: return dialect.type_descriptor(self._default_type) def process_bind_param(self, value, dialect): if value is None or dialect.name in ("postgresql", "mssql"): return value else: if not isinstance(value, uuid.UUID): value = uuid.UUID(value) return self._uuid_as_str(value) def process_result_value(self, value, dialect): if value is None: return value else: if not isinstance(value, uuid.UUID): value = uuid.UUID(value) return value class GUIDHyphens(GUID): """Platform-independent GUID type. Uses PostgreSQL's UUID type or MSSQL's UNIQUEIDENTIFIER, otherwise uses CHAR(36), storing as stringified uuid values. """ _default_type = CHAR(36) _uuid_as_str = str
将 Python uuid.UUID
链接到 ORM 映射的自定义类型
在使用 注释式声明表 映射声明 ORM 映射时,可以通过将其添加到 类型注解映射 中,将上述自定义 GUID
类型与 Python uuid.UUID
数据类型相关联,该类型通常定义在 DeclarativeBase
类上:
import uuid from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column class Base(DeclarativeBase): type_annotation_map = { uuid.UUID: GUID, }
通过上述配置,继承自 Base
的 ORM 映射类可以在注解中引用 Python uuid.UUID
,这将自动使用 GUID
:
class MyModel(Base): __tablename__ = "my_table" id: Mapped[uuid.UUID] = mapped_column(primary_key=True)
另请参见
自定义类型映射
编组 JSON 字符串
此类型使用 simplejson
将 Python 数据结构编组为 JSON。可修改为使用 Python 内置的 json 编码器:
from sqlalchemy.types import TypeDecorator, VARCHAR import json class JSONEncodedDict(TypeDecorator): """Represents an immutable structure as a json-encoded string. Usage: JSONEncodedDict(255) """ impl = VARCHAR cache_ok = True def process_bind_param(self, value, dialect): if value is not None: value = json.dumps(value) return value def process_result_value(self, value, dialect): if value is not None: value = json.loads(value) return value
添加可变性
默认情况下,ORM 不会检测上述类型的“可变性”——这意味着,对值的原地更改不会被检测到,也不会被刷新。如果没有进一步的步骤,您将需要在每个父对象上使用新对象替换现有值以检测更改:
obj.json_value["key"] = "value" # will *not* be detected by the ORM obj.json_value = {"key": "value"} # *will* be detected by the ORM
上述限制可能是可以接受的,因为许多应用程序可能不需要在创建后对值进行任何变异。对于那些确实具有此要求的应用程序,最好使用sqlalchemy.ext.mutable
扩展来支持可变性。对于以字典为导向的 JSON 结构,我们可以这样应用:
json_type = MutableDict.as_mutable(JSONEncodedDict) class MyClass(Base): # ... json_data = Column(json_type)
另请参阅
变异跟踪
处理比较操作
TypeDecorator
的默认行为是将任何表达式的“右侧”强制转换为相同类型。对于像 JSON 这样的类型,这意味着任何使用的操作符必须在 JSON 方面有意义。对于某些情况,用户可能希望在某些情况下使类型像 JSON 一样行为,在其他情况下像纯文本一样行为。一个例子是如果想要处理 JSON 类型的 LIKE 操作符。LIKE 对 JSON 结构没有意义,但对底层文本表示有意义。要使用JSONEncodedDict
这样的类型来实现这一点,我们需要在尝试使用此操作符之前使用cast()
或type_coerce()
将列强制转换为文本形式:
from sqlalchemy import type_coerce, String stmt = select(my_table).where(type_coerce(my_table.c.json_data, String).like("%foo%"))
TypeDecorator
提供了一个内置系统,用于基于操作符构建这些类型转换。如果我们想要经常使用 LIKE 操作符,并将我们的 JSON 对象解释为字符串,我们可以通过覆盖TypeDecorator.coerce_compared_value()
方法将其构建到类型中:
from sqlalchemy.sql import operators from sqlalchemy import String class JSONEncodedDict(TypeDecorator): impl = VARCHAR cache_ok = True def coerce_compared_value(self, op, value): if op in (operators.like_op, operators.not_like_op): return String() else: return self def process_bind_param(self, value, dialect): if value is not None: value = json.dumps(value) return value def process_result_value(self, value, dialect): if value is not None: value = json.loads(value) return value
以上只是处理像“LIKE”这样的操作符的一种方法。其他应用程序可能希望对于 JSON 对象没有意义的操作符(如“LIKE”)引发NotImplementedError
,而不是自动强制转换为文本。
SqlAlchemy 2.0 中文文档(四十二)(2)https://developer.aliyun.com/article/1563013