自定义类型
存在多种方法来重新定义现有类型的行为以及提供新的类型。
重写类型编译
一个常见的需求是强制更改类型的“字符串”版本,即在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
,将生成字符串 BINARY
除了sqlite以外的所有后端,在这种情况下,它将生成 BLOB
.
见剖面图 更改类型的编译 ,的一个子部分 自定义SQL构造和编译扩展 ,用于其他示例。
扩充现有类型
这个 TypeDecorator
允许创建将绑定参数和结果处理行为添加到现有类型对象的自定义类型。在Python中附加时使用 marshalling 需要与数据库进行数据交换。
注解
绑定和结果处理 TypeDecorator
是 此外 到已由托管类型执行的处理,该类型由SQLAlchemy根据每个DBAPI进行自定义,以执行特定于该DBAPI的处理。虽然可以通过直接子类化来替换给定类型的此处理,但在实践中并不需要它,并且SQLAlchemy不再将其作为公共用例支持。
ORM提示
这个 TypeDecorator
可用于提供在某种类型的值传入和传出数据库时转换这些值的一致方法。在使用ORM时,有一种类似的技术可以将用户数据从任意格式转换为使用 validates()
装饰工。当进入ORM模型的数据需要以某种特定于业务案例的方式规范化,而不是像数据类型那样通用时,这种技术可能更合适。
Object Name | Description |
---|---|
允许创建向现有类型添加附加功能的类型。 |
- class sqlalchemy.types.TypeDecorator(*args, **kwargs)
几把钥匙
TypeDecorator
食谱如下。将编码字符串强制为Unicode
关于
Unicode
类型是它要处理的 only 用Pythonunicode
对象在python端,这意味着作为绑定参数传递给它的值的形式必须是u'some string'
如果使用的是python 2而不是3。它执行的编码/解码功能仅适用于使用中的DBAPI所需的功能,并且主要是私有实现细节。可以安全地接收python字节串的类型的用例,即包含非ascii字符的字符串,而不是
u''
在python 2中,可以使用TypeDecorator
根据需要强制: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连接器,如果传递的小数位数太多,则会阻塞。这里有一个菜谱可以把它们四舍五入:
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为“隐式”时区)。另外,数据库特定的类型,如PostgreSQLs“TIMESTAMP WITH TIMEZONE”通常是因为它们更丰富的功能;但是,以普通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: 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类型
接收并返回python uuid()对象。在其他后端使用postgresql、char(32)时使用pg uuid类型,以字符串化十六进制格式存储它们。如果需要,可以修改为在char(16)中存储二进制:
from sqlalchemy.types import TypeDecorator, CHAR from sqlalchemy.dialects.postgresql import UUID import uuid class GUID(TypeDecorator): """Platform-independent GUID type. Uses PostgreSQL's UUID type, otherwise uses CHAR(32), storing as stringified hex values. """ impl = CHAR cache_ok = True def load_dialect_impl(self, dialect): if dialect.name == 'postgresql': return dialect.type_descriptor(UUID()) else: return dialect.type_descriptor(CHAR(32)) def process_bind_param(self, value, dialect): if value is None: return value elif dialect.name == 'postgresql': return str(value) else: if not isinstance(value, uuid.UUID): return "%.32x" % uuid.UUID(value).int else: # hexstring return "%.32x" % value.int 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
封送JSON字符串
这种类型的用途
simplejson
将python数据结构封送到JSON或从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操作符。对于JSON结构,like没有意义,但是对于底层的文本表示它确实有意义。为了达到这个目的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
提供一个内置的系统,用于基于运算符处理类似的类型转换。如果我们希望经常在JSON对象被解释为字符串时使用LIKE运算符,我们可以通过重写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”的操作符的一种方法。其他申请可能希望提高
NotImplementedError
对于对JSON对象(如“like”)没有意义的运算符,而不是自动强制转换为文本。应用SQL级绑定/结果处理
如本节所示 扩充现有类型 ,sqlAlchemy允许在参数发送到语句时以及从数据库加载结果行时调用python函数,以便在值发送到数据库或从数据库加载时对其应用转换。也可以定义SQL级别的转换。这里的基本原理是,只有关系数据库包含强制应用程序和持久性格式之间的传入和传出数据所必需的一系列特定函数。示例包括使用数据库定义的加密/解密函数,以及处理地理数据的存储过程。PostGIS对PostgreSQL的扩展包括大量的SQL函数,这些函数是将数据强制转换为特定格式所必需的。
任何
TypeEngine
,UserDefinedType
或TypeDecorator
子类可以包括TypeEngine.bind_expression()
和/或TypeEngine.column_expression()
,当定义为返回非“none”值时,它应返回ColumnElement
要注入到SQL语句中的表达式,可以是环绕绑定参数,也可以是列表达式。例如,构建Geometry
将应用PostGIS功能的类型ST_GeomFromText
所有输出值和函数ST_AsText
对于所有传入的数据,我们可以创建自己的子类UserDefinedType
这些方法与func
::from sqlalchemy import func from sqlalchemy.types import UserDefinedType class Geometry(UserDefinedType): def get_col_spec(self): return "GEOMETRY" def bind_expression(self, bindvalue): return func.ST_GeomFromText(bindvalue, type_=self) def column_expression(self, col): return func.ST_AsText(col, type_=self)
我们可以申请
Geometry
键入Table
元数据并将其用于select()
结构:geometry = Table('geometry', metadata, Column('geom_id', Integer, primary_key=True), Column('geom_data', Geometry) ) print(select(geometry).where( geometry.c.geom_data == 'LINESTRING(189412 252431,189631 259122)'))
生成的SQL根据需要嵌入这两个函数。
ST_AsText
应用于columns子句,以便在传递到结果集之前通过函数运行返回值,以及ST_GeomFromText
对绑定参数运行,以便转换传入的值::SELECT geometry.geom_id, ST_AsText(geometry.geom_data) AS geom_data_1 FROM geometry WHERE geometry.geom_data = ST_GeomFromText(:geom_data_2)
这个
TypeEngine.column_expression()
方法与编译器的机制交互,以便SQL表达式不会干扰包装表达式的标记。例如,如果我们select()
反对label()
在表达式中,字符串标签被移动到包装表达式的外部:print(select(geometry.c.geom_data.label('my_data')))
输出:
SELECT ST_AsText(geometry.geom_data) AS my_data FROM geometry
另一个例子是我们装饰
BYTEA
提供一个PGPString
,这将利用PostgreSQLpgcrypto
透明加密/解密值的扩展名:from sqlalchemy import create_engine, String, select, func, \ MetaData, Table, Column, type_coerce, TypeDecorator from sqlalchemy.dialects.postgresql import BYTEA class PGPString(TypeDecorator): impl = BYTEA cache_ok = True def __init__(self, passphrase): super(PGPString, self).__init__() self.passphrase = passphrase def bind_expression(self, bindvalue): # convert the bind's type from PGPString to # String, so that it's passed to psycopg2 as is without # a dbapi.Binary wrapper bindvalue = type_coerce(bindvalue, String) return func.pgp_sym_encrypt(bindvalue, self.passphrase) def column_expression(self, col): return func.pgp_sym_decrypt(col, self.passphrase) metadata_obj = MetaData() message = Table('message', metadata_obj, Column('username', String(50)), Column('message', PGPString("this is my passphrase")), ) engine = create_engine("postgresql://scott:tiger@localhost/test", echo=True) with engine.begin() as conn: metadata_obj.create_all(conn) conn.execute(message.insert(), username="some user", message="this is my message") print(conn.scalar( select(message.c.message).\ where(message.c.username == "some user") ))
这个
pgp_sym_encrypt
和pgp_sym_decrypt
函数应用于insert和select语句:INSERT INTO message (username, message) VALUES (%(username)s, pgp_sym_encrypt(%(message)s, %(pgp_sym_encrypt_1)s)) {'username': 'some user', 'message': 'this is my message', 'pgp_sym_encrypt_1': 'this is my passphrase'} SELECT pgp_sym_decrypt(message.message, %(pgp_sym_decrypt_1)s) AS message_1 FROM message WHERE message.username = %(username_1)s {'pgp_sym_decrypt_1': 'this is my passphrase', 'username_1': 'some user'}
参见
重新定义和创建新的运算符
SQLAlchemy Core defines a fixed set of expression operators available to all column expressions. Some of these operations have the effect of overloading Python's built-in operators; examples of such operators include
ColumnOperators.__eq__()
(table.c.somecolumn == 'foo'
),ColumnOperators.__invert__()
(~table.c.flag
), andColumnOperators.__add__()
(table.c.x + table.c.y
). Other operators are exposed as explicit methods on column expressions, such asColumnOperators.in_()
(table.c.value.in_(['x', 'y'])
) andColumnOperators.like()
(table.c.value.like('%ed%')
).核心表达式构造在所有情况下都会参考表达式的类型,以便确定现有运算符的行为,以及定位不属于内置集的其他运算符。这个
TypeEngine
基类定义根“比较”实现Comparator
和许多特定类型提供了自己的此类子实现。用户定义的Comparator
实现可以直接构建到特定类型的简单子类中,以便重写或定义新的操作。下面,我们创建一个Integer
重写的子类ColumnOperators.__add__()
操作员:from sqlalchemy import Integer class MyInt(Integer): class comparator_factory(Integer.Comparator): def __add__(self, other): return self.op("goofy")(other)
上面的配置创建了一个新类
MyInt
,从而确定TypeEngine.comparator_factory
属性引用一个新类,将Comparator
与关联的类Integer
类型。用法:
>>> sometable = Table("sometable", metadata, Column("data", MyInt)) >>> print(sometable.c.data + 5) sometable.data goofy :data_1
实施
ColumnOperators.__add__()
由所属的SQL表达式通过实例化Comparator
把自己当作expr
属性。表达式系统的机制是这样的:操作以递归方式继续,直到表达式对象生成新的SQL表达式构造。上面,我们也可以这么说self.expr.op("goofy")(other)
而不是self.op("goofy")(other)
.使用时
Operators.op()
对于返回布尔结果的比较操作,Operators.op.is_comparison
标志应设置为True
::class MyInt(Integer): class comparator_factory(Integer.Comparator): def is_frobnozzled(self, other): return self.op("--is_frobnozzled->", is_comparison=True)(other)
添加到
Comparator
在拥有的SQL表达式上使用__getattr__
方案,它公开添加到Comparator
拥有ColumnElement
. 例如,要添加log()
整型函数:from sqlalchemy import Integer, func class MyInt(Integer): class comparator_factory(Integer.Comparator): def log(self, other): return func.log(self.expr, other)
使用上述类型:
>>> print(sometable.c.data.log(5)) log(:log_1, :log_2)
一元运算也是可能的。例如,为了添加PostgreSQL阶乘运算符的实现,我们将
UnaryExpression
与custom_op
要生成阶乘表达式:from sqlalchemy import Integer from sqlalchemy.sql.expression import UnaryExpression from sqlalchemy.sql import operators class MyInteger(Integer): class comparator_factory(Integer.Comparator): def factorial(self): return UnaryExpression(self.expr, modifier=operators.custom_op("!"), type_=MyInteger)
使用上述类型:
>>> from sqlalchemy.sql import column >>> print(column('x', MyInteger).factorial()) x !
参见
创建新类型
这个
UserDefinedType
类作为一个简单的基类提供,用于定义全新的数据库类型。使用此项表示SQLAlchemy不知道的本机数据库类型。如果只需要python转换行为,请使用TypeDecorator
相反。Object Name Description 用户定义类型的基。
- class sqlalchemy.types.UserDefinedType
需要注意的是,修改后的数据库类型具有额外的Python行为,包括基于
TypeDecorator
以及其他用户定义的数据类型子类,在数据库模式中没有任何表示。当使用数据库时,在 反映数据库对象 ,SQLAlchemy使用了一个固定的映射,该映射将数据库服务器报告的数据类型信息链接到SQLAlchemy数据类型对象。例如,如果我们在PostgreSQL模式内部查看特定数据库列的定义,我们可能会收到返回的字符串"VARCHAR"
. SQLAlchemy的PostgreSQL方言有一个链接字符串名称的硬编码映射"VARCHAR"
对SQLAlchemyVARCHAR
类,当我们发出一个Table('my_table', m, autoload_with=engine)
, theColumn
对象中的VARCHAR
呈现在里面。这意味着如果
Table
如果我们创建一个新的Table
反对新的MetaData
在其他地方使用反射的此数据库表的集合,它将不具有此数据类型。例如::>>> from sqlalchemy import Table, Column, MetaData, create_engine, PickleType, Integer >>> metadata = MetaData() >>> my_table = Table("my_table", metadata, Column('id', Integer), Column("data", PickleType)) >>> engine = create_engine("sqlite://", echo='debug') >>> my_table.create(engine) INFO sqlalchemy.engine.base.Engine CREATE TABLE my_table ( id INTEGER, data BLOB )
上面,我们利用
PickleType
,这是一个TypeDecorator
它在LargeBinary
数据类型,它在SQLite上对应于数据库类型BLOB
. 在CREATE表中,我们看到BLOB
使用了数据类型。SQLite数据库对PickleType
我们用过。如果我们看看
my_table.c.data.type
,因为这是我们直接创建的Python对象,因此PickleType
::>>> my_table.c.data.type PickleType()
但是,如果我们创建另一个
Table
使用反射,使用PickleType
在我们创建的SQLite数据库中没有表示;而是返回BLOB
::>>> metadata_two = MetaData() >>> my_reflected_table = Table("my_table", metadata_two, autoload_with=engine) INFO sqlalchemy.engine.base.Engine PRAGMA main.table_info("my_table") INFO sqlalchemy.engine.base.Engine () DEBUG sqlalchemy.engine.base.Engine Col ('cid', 'name', 'type', 'notnull', 'dflt_value', 'pk') DEBUG sqlalchemy.engine.base.Engine Row (0, 'id', 'INTEGER', 0, None, 0) DEBUG sqlalchemy.engine.base.Engine Row (1, 'data', 'BLOB', 0, None, 0) >>> my_reflected_table.c.data.type BLOB()
通常,当应用程序定义显式
Table
对于自定义类型的元数据,不需要使用表反射,因为Table
元数据已存在。但是,对于应用程序或它们的组合需要同时使用显式和Table
元数据,包括自定义的、Python级别的数据类型,以及Table
设置其Column
对象从数据库中反映出来,但是仍然需要显示自定义数据类型的附加Python行为,必须采取额外的步骤来实现这一点。最简单的方法是重写特定列,如中所述 覆盖反射列 . 在这种技术中,我们简单地将反射与显式结合使用
Column
要对其使用自定义或修饰数据类型的列的对象:>>> metadata_three = MetaData() >>> my_reflected_table = Table("my_table", metadata_three, Column("data", PickleType), autoload_with=engine)
这个
my_reflected_table
对象将被反射,并将从SQLite数据库加载“id”列的定义。但是对于“data”列,我们用显式Column
包含所需的Python数据类型的定义PickleType
. 反射过程会留下这个Column
对象完好:>>> my_reflected_table.c.data.type PickleType()
从数据库本机类型对象转换为自定义数据类型的一种更精细的方法是使用
DDLEvents.column_reflect()
事件处理程序。如果我们知道我们想要一切BLOB
数据类型实际上是PickleType
,我们可以制定一个全面的规则:from sqlalchemy import BLOB from sqlalchemy import event from sqlalchemy import PickleType from sqlalchemy import Table @event.listens_for(Table, "column_reflect") def _setup_pickletype(inspector, table, column_info): if isinstance(column_info["type"], BLOB): column_info["type"] = PickleType()
当调用上述代码时 之前 发生任何表反射(注意应该调用它 只有一次 在应用程序中,因为这是一个全局规则)
Table
包含一个列BLOB
数据类型,则生成的数据类型将存储在Column
对象ASPickleType
.在实践中,上述基于事件的方法可能会有额外的规则,以便只影响那些数据类型重要的列,例如表名和列名的查找表,或者其他启发式方法,以便准确地确定哪些列应该使用In Python数据类型建立。