SQLAlchemy: изменяет ли ящик объекты моего объекта?

Я пытаюсь использовать SQLAlchemy для хранения моих объектов в базе данных. Для этой цели у меня есть функция save(...):

#!/usr/bin/env python
# encoding: utf-8
from sqlalchemy import Column, Integer, MetaData, String, Table, create_engine
from sqlalchemy.orm import mapper, sessionmaker
class MyClass(object):
 def __init__(self, title):
 self.title = title
 def __str__(self):
 return '%s' % (self.title)
def save(object_list):
 metadata = MetaData()
 my_class_table = Table('my_class',
 metadata,
 Column('id', Integer, primary_key=True),
 Column('title', String(255), nullable=False))
 # everything is OK here, output:
 # some title
 # another title
 # yet another title
 for obj in object_list:
 print obj
 mapper(MyClass, my_class_table)
 # on Linux / SQLAlchemy 0.6.8, this fails with
 # Traceback (most recent call last):
 # File "./test.py", line 64, in <module>
 # save(my_objects)
 # File "./test.py", line 57, in save
 # print obj
 # File "./test.py", line 11, in __str__
 # return '%s' % (self.title)
 # File "/usr/lib/python2.7/dist-packages/sqlalchemy/orm/attributes.py", line 167, in __get__
 # return self.impl.get(instance_state(instance),
 # AttributeError: 'NoneType' object has no attribute 'get'
 # on Mac OSX / SQLAlchemy 0.7.5, this fails with
 # Traceback (most recent call last):
 # File "./test.py", line 64, in <module>
 # save(my_objects)
 # File "./test.py", line 57, in save
 # print obj
 # File "./test.py", line 11, in __str__
 # return '%s' % (self.title)
 # File "/Library/Python/2.7/site-packages/sqlalchemy/orm/attributes.py", line 165, in __get__
 # if self._supports_population and self.key in dict_:
 # File "/Library/Python/2.7/site-packages/sqlalchemy/orm/attributes.py", line 139, in __getattr__
 # key)
 # AttributeError: Neither 'InstrumentedAttribute' object nor 'Comparator' object has an attribute '_supports_population'
 for obj in object_list:
 print obj
 # (more code to set up engine and session...)
if __name__ == '__main__':
 my_objects = [MyClass('some title'), MyClass('another title'), MyClass('yet another title')]
 save(my_objects)
</module></module>

Мне кажется, что mapper делает что-то с моими объектами неявно, когда я создаю mapper, и я больше не могу их использовать. Из того, что я читал в подобных вопросах, это не совсем неизвестно.

Ожидается ли такое поведение? Я здесь что-то не так? Каков правильный способ отображения и хранения объектов?

Дополнительная информация: Я использую SQLAlchemy 0.7.5 с системным по умолчанию Python 2.7.1 на Mac OSX 10.7.3 Lion и SQLAlchemy 0.6.8 с системным по умолчанию Python 2.7.2+ на виртуальной машине Kubuntu 11.10.

Update: Кажется, что SQLAlchemy mapper хорошо известен для изменения объектов в "SQLAlchemy needs". Решение в связанной статье состоит в создании объектов после вызова mapper(...). У меня уже есть допустимые объекты, но я больше не могу их использовать... Как получить SQLAlchemy для хранения моих объектов?

