El control de versiones de SQLAlchemy se preocupa por el orden de importación de clases

| | | | | | | | | |

Estaba siguiendo la guía aquí:

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

y encontré un problema. He definido mis relaciones como:

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

con cadenas para que no "No me importa el orden de importación de los módulos de mi modelo. Todo esto funciona bien normalmente, pero cuando uso el meta de control de versiones, aparece el siguiente error:

sqlalchemy.exc.InvalidRequestError: When initializing mapper Mapper|MyClass|stuffs, la expresión "Trader" no pudo ubicar un nombre ("el nombre "MyClass" no está definido"). Si este es un nombre de clase, considere agregar esta relación () a la clase después de que se hayan definido ambas clases dependientes .

Rastreé el error hasta:

 Archivo "/home/nick/workspace/gm3/gm3/lib/history_meta.py", línea 90, en __init__ mapper = class_mapper(cls) Archivo "/home/nick/venv/tg2env/lib/python2.6/site-packages/sqlalchemy/orm/util.py", línea 622, en class_mapper mapper = mapper.compile () 

class VersionedMeta(DeclarativeMeta): def __init__(cls, cl assname, bases, dict_): DeclarativeMeta.__init__(cls, classname, bases, dict_) prueba: mapper = class_mapper(cls) _history_mapper(mapper) excepto UnmappedClassError: pass 

Solucioné el problema poniendo el try: excepto cosas en un lambda y ejecutándolos todos después de que hayan ocurrido todas las importaciones. Esto funciona pero parece un poco basura, ¿alguna idea de cómo arreglar esto es una mejor manera?

¡Gracias!

Actualizar

El problema no es realmente sobre el pedido de importación. El ejemplo de control de versiones está diseñado de tal manera que mapper requiere la compilación en costructor de cada clase versionada. Y la compilación falla cuando las clases relacionadas aún no están definidas. En el caso de las relaciones circulares, no hay forma de que funcione cambiando el orden de definición de las clases mapeadas.

Actualización 2

Como indica la actualización anterior ( No sabía que podía editar las publicaciones de otras personas aquí :)) esto probablemente se deba a referencias circulares. En cuyo caso, puede que alguien encuentre útil mi truco (lo estoy usando con turbogears) (Reemplace VersionedMeta y agregue create_mappers global en history_meta)

create_mappers = [] class VersionedMeta(DeclarativeMeta) : def __init__(cls, classname, bases, dict_): DeclarativeMeta.__init__(cls, classname, bases, dict_) #Agregué este código porque fallaba, de lo contrario def make_mapper(): pruebe: mapper = class_mapper(cls) _history_mapper (mapper) excepto UnmappedClassError: pase create_mappers.append(lambda: make_mapper()) 

Entonces puede hacer algo como lo siguiente en sus modelos __init__.py

< código># Importe sus módulos de modelo aquí. 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] 

De esa forma crea solo los mapeadores después de que se hayan definido todas las clases.

Actualización 3 Ligeramente no relacionado, pero me encontré con un error de clave principal duplicada en algunas circunstancias (commitiendo 2 cambios en el mismo objeto de una sola vez) . Mi solución ha sido agregar una nueva clave principal de incremento automático. Por supuesto, no puede tener más de 1 con mysql, por lo que tuve que eliminar la clave principal de las cosas existentes utilizadas para crear la tabla de historial. Consulte mi código general (incluido un hist_id y la eliminación de la restricción de clave externa): /p>

