La gestion des versions de SQLAlchemy se soucie de l’ordre d’importation des classes

| | | | | | | | | |

Je suivais le guide ici :

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

et ont rencontré un problème. J'ai défini mes relations comme suit :

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

avec des chaînes pour qu'il ne "peu importe l'ordre d'importation de mes modules de modèle. Tout fonctionne bien normalement, mais lorsque j'utilise la méta de version, j'obtiens l'erreur suivante :

sqlalchemy.exc.InvalidRequestError : lors de l'initialisation du mappeur Mapper|MyClass|stuffs, l'expression "Trader" n'a pas réussi à localiser un nom ("name "MyClass" is not defined"). S'il s'agit d'un nom de classe, envisagez d'ajouter cette relation () à la classe après que les deux classes dépendantes ont été définies .

J'ai localisé l'erreur dans :

 Fichier "/home/nick/workspace/gm3/gm3/lib/history_meta.py", ligne 90, dans __init__ mapper = class_mapper(cls) Fichier "/home/nick/venv/tg2env/lib/python2.6/site-packages/sqlalchemy/orm/util.py", ligne 622, dans 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) except UnmappedClassError : pass 

J'ai résolu le problème en mettant le try: except stuff dans un lambda et en les exécutant tous après que toutes les importations ont eu lieu. Cela fonctionne mais semble un peu nul, avez-vous des idées sur la façon de résoudre ce problème ?

Merci !

Mettre à jour

Le problème n'est pas réellement lié à l'ordre d'importation. L'exemple de versioning est conçu de telle sorte que le mappeur nécessite la compilation dans costructor de chaque classe versionnée. Et la compilation échoue lorsque les classes associées ne sont pas encore définies. En cas de relations circulaires, il n'y a aucun moyen de le faire fonctionner en modifiant l'ordre de définition des classes mappées.

Mise à jour 2

Comme l'indique la mise à jour ci-dessus ( Je ne savais pas que vous pouviez modifier les messages d'autres personnes ici :)), cela est probablement dû à des références circulaires. Dans ce cas, quelqu'un trouvera peut-être mon hack utile (je l'utilise avec turbogears) (remplacez VersionedMeta et ajoutez create_mappers global dans history_meta)

create_mappers = [] class VersionedMeta(DeclarativeMeta) : def __init__(cls, classname, bases, dict_): DeclarativeMeta.__init__(cls, classname, bases, dict_) #J'ai ajouté ce code car il plantait sinon def make_mapper(): try: mapper = class_mapper(cls) _history_mapper (mapper) sauf UnmappedClassError : pass create_mappers.append(lambda : make_mapper()) 

Ensuite, vous pouvez faire quelque chose comme ce qui suit dans vos modèles __init__.py

# Importez vos modules de modèle ici. 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 cette façon, il crée uniquement les mappeurs une fois que toutes les classes ont été définies.

Mise à jour 3 Légèrement sans rapport mais j'ai rencontré une erreur de clé primaire en double dans certaines circonstances (commettre 2 modifications sur le même objet en une seule fois) . Ma solution de contournement a été d'ajouter une nouvelle clé primaire d'auto-incrémentation. Bien sûr, vous ne pouvez pas en avoir plus de 1 avec mysql, j'ai donc dû retirer la clé primaire des éléments existants utilisés pour créer la table d'historique. Consultez mon code global (y compris un hist_id et la suppression de la contrainte de clé étrangère):

"""Stolen from the official sqlalchemy recpies """ from sqlalchemy.ext.declarative import DeclarativeMeta from 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) : for fk in col.foreign_keys: if fk.references(table): return True return False def _history_mapper(local_mapper): cls = local_mapper.class_ # définit le drapeau "active_history" # on on mappé en colonne a ttributes pour que l'ancienne version # des informations soit toujours chargée (elle est actuellement définie sur tous les attributs) pour prop dans 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__", Aucun) polymorphic_on = Aucun super_fks = [] si ce n'est pas super_mapper ou local_mapper.local_table n'est pas super_mapper.local_table : cols = [] pour la colonne dans local_mapper.local_table.c : si column.name == "version": continue col = column.copy() col.unique = False #don"t incrémente automatiquement les choses à partir de la base de données normale si col.autoincrement : col.autoincrement = False #sqllite tombe avec des clés d'incrémentation automatique si nous avons un clé composite si col.primary_key : col.primary_key = False si super_mapper et 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) si la colonne est 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("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 : # héritage de table unique. prenez toutes les colonnes supplémentaires qui ont pu # avoir été ajoutées et ajoutez-les à la table d'historique. for column in local_mapper.local_table.c : if column.key not 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_,) sinon : 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 sinon 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_) #J'ai ajouté ce code car il plantait sinon def make_mapper(): try: mapper = class_mapper(cls) _history_mapper(mapper) except 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, delete = False) : obj_mapper = object_mapper(obj) history_mapper = obj.__history_mapper__ history_cls = history_mapper.class_obj_state = attributs.instance_state(obj) attr = {} obj_changed = False pour om, hm dans zip(obj_mapper.iterate_to_root(), history_mapper.iterate_to_root()) : si hm.single : continuer pour hist_col dans hm .local_table.c : si hist_col.key == "version" ou hist_col.key == "changed" ou hist_col.key == "hist_id": continue obj_col = om.local_table.c[hist_col.key] # obtenir la valeur de l'attribut # basé sur le MapperProperty lié à la colonne mappée #. cela permettra l'utilisation de MapperProperties # qui ont un nom de clé différent de celui de la colonne mappée. try : prop = obj_mapper.get_property_by_column(obj_col) sauf UnmappedColumnError : # dans le cas d'un héritage de table unique, il peut y avoir # colonnes sur la table mappée destinées à la sous-classe uniquement. # le statut "non mappé" de la colonne de sous-classe sur la # classe de base est une fonctionnalité du module déclaratif à partir de sqla 0.5.2. continue # les attributs d'objet expirés ainsi que les cols différés peuvent ne pas être dans le # dict. forcez-le à se charger quoi qu'il arrive en utilisant getattr(). si prop.key pas dans obj_state.dict : getattr(obj, prop.key) a, u, d = attributs.get_history(obj, prop.key) si d : attr[hist_col.key] = d[0] obj_changed = True elif u : attr[hist_col.key] = u[0] else : # si l'attribut n'avait pas de valeur. attr[hist_col.key] = a[0] obj_changed = Vrai sinon obj_changed : # pas changé, mais nous avons des relations. OK # vérifiez également ceux-ci pour prop dans obj_mapper.iterate_properties : si isinstance(prop, RelationshipProperty) et attributs.get_history(obj, prop.key).has_changes() : obj_changed = True break si non obj_changed et non supprimé : return attr[" version"] = obj.version hist = history_cls() pour la clé, valeur dans attr.iteritems() : setattr(hist, clé, valeur) obj.version += 1 session.add(hist) classe VersionedListener(SessionExtension) : def before_flush(self, session, flush_context, instances): pour obj dans versioned_objects(session.dirty): create_version(obj, session) pour obj dans 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