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

TypeDecorator

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 impl ist erforderlich und kann auf jede TypeEngine-Klasse verweisen. Alternativ kann die Methode load_dialect_impl() verwendet werden, um unterschiedliche Typklassen basierend auf der gegebenen Dialekt bereitzustellen; in diesem Fall kann die Variable impl als Platzhalter auf TypeEngine verweisen.

Das Klassenflag TypeDecorator.cache_ok gibt an, ob dieser benutzerdefinierte TypeDecorator sicher als Teil eines Cache-Schlüssels verwendet werden kann. Dieses Flag hat standardmäßig den Wert None, 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 der TypeDecorator nicht garantiert, dass er bei jeder Ausführung das gleiche Bindungs-/Ergebnisverhalten und die gleiche SQL-Generierung erzeugt, sollte dieses Flag auf False gesetzt werden; andernfalls, wenn die Klasse jedes Mal das gleiche Verhalten erzeugt, kann sie auf True gesetzt werden. Siehe TypeDecorator.cache_ok fü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 Ausdruck

mytable.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 – TypeDecorator wandelt 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 + date mit dem obigen Typ wandelt das „date“ auf der rechten Seite so um, dass es ebenfalls als MyEpochType behandelt 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 als Integer behandelt wird, und jeder andere Wert als Datum angenommen und als MyEpochType behandelt 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 TypeDecorator einen Typ erweitert, der spezielle Logik für bestimmte Operatortypen erfordert, muss diese Methode überschrieben werden. Ein wichtiges Beispiel ist die Dekoration der Typen JSON und JSONB; die Standardregeln von TypeEngine.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 ARRAY die Typumwandlung für Indexoperationen (z. B. mycol[5]) ebenfalls von TypeDecorator.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)

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_ok Attribut von ExternalType

Gibt an, ob Anweisungen, die diesen ExternalType verwenden, „sicher zum Cachen“ sind.

Der Standardwert None gibt eine Warnung aus und erlaubt dann nicht das Caching einer Anweisung, die diesen Typ enthält. Setzen Sie ihn auf False, um das Caching von Anweisungen, die diesen Typ verwenden, ohne Warnung zu deaktivieren. Wenn er auf True gesetzt ist, werden die Klasse des Objekts und ausgewählte Elemente seines Zustands als Teil des Cache-Schlüssels verwendet. Zum Beispiel die Verwendung eines TypeDecorator

class 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_ok hinzugefügt, um einige Konfigurierbarkeiten des Cachings für TypeDecorator-Klassen zu ermöglichen.

Neu in Version 1.4.28: - die Mixin ExternalType hinzugefügt, die das Flag cache_ok sowohl für die Klassen TypeDecorator als auch UserDefinedType verallgemeinert.

class Comparator

Ein Comparator, der spezifisch für TypeDecorator ist.

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 NotImplementedError aus.

Das Überschreiben dieser Methode in einer Unterklasse kann es ermöglichen, allgemeines Verhalten auf alle Operationen anzuwenden. Zum Beispiel, das Überschreiben von ColumnOperators, um func.lower() auf die linke und rechte Seite anzuwenden

class MyComparator(ColumnOperators):
    def operate(self, op, other, **kwargs):
        return op(func.lower(self), func.lower(other), **kwargs)
Parameter:
  • op – Operator-Aufruf.

  • *other – die „andere“ Seite der Operation. Wird für die meisten Operationen ein einzelner Skalar sein.

  • **kwargs – Modifikatoren. Diese können von speziellen Operatoren wie ColumnOperators.contains() übergeben werden.

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 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 impl zugewiesen ist, vorausgesetzt, impl ist aufrufbar, und das resultierende Objekt wird dem Instanzattribut self.impl zugewiesen (wodurch das Klassenattribut desselben Namens überschrieben wird).

Wenn impl auf 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.impl vollstä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 TypeDecorator kö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 Dialekt bereit.

Dies ist die Methode, die den TypeEngine-Vertrag für die Konvertierung gebundener Werte erfüllt, die normalerweise über die Methode TypeEngine.bind_processor() erfolgt.

Hinweis

Benutzerdefinierte Unterklassen von TypeDecorator sollten diese Methode nicht implementieren und stattdessen TypeDecorator.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 somecolumn diesen Typ verwendet, wird diese Methode mit dem Wert operator.add und 35 aufgerufen. Der Rückgabewert ist der SQLAlchemy-Typ, der für 35 fü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ür IS NOT in Verbindung mit !=).

Für die meisten SQLAlchemy-Typen umfasst dies NoneType sowie bool.

TypeDecorator modifiziert diese Liste, um nur NoneType einzuschließ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 TypeDecorator kö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 besitzenden ColumnElement-Objekten ausgeführt werden.

Das Attribut comparator_factory ist ein Hook, der vom Core-Ausdruckssystem konsultiert wird, wenn Spalten- und SQL-Ausdrucksoperationen ausgeführt werden. Wenn eine Comparator-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__() und ColumnOperators.__eq__(), sowie diejenigen, die als Standardattribute von ColumnOperators bereitgestellt werden, wie z. B. ColumnOperators.like() und ColumnOperators.in_().

Eine rudimentäre Verwendung dieses Hooks ist durch einfaches Ableiten von vorhandenen Typen oder alternativ durch die Verwendung von TypeDecorator mö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 benutzerdefinierte TypeDecorator hat lokalen Zustand, der tief kopiert werden soll.

