Die SQLAlchemy-Versionierung kümmert sich um die Klassenimportreihenfolge

| | | | | | | | | |

Ich habe die Anleitung hier befolgt:

http ://www.sqlalchemy.org/docs/orm/examples.html?highlight=versioning#versioned-objects

und sind auf ein Problem gestoßen. Ich habe meine Beziehungen wie folgt definiert:

generic_ticker = relation("MyClass", backref=backref("stuffs")) 

mit Strings, damit es funktioniert "Die Importreihenfolge meiner Modellmodule ist mir egal. Das funktioniert normalerweise gut, aber wenn ich das Versionierungsmeta verwende, erhalte ich die folgende Fehlermeldung:

sqlalchemy.exc.InvalidRequestError: When initializing mapper Mapper|MyClass|stuffs, Ausdruck „Trader“ konnte keinen Namen finden („Name „MyClass“ ist nicht definiert“). Wenn dies ein Klassenname ist, sollten Sie diese Beziehung () zur Klasse hinzufügen, nachdem beide abhängigen Klassen definiert wurden .

Ich habe den Fehler gefunden in:

 Datei "/home/nick/workspace/gm3/gm3/lib/history_meta.py", Zeile 90, in __init__ mapper = class_mapper(cls) Datei „/home/nick/venv/tg2env/lib/python2.6/site-packages/sqlalchemy/orm/util.py“, Zeile 622, in class_mapper mapper = mapper.compile () 

class VersionedMeta(DeclarativeMeta): def __init__(cls, cl assname, bases, dict_): DeclarativeMeta.__init__(cls, classname, bases, dict_) try: mapper = class_mapper(cls) _history_mapper(mapper) außer UnmappedClassError: pass 

Ich habe das Problem behoben indem Sie try: except stuff in ein Lambda einfügen und alle ausführen, nachdem alle Importe stattgefunden haben. Das funktioniert, scheint aber ein bisschen Müll zu sein. Irgendwelche Ideen, wie man das besser beheben kann?

Danke!

Aktualisieren

Das Problem liegt eigentlich nicht an der Importreihenfolge. Das Versionierungsbeispiel ist so konzipiert, dass der Mapper im Costructor jeder versionierten Klasse kompiliert werden muss. Und die Kompilierung schlägt fehl, wenn zugehörige Klassen noch nicht definiert sind. Im Falle von zirkulären Beziehungen gibt es keine Möglichkeit, es zum Laufen zu bringen, indem die Definitionsreihenfolge der zugeordneten Klassen geändert wird.

Update 2

Wie das obige Update besagt ( Ich wusste nicht, dass Sie die Beiträge anderer Leute hier bearbeiten können :)) Dies liegt wahrscheinlich an Zirkelverweisen. In diesem Fall könnte jemand meinen Hack nützlich finden (ich verwende ihn mit Turbogears) (Ersetzen Sie VersionedMeta und fügen Sie create_mappers global in history_meta hinzu)

create_mappers = [] class VersionedMeta(DeclarativeMeta) : def __init__(cls, classname, bases, dict_): DeclarativeMeta.__init__(cls, classname, bases, dict_) #Ich habe diesen Code hinzugefügt, da er sonst abstürzte def make_mapper(): try: mapper = class_mapper(cls) _history_mapper (Mapper) außer UnmappedClassError: pass create_mappers.append(lambda: make_mapper()) 

Dann können Sie in Ihren Modellen etwa Folgendes tun: __init__.py

# Importieren Sie hier Ihre Modellmodule from myproj.lib.history_meta import create_mappers from myproj.model.misc import * from myproj.model.actor import * from myproj.model.stuff1 import * from myproj.model.instrument import * from myproj.model.stuff import * #setup the history [func() for func in create_mappers] 

Auf diese Weise werden nur die Mapper erstellt nachdem alle Klassen definiert wurden.

Update 3 Etwas ohne Bezug, aber ich bin unter bestimmten Umständen auf einen doppelten Primärschlüsselfehler gestoßen (zwei Änderungen an demselben Objekt auf einmal). . Meine Problemumgehung bestand darin, einen neuen primären automatisch inkrementierenden Schlüssel hinzuzufügen. Natürlich können Sie mit mysql nicht mehr als 1 haben, also musste ich das vorhandene Zeug, das zum Erstellen der Verlaufstabelle verwendet wurde, entprimieren. Sehen Sie sich meinen Gesamtcode an (einschließlich einer hist_id und dem Entfernen der Fremdschlüsselbeschränkung):

