SQLAlchemy─将一个类映射到多个表

# ! /usr/bin/env python # -*- coding: utf-8 -*- # login_frontend.py """ Python 2.7.3 Cherrypy 3.2.2 PostgreSQL 9.1 psycopy2 2.4.5 SQLAlchemy 0.7.10 """ 

我在join一个Python / SQLAlchemy类中的四个表时遇到问题。 我试着这样做,所以我可以遍历这个类的实例,而不是通过ORM连接表获得的名称元组。

为什么这一切? 因为我已经开始这么做了,所以我走得太远了,放弃了。 另外,它必须是可能的,所以我想知道它是如何完成的。

对于这个项目(cherrypy web-frontend),我得到了一个已经完成的表类的模块。 我把它移到了这篇文章的底部,因为也许这对你来说甚至是不必要的。

以下仅是联合多表类尝试的一个示例。 我选了一个不止两个表格和一个联结表的简单情况。 在这里,我不写入这些连接的表,但它是必要的其他地方。 这就是为什么课程将是这个问题的一个很好的解决scheme。


我的一个联合class的尝试,

这是给定的表类模块和这两个网站的例子的组合:

– 将一个类映射到多个表
– SQLAlchemy:一个类 – 两个表

 class JoinUserGroupPerson (Base): persons = md.tables['persons'] users = md.tables['users'] user_groups = md.tables['user_groups'] groups = md.tables['groups'] user_group_person =( join(persons, users, persons.c.id == users.c.id). join(user_groups, users.c.id == user_groups.c.user_id). join(groups, groups.c.id == user_groups.c.group_id)) __table__ = user_group_person """ I expanded the redefinition of 'id' to three tables, and removed this following one, since it made no difference: users_id = column_property(users.c.id, user_groups.c.user_id) """ id = column_property(persons.c.id, users.c.id, user_groups.c.user_id) groups_id = column_property(groups.c.id, user_groups.c.group_id) groups_name = groups.c.name def __init__(self, group_name, login, name, email=None, phone=None): self.groups_name = group_name self.login = login self.name = name self.email = email self.phone = phone def __repr__(self): return( "<JoinUserGroupPerson('%s', '%s', '%s', '%s', '%s')>" %( self.groups_name, self.login, self.name, self.email, self.phone)) 

使用此连接类访问不同的表

  • 这是我试图在另一个模块中查询这个类的方法:

     pg = sqlalchemy.create_engine( 'postgresql://{}:{}@{}:{}/{}'. format(user, password, server, port, data)) Session = sessionmaker(bind=pg) s1 = Session() query = (s1.query(JoinUserGroupPerson). filter(JoinUserGroupPerson.login==user). order_by(JoinUserGroupPerson.id)) record = {} for rowX in query: for colX in rowX.__table__.columns: record[column.name] = getattr(rowX,colX.name) """ RESULT: """ Traceback (most recent call last): File "/usr/local/lib/python2.7/dist-packages/cherrypy/_cprequest.py", line 656, in respond response.body = self.handler() File "/usr/local/lib/python2.7/dist-packages/cherrypy/lib/encoding.py", line 228, in __call__ ct.params['charset'] = self.find_acceptable_charset() File "/usr/local/lib/python2.7/dist-packages/cherrypy/lib/encoding.py", line 134, in find_acceptable_charset if encoder(encoding): File "/usr/local/lib/python2.7/dist-packages/cherrypy/lib/encoding.py", line 86, in encode_string for chunk in self.body: File "XXX.py", line YYY, in ZZZ record[colX.name] = getattr(rowX,colX.name) AttributeError: 'JoinUserGroupPerson' object has no attribute 'user_id' 
  • 然后我检查了表的属性:

     for rowX in query: return (u'{}'.format(rowX.__table__.columns)) """ RESULT: """ ['persons.id', 'persons.name', 'persons.email', 'persons.phone', 'users.id', 'users.login', 'user_groups.user_id', 'user_groups.group_id', 'groups.id', 'groups.name'] 
  • 然后,我检查了,如果查询或我的class级根本不工作,通过使用计数器。 我起床(count == 5),所以前两个连接的表。 但是当我将条件设置为(count == 6)时,我又收到了第一条错误消息。 AttributeError:'JoinUserGroupPerson'对象没有属性'user_id':

     list = [] for rowX in query: for count, colX in enumerate(rowX.__table__.columns): list.append(getattr(rowX,colX.name)) if count == 5: break return (u'{}'.format(list)) """ RESULT: """ [4, u'user real name', None, None, 4, u'user'] """ which are these following six columns: persons[id, name, email, phone], users[id, login] """ 
  • 然后我检查了每一列:

     list = [] for rowX in query: for colX in rowX.__table__.columns: list.append(colX) return (u'{}'.format(list)) """ RESULT: """ [Column(u'id', INTEGER(), table=, primary_key=True, nullable=False, server_default=DefaultClause(, for_update=False)), Column(u'name', VARCHAR(length=252), table=, nullable=False), Column(u'email', VARCHAR(), table=), Column(u'phone', VARCHAR(), table=), Column(u'id', INTEGER(), ForeignKey(u'persons.id'), table=, primary_key=True, nullable=False), Column(u'login', VARCHAR(length=60), table=, nullable=False), Column(u'user_id', INTEGER(), ForeignKey(u'users.id'), table=, primary_key=True, nullable=False), Column(u'group_id', INTEGER(), ForeignKey(u'groups.id'), table=, primary_key=True, nullable=False), Column(u'id', INTEGER(), table=, primary_key=True, nullable=False), Column(u'name', VARCHAR(length=60), table=, nullable=False)] 
  • 然后我又尝试了两次直接访问,这两次访问都让我有'id'和'persons.id'两个KeyErrors:

     for rowX in query: return (u'{}'.format(rowX.__table__.columns['id'].name)) for rowX in query: return (u'{}'.format(rowX.__table__.columns['persons.id'].name)) 

