Mapping a class against multiple tables in SQLAlchemy - python
# ! /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
"""
I'm having a problem joining four tables in one Python/SQLAlchemy class. I'm trying this, so I can iterate the instance of this class, instead of the named tuple, which I get from joining tables with the ORM.
Why all of this? Because I already started that way and I came too far, to just leave it. Also, it has to be possible, so I want to know how it's done.
For this project (cherrypy web-frontend) I got an already completed module with the table classes. I moved it to the bottom of this post, because maybe it isn't even necessary for you.
The following is just one example of a joined multiple tables class attempt. I picked a simple case with more than only two tables and a junction table. Here I don't write into these joined tables, but it is necessary somewhere else. That's why classes would be a nice solution to this problem.
My attempt of a join class,
which is a combination of the given table classes module and the examples from these two websites:
-Mapping a Class against Multiple Tables
-SQLAlchemy: one classes – two tables
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))
Different table accesses with this join class
This is how I tried to query this class in another module:
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'
Then I checked the table attributes:
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']
Then I checked, if the query or my class isn't working at all, by using a counter.
I got up to (count == 5), so the first two joined tables. But when I set the condition to (count == 6), I got the first error message again. AttributeError: 'JoinUserGroupPerson' object has no attribute '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]
"""
Then I checked each column:
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)]
Then I tried another two direct accesses, which got me both KeyErrors for 'id' and 'persons.id':
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))
Conclusion
I tried a few other things, which were even more confusing. Since they didn't reveal any more information, I didn't add them. I don't see where my class is wrong.
I guess, somehow I must have set the class in a way, which would only correctly join the first two tables. But the join works at least partially, because when the 'user_groups' table was empty, I got an empty query as well.
Or maybe I did something wrong with the mapping of this 'user_groups' table. Since with the join some columns are double, they need an additional definition. And the 'user_id' is already part of the persons and users table, so I had to map it twice.
I even tried to remove the 'user_groups' table from the join, because it's in the relationships (with secondary). It got me a foreign key error message. But maybe I just did it wrong.
Admittedly, I even don't know why ...
rowX.__table__.columns # column names as table name suffix
... has different attribute names than ...
colX in rowX.__table__.columns # column names without table names
Extra Edits
Another thought! Would all of this be possible with inheritance? Each class has its own mapping, but then the user_groups class may be necessary. The joins had to be between the single classes instead. The init() and repr() still had to be redefined.
It probably has something to do with the 'user_groups' table, because I even couldn't join it with the 'groups' or 'users' table. And it always says, that the class object has no attribute 'user_id'. Maybe it's something about the many-to-many relationship.
Attachment
Here is the already given SQLAlchemy module, with header, without specific information about the database, and the classes of the joined tables:
#!/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))
Maybe the following script, which created the database, and also was already given, will prove useful here. As last part of it comes some test data - but between the columns are supposed to be tabs, no spaces. Because of that, this script also can be found as gist on github:
-- 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;
Regarding the KeyError: The strings that are printed in the repr of the __table__.columns object are NOT the keys, and because you have multiple id columns there is some name munging going on. You probably want to do "persons_id" rather than "persons.id" but I recommend printing __table__.columns.keys() to be sure.
Regarding the AttributeError: SQLAlchemy maps column names directly to attributes by default, unless you define attribute mappings yourself, which you are. The fact that you are defining the id attribute as a column_property on persons.c.id, users.c.id, user_groups.c.user_id means that none of those columns is being directly mapped to an attribute on the ORM class anymore, but they will still be in the columns collection. So you just can't use columns as an iterable of attribute names.
I did not reproduce all of your code/data, but I put together a simpler test case with 3 tables (including a m2m relationship) to verify these items.
Related
Filtering a PostgreSQL function that returns a table in sqlalchemy
I am trying to solve the following two problems: Constructing an ORM model to use in SQLAlchemy with a table that is generated from a user defined function in PostgreSQL Filtering said table (either in ORM model or not) I have defined a function that returns a table called transformed. It takes a user_id as an input and returns 3 columns. Looks something like this below (this is extremely simplified, the query is obviously a little more complicated). CREATE OR REPLACE FUNCTION transformed (user_id integer) RETURNS TABLE ( id integer, user varchar, description varchar, ) AS $body$ SELECT id, user, description FROM table WHERE id = $1 $body$ LANGUAGE sql; def build_query(user_id): base_query: Query = select( Column("id", Integer), Column("user", String), Column("description", String), ).select_from(func.transformed)_data(user_id)) return base_query I have no problem getting the three columns. The challenge is to filter and sort them. query = build_query(user_id).filter_by(description = "Foo") This returns the following error: sqlalchemy.exc.InvalidRequestError: Entity namespace for "transformed(:transformed_1) has no property "description". I would love to know how to either a) properly filter or b) turn the pure columns I am defining into some sort of ORM model that I can then use to filter on the properties of the ORM model.
SQLAlchemy is pretty awesome. You can define the function using DDLEvents and then call the function using func and define the output with table_valued there is a different method for a scalar but its in the same docs. import sys from sqlalchemy import ( create_engine, Integer, String, event, column, ) from sqlalchemy.schema import ( Column, DDL, ) from sqlalchemy.sql import select, func from sqlalchemy.orm import declarative_base, Session Base = declarative_base() username, password, db = sys.argv[1:4] engine = create_engine(f"postgresql+psycopg2://{username}:{password}#/{db}", echo=True) class User(Base): __tablename__ = "users" id = Column(Integer, primary_key=True) name = Column(String(8), index=True) event.listen( User.__table__, "after_create", DDL("""CREATE OR REPLACE FUNCTION transformed (int) RETURNS TABLE (id integer, username varchar) AS $$ SELECT id, name AS username FROM users WHERE id = $1 $$ LANGUAGE sql; """)) Base.metadata.create_all(engine) with Session(engine) as session, session.begin(): for name in ['User 1', 'User 2', 'User 3']: session.add(User(name=name)) with Session(engine) as session, session.begin(): # Get a single row with id 1 fn = func.transformed(1).table_valued( column("id", Integer), column("username", String)) # This filtering is redundant because we selected # a user by its id but it demonstrates referencing the column # we defined above. q = select(fn).where(fn.c.username.like('User %')) for id, user in session.execute(q).all(): print(id, user) This prints out 1 User 1 The actual query looks like this, via echo=True: SELECT anon_1.id, anon_1.username FROM transformed(%(transformed_1)s) AS anon_1 WHERE anon_1.username LIKE %(username_1)s
There is one possible solution but it's very similar to simply constructing the query via raw text: query = build_query(user_id).where(text("id = 4")) Not a huge fan since it's not easy to expand on this dynamically.
SQLAlchemy: Could not locate any relevant foreign key columns for primary join condition
I have built two tables in my postgres CLI (test2_table: id, name, age, profession, city, country). The second is referenced to my 1st table's id and has a column called wage. When I run the syntax everything is fine. SELECT name, age, profession, city, country, wage FROM test2_table JOIN salary ON salary.test2_id = test2_table.id; This works - a table with wages is printed for me to see. My only problem is when I try the same thing to my SQLAlchemy database class. When I try to join the classes together I get an error: sqlalchemy.exc.ArgumentError: Could not locate any relevant foreign key columns for primary join condition 'test2_table.id = salary.id' on relationship Salary.wages. Ensure that referencing columns are associated with a ForeignKey or ForeignKeyConstraint, or are annotated in the join condition with the foreign() annotation. my database classes: from sqlalchemy.ext.declarative import declarative_base Base = declarative_base() class Test_db_02(Base): __tablename__ = 'test2_table' id = Column('id', Integer, primary_key=True) name = Column('name', String(40)) age = Column('age', Integer) profession = Column('profession', String(60)) city = Column('city', String(60)) country = Column('country', String(40)) class Salary(Base): __tablename__ = 'salary' id = Column('id', Integer, primary_key=True) wage = Column('wage', String(20)) test2_id = Column('test2_id', Integer, ForeignKey('test2_table.id')) wages = relationship("Test_db_02", backref="salary", primaryjoin="Test_db_02.id == Salary.id") I have a simple report page that prompts the user to chose from a drop-down option selector. Before I tried to join the two tables togteher, my queries were working well, now after my attempt to join I get the error. The reports #app has been scaled down for this example. Reports #app.route Session = sessionmaker(bind=engine) session = Session() #app.route('/reports', methods=['GET', 'POST']) if request.method == 'GET': return render_template('reports.html') else: if request.form.get("report_options") == "all_db": db_entry = session.query(Test_db_02).order_by(Test_db_02.id) db_entry = db_entry.all() return render_template('reports.html', db_entry=db_entry) elif request.form.get("report_options") == "name": db_entry = session.query(Test_db_02).order_by(Test_db_02.id) data_name = db_entry.all() return render_template('reports.html', data_name=data_name) elif request.form.get("report_options") == "media_prof": db_entry = session.query(Test_db_02).join(test2_table.salary) media_prof = db_entry.all() return render_template('reports.html', media_prof=media_prof) If I am honest I have read through the sqlalchemy documentation on relationships, joins & foreign keys (and watched some YouTube tutorials) but it still seems a bit confusing.. The main challenge for me is to be able to join two tables together. Once I have accomplished that, I will attempt to iterate through them with the flask/jinja set up.
Your primaryjoin condition in Salary.wages is wrong. Presumably, Salary.idis an autoincrement primary key column and as you have no ForeignKey constraining values of Salary.id to values of Test_db_02.id, its unlikely that you want to include that column in the join condition as you have done: primaryjoin="Test_db_02.id == Salary.id" Its more likely that you want to relate the two models through the Salary.test2_id attribute, as you have the ForeignKey constraint on that column: primaryjoin="Test_db_02.id == Salary.test2_id"
SQLAlchemy + Postgres (print created types, e.g. enum types)
I would like to be able to get info about what types will be created during SQLAlchemy's create_all(). Yes, they can be printed if I set up echo-ing of generated SQL, but how can i print it without actually hitting database? For example, I have a model: class MyModel(Base): id = Column(Integer, primary_key=True) indexed_field = Column(String(50)) enum_field = Column(Enum(MyEnum)) __table_args__ = ( Index("my_ix", indexed_field), ) where MyEnum is: class MyEnum(enum.Enum): A = 0 B = 1 I can get CREATE TABLE statement and all CREATE INDEX statements like this: from sqlalchemy.schema import CreateTable, CreateIndex print(str(CreateTable(MyModel.__table__).compile(postgres_engine))) for idx in MyModel.__table__.indexes: print(str(CreateIndex(idx)).compile(postgres_engine)) Result will be something like that: CREATE TABLE my_model ( id SERIAL NOT NULL, indexed_field VARCHAR(50), enum_field myenum, PRIMARY KEY (id) ) CREATE INDEX my_ix ON my_model (indexed_field) Notice the line enum_field myenum. How can I get generated SQL for CREATE TYPE myenum... statement?
I've found the answer! from sqlalchemy.dialects.postgresql.base import PGInspector PGInspector(postgres_engine).get_enums() It returns a list of all created enums, which IMO is even better than raw sql, documentation is here.
SQLAlchemy mapping joined tables' columns to one object
I have three tables: UserTypeMapper, User, and SystemAdmin. In my get_user method, depending on the UserTypeMapper.is_admin row, I then query either the User or SystemAdmin table. The user_id row correlates to the primary key id in the User and SystemAdmin tables. class UserTypeMapper(Base): __tablename__ = 'user_type_mapper' id = Column(BigInteger, primary_key=True) is_admin = Column(Boolean, default=False) user_id = Column(BigInteger, nullable=False) class SystemAdmin(Base): __tablename__ = 'system_admin' id = Column(BigInteger, primary_key=True) name = Column(Unicode) email = Column(Unicode) class User(Base): __tablename__ = 'user' id = Column(BigInteger, primary_key=True) name = Column(Unicode) email = Column(Unicode) I want to be able to get any user – system admin or regular user – from one query, so I do a join, on either User or SystemAdmin depending on the is_admin row. For example: DBSession.query(UserTypeMapper, SystemAdmin).join(SystemAdmin, UserTypeMapper.user_id==SystemAdmin.id).first() and DBSession.query(UserTypeMapper, User).join(User, UserTypeMapper.user_id==User.id).first() This works fine; however, I then would like to be access these, like so: >>> my_admin_obj.is_admin True >>> my_admin_obj.name Bob Smith versus >>> my_user_obj.is_admin False >>> my_user_obj.name Bob Stevens Currently, I have to specify: my_user_obj.UserTypeMapper.is_admin and my_user_obj.User.name. From what I've been reading, I need to map the tables so that I don't need to specify which table the attribute belongs to. My problem is that I do not understand how I can specify this given that I have two potential tables that the name attribute, for example, may come from. This is the example I am referring to: Mapping a Class against Multiple Tables How can I achieve this? Thank you.
You have discovered why "dual purpose foreign key", is an antipattern. There is a related problem to this that you haven't quite pointed out; there's no way to use a foreign key constraint to enforce the data be in a valid state. You want to be sure that there's exactly one of something for each row in UserTypeMapper, but that 'something' is not any one table. formally you want a functional dependance on user_type_mapper → (system_admin× 1) ∪ (user× 0) But most sql databses won't allow you to write a foreign key constraint expressing that. It looks complicated because it is complicated. instead, lets consider what we really want to say; "every system_admin should be a user; or system_admin → user In sql, that would be written: CREATE TABLE user ( id INTEGER PRIMARY KEY, name VARCHAR, email VARCHAR ); CREATE TABLE system_admin ( user_id INTEGER PRIMARY KEY REFERENCES user(id) ); Or, in sqlalchemy declarative style class User(Base): __tablename__ = 'user' id = Column(Integer, primary_key=True) name = Column(String) email = Column(String) class SystemAdmin(Base): __tablename__ = 'system_admin' user_id = Column(ForeignKey(User.id), primary_key=True) What sort of questions does this schema allow us to ask? "Is there a SystemAdmin by the name of 'john doe'"? >>> print session.query(User).join(SystemAdmin).filter(User.name == 'john doe').exists() EXISTS (SELECT 1 FROM "user" JOIN system_admin ON "user".id = system_admin.user_id WHERE "user".name = :name_1) "How many users are there? How many sysadmins?" >>> print session.query(func.count(User.id), func.count(SystemAdmin.user_id)).outerjoin(SystemAdmin) SELECT count("user".id) AS count_1, count(system_admin.user_id) AS count_2 FROM "user" LEFT OUTER JOIN system_admin ON "user".id = system_admin.user_id I hope you can see why the above is prefereable to the design you describe in your question; but in the off chance you don't have a choice (and only in that case, if you still feel what you've got is better, please refine your question), you can still cram that data into a single python object, which will be very difficult to work with, by providing an alternate mapping to the tables; specifically one which follows the rough structure in the first equation. We need to mention UserTypeMapper twice, once for each side of the union, for that, we need to give aliases. >>> from sqlalchemy.orm import aliased >>> utm1 = aliased(UserTypeMapper) >>> utm2 = aliased(UserTypeMapper) For the union bodies join each alias to the appropriate table: Since SystemAdmin and User have the same columns in the same order, we don't need to describe them in detail, but if they are at all different, we need to make them "union compatible", by mentioning each column explicitly; this is left as an exercise. >>> utm_sa = Query([utm1, SystemAdmin]).join(SystemAdmin, (utm1.user_id == SystemAdmin.id) & (utm1.is_admin == True)) >>> utm_u = Query([utm2, User]).join(User, (utm2.user_id == User.id) & (utm2.is_admin == False)) And then we join them together... >>> print utm_sa.union(utm_u) SELECT anon_1.user_type_mapper_1_id AS anon_1_user_type_mapper_1_id, anon_1.user_type_mapper_1_is_admin AS anon_1_user_type_mapper_1_is_admin, anon_1.user_type_mapper_1_user_id AS anon_1_user_type_mapper_1_user_id, anon_1.system_admin_id AS anon_1_system_admin_id, anon_1.system_admin_name AS anon_1_system_admin_name, anon_1.system_admin_email AS anon_1_system_admin_email FROM (SELECT user_type_mapper_1.id AS user_type_mapper_1_id, user_type_mapper_1.is_admin AS user_type_mapper_1_is_admin, user_type_mapper_1.user_id AS user_type_mapper_1_user_id, system_admin.id AS system_admin_id, system_admin.name AS system_admin_name, system_admin.email AS system_admin_email FROM user_type_mapper AS user_type_mapper_1 JOIN system_admin ON user_type_mapper_1.user_id = system_admin.id AND user_type_mapper_1.is_admin = 1 UNION SELECT user_type_mapper_2.id AS user_type_mapper_2_id, user_type_mapper_2.is_admin AS user_type_mapper_2_is_admin, user_type_mapper_2.user_id AS user_type_mapper_2_user_id, "user".id AS user_id, "user".name AS user_name, "user".email AS user_email FROM user_type_mapper AS user_type_mapper_2 JOIN "user" ON user_type_mapper_2.user_id = "user".id AND user_type_mapper_2.is_admin = 0) AS anon_1 While it's theoretically possible to wrap this all up into a python class that looks a bit like standard sqlalchemy orm stuff, I would certainly not do that. working with non-table mappings, especially when they are more than simple joins (this is a union), is lots of work for zero payoff.
delete not cascaded to table in sqlalchemy
I am developing an extension to an existing app which uses sqlalchemy 0.6. The app has sqlalchemy tables created the non-declarative way. I am trying to create in my extension a new table with a foreign key column pointing at the primary key of the main table in the application database and I am creating it declaratively. This all works fine, with the table created once the extension is loaded, and with no complaints at all. My table prints out and demonstrates that new rows have been added ok. What I want and think is possible (but don't know as I have never used sql or any other database) is for the corresponding row in my table to be deleted when the row in the app's main table with the corresponding foreign key is deleted. So far, and with many permutations having been tried, nothing has worked. I thought that with a backref set and with a relation defined with delete being cascaded, there shouldn't be a problem. Because the new table is defined in an extension which should just plugin, I don't want to edit the code in the main app at all, at least that is my goal. One of the problems that I have, though, is that the main app table that I want to reference, has no member variables defined in its class, does not declare its primary key in its mapper and only has the primary key declared in the table. This makes it difficult to create a relation(ship) clause, the first argument of which must be to a class or mapper (in this case neither of which have the primary key declared). Is there any way of achieving this? ps - here is some of the code that I am using. LocalFile is the declarative class. All the connection details are taken care of by the main application. if not self.LocalFile.__table__.exists(bind=Engine): self.LocalFile__table__.create(bind=Engine) Here is the LocalFile class - Base is a declarative base class with bind=Engine passed in the constructor: class LocalFile(Base): __tablename__ = 'local_file' _id = Column(Integer, Sequence('local_file_sequence', start=1, increment=1), primary_key=True) _filename = Column(String(50), nullable=False) _filepath = Column(String(128), nullable=False) _movieid = Column(Integer, ForeignKey(db.tables.movies.c.movie_id, onupdate='CASCADE', ondelete='CASCADE')) #movies = relation(db.Movie, backref="local_file", cascade="all") #property def filename(self): return self._filename #filename.setter def filename(self, filename): self._filename = filename #property def filepath(self): return self._filepath #filepath.setter def filepath(self, filepath): self._filepath = filepath #property def movieid(self): return self._movieid #movieid.setter def movieid(self, movieid): self._movieid = movieid #property def id(self): return self._id #id.setter def id(self, id): self._id = id filename = synonym('_filename', descriptor=filename) movieid = synonym('_movieid', descriptor=movieid) filepath = synonym('_filepath', descriptor=filepath) id = synonym('_id', descriptor=id) def __init__(self, filename, filepath, movieid): self._filename = filename self._filepath = filepath self._movieid = movieid def __repr__(self): return "<User('%s','%s', '%s')>" % (self.filename, self.filepath, self.movieid) Edit: The backend is sqlite3. Below is the code from the creation of the table produced by using the echo command (thanks for pointing that out, it's very useful - already I suspect that the existing application is generating far more sql than is necessary). Following the reported sql table creation is the code generated when a row is removed. I personally can't see any statement that references the possible deletion of a row in the local file table, but I know very little sql currently. Thanks. 2011-12-29 16:29:18,530 INFO sqlalchemy.engine.base.Engine.0x...0650 CREATE TABLE local_file ( _id INTEGER NOT NULL, _filename VARCHAR(50) NOT NULL, _filepath VARCHAR(128) NOT NULL, _movieid INTEGER, PRIMARY KEY (_id), FOREIGN KEY(_movieid) REFERENCES movies (movie_id) ON DELETE CASCADE ON UPDATE CASCADE ) 2011-12-29T16:29:18: I: sqlalchemy.engine.base.Engine.0x...0650(base:1387): CREATE TABLE local_file ( _id INTEGER NOT NULL, _filename VARCHAR(50) NOT NULL, _filepath VARCHAR(128) NOT NULL, _movieid INTEGER, PRIMARY KEY (_id), FOREIGN KEY(_movieid) REFERENCES movies (movie_id) ON DELETE CASCADE ON UPDATE CASCADE ) 2011-12-29 16:29:18,534 INFO sqlalchemy.engine.base.Engine.0x...0650 () 2011-12-29T16:29:18: I: sqlalchemy.engine.base.Engine.0x...0650(base:1388): () 2011-12-29 16:29:18,643 INFO sqlalchemy.engine.base.Engine.0x...0650 COMMIT 2011-12-29T16:29:18: I: sqlalchemy.engine.base.Engine.0x...0650(base:1095): COMMIT for row in table produces the following for the two tables: the local file table: (, u' 310 To Yuma') (, u' Ravenous') the movie table in the existing app: (, u'IMDb - 3:10 to Yuma') (, u'Ravenous') The code when deleting a row is so long that I cannot include it here (200 lines or so - isn't that a little too many for deleting one row?), but it makes no reference to deleting a row in the localfile table. There are statements like: 2011-12-29 17:09:17,141 INFO sqlalchemy.engine.base.Engine.0x...0650 UPDATE movies SET poster_md5=?, updated=? WHERE movies.movie_id = ? 2011-12-29T17:09:17: I: sqlalchemy.engine.base.Engine.0x...0650(base:1387): UPDATE movies SET poster_md5=?, updated=? WHERE movies.movie_id = ? 2011-12-29 17:09:17,142 INFO sqlalchemy.engine.base.Engine.0x...0650 (None, '2011-12-29 17:09:17.141019', 2) 2011-12-29T17:09:17: I: sqlalchemy.engine.base.Engine.0x...0650(base:1388): (None, '2011-12-29 17:09:17.141019', 2) 2011-12-29 17:09:17,150 INFO sqlalchemy.engine.base.Engine.0x...0650 DELETE FROM posters WHERE posters.md5sum = ? 2011-12-29T17:09:17: I: sqlalchemy.engine.base.Engine.0x...0650(base:1387): DELETE FROM posters WHERE posters.md5sum = ? 2011-12-29 17:09:17,157 INFO sqlalchemy.engine.base.Engine.0x...0650 (u'083841e14b8bb9ea166ea4b2b976f03d',)
In SQLite you must turn on support for foreign keys explicitly or it just ignores any SQL related to foreign keys. engine = create_engine(database_url) def on_connect(conn, record): conn.execute('pragma foreign_keys=ON') from sqlalchemy import event event.listen(engine, 'connect', on_connect)