"""Gestohlen aus den offiziellen sqlalchemy-Rezepten """ aus sqlalchemy.ext.declarative import DeclarativeMeta aus sqlalchemy.orm import mapper, class_mapper, attributes, object_mapper from sqlalchemy.orm.exc import UnmappedClassError, UnmappedColumnError from sqlalchemy import Table, Column, ForeignKeyConstraint, Integer from sqlalchemy.orm.interfaces import SessionExtension from sqlalchemy.orm.properties import RelationshipProperty from sqlalchemy.types import DateTime import datetime from sqlalchemy.orm.session import Session def col_references_table(col, table) : für fk in col.foreign_keys: if fk.references(table): return True return False def _history_mapper(local_mapper): cls = local_mapper.class_ # setze das "active_history"-Flag # auf spaltenzugeordnet a ttributes so, dass immer die alte Version # der Info geladen wird (derzeit wird sie auf alle Attribute gesetzt) für prop in local_mapper.iterate_properties: getattr(local_mapper.class_, prop.key).impl.active_history = True super_mapper = local_mapper.inherits super_history_mapper = getattr(cls, "__history_mapper__", None) polymorphic_on = None super_fks = [] wenn nicht super_mapper oder local_mapper.local_table ist nicht super_mapper.local_table: cols = [] für Spalte in local_mapper.local_table.c: if column.name == "version": Continue col = column.copy() col.unique = False #nicht automatisch inkrementieren von Sachen aus der normalen Datenbank, wenn col.autoincrement: col.autoincrement = False #sqllite fällt mit automatisch inkrementierenden Schlüsseln um, wenn wir eine haben zusammengesetzter Schlüssel, wenn col.primary_key: col.primary_key = False, wenn super_mapper und col_references_table(column, super_mapper.local_table): super_fks.append((col.key, list(super_history_mapper.base_mapper.local_table.primary_key)[0])) cols. append(col) wenn Spalte local_mapper.polymorphi ist c_on: polymorphic_on = col #if super_mapper: # super_fks.append(("version", super_history_mapper.base_mapper.local_table.c.version)) cols.append(Column("hist_id", Integer, primary_key=True, autoincrement=True) ) cols.append(Column("version", Integer)) cols.append(Column("changed", DateTime, default=datetime.datetime.now)) if super_fks: cols.append(ForeignKeyConstraint(*zip(*super_fks) )) table = Table(local_mapper.local_table.name + "_history", local_mapper.local_table.metadata, *cols, mysql_engine="InnoDB") else: # einzelne Tabellenvererbung. Nehmen Sie alle zusätzlichen Spalten, die möglicherweise # hinzugefügt wurden, und fügen Sie sie der Verlaufstabelle hinzu. für Spalte in local_mapper.local_table.c: wenn column.key nicht in super_history_mapper.local_table.c: col = column.copy() super_history_mapper.local_table.append_column(col) table = None if super_history_mapper: bases = (super_history_mapper.class_,) sonst: bases = local_mapper.base_mapper.class_.__bases__ versioned_cls = type.__new__(type, "%sHistory" % cls.__name__, bases, {}) m = mapper( versioned_cls, table, inherits=super_history_mapper, polymorphic_on=polymorphic_on, polymorphic_identity =local_mapper.polymorphic_identity ) cls.__history_mapper__ = m wenn nicht super_history_mapper: cls.version = Column("version", Integer, default=1, nullable=False) create_mappers = [] class VersionedMeta(DeclarativeMeta): def __init__(cls, classname , bases, dict_): DeclarativeMeta.__init__(cls, classname, bases, dict_) #Ich habe diesen Code hinzugefügt, da er sonst abstürzte def make_mapper(): try: mapper = class_mapper(cls) _history_mapper(mapper) außer UnmappedClassError: pass create_mappers.append(lambda: make_mapper()) def versioned_objects(iter): for obj in iter: if hasattr(obj, "__history_mapper__"): yield obj def create_version(obj, session, deleted = False): obj_mapper = object_mapper(obj) history_mapper = obj.__history_mapper__ history_cls = history_mapper.class_ obj_state = attributes.instance_state(obj) attr = {} obj_changed = False für om, hm in zip(obj_mapper.iterate_to_root(), history_mapper.iterate_to_root()): if hm.single: weiter für hist_col in hm .local_table.c: if hist_col.key == "version" or hist_col.key == "changed" or hist_col.key == "hist_id": Continue obj_col = om.local_table.c[hist_col.key] # Hole den Wert des #-Attributs basierend auf der MapperProperty, die sich auf die # zugeordnete Spalte bezieht. Dies ermöglicht die Verwendung von MapperProperties #, die einen anderen Schlüsselnamen als den der zugeordneten Spalte haben. try: prop = obj_mapper.get_property_by_column(obj_col) außer UnmappedColumnError: # Im Fall der Vererbung einer einzelnen Tabelle kann es # Spalten in der zugeordneten Tabelle geben, die nur für die Unterklasse bestimmt sind. # Der Status "nicht zugeordnet" der Unterklassenspalte in der # Basisklasse ist eine Funktion des deklarativen Moduls ab sqla 0.5.2. Continue # abgelaufene Objektattribute und auch verzögerte Spalten sind möglicherweise nicht im # Dikt. zwingen Sie es, egal was zu laden, indem Sie getattr() verwenden. if prop.key not in obj_state.dict: getattr(obj, prop.key) a, u, d = attributes.get_history(obj, prop.key) if d: attr[hist_col.key] = d[0] obj_changed = True elif u: attr[hist_col.key] = u[0] else: # wenn das Attribut keinen Wert hatte. attr[hist_col.key] = a[0] obj_changed = True if not obj_changed: # nicht geändert, aber wir haben Beziehungen. OK # auch diese auf prop in obj_mapper.iterate_properties prüfen: if isinstance(prop, RelationshipProperty) and attributes.get_history(obj, prop.key).has_changes(): obj_changed = True break if not obj_changed and not delete: return attr[" version"] = obj.version hist = history_cls() für Schlüssel, Wert in attr.iteritems(): setattr(hist, key, value) obj.version += 1 session.add(hist) class VersionedListener(SessionExtension): def before_flush(self, session, flush_context, instances): für obj in versioned_objects(session.dirty): create_version(obj, session) für obj in versioned_objects(session.deleted): create_version(obj, session, deleted = True)