Управление версиями SQLAlchemy заботится о порядке импорта классов

| | | | | | | | | |

Я следовал руководству здесь:

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

и столкнулись с проблемой. Я определил свои отношения следующим образом:

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

со строками, поэтому он не "не заботится о порядке импорта модулей моей модели. Все это нормально работает нормально, но когда я использую метаданные управления версиями, я получаю следующую ошибку:

sqlalchemy.exc.InvalidRequestError: При инициализации картографа Mapper|MyClass|stuffs, выражению "Trader" не удалось найти имя ("имя "MyClass" не определено"). Если это имя класса, рассмотрите возможность добавления этого отношения () в класс после определения обоих зависимых классов .

Я отследил ошибку до:

 Файл "/home/nick/workspace/gm3/gm3/lib/history_meta.py", строка 90, в __init__ mapper = class_mapper(cls) File "/home/nick/venv/tg2env/lib/python2.6/site-packages/sqlalchemy/orm/util.py", строка 622, в class_mapper mapper = mapper.compile () 

класс VersionedMeta(DeclarativeMeta): def __init__(cls, cl assname, bases, dict_): DeclarativeMeta.__init__(cls, classname, bases, dict_) try: mapper = class_mapper(cls) _history_mapper(mapper) за исключением UnmappedClassError: pass 

Я исправил проблему поместив try: кроме материала в лямбду и запустив их все после того, как все импорты произошли. Это работает, но кажется немного ерундой, есть идеи, как исправить это лучше?

Спасибо!

Обновить

Проблема на самом деле не в заказе на импорт. Пример управления версиями разработан таким образом, что картограф требует компиляции в конструкторе каждого класса версии. И компиляция завершается ошибкой, когда связанные классы еще не определены. В случае циклических отношений невозможно заставить его работать, изменив порядок определения сопоставленных классов.

Обновление 2

Как указано в приведенном выше обновлении ( Я не знал, что здесь можно редактировать посты других людей :)) скорее всего, это связано с циклическими ссылками. В этом случае может быть кому-то пригодится мой хак (я использую его с турборедуктором) (замените VersionedMeta и добавьте в create_mappers global в history_meta)

create_mappers = [] class VersionedMeta(DeclarativeMeta) : def __init__(cls, classname, bases, dict_): DeclarativeMeta.__init__(cls, classname, bases, dict_) #Я добавил этот код, поскольку он давал сбой, иначе def make_mapper(): try: mapper = class_mapper(cls) _history_mapper (mapper), кроме UnmappedClassError: pass create_mappers.append(lambda: make_mapper()) 

Затем вы можете сделать что-то вроде следующего в своих моделях __init__.py

# Импортируйте модули вашей модели сюда 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] 

Таким образом создаются только преобразователи после того, как все классы были определены.

Обновление 3 Немного не связано, но в некоторых случаях я столкнулся с ошибкой дублирования первичного ключа (фиксация 2 изменений в одном и том же объекте за один раз) . Мой обходной путь состоял в том, чтобы добавить новый первичный автоматически увеличивающийся ключ. Конечно, вы не можете иметь более 1 с mysql, поэтому мне пришлось отменить первичный ключ существующего материала, используемого для создания таблицы истории. Проверьте мой общий код (включая hist_id и избавление от ограничения внешнего ключа):

