Форум сайта python.su
Решил постичь таинства ORM, выбрал SQLAlchemy.
Возник некоторый затык с relationship, благо литературы на русском не нашел (за исключением викиучебника с азами), а английский не настолько хорош, чтобы всё в англоязычной документации понять.
Вопрос такой.
В самом низу поста описанный код модели, самые незначительные атрибуты я убрал. Пока что обращаем внимание на первые два класса.
В БД есть две таблицы (помимо прочих).
В первой хранятся классы:
1. их id;
2. name (системное имя);
3. table (имя таблицы БД, в которой хранятся экземпляры класса)
4. пр.
Во второй - список свойств этих классов:
1. id свойства;
2. id класса, которому принадлежит свойство - object.object_id
3. системное название свойство, совпадает с именем поля в таблице object.table
4. ref_object - id объекта, на которое это свойство ссылается
5. пр.
В текущем варианте все ок, но properties.ref_object определен как обычный INTEGER, а должен являться ещё и Foreign key'ем с соответствующими relationship. Этот вариант сейчас, как можно увидеть, закомментирован, как и соответствующий relationship
Что нужно:
1. Получаем объект obj=session.query(object).filter(object.object_id==1).one()
2. lst=o.properties_t - список свойств объекта
3.
for el in lst:
if el.ref_object:
obj2=el.object
break
>>> report=class_getter().get(classname='bill_report')
>>> el=session.query(report).filter(report.msisdn==79030000000).filter(report.period==201410).one()
>>> print("Счет номера {} за период {} : {}".format(el.msisdn,el.period,el.sumAmount))
Счет номера 79030000000 за период 201410 : 0.0000))
sqlalchemy.exc.AmbiguousForeignKeysError: Could not determine join condition between parent/child tables on relationship object.properties_t - there are multiple foreign key paths linking the tables. Specify the 'foreign_keys' argument, providing a list of those columns which should be counted as containing a foreign key reference to the parent table.
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import create_engine,Column,ForeignKey from sqlalchemy.dialects.mysql import MEDIUMINT,DECIMAL,BIGINT,INTEGER,VARCHAR,DATETIME,TINYINT,ENUM,LONGTEXT from sqlalchemy.orm.session import sessionmaker from sqlalchemy.orm import relationship,backref from sqlalchemy.orm.exc import MultipleResultsFound engine=create_engine('mysql+mysqlconnector://' 'user:password' '@host:3306/db') Base=declarative_base() class object(Base): __tablename__='a_objects' object_id=Column(INTEGER(unsigned=True),primary_key=True,nullable=False) name=Column(VARCHAR(length=50),nullable=False,unique=True) ru_name=Column(VARCHAR(length=50),nullable=False) description=Column(VARCHAR(length=250)) table=Column(VARCHAR(length=50)) date_in=Column(DATETIME,nullable=False) link=Column(TINYINT(unsigned=True),nullable=False) properties_t=relationship('properties',backref='a_properties') class properties(Base): __tablename__='a_properties' property_id=Column(MEDIUMINT(unsigned=True),primary_key=True,nullable=False) object_id=Column(INTEGER(unsigned=True),ForeignKey('a_objects.object_id'),nullable=False) #ID класса, которому принадлежит свойство name=Column(VARCHAR(length=50),nullable=False) #системное именование свойства ru_name=Column(VARCHAR(length=64),nullable=False) #Отображаемое название свойства data_type=Column(ENUM('varchar','int','date','dec','text'),nullable=False) #Тип данных свойства #ref_object=Column(INTEGER(unsigned=True),ForeignKey('a_objects.object_id')) #ID класса, на экземпляр которого ссылается свойство ref_object=Column(INTEGER(unsigned=True)) #ID класса, на экземпляр которого ссылается свойство ref_object_label=Column(VARCHAR(length=50,unicode=True,charset='utf8')) #название отображаемого свойства ref_object_label_property=Column(INTEGER(unsigned=True)) #ID свойства класса, значение которого отображать required=Column(TINYINT(unsigned=True),nullable=False) #Обязательно к указанию значения #object=relationship('object',uselist=False,backref=backref('a_object')) class class_getter(): def get(self,classname=None,class_id=None): def get_link_attrib(object_id,prop_id): rez_obj=class_getter().get(class_id=object_id) rel=relationship('rez_obj') #из атрибутов класса собираем модель. #каждый атрибут - новый Column def getattr(attrib): types={ 'varchar':VARCHAR, 'int':INTEGER, 'date':DATETIME, 'dec':DECIMAL, 'text':LONGTEXT } out_attrib=Column(attrib.name,types[attrib.data_type]) if attrib.required: out_attrib.nullable=False else: out_attrib.nullable=True try: get_link_attrib(attrib.ref_object,attrib.ref_object_label_property) except: pass return out_attrib if classname: try: rezult=session.query(object).filter(object.name==classname).one() except MultipleResultsFound: print('Objects with classname="{}">1'.format(classname)) return if class_id: rezult=session.query(object).filter(object.object_id==class_id).one() attributes={'__tablename__':'o_'+rezult.table} for attrib in rezult.properties_t: attributes[attrib.name]=getattr(attrib) attributes['i_id']=Column('i_id',INTEGER,nullable=False,primary_key=True) return type(rezult.name,(Base,),attributes) #возвращаем новый динамически созданный класс
Офлайн
Ад…
Офлайн
Угу. Но раньше было хуже)
Можно было бы описать все таблицы, но их, извините, 310 штук. А такая структура позволяет описывать все объекты динамически, посредством class_getter. Если, конечно, всё срастется)
Как вариант - можно получать не все связанные объекты, а только
ref_object_label_property=Column(INTEGER(unsigned=True)) #ID свойства класса, значение которого отображать
Офлайн
aCL
В первой хранятся классы:
1. их id;
2. name (системное имя);
3. table (имя таблицы БД, в которой хранятся экземпляры класса)
aCL
Во второй - список свойств этих классов:
1. id свойства;
2. id класса, которому принадлежит свойство - object.object_id
3. системное название свойство, совпадает с именем поля в таблице object.table
4. ref_object - id объекта, на которое это свойство ссылается
5. пр.
Офлайн
Удобство заключено ещё в одном.
Проект очень динамично развивается. Регулярно (несколько раз в месяц) появляются новые таблицы БД и новые свойства, в т.ч. для уже существующих таблиц. Если метаданные новых/существующих таблиц описываются (как сейчас) внутри таблиц a_object и a_properties, это лишает нас необходимости вносить изменения в код модели (моделей) - достаточно добавить новые записи в список объектов и свойств. Просто и удобно.
На Perl'е у ребят всё реализовано. Увы, с Python'ом они не знакомы…
Офлайн
Я же не спрашивал про “удобство”. Я задал конкретные вопросы. Вы сможете на них ответить?
Уверен, что такая схема имеет место быть и возможно зачем-то нужна, но чтобы помочь, нужно понять - зачем…
Отредактировано 4kpt_II (Ноя. 11, 2014 15:31:27)
Офлайн
а в чем разница между properties.object_id и properties.ref_object ?
Офлайн
Когда писал свой ответ, Вашего ещё не было. Уж извините за нерасторопность
Имеются в виду классы архитектуры проекта.
Если совсем просто, то:
1. “классы” (a_objects) - список всех таблицы БД с рядом служебных свойств;
2. “свойства” (a_properties) - список всех полей таблиц, перечисленных в a_objects с рядом служебных свойств - метаданных, “свойств свойств”.
Например, есть “класс” с a_object.object_id=9 “agree” (таблица o_agree), в которой хранятся договора с клиентами. Если мы откроем таблицу agree, то увидим там поля agree_id,name,manager,begin_state и т.п.
И! Если мы применим запрос SELECT name FROM a_properties WHERE object_id=9, то получим точно такой же список полей: agree_id,name,manager и т.д. Только помимо поля, хранящего названий полей таблиц, в a_properties определены поля, хранящие и ряд прочих параметров. Например, тип данных каждого свойства.
Метод `get` класса `class_getter` должен динамически описать новый объект модели SQLAlchemy (новую таблицу) по запросу id класса (таблицы БД, a_objects.object_id), либо его имени (a_objects.name).
>>> agr=class_getter().get('agree') #создаем новый ORM-объект, не описанный в моей модели >>> agr <class 'bill_classes.agree'> #код выше, такого класса вы в нем не найдете >>> agr.__dict__ #свойства этого объекта. #Названия Column-свойств совпадают с названием столбцов таблицы agree... #... и с названием свойств объекта object_id.name=='agree',... #... перечисленных в таблице a_properties вместе с именами полей других таблиц mappingproxy({'__module__': 'bill_classes', 'manager': <sqlalchemy.orm.attributes.InstrumentedAttribute object at 0x033AEC50>, '__tabl e__': Table('o_agree', MetaData(bind=None), Column('status', INTEGER(), table=<o _agree>), Column('export_1c', INTEGER(), table=<o_agree>), Column('number', VARC HAR(), table=<o_agree>), Column('begin_date', DATETIME(), table=<o_agree>), Colu mn('end_date', DATETIME(), table=<o_agree>)...}) >>>
Отредактировано aCL (Ноя. 11, 2014 16:18:38)
Офлайн
bismigalisobject_id - id объекта, которому принадлежит свойство, Column(Integer)
а в чем разница между properties.object_id и properties.ref_object ?
Отредактировано aCL (Ноя. 11, 2014 16:20:32)
Офлайн
т.е. свойство может принадлежать одному объекту и ссылаться на другой?
Отредактировано bismigalis (Ноя. 11, 2014 17:07:56)
Офлайн