结论

我尝试了其他一些更令人困惑的事情。 由于他们没有透露更多的信息,所以我没有添加它们,反正这个post已经够长了。 我真的很感谢这个事情的一些帮助,因为我没有看到,我的class级错在哪里。

我想,不知何故,我必须以某种方式设置课程,只能正确join前两个表格。 但是连接至less部分工作,因为当'user_groups'表为空时,我也得到一个空的查询。

或者,也许我做了这个'user_groups'表的映射错误。 由于连接一些列是双重的,他们需要一个额外的定义。 而'user_id'已经是users和users表的一部分,所以我必须映射两次。

我甚至试图从联接中删除'user_groups'表,因为它在关系(与次要)。 它给了我一个外键的错误信息。 但也许我做错了。

无可否认,我甚至不知道为什么…

 rowX.__table__.columns # column names as table name suffix 

…具有不同的属性名称…

 colX in rowX.__table__.columns # column names without table names 

所以请帮助,谢谢。


额外的编辑

  • 另一个想法! 所有这一切都可以inheritance? 每个类都有自己的映射,但是user_groups类可能是必需的。 连接必须在单个类之间。 init()和repr()仍然需要重新定义。

  • 这可能与'user_groups'表有关,因为我甚至无法用'groups'或'users'表join它。 它总是说,类对象没有属性“user_id”。 也许这是关于多对多的关系。


附件

下面是已经给定的SQLAlchemy模块,带有头文件,没有关于数据库的特定信息以及连接表的类:

 #!/usr/bin/python # vim: set fileencoding=utf-8 : import sqlalchemy from sqlalchemy import join from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import relationship, backref, column_property pg = sqlalchemy.create_engine( 'postgresql://{}@{}:{}/{}'.format(user, host, port, data)) md = sqlalchemy.MetaData(pg, True) Base = declarative_base() """ ... following, three of the four joined tables. UserGroups isn't necessary, so it wasn't part of the module. And the other six classes shouldn't be important for this ... """ class Person(Base): __table__ = md.tables['persons'] def __init__(self, name, email=None, phone=None): self.name = name self.email = email self.phone = phone def __repr__(self): return( "<Person(%s, '%s', '%s', '%s')>" %( self.id, self.name, self.email, self.phone)) class Group(Base): __table__ = md.tables['groups'] def __init__(self, name): self.name = name def __repr__(self): return("<Group(%s, '%s')>" %(self.id, self.name)) class User(Base): __table__ = md.tables['users'] person = relationship('Person') groups = relationship( 'Group', secondary=md.tables['user_groups'], order_by='Group.id', backref=backref('users', order_by='User.login')) def __init__(self, person, login): if isinstance(person, Person): self.person = person else: self.id = person self.login = login def __repr__(self): return("<User(%s, '%s')>" %(self.id, self.login)) 