""" Robado de las recetas oficiales de sqlalchemy """ de sqlalchemy.ext.declarative import DeclarativeMeta from sqlalchemy.orm import mapper, class_mapper, atributos, object_mapper from sqlalchemy.orm.exc import UnmappedClassError, UnmappedColumnError de 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) : for fk in col.foreign_keys: if fk.references(table): return True return False def _history_mapper(local_mapper): cls = local_mapper.class_ # establecer el indicador "active_history" # en la columna mapeada a atributos para que la versión anterior # de la información siempre se cargue (actualmente se configura en todos los atributos) para prop en 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__", Ninguno) polymorphic_on = Ninguno super_fks = [] si no es super_mapper o local_mapper.local_table no es super_mapper.local_table: cols = [] for column in local_mapper.local_table.c: if column.name == "version": continue col = column.copy() col.unique = False #no incremente automáticamente cosas de la base de datos normal si col.autoincrement: col.autoincrement = False #sqllite falla con claves de incremento automático si tenemos un clave compuesta si col.primary_key: col.primary_key = False si super_mapper y 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. agregar (col) si la columna es local_mapper.polymorphi 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("cambiado", 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: # herencia de tabla única. tome cualquier columna adicional que se haya # agregado y agréguela a la tabla de historial. for column in local_mapper.local_table.c: si column.key no está en 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_,) else: bases = local_mapper.base_mapper.class_.__bases__ versioned_cls = type.__new__(type, "%sHistory" % cls.__name__, bases, {}) m = mapper( versioned_cls, table, heres=super_history_mapper, polymorphic_on=polymorphic_on, polymorphic_identity =local_mapper.polymorphic_identity ) cls.__history_mapper__ = m si no es 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_) #Agregué este código porque fallaba, de lo contrario def make_mapper(): try: mapper = class_mapper(cls) _history_mapper(mapper) excepto 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 = atributos.instance_state(obj) attr = {} obj_changed = False for om, hm in zip(obj_mapper.iterate_to_root(), history_mapper.iterate_to_root()): if hm.single: continuar para hist_col en hm .local_table.c: si hist_col.key == "versión" o hist_col.key == "cambiado" o hist_col.key == "hist_id": continuar obj_col = om.local_table.c[hist_col.key] # obtener el valor del atributo # basado en MapperProperty relacionado con la columna # mapeada. esto permitirá el uso de MapperProperties # que tienen un nombre de clave diferente al de la columna asignada. pruebe: prop = obj_mapper.get_property_by_column(obj_col) excepto UnmappedColumnError: # en el caso de la herencia de una sola tabla, puede haber # columnas en la tabla asignada destinadas solo a la subclase. # el estado "no mapeado" de la columna de la subclase en la # clase base es una característica del módulo declarativo a partir de sqla 0.5.2. continuar # atributos de objetos caducados y también cols diferidos podrían no estar en el # dict. forzarlo a cargar pase lo que pase usando getattr(). si prop.key no está en obj_state.dict: getattr(obj, prop.key) a, u, d = atributos.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: # si el atributo no tiene valor. attr[hist_col.key] = a[0] obj_changed = True if not obj_changed: # no ha cambiado, pero tenemos relaciones. OK # verifique esos también para prop en obj_mapper.iterate_properties: if isinstance(prop, RelationshipProperty) y atributos.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() for key, value in attr.iteritems(): setattr(hist, key, value) obj.version += 1 session.add(hist) class VersionedListener(SessionExtension): def before_flush(self, session, flush_context, instances): for obj in versioned_objects(session.dirty): create_version(obj, session) for obj in versioned_objects(session.deleted): create_version(obj, session, delete = True) 

Shop

Learn programming in R: courses

$

Best Python online courses for 2022

$

Best laptop for Fortnite

$

Best laptop for Excel

$

Best laptop for Solidworks

$

Best laptop for Roblox

$

Best computer for crypto mining

$

Best laptop for Sims 4

$

Latest questions

NUMPYNUMPY

psycopg2: insert multiple rows with one query

12 answers

NUMPYNUMPY

How to convert Nonetype to int or string?

12 answers

NUMPYNUMPY

How to specify multiple return types using type-hints

12 answers

NUMPYNUMPY

Javascript Error: IPython is not defined in JupyterLab

12 answers

News


Wiki

Python OpenCV | cv2.putText () method

numpy.arctan2 () in Python

Python | os.path.realpath () method

Python OpenCV | cv2.circle () method

Python OpenCV cv2.cvtColor () method

Python - Move item to the end of the list

time.perf_counter () function in Python

Check if one list is a subset of another in Python

Python os.path.join () method