methode sqlalchemy.types.TypeDecorator.get_dbapi_type(dbapi: module) Any | None

Gibt das von diesem TypeDecorator reprä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 Dialect bereit.

Dies ist die Methode, die den TypeEngine-Vertrag für die Literalwertkonvertierung erfüllt, die normalerweise über die Methode TypeEngine.literal_processor() erfolgt.

Hinweis

Benutzerdefinierte Unterklassen von TypeDecorator sollten diese Methode nicht implementieren, sondern stattdessen TypeDecorator.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 von type_engine() verwendet, um zu helfen zu bestimmen, welcher Typ letztendlich für einen gegebenen TypeDecorator zurückgegeben werden soll.

Gibt standardmäßig self.impl zurück.

methode sqlalchemy.types.TypeDecorator.process_bind_param(value: _T | None, dialect: Dialect) Any

Empfängt einen zu konvertierenden gebundenen Parameterwert.

Benutzerdefinierte Unterklassen von TypeDecorator sollten 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.

Parameter:
  • value – Zu bearbeitende Daten, von jedem Typ, den diese Methode in der Unterklasse erwartet. Kann None sein.

  • dialect – die verwendete Dialect.

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 TypeDecorator sollten 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 TypeDecorator sollten 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.

Parameter:
  • value – Zu bearbeitende Daten, von jedem Typ, den diese Methode in der Unterklasse erwartet. Kann None sein.

  • dialect – die verwendete Dialect.

methode sqlalchemy.types.TypeDecorator.result_processor(dialect: Dialect, coltype: Any) _ResultProcessorType[_T] | None

Stellt eine Funktion zur Verarbeitung von Ergebniswerten für die gegebene Dialect bereit.

Dies ist die Methode, die den TypeEngine-Vertrag für die gebundene Wertkonvertierung erfüllt, die normalerweise über die Methode TypeEngine.result_processor() erfolgt.

Hinweis

Benutzerdefinierte Unterklassen von TypeDecorator sollten diese Methode nicht implementieren, sondern stattdessen TypeDecorator.process_result_value() implementieren, damit die „interne“ Verarbeitung, die vom implementierenden Typ bereitgestellt wird, erhalten bleibt.

Parameter:
  • dialect – Verwendete Dialektinstanz.

  • coltype – Ein SQLAlchemy-Datentyp.

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 None bedeutet, 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 diesen TypeDecorator zurück.

In den meisten Fällen gibt dies eine dialektangepasste Form des TypeEngine-Typs zurück, der durch self.impl repräsentiert wird. Nutzt `dialect_impl()`. Das Verhalten kann hier durch Überschreiben von load_dialect_impl() angepasst werden.

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 value

Runden 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 value

Zeitzonenbewusste 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 value

Backend-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 = str

Verknü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)

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 value

Mutability 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 ORM

Die 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

Mutationsverfolgung

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 value

Oben 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 geometry

Ein 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

UserDefinedType

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 TypeDecorator wahrscheinlich 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üsselwortargument type_expression, das sich auf den besitzenden Ausdruck des Typs bezieht, der gerade kompiliert wird, z. B. eine Column oder ein cast()-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_ok gibt an, ob dieser benutzerdefinierte UserDefinedType als Teil eines Cache-Schlüssels sicher verwendet werden kann. Dieses Flag ist standardmäßig None, 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 der UserDefinedType nicht garantiert, jedes Mal dasselbe Bindungs-/Ergebnisverhalten und dieselbe SQL-Generierung zu erzeugen, sollte dieses Flag auf False gesetzt werden; andernfalls, wenn die Klasse jedes Mal dasselbe Verhalten erzeugt, kann sie auf True gesetzt werden. Siehe UserDefinedType.cache_ok für weitere Hinweise, wie dies funktioniert.

Neu in Version 1.4.28: Das Flag ExternalType.cache_ok wurde verallgemeinert, sodass es sowohl für TypeDecorator als auch für UserDefinedType verfügbar ist.

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_ok Attribut von ExternalType

Gibt an, ob Anweisungen, die diesen ExternalType verwenden, „sicher zum Cachen“ sind.

Der Standardwert None gibt eine Warnung aus und erlaubt dann nicht das Caching einer Anweisung, die diesen Typ enthält. Setzen Sie ihn auf False, um das Caching von Anweisungen, die diesen Typ verwenden, ohne Warnung zu deaktivieren. Wenn er auf True gesetzt ist, werden die Klasse des Objekts und ausgewählte Elemente seines Zustands als Teil des Cache-Schlüssels verwendet. Zum Beispiel die Verwendung eines TypeDecorator

class 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_ok hinzugefügt, um einige Konfigurierbarkeiten des Cachings für TypeDecorator-Klassen zu ermöglichen.

Neu in Version 1.4.28: - die Mixin ExternalType hinzugefügt, die das Flag cache_ok sowohl für die Klassen TypeDecorator als auch UserDefinedType verallgemeinert.

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 UserDefinedType ist dasselbe wie für TypeDecorator; standardmäßig gibt es self zurück, vorausgesetzt, der verglichene Wert sollte in denselben Typ wie dieser umgewandelt werden. Siehe TypeDecorator.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.

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.