也许下面的脚本,创build数据库,也已经给出,将在这里certificate是有用的。 作为它的最后一部分来了一些testing数据 – 但列之间应该是制表符,没有空格。 正因为如此,这个脚本也可以在github上find:

 -- file create_str.sql -- database creation script -- central script for creating all database objects -- set the database name \set strdbname logincore \c admin BEGIN; \i str_roles.sql COMMIT; DROP DATABASE IF EXISTS :strdbname; CREATE DATABASE :strdbname TEMPLATE template1 OWNER str_db_owner ENCODING 'UTF8'; \c :strdbname SET ROLE str_db_owner; BEGIN; \i str.sql COMMIT; RESET ROLE; -- file str_roles.sql -- create roles for the database -- owner of the database objects SELECT create_role('str_db_owner', 'NOINHERIT'); -- role for using SELECT create_role('str_user'); -- make str_db_owner member in all relevant roles GRANT str_user TO str_db_owner WITH ADMIN OPTION; -- file str.sql -- creation of database -- prototypes \i str_prototypes.sql -- domain for non empty text CREATE DOMAIN ntext AS text CHECK (VALUE<>''); -- domain for email addresses CREATE DOMAIN email AS varchar(252) CHECK (is_email_address(VALUE)); -- domain for phone numbers CREATE DOMAIN phone AS varchar(60) CHECK (is_phone_number(VALUE)); -- persons CREATE TABLE persons ( id serial PRIMARY KEY, name varchar(252) NOT NULL, email email, phone phone ); GRANT SELECT, INSERT, UPDATE, DELETE ON persons TO str_user; GRANT USAGE ON SEQUENCE persons_id_seq TO str_user; CREATE TABLE groups ( id integer PRIMARY KEY, name varchar(60) UNIQUE NOT NULL ); GRANT SELECT ON groups TO str_user; -- database users CREATE TABLE users ( id integer PRIMARY KEY REFERENCES persons(id) ON UPDATE CASCADE, login varchar(60) UNIQUE NOT NULL ); GRANT SELECT ON users TO str_user; -- user <-> groups CREATE TABLE user_groups ( user_id integer NOT NULL REFERENCES users(id) ON UPDATE CASCADE ON DELETE CASCADE, group_id integer NOT NULL REFERENCES groups(id) ON UPDATE CASCADE ON DELETE CASCADE, PRIMARY KEY (user_id, group_id) ); -- functions \i str_functions.sql -- file str_prototypes.sql -- prototypes for database -- simple check for correct email address CREATE FUNCTION is_email_address(email varchar) RETURNS boolean AS $CODE$ SELECT FALSE $CODE$ LANGUAGE sql IMMUTABLE STRICT; -- simple check for correct phone number CREATE FUNCTION is_phone_number(nr varchar) RETURNS boolean AS $CODE$ SELECT FALSE $CODE$ LANGUAGE sql IMMUTABLE STRICT; -- file str_functions.sql -- functions for database -- simple check for correct email address CREATE OR REPLACE FUNCTION is_email_address(email varchar) RETURNS boolean AS $CODE$ SELECT $1 ~ E'^[A-Za-z0-9.!#$%&\'\*\+\-/=\?\^_\`{\|}\~\.]+@[-a-z0-9\.]+$' $CODE$ LANGUAGE sql IMMUTABLE STRICT; -- simple check for correct phone number CREATE OR REPLACE FUNCTION is_phone_number(nr varchar) RETURNS boolean AS $CODE$ SELECT $1 ~ E'^[-+0-9\(\)/ ]+$' $CODE$ LANGUAGE sql IMMUTABLE STRICT; -- file fill_str_test.sql -- test data for database -- between the columns are supposed to be tabs, no spaces !!! BEGIN; COPY persons (id, name, email) FROM STDIN; 1 Joseph Schneider jschneid@lab.uni.de 2 Test User jschneid@lab.uni.de 3 Hans Dampf \N \. SELECT setval('persons_id_seq', (SELECT max(id) FROM persons)); COPY groups (id, name) FROM STDIN; 1 IT 2 SSG \. COPY users (id, login) FROM STDIN; 1 jschneid 2 tuser 3 dummy \. COPY user_groups (user_id, group_id) FROM STDIN; 1 1 2 1 3 2 \. COMMIT; 

关于__table__.columns :在__table__.columns对象的repr中打印的string不是键,因为你有多个id列,所以有一些名字正在进行着。 你可能想要做"persons_id"而不是"persons_id"但我build议打印__table__.columns.keys()来确保。

关于AttributeError :除非你自己定义属性映射,否则默认情况下,SQLAlchemy直接将列名映射到属性。 事实上,您将id属性定义为column_property persons.c.id, users.c.id, user_groups.c.user_id意味着这些列中的任何一个都不会直接映射到ORM类上的属性,而是他们仍然会在columns收集。 所以你不能使用columns作为属性名称的迭代。

我没有复制你所有的代码/数据,但是我把一个简单的testing用例与3个表格(包括一个m2m关系)进行比较来validation这些项目。