"""Украдено из официальных рецептов sqlalchemy """ из sqlalchemy.ext.declarative import DeclarativeMeta из sqlalchemy.orm import mapper, class_mapper, attribute, object_mapper из sqlalchemy.orm.exc import UnmappedClassError, UnmappedColumnError из sqlalchemy. : for fk in col.foreign_keys: if fk.references(table): return True return False def _history_mapper(local_mapper): cls = local_mapper.class_ # установить флаг "active_history" # на сопоставлении столбцов a ttributes, чтобы старая версия # информации всегда загружалась (в настоящее время устанавливает ее для всех атрибутов) для prop в 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 = [] если не super_mapper или local_mapper.local_table не является super_mapper.local_table: cols = [] для столбца в local_mapper.local_table.c: if column.name == «версия»: continue col = column.copy() col.unique = False # не следует автоматически увеличивать материал из обычной базы данных, если col.autoincrement: col.autoincrement = False #sqllite падает с ключами автоматического увеличения, если у нас есть составной ключ, если col.primary_key: col.primary_key = False, если super_mapper и 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. добавить (столбец), если столбец local_mapper.polymorphi c_on: polymorphic_on = col #if super_mapper: # super_fks.append(("версия", 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: # наследование одной таблицы. возьмите любые дополнительные столбцы, которые могли # быть добавлены, и добавьте их в таблицу истории. для столбца в local_mapper.local_table.c: если column.key отсутствует в super_history_mapper.local_table.c: col = column.copy() super_history_mapper.local_table.append_column(col) table = None, если super_history_mapper: bases = (super_history_mapper.class_,) еще: 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, если не 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_) #Я добавил этот код, так как он давал сбой, иначе def make_mapper(): try: mapper = class_mapper(cls) _history_mapper(mapper) кроме UnmappedClassError: pass create_mappers.append (лямбда: make_mapper()) def versioned_objects(iter): для объектов в 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 = attribute.instance_state (obj) attr = {} obj_changed = False для om, hm в zip (obj_mapper.iterate_to_root(), history_mapper.iterate_to_root()): если hm.single: продолжить для hist_col в hm .local_table.c: если hist_col.key == "версия" или hist_col.key == "изменено" или hist_col.key == "hist_id": continue obj_col = om.local_table.c[hist_col.key] # получить значение атрибута # на основе свойства MapperProperty, связанного с сопоставленным столбцом #. это позволит использовать MapperProperties # с именем ключа, отличным от имени сопоставленного столбца. try: prop = obj_mapper.get_property_by_column(obj_col) кроме UnmappedColumnError: # в случае наследования одной таблицы в сопоставленной таблице могут быть # столбцы, предназначенные только для подкласса. # Статус "несопоставленный" столбца подкласса в # базовом классе является функцией декларативного модуля, начиная с sqla 0.5.2. continue # Атрибуты объектов с истекшим сроком действия, а также отложенные столбцы могут отсутствовать # в словаре. заставить его загружаться, несмотря ни на что, используя getattr(). если prop.key отсутствует в obj_state.dict: getattr(obj, prop.key) a, u, d = attribute.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: # если атрибут не имеет значения. attr[hist_col.key] = a[0] obj_changed = True, если не obj_changed: # не изменено, но отношения есть. ОК # проверьте и их на наличие реквизита в obj_mapper.iterate_properties: if isinstance(prop, RelationshipProperty) and attribute.get_history(obj, prop.key).has_changes(): obj_changed = True break, если не obj_changed и не удален: return attr[" версия"] = obj.version hist = history_cls() для ключа, значение в attr.iteritems(): setattr(hist, key, value) obj.version += 1 класс session.add(hist) VersionedListener(SessionExtension): def before_flush(self, session, flush_context, instances): для obj в versioned_objects(session.dirty): create_version(obj, session) для obj в versioned_objects(session.deleted): create_version(obj, session, delete = True) 

Мы надеемся, что эта статья помогла вам решить проблему. Помимо Управление версиями SQLAlchemy заботится о порядке импорта классов, проверьте другие темы, связанные с __del__.

Хотите преуспеть в Python? Посмотрите наш обзор лучших онлайн-курсов Python 2022. Если вас интересует наука о данных, узнайте также, как изучить программирование на R.

Кстати, этот материал доступен и на других языках:



Angelo Williams

Munchen | 2022-12-01

Я готовился к собеседованию по программированию., спасибо за разъяснение - Управление версиями SQLAlchemy заботится о порядке импорта классов в Python не самый простой.. Вернусь завтра с обратной связью

Dmitry Schteiner

Milan | 2022-12-01

identity всегда немного напрягает 😭 Управление версиями SQLAlchemy заботится о порядке импорта классов это не единственная проблема, с которой я столкнулся. Я просто не совсем уверен, что это лучший метод

Javier Nickolson

Vigrinia | 2022-12-01

Спасибо за объяснение! Я застрял с Управление версиями SQLAlchemy заботится о порядке импорта классов несколько часов, наконец-то получилось 🤗. Вернусь завтра с обратной связью

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

Common xlabel/ylabel for matplotlib subplots

12 answers

NUMPYNUMPY

How to specify multiple return types using type-hints

12 answers

NUMPYNUMPY

Why do I get "Pickle - EOFError: Ran out of input" reading an empty file?

12 answers

NUMPYNUMPY

Flake8: Ignore specific warning for entire file

12 answers

NUMPYNUMPY

glob exclude pattern

12 answers

NUMPYNUMPY

How to avoid HTTP error 429 (Too Many Requests) python

12 answers

NUMPYNUMPY

Python CSV error: line contains NULL byte

12 answers

NUMPYNUMPY

csv.Error: iterator should return strings, not bytes

12 answers

News


Wiki

Python | How to copy data from one Excel sheet to another

Common xlabel/ylabel for matplotlib subplots

Check if one list is a subset of another in Python

sin

How to specify multiple return types using type-hints

exp

Printing words vertically in Python

exp

Python Extract words from a given string

Cyclic redundancy check in Python

Finding mean, median, mode in Python without libraries

cos

Python add suffix / add prefix to strings in a list

Why do I get "Pickle - EOFError: Ran out of input" reading an empty file?

Python - Move item to the end of the list

Python - Print list vertically