SQLAlchemy 2.0 Dokumentation
SQLAlchemy Core
- API für SQL-Anweisungen und Ausdrücke
- Schema Definition Language
- SQL Datentyp-Objekte
- Die Typenhierarchie
- Benutzerdefinierte Typen¶
- Überschreiben der Typenkompilierung
- Erweitern bestehender Typen
TypeDecoratorTypeDecorator.cache_okTypeDecorator.ComparatorTypeDecorator.__init__()TypeDecorator.bind_expression()TypeDecorator.bind_processor()TypeDecorator.coerce_compared_value()TypeDecorator.coerce_to_is_typesTypeDecorator.column_expression()TypeDecorator.comparator_factoryTypeDecorator.compare_values()TypeDecorator.copy()TypeDecorator.get_dbapi_type()TypeDecorator.literal_processor()TypeDecorator.load_dialect_impl()TypeDecorator.process_bind_param()TypeDecorator.process_literal_param()TypeDecorator.process_result_value()TypeDecorator.result_processor()TypeDecorator.sort_key_functionTypeDecorator.type_engine()
- TypeDecorator Rezepte
- SQL-Ebene Bindungs-/Ergebnisverarbeitung anwenden
- Neudefinition und Erstellung neuer Operatoren
- Neue Typen erstellen
- Arbeiten mit benutzerdefinierten Typen und Reflexion
- Basis-Typ-API
- Engine und Connection verwenden
- Grundlagen der Core API
Projektversionen
- Vorher: Die Typenhierarchie
- Nächste: Basis-Typen-API
- Nach oben: Startseite
- Auf dieser Seite
- Benutzerdefinierte Typen
- Überschreiben der Typenkompilierung
- Erweitern bestehender Typen
TypeDecoratorTypeDecorator.cache_okTypeDecorator.ComparatorTypeDecorator.__init__()TypeDecorator.bind_expression()TypeDecorator.bind_processor()TypeDecorator.coerce_compared_value()TypeDecorator.coerce_to_is_typesTypeDecorator.column_expression()TypeDecorator.comparator_factoryTypeDecorator.compare_values()TypeDecorator.copy()TypeDecorator.get_dbapi_type()TypeDecorator.literal_processor()TypeDecorator.load_dialect_impl()TypeDecorator.process_bind_param()TypeDecorator.process_literal_param()TypeDecorator.process_result_value()TypeDecorator.result_processor()TypeDecorator.sort_key_functionTypeDecorator.type_engine()
- TypeDecorator Rezepte
- SQL-Ebene Bindungs-/Ergebnisverarbeitung anwenden
- Neudefinition und Erstellung neuer Operatoren
- Neue Typen erstellen
- Arbeiten mit benutzerdefinierten Typen und Reflexion
Benutzerdefinierte Typen¶
Eine Vielzahl von Methoden existiert, um das Verhalten bestehender Typen neu zu definieren und neue bereitzustellen.
Überschreiben der Typenkompilierung¶
Ein häufiges Bedürfnis ist es, die "String"-Version eines Typs zu erzwingen, d. h. diejenige, die in einer CREATE TABLE-Anweisung oder anderen SQL-Funktionen wie CAST gerendert wird, zu ändern. Beispielsweise möchte eine Anwendung die Wiedergabe von BINARY für alle Plattformen außer einer erzwingen, auf der sie BLOB gerendert haben möchte. Die Verwendung eines vorhandenen generischen Typs, in diesem Fall LargeBinary, wird für die meisten Anwendungsfälle bevorzugt. Um Typen jedoch genauer zu steuern, kann eine kompilierungsdirektive, die pro Dialekt ist, jedem Typ zugeordnet werden.
from sqlalchemy.ext.compiler import compiles
from sqlalchemy.types import BINARY
@compiles(BINARY, "sqlite")
def compile_binary_sqlite(type_, compiler, **kw):
return "BLOB"Der obige Code erlaubt die Verwendung von BINARY, was den String BINARY für alle Backends außer SQLite ergibt, in welchem Fall er BLOB ergibt.
Siehe den Abschnitt Ändern der Kompilierung von Typen, eine Untersektion von Benutzerdefinierte SQL-Konstrukte und Kompilierungserweiterung, für weitere Beispiele.
Erweiterung bestehender Typen¶
Der TypeDecorator ermöglicht die Erstellung benutzerdefinierter Typen, die einem vorhandenen Typobjekt ein Bindungsparameter- und Ergebnisverarbeitungsverhalten hinzufügen. Er wird verwendet, wenn eine zusätzliche In-Python-Marshalling von Daten zur und/oder von der Datenbank erforderlich ist.
Hinweis
Die Bindungs- und Ergebnisverarbeitung von TypeDecorator erfolgt zusätzlich zur Verarbeitung, die bereits vom gehosteten Typ durchgeführt wird, der von SQLAlchemy pro DBAPI angepasst wird, um spezifische Verarbeitungen für diesen DBAPI durchzuführen. Obwohl es möglich ist, diese Handhabung für einen bestimmten Typ durch direkte Unterklassifizierung zu ersetzen, ist dies in der Praxis nie erforderlich und SQLAlchemy unterstützt dies nicht mehr als öffentliches Anwendungsfall.
| Objektname | Beschreibung |
|---|---|
Ermöglicht die Erstellung von Typen, die einem vorhandenen Typ zusätzliche Funktionalität hinzufügen. |
- class sqlalchemy.types.TypeDecorator¶
Ermöglicht die Erstellung von Typen, die einem vorhandenen Typ zusätzliche Funktionalität hinzufügen.
Diese Methode wird gegenüber der direkten Unterklassifizierung der integrierten SQLAlchemy-Typen bevorzugt, da sie sicherstellt, dass alle erforderlichen Funktionalitäten des zugrunde liegenden Typs erhalten bleiben.
Typische Verwendung
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)
Das Klassenattribut
implist erforderlich und kann auf jedeTypeEngine-Klasse verweisen. Alternativ kann die Methodeload_dialect_impl()verwendet werden, um unterschiedliche Typklassen basierend auf der gegebenen Dialekt bereitzustellen; in diesem Fall kann die Variableimplals Platzhalter aufTypeEngineverweisen.Das Klassenflag
TypeDecorator.cache_okgibt an, ob dieser benutzerdefinierteTypeDecoratorsicher als Teil eines Cache-Schlüssels verwendet werden kann. Dieses Flag hat standardmäßig den WertNone, was zunächst eine Warnung ausgibt, wenn der SQL-Compiler versucht, einen Cache-Schlüssel für eine Anweisung zu generieren, die diesen Typ verwendet. Wenn derTypeDecoratornicht garantiert, dass er bei jeder Ausführung das gleiche Bindungs-/Ergebnisverhalten und die gleiche SQL-Generierung erzeugt, sollte dieses Flag aufFalsegesetzt werden; andernfalls, wenn die Klasse jedes Mal das gleiche Verhalten erzeugt, kann sie aufTruegesetzt werden. SieheTypeDecorator.cache_okfür weitere Hinweise, wie dies funktioniert.Typen, die einen Python-Typ erhalten, der nicht mit dem endgültigen verwendeten Typ übereinstimmt, möchten möglicherweise die Methode
TypeDecorator.coerce_compared_value()definieren. Dies wird verwendet, um dem Ausdruckssystem einen Hinweis zu geben, wenn Python-Objekte innerhalb von Ausdrücken in Bindungsparameter umgewandelt werden. Betrachten Sie diesen Ausdruckmytable.c.somecol + datetime.date(2009, 5, 15)
Oben, wenn „somecol“ eine
Integer-Variante ist, ist es sinnvoll, dass wir Datumsarithmetik betreiben, wobei das Obige von Datenbanken normalerweise so interpretiert wird, dass eine Anzahl von Tagen zum gegebenen Datum addiert wird. Das Ausdruckssystem tut das Richtige, indem es nicht versucht, den Wert „date()“ in einen integer-orientierten Bindungsparameter umzuwandeln.Im Fall von
TypeDecoratorändern wir jedoch normalerweise einen eingehenden Python-Typ in etwas Neues –TypeDecoratorwandelt standardmäßig die nicht-typisierte Seite in den gleichen Typ wie sich selbst um. Wie unten gezeigt, definieren wir einen „epoch“-Typ, der einen Datumswert als Integer speichert.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)
Unser Ausdruck von
somecol + datemit dem obigen Typ wandelt das „date“ auf der rechten Seite so um, dass es ebenfalls alsMyEpochTypebehandelt wird.Dieses Verhalten kann über die Methode
TypeDecorator.coerce_compared_value()überschrieben werden, die einen Typ zurückgibt, der für den Wert des Ausdrucks verwendet werden soll. Unten legen wir es so fest, dass ein Integer-Wert alsIntegerbehandelt wird, und jeder andere Wert als Datum angenommen und alsMyEpochTypebehandelt wird.def coerce_compared_value(self, op, value): if isinstance(value, int): return Integer() else: return self
Warnung
Beachten Sie, dass das Verhalten von coerce_compared_value standardmäßig nicht vom Basis-Typ geerbt wird. Wenn der
TypeDecoratoreinen Typ erweitert, der spezielle Logik für bestimmte Operatortypen erfordert, muss diese Methode überschrieben werden. Ein wichtiges Beispiel ist die Dekoration der TypenJSONundJSONB; die Standardregeln vonTypeEngine.coerce_compared_value()sollten verwendet werden, um Operatoren wie Indexoperationen zu behandeln.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)
Ohne den obigen Schritt werden Indexoperationen wie
mycol['foo']dazu führen, dass der Indexwert'foo'als JSON codiert wird.Ähnlich wird bei der Arbeit mit dem Datentyp
ARRAYdie Typumwandlung für Indexoperationen (z. B.mycol[5]) ebenfalls vonTypeDecorator.coerce_compared_value()gehandhabt, wobei auch hier eine einfache Überschreibung ausreicht, es sei denn, für bestimmte Operatoren sind spezielle Regeln erforderlich.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)
Mitglieder
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()
Klassensignatur
class
sqlalchemy.types.TypeDecorator(sqlalchemy.sql.expression.SchemaEventTarget,sqlalchemy.types.ExternalType,sqlalchemy.types.TypeEngine)-
attribute
sqlalchemy.types.TypeDecorator.cache_ok: bool | None = None¶ vererbt von dem
ExternalType.cache_okAttribut vonExternalTypeGibt an, ob Anweisungen, die diesen
ExternalTypeverwenden, „sicher zum Cachen“ sind.Der Standardwert
Nonegibt eine Warnung aus und erlaubt dann nicht das Caching einer Anweisung, die diesen Typ enthält. Setzen Sie ihn aufFalse, um das Caching von Anweisungen, die diesen Typ verwenden, ohne Warnung zu deaktivieren. Wenn er aufTruegesetzt ist, werden die Klasse des Objekts und ausgewählte Elemente seines Zustands als Teil des Cache-Schlüssels verwendet. Zum Beispiel die Verwendung einesTypeDecoratorclass MyType(TypeDecorator): impl = String cache_ok = True def __init__(self, choices): self.choices = tuple(choices) self.internal_only = True
Der Cache-Schlüssel für den obigen Typ wäre äquivalent zu
>>> MyType(["a", "b", "c"])._static_cache_key (<class '__main__.MyType'>, ('choices', ('a', 'b', 'c')))
Das Caching-Schema extrahiert Attribute aus dem Typ, die den Namen der Parameter in der Methode
__init__()entsprechen. Oben wird das Attribut "choices" Teil des Cache-Schlüssels, "internal_only" jedoch nicht, da es keinen Parameter namens "internal_only" gibt.Die Anforderungen an cachebare Elemente sind, dass sie hashbar sind und auch, dass sie für gegebene Cache-Werte bei jeder Ausführung dieselbe SQL-Darstellung für Ausdrücke anzeigen, die diesen Typ verwenden.
Um Datentypen zu unterstützen, die sich auf nicht hashbare Strukturen wie Wörterbücher, Mengen und Listen beziehen, können diese Objekte "cachebar" gemacht werden, indem hashbare Strukturen den Attributen zugewiesen werden, deren Namen mit den Namen der Argumente übereinstimmen. Zum Beispiel kann ein Datentyp, der ein Wörterbuch mit Nachschlage-Werten akzeptiert, dieses als sortierte Reihe von Tupeln veröffentlichen. Angenommen, ein zuvor nicht cachebarer Typ als
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" ...
wobei "lookup" ein Wörterbuch ist. Der Typ kann keinen Cache-Schlüssel generieren
>>> 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')
Wenn wir einen solchen Cache-Schlüssel **einrichten** würden, wäre er nicht verwendbar. Wir würden eine Tupelstruktur erhalten, die ein Wörterbuch enthält, das selbst nicht als Schlüssel in einem "Cache-Wörterbuch" wie dem Statement-Cache von SQLAlchemy verwendet werden kann, da Python-Wörterbücher nicht hashbar sind.
>>> # 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'
Der Typ kann cachebar gemacht werden, indem ein sortiertes Tupel von Tupeln dem Attribut ".lookup" zugewiesen wird
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" ...
wobei der Cache-Schlüssel für
LookupType({"a": 10, "b": 20})ist>>> LookupType({"a": 10, "b": 20})._static_cache_key (<class '__main__.LookupType'>, ('lookup', (('a', 10), ('b', 20))))
Neu in Version 1.4.14: - das Flag
cache_okhinzugefügt, um einige Konfigurierbarkeiten des Cachings fürTypeDecorator-Klassen zu ermöglichen.Neu in Version 1.4.28: - die Mixin
ExternalTypehinzugefügt, die das Flagcache_oksowohl für die KlassenTypeDecoratorals auchUserDefinedTypeverallgemeinert.Siehe auch
- class Comparator¶
Ein
Comparator, der spezifisch fürTypeDecoratorist.Benutzerdefinierte
TypeDecorator-Klassen müssen dies normalerweise nicht ändern.Klassensignatur
class
sqlalchemy.types.TypeDecorator.Comparator(sqlalchemy.types.Comparator)-
method
sqlalchemy.types.TypeDecorator.Comparator.operate(op: OperatorType, *other: Any, **kwargs: Any) → ColumnElement[_CT]¶ Operiert auf einem Argument.
Dies ist die niedrigste Ebene der Operation, löst standardmäßig
NotImplementedErroraus.Das Überschreiben dieser Methode in einer Unterklasse kann es ermöglichen, allgemeines Verhalten auf alle Operationen anzuwenden. Zum Beispiel, das Überschreiben von
ColumnOperators, umfunc.lower()auf die linke und rechte Seite anzuwendenclass MyComparator(ColumnOperators): def operate(self, op, other, **kwargs): return op(func.lower(self), func.lower(other), **kwargs)
-
method
sqlalchemy.types.TypeDecorator.Comparator.reverse_operate(op: OperatorType, other: Any, **kwargs: Any) → ColumnElement[_CT]¶ Umgekehrte Operation auf ein Argument.
Die Verwendung ist die gleiche wie bei
operate().
-
method
-
method
sqlalchemy.types.TypeDecorator.__init__(*args: Any, **kwargs: Any)¶ Konstruiert einen
TypeDecorator.Die hier übergebenen Argumente werden an den Konstruktor der Klasse übergeben, die dem Klassenattribut
implzugewiesen ist, vorausgesetzt,implist aufrufbar, und das resultierende Objekt wird dem Instanzattributself.implzugewiesen (wodurch das Klassenattribut desselben Namens überschrieben wird).Wenn
implauf Klassenebene kein aufrufbares Objekt ist (ein seltener Fall), wird es „wie es ist“ demselben Instanzattribut zugewiesen, wobei die an den Konstruktor übergebenen Argumente ignoriert werden.Unterklassen können dies überschreiben, um die Generierung von
self.implvollständig anzupassen.
-
method
sqlalchemy.types.TypeDecorator.bind_expression(bindparam: BindParameter[_T]) → ColumnElement[_T] | None¶ Gibt für einen Bindungswert (d. h. eine Instanz von
BindParameter) einen SQL-Ausdruck zurück, der den gegebenen Parameter normalerweise umschließt.Hinweis
Diese Methode wird während der SQL-Kompilierungsphase einer Anweisung aufgerufen, wenn ein SQL-String gerendert wird. Sie wird nicht notwendigerweise gegen bestimmte Werte aufgerufen und sollte nicht mit der Methode
TypeDecorator.process_bind_param()verwechselt werden, die die typischere Methode ist, die den tatsächlichen Wert verarbeitet, der einem bestimmten Parameter zur Ausführungszeit der Anweisung übergeben wird.Unterklassen von
TypeDecoratorkönnen diese Methode überschreiben, um ein benutzerdefiniertes Bindungsausdrucksverhalten für den Typ bereitzustellen. Diese Implementierung wird diejenige der zugrunde liegenden Implementierung ersetzt.
-
method
sqlalchemy.types.TypeDecorator.bind_processor(dialect: Dialect) → _BindProcessorType[_T] | None¶ Stellt eine Funktion zur Verarbeitung gebundener Werte für die gegebene
Dialektbereit.Dies ist die Methode, die den
TypeEngine-Vertrag für die Konvertierung gebundener Werte erfüllt, die normalerweise über die MethodeTypeEngine.bind_processor()erfolgt.Hinweis
Benutzerdefinierte Unterklassen von
TypeDecoratorsollten diese Methode nicht implementieren und stattdessenTypeDecorator.process_bind_param()implementieren, damit die „innere“ Verarbeitung, die vom implementierenden Typ bereitgestellt wird, erhalten bleibt.- Parameter:
dialect¶ – Instanz der verwendeten Dialekt.
-
method
sqlalchemy.types.TypeDecorator.coerce_compared_value(op: OperatorType | None, value: Any) → Any¶ Schlagen Sie einen Typ für einen „koerzierten“ Python-Wert in einem Ausdruck vor.
Gibt standardmäßig self zurück. Diese Methode wird vom Ausdruckssystem aufgerufen, wenn ein Objekt, das diesen Typ verwendet, auf der linken oder rechten Seite eines Ausdrucks mit einem einfachen Python-Objekt steht, dem noch kein SQLAlchemy-Typ zugewiesen ist.
expr = table.c.somecolumn + 35
Wo oben, wenn
somecolumndiesen Typ verwendet, wird diese Methode mit dem Wertoperator.addund35aufgerufen. Der Rückgabewert ist der SQLAlchemy-Typ, der für35für diese bestimmte Operation verwendet werden soll.
-
attribute
sqlalchemy.types.TypeDecorator.coerce_to_is_types: Sequence[Type[Any]] = (<class 'NoneType'>,)¶ Geben Sie die Python-Typen an, die auf Expressionsebene zu „IS <Konstante>“ umgewandelt werden sollen, wenn sie mit
==verglichen werden (und dasselbe fürIS NOTin Verbindung mit!=).Für die meisten SQLAlchemy-Typen umfasst dies
NoneTypesowiebool.TypeDecoratormodifiziert diese Liste, um nurNoneTypeeinzuschließen, da Implementierungen von Typedecorator, die mit booleschen Typen arbeiten, üblich sind.Benutzerdefinierte
TypeDecorator-Klassen können dieses Attribut überschreiben, um ein leeres Tupel zurückzugeben, in welchem Fall keine Werte in Konstanten umgewandelt werden.
-
method
sqlalchemy.types.TypeDecorator.column_expression(column: ColumnElement[_T]) → ColumnElement[_T] | None¶ Gibt für einen SELECT-Spaltenausdruck einen umhüllenden SQL-Ausdruck zurück.
Hinweis
Diese Methode wird während der SQL-Kompilierungsphase einer Anweisung aufgerufen, wenn ein SQL-String gerendert wird. Sie wird nicht gegen bestimmte Werte aufgerufen und sollte nicht mit der Methode
TypeDecorator.process_result_value()verwechselt werden, die die typischere Methode ist, die den tatsächlichen Wert verarbeitet, der nach der Ausführungszeit der Anweisung in einer Ergebniszeile zurückgegeben wird.Unterklassen von
TypeDecoratorkönnen diese Methode überschreiben, um ein benutzerdefiniertes Spaltenausdrucksverhalten für den Typ bereitzustellen. Diese Implementierung wird diejenige des zugrunde liegenden Implementierungstyps ersetzen.Siehe die Beschreibung von
TypeEngine.column_expression()für eine vollständige Beschreibung der Verwendung der Methode.
-
attribut
sqlalchemy.types.TypeDecorator.comparator_factory: _ComparatorFactory[Any]¶ Eine
Comparator-Klasse, die auf Operationen angewendet wird, die von besitzendenColumnElement-Objekten ausgeführt werden.Das Attribut
comparator_factoryist ein Hook, der vom Core-Ausdruckssystem konsultiert wird, wenn Spalten- und SQL-Ausdrucksoperationen ausgeführt werden. Wenn eineComparator-Klasse mit diesem Attribut verknüpft ist, ermöglicht dies die benutzerdefinierte Neudefinition aller vorhandenen Operatoren sowie die Definition neuer Operatoren. Vorhandene Operatoren umfassen diejenigen, die durch Python-Operatorüberladung bereitgestellt werden, wie z. B.ColumnOperators.__add__()undColumnOperators.__eq__(), sowie diejenigen, die als Standardattribute vonColumnOperatorsbereitgestellt werden, wie z. B.ColumnOperators.like()undColumnOperators.in_().Eine rudimentäre Verwendung dieses Hooks ist durch einfaches Ableiten von vorhandenen Typen oder alternativ durch die Verwendung von
TypeDecoratormöglich. Beispiele finden Sie im Dokumentationsabschnitt Neudefinition und Erstellung neuer Operatoren.
-
methode
sqlalchemy.types.TypeDecorator.compare_values(x: Any, y: Any) → bool¶ Vergleiche zwei Werte auf Gleichheit.
Standardmäßig ruft dies die Methode
TypeEngine.compare_values()des zugrundeliegenden „impl“ auf, welche wiederum normalerweise den Python-Gleichheitsoperator==verwendet.Diese Funktion wird vom ORM verwendet, um einen ursprünglich geladenen Wert mit einem abgefangenen „geänderten“ Wert zu vergleichen, um festzustellen, ob eine Nettoänderung aufgetreten ist.
-
methode
sqlalchemy.types.TypeDecorator.copy(**kw: Any) → Self¶ Erzeugt eine Kopie dieser
TypeDecorator-Instanz.Dies ist eine flache Kopie und dient zur Erfüllung eines Teils des
TypeEngine-Vertrags. Sie muss normalerweise nicht überschrieben werden, es sei denn, der benutzerdefinierteTypeDecoratorhat lokalen Zustand, der tief kopiert werden soll.
-
methode
sqlalchemy.types.TypeDecorator.get_dbapi_type(dbapi: module) → Any | None¶ Gibt das von diesem
TypeDecoratorrepräsentierte DBAPI-Typobjekt zurück.Standardmäßig ruft dies die Methode
TypeEngine.get_dbapi_type()des zugrundeliegenden „impl“ auf.
-
methode
sqlalchemy.types.TypeDecorator.literal_processor(dialect: Dialect) → _LiteralProcessorType[_T] | None¶ Stellt eine Funktion zur Literalverarbeitung für die gegebene
Dialectbereit.Dies ist die Methode, die den
TypeEngine-Vertrag für die Literalwertkonvertierung erfüllt, die normalerweise über die MethodeTypeEngine.literal_processor()erfolgt.Hinweis
Benutzerdefinierte Unterklassen von
TypeDecoratorsollten diese Methode nicht implementieren, sondern stattdessenTypeDecorator.process_literal_param()implementieren, damit die „interne“ Verarbeitung, die vom implementierenden Typ bereitgestellt wird, erhalten bleibt.
-
methode
sqlalchemy.types.TypeDecorator.load_dialect_impl(dialect: Dialect) → TypeEngine[Any]¶ Gibt ein
TypeEngine-Objekt zurück, das einer Dialekt entspricht.Dies ist ein Hook zur Überschreibung durch Endbenutzer, der verwendet werden kann, um unterschiedliche Typen abhängig von der gegebenen Dialekt bereitzustellen. Er wird von der
TypeDecorator-Implementierung vontype_engine()verwendet, um zu helfen zu bestimmen, welcher Typ letztendlich für einen gegebenenTypeDecoratorzurückgegeben werden soll.Gibt standardmäßig
self.implzurück.
-
methode
sqlalchemy.types.TypeDecorator.process_bind_param(value: _T | None, dialect: Dialect) → Any¶ Empfängt einen zu konvertierenden gebundenen Parameterwert.
Benutzerdefinierte Unterklassen von
TypeDecoratorsollten diese Methode überschreiben, um benutzerdefinierte Verhaltensweisen für eingehende Datenwerte bereitzustellen. Diese Methode wird zur **Ausführungszeit der Anweisung** aufgerufen und erhält den wörtlichen Python-Datenwert, der einem gebundenen Parameter in der Anweisung zugeordnet werden soll.Die Operation könnte alles sein, was zur Durchführung benutzerdefinierter Verhaltensweisen gewünscht wird, wie z. B. das Transformieren oder Serialisieren von Daten. Dies könnte auch als Hook für Validierungslogik verwendet werden.
-
methode
sqlalchemy.types.TypeDecorator.process_literal_param(value: _T | None, dialect: Dialect) → str¶ Empfängt einen wörtlichen Parameterwert, der inline in einer Anweisung gerendert werden soll.
Hinweis
Diese Methode wird während der **SQL-Kompilierungsphase** einer Anweisung aufgerufen, wenn eine SQL-Zeichenkette gerendert wird. Im Gegensatz zu anderen SQL-Kompilierungsmethoden wird ihr ein bestimmter Python-Wert übergeben, der als Zeichenkette gerendert werden soll. Sie sollte jedoch nicht mit der Methode
TypeDecorator.process_bind_param()verwechselt werden, die die typischere Methode ist, die den tatsächlichen Wert verarbeitet, der zu einem bestimmten Zeitpunkt der Ausführung einer Anweisung an einen Parameter übergeben wird.Benutzerdefinierte Unterklassen von
TypeDecoratorsollten diese Methode überschreiben, um benutzerdefinierte Verhaltensweisen für eingehende Datenwerte bereitzustellen, die sich im Sonderfall des Renderns als Literale befinden.Die zurückgegebene Zeichenkette wird in die Ausgabestring gerendert.
-
methode
sqlalchemy.types.TypeDecorator.process_result_value(value: Any | None, dialect: Dialect) → _T | None¶ Empfängt einen Ergebniszeilen-Spaltenwert, der konvertiert werden soll.
Benutzerdefinierte Unterklassen von
TypeDecoratorsollten diese Methode überschreiben, um benutzerdefinierte Verhaltensweisen für Datenwerte bereitzustellen, die in Ergebniszeilen von der Datenbank empfangen werden. Diese Methode wird zur **Erfassungszeit der Ergebnisse** aufgerufen und erhält den wörtlichen Python-Datenwert, der aus einer Datenbankergebniszeile extrahiert wird.Die Operation könnte alles sein, was zur Durchführung benutzerdefinierter Verhaltensweisen gewünscht wird, wie z. B. das Transformieren oder Deserialisieren von Daten.
-
methode
sqlalchemy.types.TypeDecorator.result_processor(dialect: Dialect, coltype: Any) → _ResultProcessorType[_T] | None¶ Stellt eine Funktion zur Verarbeitung von Ergebniswerten für die gegebene
Dialectbereit.Dies ist die Methode, die den
TypeEngine-Vertrag für die gebundene Wertkonvertierung erfüllt, die normalerweise über die MethodeTypeEngine.result_processor()erfolgt.Hinweis
Benutzerdefinierte Unterklassen von
TypeDecoratorsollten diese Methode nicht implementieren, sondern stattdessenTypeDecorator.process_result_value()implementieren, damit die „interne“ Verarbeitung, die vom implementierenden Typ bereitgestellt wird, erhalten bleibt.
-
attribut
sqlalchemy.types.TypeDecorator.sort_key_function: Callable[[Any], Any] | None¶ Eine Sortierfunktion, die als Schlüssel für `sorted` übergeben werden kann.
Der Standardwert
Nonebedeutet, dass die von diesem Typ gespeicherten Werte selbstsortierend sind.Neu seit Version 1.3.8.
-
methode
sqlalchemy.types.TypeDecorator.type_engine(dialect: Dialect) → TypeEngine[Any]¶ Gibt eine dialektspezifische
TypeEngine-Instanz für diesenTypeDecoratorzurück.In den meisten Fällen gibt dies eine dialektangepasste Form des
TypeEngine-Typs zurück, der durchself.implrepräsentiert wird. Nutzt `dialect_impl()`. Das Verhalten kann hier durch Überschreiben vonload_dialect_impl()angepasst werden.
-
attribute
TypeDecorator-Rezepte¶
Einige wichtige TypeDecorator-Rezepte folgen.
Umwandlung von kodierten Zeichenketten in Unicode¶
Eine häufige Quelle der Verwirrung bezüglich des Unicode-Typs ist, dass er nur mit Python unicode-Objekten auf der Python-Seite umgeht, d. h., Werte, die ihm als gebundene Parameter übergeben werden, müssen im Format u'ein string' vorliegen, wenn Python 2 und nicht 3 verwendet wird. Die von ihm durchgeführten Kodierungs-/Dekodierungsfunktionen dienen nur dazu, dem verwendeten DBAPI entgegenzukommen, und sind hauptsächlich ein privates Implementierungsdetail.
Der Anwendungsfall eines Typs, der Python-Bytestrings sicher empfangen kann, d. h. Zeichenketten, die Nicht-ASCII-Zeichen enthalten und in Python 2 keine u''-Objekte sind, kann mit einem TypeDecorator erreicht werden, der bei Bedarf konvertiert.
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 valueRunden von Zahlen¶
Einige Datenbankkonnektoren, wie z. B. die von SQL Server, stolpern, wenn ein Decimal mit zu vielen Dezimalstellen übergeben wird. Hier ist ein Rezept, das sie abrundet.
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 valueZeitzonenbewusste Zeitstempel als Zeitzonenunbewusste UTC speichern¶
Zeitstempel in Datenbanken sollten immer zeitzonenunabhängig gespeichert werden. Für die meisten Datenbanken bedeutet dies, sicherzustellen, dass ein Zeitstempel zuerst in der UTC-Zeitzone liegt, bevor er gespeichert wird, und ihn dann als zeitzonenunbewusst zu speichern (d. h. ohne assoziierte Zeitzone; UTC wird als „implizite“ Zeitzone angenommen). Alternativ werden datenbankspezifische Typen wie das „TIMESTAMP WITH TIMEZONE“ von PostgreSQL oft wegen ihrer reichhaltigeren Funktionalität bevorzugt; die Speicherung als reines UTC funktioniert jedoch auf allen Datenbanken und Treibern. Wenn ein zeitzonenintelligenter Datenbanktyp keine Option ist oder nicht bevorzugt wird, kann der TypeDecorator verwendet werden, um einen Datentyp zu erstellen, der zeitzonenbewusste Zeitstempel in zeitzonenunbewusste umwandelt und umgekehrt. Nachfolgend wird die integrierte datetime.timezone.utc-Zeitzone von Python verwendet, um zu normalisieren und zu denormalisieren.
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 valueBackend-unabhängiger GUID-Typ¶
Hinweis
Seit Version 2.0 sollte der integrierte Uuid-Typ, der sich ähnlich verhält, bevorzugt werden. Dieses Beispiel wird nur als Beispiel für einen Typ-Decorator vorgestellt, der Python-Objekte empfängt und zurückgibt.
Empfängt und gibt Python `uuid()`-Objekte zurück. Verwendet den PG UUID-Typ bei Verwendung von PostgreSQL, UNIQUEIDENTIFIER bei Verwendung von MSSQL, CHAR(32) auf anderen Backends und speichert sie im stringifizierten Format. Die Version GUIDHyphens speichert den Wert mit Bindestrichen anstelle nur des Hex-Strings und verwendet den Typ 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 = strVerknüpfung von Python uuid.UUID mit dem benutzerdefinierten Typ für ORM-Mappings¶
Bei der Deklaration von ORM-Mappings mit Annotated Declarative Table-Mappings kann der benutzerdefinierte GUID-Typ, der oben definiert wurde, mit dem Python uuid.UUID-Datentyp verknüpft werden, indem er der Typ-Annotation-Map hinzugefügt wird, die typischerweise auf der DeclarativeBase-Klasse definiert ist.
import uuid
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
class Base(DeclarativeBase):
type_annotation_map = {
uuid.UUID: GUID,
}Mit der obigen Konfiguration können ORM-gemappte Klassen, die von Base erben, Python uuid.UUID in Annotationen referenzieren, was automatisch GUID verwendet.
class MyModel(Base):
__tablename__ = "my_table"
id: Mapped[uuid.UUID] = mapped_column(primary_key=True)Siehe auch
JSON-Zeichenketten bearbeiten¶
Dieser Typ verwendet simplejson, um Python-Datenstrukturen zu/von JSON zu bearbeiten. Kann geändert werden, um Pythons integrierten JSON-Encoder zu verwenden.
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 valueMutability hinzufügen¶
Das ORM erkennt standardmäßig keine „Mutationsfähigkeit“ eines solchen Typs wie oben – das bedeutet, dass In-Place-Änderungen an Werten nicht erkannt und nicht geflusht werden. Ohne weitere Schritte müssten Sie stattdessen den vorhandenen Wert bei jedem übergeordneten Objekt durch einen neuen ersetzen, um Änderungen zu erkennen.
obj.json_value["key"] = "value" # will *not* be detected by the ORM
obj.json_value = {"key": "value"} # *will* be detected by the ORMDie obige Einschränkung ist möglicherweise in Ordnung, da viele Anwendungen möglicherweise nicht erfordern, dass die Werte jemals mutiert werden, sobald sie erstellt wurden. Für diejenigen, die diese Anforderung haben, wird die Unterstützung für Mutability am besten mit der Erweiterung sqlalchemy.ext.mutable angewendet. Für eine wörterbuchorientierte JSON-Struktur können wir dies wie folgt anwenden:
json_type = MutableDict.as_mutable(JSONEncodedDict)
class MyClass(Base):
# ...
json_data = Column(json_type)Siehe auch
Umgang mit Vergleichsoperationen¶
Das Standardverhalten von TypeDecorator ist, die „rechte Seite“ eines beliebigen Ausdrucks in denselben Typ umzuwandeln. Für einen Typ wie JSON bedeutet dies, dass jeder verwendete Operator im Hinblick auf JSON Sinn ergeben muss. In manchen Fällen wünschen Benutzer, dass sich der Typ unter bestimmten Umständen wie JSON und unter anderen wie reiner Text verhält. Ein Beispiel dafür ist, wenn man den LIKE-Operator für den JSON-Typ behandeln möchte. LIKE ergibt keinen Sinn für eine JSON-Struktur, wohl aber für die zugrunde liegende Textdarstellung. Um dies mit einem Typ wie JSONEncodedDict zu erreichen, müssen wir die Spalte mit cast() oder type_coerce() in eine Textform **umwandeln**, bevor versucht wird, diesen Operator zu verwenden.
from sqlalchemy import type_coerce, String
stmt = select(my_table).where(type_coerce(my_table.c.json_data, String).like("%foo%"))TypeDecorator stellt ein integriertes System für die Arbeit mit Typübersetzungen wie diesen basierend auf Operatoren bereit. Wenn wir den LIKE-Operator häufig mit unserem JSON-Objekt, interpretiert als Zeichenkette, verwenden möchten, können wir ihn in den Typ einbauen, indem wir die Methode TypeDecorator.coerce_compared_value() überschreiben.
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 valueOben ist nur ein Ansatz zur Behandlung eines Operators wie „LIKE“ dargestellt. Andere Anwendungen möchten möglicherweise NotImplementedError für Operatoren auslösen, die bei einem JSON-Objekt keine Bedeutung haben, wie z. B. „LIKE“, anstatt automatisch in Text umzuwandeln.
Anwenden von SQL-Level-Bind-/Ergebnisverarbeitung¶
Wie im Abschnitt Erweitern bestehender Typen gezeigt, erlaubt SQLAlchemy das Aufrufen von Python-Funktionen sowohl beim Senden von Parametern an eine Anweisung als auch beim Laden von Ergebniszeilen aus der Datenbank, um Transformationen auf die Werte anzuwenden, während sie an die Datenbank gesendet oder von ihr empfangen werden. Es ist auch möglich, SQL-Level-Transformationen zu definieren. Der Grund dafür ist, wenn nur die relationale Datenbank eine bestimmte Reihe von Funktionen enthält, die erforderlich sind, um eingehende und ausgehende Daten zwischen einer Anwendung und einem Persistenzformat zu konvertieren. Beispiele hierfür sind die Verwendung datenbankdefinierter Verschlüsselungs-/Entschlüsselungsfunktionen sowie von gespeicherten Prozeduren, die geografische Daten verarbeiten.
Jeder TypeEngine, UserDefinedType oder TypeDecorator-Unterklasse kann Implementierungen von TypeEngine.bind_expression() und/oder TypeEngine.column_expression() enthalten, die, wenn sie so definiert sind, dass sie einen Wert ungleich None zurückgeben, einen ColumnElement-Ausdruck zurückgeben sollten, der in die SQL-Anweisung eingefügt wird, entweder um gebundene Parameter oder einen Spaltenausdruck herum. Um beispielsweise einen Geometry-Typ zu erstellen, der die PostGIS-Funktion ST_GeomFromText auf alle ausgehenden Werte und die Funktion ST_AsText auf alle eingehenden Daten anwendet, können wir unsere eigene Unterklasse von UserDefinedType erstellen, die diese Methoden in Verbindung mit func bereitstellt.
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)Wir können den Geometry-Typ in die Table-Metadaten einfügen und ihn in einem select()-Konstrukt verwenden.
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)"
)
)Die resultierende SQL-Anweisung bettet beide Funktionen wie gewünscht ein. ST_AsText wird auf die Spaltenklausel angewendet, sodass der Rückgabewert durch die Funktion läuft, bevor er in ein Ergebnisset übernommen wird, und ST_GeomFromText wird auf den gebundenen Parameter angewendet, sodass der übergebene Wert konvertiert wird.
SELECT geometry.geom_id, ST_AsText(geometry.geom_data) AS geom_data_1
FROM geometry
WHERE geometry.geom_data = ST_GeomFromText(:geom_data_2)Die Methode TypeEngine.column_expression() interagiert mit der Mechanik des Compilers, sodass der SQL-Ausdruck die Beschriftung des umschlossenen Ausdrucks nicht beeinträchtigt. Wenn wir beispielsweise ein select() gegen eine label() unseres Ausdrucks rendern, wird die String-Beschriftung nach außen des umschlossenen Ausdrucks verschoben.
print(select(geometry.c.geom_data.label("my_data")))Ausgabe
SELECT ST_AsText(geometry.geom_data) AS my_data
FROM geometryEin weiteres Beispiel ist die Dekoration von BYTEA zur Bereitstellung eines PGPString, das die PostgreSQL pgcrypto-Erweiterung zur transparenten Verschlüsselung/Entschlüsselung von Werten verwendet.
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+psycopg2://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"))
)Die Funktionen pgp_sym_encrypt und pgp_sym_decrypt werden auf die INSERT- und SELECT-Anweisungen angewendet.
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'}Neudefinition und Erstellung neuer Operatoren¶
SQLAlchemy Core definiert eine feste Menge von Ausdrucksoperatoren, die für alle Spaltenausdrücke verfügbar sind. Einige dieser Operationen haben den Effekt, die eingebauten Operatoren von Python zu überladen; Beispiele für solche Operatoren sind ColumnOperators.__eq__() (table.c.somecolumn == 'foo'), ColumnOperators.__invert__() (~table.c.flag) und ColumnOperators.__add__() (table.c.x + table.c.y). Andere Operatoren werden als explizite Methoden für Spaltenausdrücke bereitgestellt, wie z. B. ColumnOperators.in_() (table.c.value.in_(['x', 'y'])) und ColumnOperators.like() (table.c.value.like('%ed%')).
Wenn ein SQL-Operator benötigt wird, der nicht direkt von den oben genannten Methoden unterstützt wird, ist der schnellste Weg, diesen Operator zu erstellen, die Verwendung der Methode Operators.op() für jedes SQL-Ausdrucksobjekt. Diese Methode erhält einen String, der den zu rendernden SQL-Operator darstellt, und gibt einen Python-Aufruf zurück, der jeden beliebigen Ausdruck auf der rechten Seite akzeptiert.
>>> from sqlalchemy import column
>>> expr = column("x").op(">>")(column("y"))
>>> print(expr)
x >> y
Bei der Verwendung benutzerdefinierter SQL-Typen gibt es auch eine Möglichkeit, benutzerdefinierte Operatoren wie oben zu implementieren, die automatisch für jeden Spaltenausdruck vorhanden sind, der diesen Spaltentyp verwendet, ohne dass Operators.op() jedes Mal direkt aufgerufen werden muss, wenn der Operator verwendet werden soll.
Um dies zu erreichen, konsultiert ein SQL-Ausdruckskonstrukt das TypeEngine-Objekt, das dem Konstrukt zugeordnet ist, um das Verhalten der eingebauten Operatoren zu bestimmen und nach neuen Methoden zu suchen, die möglicherweise aufgerufen wurden. TypeEngine definiert ein „Vergleichs“-Objekt, das von der Klasse Comparator implementiert wird, um das Basisverhalten für SQL-Operatoren bereitzustellen, und viele spezifische Typen stellen ihre eigenen Unterimplementierungen dieser Klasse bereit. Benutzerdefinierte Comparator-Implementierungen können direkt in eine einfache Unterklasse eines bestimmten Typs integriert werden, um neue Operationen zu überschreiben oder zu definieren. Unten erstellen wir eine Unterklasse von Integer, die den Operator ColumnOperators.__add__() überschreibt, der wiederum Operators.op() verwendet, um die benutzerdefinierte SQL-Anweisung selbst zu generieren.
from sqlalchemy import Integer
class MyInt(Integer):
class comparator_factory(Integer.Comparator):
def __add__(self, other):
return self.op("goofy")(other)Die obige Konfiguration erstellt eine neue Klasse MyInt, die das Attribut TypeEngine.comparator_factory so festlegt, dass es auf eine neue Klasse verweist, die die Klasse Comparator untervererbt, die mit dem Typ Integer verknüpft ist.
Verwendung
>>> sometable = Table("sometable", metadata, Column("data", MyInt))
>>> print(sometable.c.data + 5)
sometable.data goofy :data_1
Die Implementierung für ColumnOperators.__add__() wird von einem besitzenden SQL-Ausdruck konsultiert, indem der Comparator mit sich selbst als Attribut expr instanziiert wird. Dieses Attribut kann verwendet werden, wenn die Implementierung direkt auf das ursprüngliche ColumnElement-Objekt verweisen muss.
from sqlalchemy import Integer
class MyInt(Integer):
class comparator_factory(Integer.Comparator):
def __add__(self, other):
return func.special_addition(self.expr, other)Neue Methoden, die einem Comparator hinzugefügt werden, werden auf einem besitzenden SQL-Ausdrucksobjekt über ein dynamisches Nachschlageverfahren bereitgestellt, das Methoden, die Comparator hinzugefügt wurden, für den besitzenden ColumnElement-Ausdruckskonstrukt bereitstellt. Um beispielsweise eine log()-Funktion zu ganzen Zahlen hinzuzufügen.
from sqlalchemy import Integer, func
class MyInt(Integer):
class comparator_factory(Integer.Comparator):
def log(self, other):
return func.log(self.expr, other)Verwendung des obigen Typs
>>> print(sometable.c.data.log(5))
log(:log_1, :log_2)
Bei Verwendung von Operators.op() für Vergleichsoperationen, die ein boolesches Ergebnis zurückgeben, sollte das Flag Operators.op.is_comparison auf True gesetzt werden.
class MyInt(Integer):
class comparator_factory(Integer.Comparator):
def is_frobnozzled(self, other):
return self.op("--is_frobnozzled->", is_comparison=True)(other)Unäre Operationen sind ebenfalls möglich. Um beispielsweise eine Implementierung des PostgreSQL-Fakultätsoperators hinzuzufügen, kombinieren wir das Konstrukt UnaryExpression mit einem custom_op, um den Fakultätsausdruck zu erzeugen.
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
)Verwendung des obigen Typs
>>> from sqlalchemy.sql import column
>>> print(column("x", MyInteger).factorial())
x !
Erstellung neuer Typen¶
Die Klasse UserDefinedType wird als einfache Basisklasse für die Definition völlig neuer Datenbanktypen bereitgestellt. Verwenden Sie dies, um native Datenbanktypen darzustellen, die SQLAlchemy nicht kennt. Wenn nur Python-Übersetzungsverhalten benötigt wird, verwenden Sie stattdessen TypeDecorator.
| Objektname | Beschreibung |
|---|---|
Basis für benutzerdefinierte Typen. |
- class sqlalchemy.types.UserDefinedType¶
Basis für benutzerdefinierte Typen.
Dies sollte die Basis für neue Typen sein. Beachten Sie, dass für die meisten Fälle
TypeDecoratorwahrscheinlich besser geeignet ist.import sqlalchemy.types as types class MyType(types.UserDefinedType): cache_ok = True def __init__(self, precision=8): self.precision = precision def get_col_spec(self, **kw): return "MYTYPE(%s)" % self.precision def bind_processor(self, dialect): def process(value): return value return process def result_processor(self, dialect, coltype): def process(value): return value return process
Sobald der Typ erstellt ist, ist er sofort verwendbar.
table = Table( "foo", metadata_obj, Column("id", Integer, primary_key=True), Column("data", MyType(16)), )
Die Methode
get_col_spec()erhält in den meisten Fällen ein Schlüsselwortargumenttype_expression, das sich auf den besitzenden Ausdruck des Typs bezieht, der gerade kompiliert wird, z. B. eineColumnoder eincast()-Konstrukt. Dieses Schlüsselwort wird nur gesendet, wenn die Methode Schlüsselwortargumente (z. B.**kw) in ihrer Argument signatur akzeptiert; die Introspektion wird verwendet, um dies zu prüfen, um ältere Formen dieser Funktion zu unterstützen.Das Klassenmerkmal
UserDefinedType.cache_okgibt an, ob dieser benutzerdefinierteUserDefinedTypeals Teil eines Cache-Schlüssels sicher verwendet werden kann. Dieses Flag ist standardmäßigNone, was zunächst eine Warnung auslöst, wenn der SQL-Compiler versucht, einen Cache-Schlüssel für eine Anweisung zu generieren, die diesen Typ verwendet. Wenn derUserDefinedTypenicht garantiert, jedes Mal dasselbe Bindungs-/Ergebnisverhalten und dieselbe SQL-Generierung zu erzeugen, sollte dieses Flag aufFalsegesetzt werden; andernfalls, wenn die Klasse jedes Mal dasselbe Verhalten erzeugt, kann sie aufTruegesetzt werden. SieheUserDefinedType.cache_okfür weitere Hinweise, wie dies funktioniert.Neu in Version 1.4.28: Das Flag
ExternalType.cache_okwurde verallgemeinert, sodass es sowohl fürTypeDecoratorals auch fürUserDefinedTypeverfügbar ist.Mitglieder
Klassensignatur
class
sqlalchemy.types.UserDefinedType(sqlalchemy.types.ExternalType,sqlalchemy.types.TypeEngineMixin,sqlalchemy.types.TypeEngine,sqlalchemy.util.langhelpers.EnsureKWArg)-
attribute
sqlalchemy.types.UserDefinedType.cache_ok: bool | None = None¶ vererbt von dem
ExternalType.cache_okAttribut vonExternalTypeGibt an, ob Anweisungen, die diesen
ExternalTypeverwenden, „sicher zum Cachen“ sind.Der Standardwert
Nonegibt eine Warnung aus und erlaubt dann nicht das Caching einer Anweisung, die diesen Typ enthält. Setzen Sie ihn aufFalse, um das Caching von Anweisungen, die diesen Typ verwenden, ohne Warnung zu deaktivieren. Wenn er aufTruegesetzt ist, werden die Klasse des Objekts und ausgewählte Elemente seines Zustands als Teil des Cache-Schlüssels verwendet. Zum Beispiel die Verwendung einesTypeDecoratorclass MyType(TypeDecorator): impl = String cache_ok = True def __init__(self, choices): self.choices = tuple(choices) self.internal_only = True
Der Cache-Schlüssel für den obigen Typ wäre äquivalent zu
>>> MyType(["a", "b", "c"])._static_cache_key (<class '__main__.MyType'>, ('choices', ('a', 'b', 'c')))
Das Caching-Schema extrahiert Attribute aus dem Typ, die den Namen der Parameter in der Methode
__init__()entsprechen. Oben wird das Attribut "choices" Teil des Cache-Schlüssels, "internal_only" jedoch nicht, da es keinen Parameter namens "internal_only" gibt.Die Anforderungen an cachebare Elemente sind, dass sie hashbar sind und auch, dass sie für gegebene Cache-Werte bei jeder Ausführung dieselbe SQL-Darstellung für Ausdrücke anzeigen, die diesen Typ verwenden.
Um Datentypen zu unterstützen, die sich auf nicht hashbare Strukturen wie Wörterbücher, Mengen und Listen beziehen, können diese Objekte "cachebar" gemacht werden, indem hashbare Strukturen den Attributen zugewiesen werden, deren Namen mit den Namen der Argumente übereinstimmen. Zum Beispiel kann ein Datentyp, der ein Wörterbuch mit Nachschlage-Werten akzeptiert, dieses als sortierte Reihe von Tupeln veröffentlichen. Angenommen, ein zuvor nicht cachebarer Typ als
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" ...
wobei "lookup" ein Wörterbuch ist. Der Typ kann keinen Cache-Schlüssel generieren
>>> 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')
Wenn wir einen solchen Cache-Schlüssel **einrichten** würden, wäre er nicht verwendbar. Wir würden eine Tupelstruktur erhalten, die ein Wörterbuch enthält, das selbst nicht als Schlüssel in einem "Cache-Wörterbuch" wie dem Statement-Cache von SQLAlchemy verwendet werden kann, da Python-Wörterbücher nicht hashbar sind.
>>> # 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'
Der Typ kann cachebar gemacht werden, indem ein sortiertes Tupel von Tupeln dem Attribut ".lookup" zugewiesen wird
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" ...
wobei der Cache-Schlüssel für
LookupType({"a": 10, "b": 20})ist>>> LookupType({"a": 10, "b": 20})._static_cache_key (<class '__main__.LookupType'>, ('lookup', (('a', 10), ('b', 20))))
Neu in Version 1.4.14: - das Flag
cache_okhinzugefügt, um einige Konfigurierbarkeiten des Cachings fürTypeDecorator-Klassen zu ermöglichen.Neu in Version 1.4.28: - die Mixin
ExternalTypehinzugefügt, die das Flagcache_oksowohl für die KlassenTypeDecoratorals auchUserDefinedTypeverallgemeinert.Siehe auch
-
method
sqlalchemy.types.UserDefinedType.coerce_compared_value(op: OperatorType | None, value: Any) → TypeEngine[Any]¶ Schlagen Sie einen Typ für einen „koerzierten“ Python-Wert in einem Ausdruck vor.
Das Standardverhalten für
UserDefinedTypeist dasselbe wie fürTypeDecorator; standardmäßig gibt esselfzurück, vorausgesetzt, der verglichene Wert sollte in denselben Typ wie dieser umgewandelt werden. SieheTypeDecorator.coerce_compared_value()für weitere Details.
-
attribute
sqlalchemy.types.UserDefinedType.ensure_kwarg: str = 'get_col_spec'¶ ein regulärer Ausdruck, der Methodennamen angibt, für die die Methode
**kw-Argumente akzeptieren soll.Die Klasse sucht nach Methoden, die dem Namensmuster entsprechen, und dekoriert sie bei Bedarf, um sicherzustellen, dass
**kw-Parameter akzeptiert werden.
-
attribute
Arbeiten mit benutzerdefinierten Typen und Reflexion¶
Es ist wichtig zu beachten, dass Datenbanktypen, die modifiziert wurden, um zusätzliche In-Python-Verhaltensweisen zu haben, einschließlich Typen, die auf TypeDecorator sowie andere benutzerdefinierte Unterklassen von Datentypen basieren, keine Darstellung innerhalb eines Datenbankschemas haben. Bei der Verwendung der Datenbank-Introspektionsfunktionen, die unter Datenbankobjekte reflektieren beschrieben sind, verwendet SQLAlchemy eine feste Zuordnung, die die von einem Datenbankserver gemeldeten Datentypinformationen mit einem SQLAlchemy-Datentypobjekt verknüpft. Wenn wir beispielsweise ein PostgreSQL-Schema nach der Definition einer bestimmten Spalte durchsuchen, erhalten wir möglicherweise den String "VARCHAR" zurück. Die PostgreSQL-Dialekt von SQLAlchemy hat eine fest kodierte Zuordnung, die den String-Namen "VARCHAR" mit der SQLAlchemy-Klasse VARCHAR verknüpft, und so geschieht es, wenn wir eine Anweisung wie Table('my_table', m, autoload_with=engine) ausgeben, dass das Column-Objekt darin eine Instanz von VARCHAR enthält.
Die Implikation davon ist, dass wenn ein Table-Objekt Typobjekte verwendet, die nicht direkt dem nativen Datenbanktypnamen entsprechen, wenn wir ein neues Table-Objekt auf einer neuen MetaData-Sammlung für diese Datenbanktabelle anderswo mittels Reflexion erstellen, wird sie diesen Datentyp nicht haben. Zum Beispiel:
>>> 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
)
Oben haben wir PickleType verwendet, was ein TypeDecorator ist, der auf dem LargeBinary-Datentyp basiert, der bei SQLite dem Datenbanktyp BLOB entspricht. In der CREATE TABLE sehen wir, dass der BLOB-Datentyp verwendet wird. Die SQLite-Datenbank weiß nichts von dem PickleType, den wir verwendet haben.
Wenn wir den Datentyp von my_table.c.data.type betrachten, da es sich um ein Python-Objekt handelt, das wir direkt erstellt haben, ist es PickleType.
>>> my_table.c.data.type
PickleType()Wenn wir jedoch eine weitere Instanz von Table durch Reflexion erstellen, ist die Verwendung von PickleType nicht in der SQLite-Datenbank dargestellt, die wir erstellt haben; stattdessen erhalten wir BLOB zurück.
>>> 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()
Normalerweise, wenn eine Anwendung explizite Table-Metadaten mit benutzerdefinierten Typen definiert, ist keine Verwendung von Tabellenreflexion erforderlich, da die erforderlichen Table-Metadaten bereits vorhanden sind. Für den Fall jedoch, dass eine Anwendung oder eine Kombination von ihnen sowohl explizite Table-Metadaten, die benutzerdefinierte, Python-Level-Datentypen enthalten, als auch Table-Objekte verwenden müssen, die ihre Column-Objekte wie aus der Datenbank reflektiert einrichten, die dennoch die zusätzlichen Python-Verhaltensweisen der benutzerdefinierten Datentypen aufweisen müssen, müssen zusätzliche Schritte unternommen werden, um dies zu ermöglichen.
Am einfachsten ist es, spezifische Spalten wie unter Reflektierte Spalten überschreiben beschrieben zu überschreiben. Bei dieser Technik verwenden wir einfach Reflexion in Kombination mit expliziten Column-Objekten für diejenigen Spalten, für die wir einen benutzerdefinierten oder dekorierten Datentyp verwenden möchten.
>>> metadata_three = MetaData()
>>> my_reflected_table = Table(
... "my_table",
... metadata_three,
... Column("data", PickleType),
... autoload_with=engine,
... )Das obige Objekt my_reflected_table wird reflektiert und lädt die Definition der Spalte „id“ aus der SQLite-Datenbank. Aber für die Spalte „data“ haben wir das reflektierte Objekt mit einer expliziten Column-Definition überschrieben, die unseren gewünschten In-Python-Datentyp, den PickleType, enthält. Der Reflexionsprozess lässt dieses Column-Objekt unverändert.
>>> my_reflected_table.c.data.type
PickleType()Eine aufwendigere Methode zur Umwandlung von nativen Datenbanktypobjekten in benutzerdefinierte Datentypen ist die Verwendung des Ereignisbehandlers DDLEvents.column_reflect(). Wenn wir beispielsweise wüssten, dass wir alle BLOB-Datentypen in der Tat PickleType sein sollen, könnten wir eine generelle Regel aufstellen.
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()Wenn der obige Code aufgerufen wird, bevor eine Tabellenreflexion stattfindet (beachten Sie auch, dass er nur einmal in der Anwendung aufgerufen werden sollte, da es sich um eine globale Regel handelt), wird beim Reflexionieren jeder Table, die eine Spalte mit dem Datentyp BLOB enthält, der resultierende Datentyp als PickleType im Column-Objekt gespeichert.
In der Praxis würde der obige ereignisbasierte Ansatz wahrscheinlich zusätzliche Regeln beinhalten, um nur die Spalten zu beeinflussen, bei denen der Datentyp wichtig ist, wie z. B. eine Nachschlagetabelle von Tabellennamen und möglicherweise Spaltennamen, oder andere Heuristiken, um genau zu bestimmen, welche Spalten mit einem Python-Datentyp eingerichtet werden sollen.
Die Designs von flambé! dem Drachen und Der Alchemist wurden von Rotem Yaari erstellt und großzügig gespendet.
Erstellt mit Sphinx 7.2.6. Dokumentation zuletzt generiert: Di 11 Mär 2025 14:40:17 EDT