Уведомления

Группа в Telegram: @pythonsu
  • Начало
  • » Базы данных
  • » SQLAlchemy, ссылка объекта на самого себя и динамическое создание объекта модели [RSS Feed]

#1 Ноя. 11, 2014 13:58:04

aCL
Зарегистрирован: 2013-11-13
Сообщения: 34
Репутация: +  0  -
Профиль   Отправить e-mail  

SQLAlchemy, ссылка объекта на самого себя и динамическое создание объекта модели

Решил постичь таинства 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
Полученный оbj2 должен быть объектом класса object со всеми теми же атрибутами, в т.ч. properties_t. Этакая рекурсия…

В последствии хотелось бы посредством третьего класса class_getter, динамически создавать новые объекты модели и в последствии с ними работать. Примерно таким образом:
>>> 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))
Как видно, сейчас всё работает прекрасно. Но вот если раскомментировать reletionship и ref_object, то получается (даже при obj=session.query(object).filter(object.object_id==1).one()), то получаю ошибку:
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.

Собсна, два вопроса
1. Возможно ли достичь желаемой “рекурсии”
2. Если возможно - как это сделать (где у меня ошибка)?

Буду очень благодарен за потраченное на меня время.

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) #возвращаем новый динамически созданный класс

Офлайн

#2 Ноя. 11, 2014 15:05:44

4kpt_II
От: Харьков
Зарегистрирован: 2013-10-24
Сообщения: 999
Репутация: +  58  -
Профиль   Отправить e-mail  

SQLAlchemy, ссылка объекта на самого себя и динамическое создание объекта модели

Ад…

Офлайн

#3 Ноя. 11, 2014 15:11:59

aCL
Зарегистрирован: 2013-11-13
Сообщения: 34
Репутация: +  0  -
Профиль   Отправить e-mail  

SQLAlchemy, ссылка объекта на самого себя и динамическое создание объекта модели

Угу. Но раньше было хуже)
Можно было бы описать все таблицы, но их, извините, 310 штук. А такая структура позволяет описывать все объекты динамически, посредством class_getter. Если, конечно, всё срастется)

Как вариант - можно получать не все связанные объекты, а только

ref_object_label_property=Column(INTEGER(unsigned=True)) #ID свойства класса, значение которого отображать

Офлайн

#4 Ноя. 11, 2014 15:24:28

4kpt_II
От: Харьков
Зарегистрирован: 2013-10-24
Сообщения: 999
Репутация: +  58  -
Профиль   Отправить e-mail  

SQLAlchemy, ссылка объекта на самого себя и динамическое создание объекта модели

aCL
В первой хранятся классы:
1. их id;
2. name (системное имя);
3. table (имя таблицы БД, в которой хранятся экземпляры класса)

Я вообще не пойму, что это
Какие классы? Что имеется ввиду? Пример такого класса.

aCL
Во второй - список свойств этих классов:
1. id свойства;
2. id класса, которому принадлежит свойство - object.object_id
3. системное название свойство, совпадает с именем поля в таблице object.table
4. ref_object - id объекта, на которое это свойство ссылается
5. пр.

Вообще просто стало плохо. Т.е. у разных классов разные свойства? Или их значения?

Хард. У меня возникает ощущение, что Вы что-то не то или не совсем так делаете…

Офлайн

#5 Ноя. 11, 2014 15:25:01

aCL
Зарегистрирован: 2013-11-13
Сообщения: 34
Репутация: +  0  -
Профиль   Отправить e-mail  

SQLAlchemy, ссылка объекта на самого себя и динамическое создание объекта модели

Удобство заключено ещё в одном.
Проект очень динамично развивается. Регулярно (несколько раз в месяц) появляются новые таблицы БД и новые свойства, в т.ч. для уже существующих таблиц. Если метаданные новых/существующих таблиц описываются (как сейчас) внутри таблиц a_object и a_properties, это лишает нас необходимости вносить изменения в код модели (моделей) - достаточно добавить новые записи в список объектов и свойств. Просто и удобно.

На Perl'е у ребят всё реализовано. Увы, с Python'ом они не знакомы…

Офлайн

#6 Ноя. 11, 2014 15:31:01

4kpt_II
От: Харьков
Зарегистрирован: 2013-10-24
Сообщения: 999
Репутация: +  58  -
Профиль   Отправить e-mail  

SQLAlchemy, ссылка объекта на самого себя и динамическое создание объекта модели

Я же не спрашивал про “удобство”. Я задал конкретные вопросы. Вы сможете на них ответить?
Уверен, что такая схема имеет место быть и возможно зачем-то нужна, но чтобы помочь, нужно понять - зачем…

Отредактировано 4kpt_II (Ноя. 11, 2014 15:31:27)

Офлайн

#7 Ноя. 11, 2014 16:07:01

bismigalis
Зарегистрирован: 2010-10-02
Сообщения: 449
Репутация: +  47  -
Профиль   Отправить e-mail  

SQLAlchemy, ссылка объекта на самого себя и динамическое создание объекта модели

а в чем разница между properties.object_id и properties.ref_object ?

Офлайн

#8 Ноя. 11, 2014 16:13:57

aCL
Зарегистрирован: 2013-11-13
Сообщения: 34
Репутация: +  0  -
Профиль   Отправить e-mail  

SQLAlchemy, ссылка объекта на самого себя и динамическое создание объекта модели

Когда писал свой ответ, Вашего ещё не было. Уж извините за нерасторопность

Имеются в виду классы архитектуры проекта.
Если совсем просто, то:
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)

Офлайн

#9 Ноя. 11, 2014 16:20:04

aCL
Зарегистрирован: 2013-11-13
Сообщения: 34
Репутация: +  0  -
Профиль   Отправить e-mail  

SQLAlchemy, ссылка объекта на самого себя и динамическое создание объекта модели

bismigalis
а в чем разница между properties.object_id и properties.ref_object ?
object_id - id объекта, которому принадлежит свойство, Column(Integer)
ref_object - ссылка на объект, Column(Integer, ForeignKey('a_objects.object_id')).

Показывает, собственно, что у таблицы с a_object.object_id=object_id есть связь с таблицей a_objects.object_id=ref_object

Отредактировано aCL (Ноя. 11, 2014 16:20:32)

Офлайн

#10 Ноя. 11, 2014 16:47:24

bismigalis
Зарегистрирован: 2010-10-02
Сообщения: 449
Репутация: +  47  -
Профиль   Отправить e-mail  

SQLAlchemy, ссылка объекта на самого себя и динамическое создание объекта модели

т.е. свойство может принадлежать одному объекту и ссылаться на другой?

Отредактировано bismigalis (Ноя. 11, 2014 17:07:56)

Офлайн

  • Начало
  • » Базы данных
  • » SQLAlchemy, ссылка объекта на самого себя и динамическое создание объекта модели[RSS Feed]

Board footer

Модераторировать

Powered by DjangoBB

Lo-Fi Version