Обновление 2: У меня создается впечатление, что я неправильно понимаю что-то фундаментальное в отношении ORM: Я думал, что концепция mapper SQLAlchemy дает мне способ определить мои объекты и работать с ними в моем приложении, отделенном от любой базы данных, - и только один раз, когда я хочу их сохранить, я привожу SQLAlchemy и выполняю всю тяжелую работу по отображению класса к таблице базы данных. Кроме того, я не вижу никакой архитектурной причины, по которой SQLAlchemy следует упоминать где-либо еще в моем коде, чем в функциях сохранения save(...) и load(...). Однако, посмотрев первый ответ ниже, я понимаю, что должен сопоставлять класс с таблицей базы данных, прежде чем использовать какой-либо из моих объектов; это будет в самом начале моей программы. Может быть, я здесь что-то не так, но это выглядит довольно решительным ограничением дизайна со стороны SQLAlchemy - все свободное соединение исчезло. Имея это в виду, я также не вижу преимущества наличия картографа в первую очередь, поскольку "поздняя связь", которую я хочу, не представляется технически возможной с SQLAlchemy, и я мог бы просто сделать это "декларативный стиль", объединить бизнес-логику с кодом стойкости: - (

Обновление 3: Я вернулся к квадрату и снова прочитал SQLAlchemy. Он говорит прямо на главной странице :

SQLAlchemy наиболее известен своим объектно-реляционным картотером (ORM), необязательным компонентом, который предоставляет шаблон отображения данных, где классы могут быть сопоставлены с базой данных открытым и несколькими способами, что позволяет модели объекта и схемы базы данных развиваться в чистом виде с самого начала.

Однако, основываясь на моем опыте до сих пор, SQLAlchemy, похоже, вообще не выполняет этого обещания, поскольку он заставляет меня с самого начала сочетать объектную модель и схему базы данных. В конце концов, я слышал о SQLAlchemy, мне действительно трудно поверить, что это действительно так, и я предпочел бы, что я еще не понял что-то основное. Что я делаю неправильно?

Обновление 4:

Для полноты я хотел бы добавить рабочий пример кода, который выполняет все сопоставление, прежде чем создавать какие-либо бизнес-объекты:

#!/usr/bin/env python
# encoding: utf-8
from sqlalchemy import Column, Integer, MetaData, String, Table, create_engine
from sqlalchemy.orm import mapper, sessionmaker
class MyClass(object):
 def __init__(self, title):
 self.title = title
 def __str__(self):
 return '%s' % (self.title)
def save(object_list, metadata):
 engine = create_engine('sqlite:///:memory:')
 metadata.create_all(engine)
 Session = sessionmaker(bind=engine)
 session = Session()
 for obj in object_list:
 try:
 session.add(obj)
 session.commit()
 except Exception as e:
 print e
 session.rollback()
 # query objects to test they're in there
 all_objs = session.query(MyClass).all()
 for obj in all_objs:
 print 'object id :', obj.id
 print 'object title:', obj.title
if __name__ == '__main__':
 metadata = MetaData()
 my_class_table = Table('my_class',
 metadata,
 Column('id', Integer, primary_key=True),
 Column('title', String(255), nullable=False))
 mapper(MyClass, my_class_table)
 my_objects = [MyClass('some title'), MyClass('another title'), MyClass('yet another title')]
 save(my_objects, metadata)

В моем собственном (не выборочном) коде я импортирую MyClass из своего собственного модуля и выполняю сопоставление в отдельном классе "репозиторий объектов", который создает MetaData в своем методе __init__(...) и сохраняет его в член, поэтому методы устойчивости save(...) и load(...) могут получить к нему доступ по мере необходимости. Все основное приложение, которое нужно сделать, это создать объект репозитория в самом начале; это содержит влияние SQLAlchemy на дизайн для одного класса, но также распределяет бизнес-объект и сопоставленные определения таблиц для разделения местоположений в коде. Не уверен, что если я займусь этим долговременным сроком, но, похоже, он работает на данный момент. Окончательный быстрый совет для SQLAlchemy noobs, как и я: вы должны работать с одним и тем же объектом метаданных, иначе вы получите исключения, такие как no such table или class not mapped.

2 ответа

SQLAlchemy абсолютно модифицирует отображаемый класс. SQLAlchemy называет эту аппаратуру.

Обратите внимание:

>>> print(MyClass.__dict__)
{'__module__': '__main__', '__str__': <function __str__="" at="" 0x106d50398="">, '__dict__':
<attribute '__dict__'="" of="" 'myclass'="" objects="">, '__weakref__': <attribute '__weakref__'="" of="" 'myclass'="" objects="">, '__doc__': None, '__init__': <function __init__="" at="" 0x106d4b848="">}
>>> mapper(MyClass, my_class_table)
Mapper|MyClass|my_class
>>> print(MyClass.__dict__)
{'__module__': '__main__', '_sa_class_manager': <classmanager of="" <class="" '__main__.myclass'=""> at 7febea6a7f40>, 'title': 
<sqlalchemy.orm.attributes.instrumentedattribute object="" at="" 0x10fba4f90="">, '__str__': 
<function __str__="" at="" 0x10fbab398="">, 'id': <sqlalchemy.orm.attributes.instrumentedattribute object="" at="" 0x10fba4e90="">, '__dict__': <attribute '__dict__'="" of="" 'myclass'="" objects="">, 
'__weakref__': <attribute '__weakref__'="" of="" 'myclass'="" objects="">, '__doc__': None, '__init__':
 <function __init__="" at="" 0x10fbab410="">}
</function></attribute></attribute></sqlalchemy.orm.attributes.instrumentedattribute></function></sqlalchemy.orm.attributes.instrumentedattribute></classmanager></function></attribute></attribute></function>

Также существует разница между обычными экземплярами MyClass и инструментальными MyClass экземплярами:

>>> prem = MyClass('premapper')
>>> mapper(MyClass, my_class_table)
Mapper|MyClass|my_class
>>> postm = MyClass('postmapper')
>>> print prem
{'title': 'premapper'}
>>> print postm
{'_sa_instance_state': <sqlalchemy.orm.state.instancestate object="" at="" 0x10e5b6d50="">, 'title': 'postmapper'}
</sqlalchemy.orm.state.instancestate>

Ошибка NoneType заключается в том, что теперь инструментальная MyClass.title (которая была заменена дескриптором InstrumentedAttribute) пытается получить свойство _sa_instance_state через instance_state(instance). _sa_instance_state создается инструментальным MyClass при создании объекта, но не с помощью неинструментального MyClass.

Инструментарий необходим. Это делается для того, чтобы атрибут доступа, назначения и других важных изменений состояния объекта мог быть передан картографу с использованием дескрипторов. (Например, как можно было бы отложить загрузку столбцов или даже доступ к коллекции без изменения класса для контроля доступа к атрибутам в объекте?) MyClass объекты не привязаны к таблице, но их необходимо привязать к картографу. Вы можете изменить параметры сопоставления и таблицу без изменения кода объектов домена, но не обязательно сами объекты домена. (В конце концов, вы можете подключить один объект к нескольким картам и нескольким таблицам.)

Подумайте о том, что инструментарий прозрачно добавляет реализацию "Тема" к отображенному классу для картографа "Наблюдатель" в шаблоне субъекта-наблюдателя. Очевидно, что вы не можете реализовать шаблон субъекта-наблюдателя на произвольном объекте - вам нужно согласовать наблюдаемый объект с интерфейсом Subject.

Я полагаю, что возможно создать экземпляр на месте, создав для него объект _sa_instance_state (я не знаю, как - мне нужно было бы прочитать код mapper()), но я не знаю, почему это необходимо. Если есть вероятность, что ваш объект будет сохранен SQLAlchemy, тогда просто определите отображение и таблицу для этой персистенции, прежде чем создавать какие-либо экземпляры объекта. Вам не нужно создавать движок или сеанс или иметь какую-либо базу данных, чтобы определить отображение.

Единственный способ, которым вы даже можете избавиться от использования полностью неинструментальных объектов приложения, - это то, что ваш случай использования чрезвычайно тривиален, что-то вроде эквивалента pickle или unpickle dicts. Например, без инструментария вы никогда не сможете загружать или сохранять связанные объекты в атрибутах коллекции. Вы действительно никогда не намереваетесь это делать? Если это так, возможно, вы будете счастливее, если вообще не используете sqlalchemy.orm и просто используете базовый Expression API, чтобы сохранить свои экземпляры?


Вы должны выполнить сопоставление перед созданием объектов MyClass, либо в начале "if", либо до этого.

Есть ли какая-то конкретная причина, по которой вам нужно создавать экземпляры, прежде чем даже решить, какую таблицу они сопоставляют?

licensed under cc by-sa 3.0 with attribution.