Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SQLAlchemy support of Postgres Schemas

We host a multitenant app with SQLAlchemy and postgres. I am looking at moving from having separate databases for each tenant to a single database with multiple schemas. Does SQLAlchemy support this natively? I basically just want every query that comes out to be prefixed with a predetermined schema... e.g

select * from client1.users 

instead of just

select * from users 

Note that I want to switch the schema for all tables in a particular request/set of requests, not just a single table here and there.

I imagine that this could be accomplished with a custom query class as well but I can't imagine that something hasn't been done in this vein already.

like image 989
eleddy Avatar asked Feb 15 '12 17:02

eleddy


People also ask

Does SQLAlchemy support Postgres?

PostgreSQL supports sequences, and SQLAlchemy uses these as the default means of creating new primary key values for integer-based primary key columns.

What databases are supported by SQLAlchemy?

Supported Databases. SQLAlchemy includes dialects for SQLite, Postgresql, MySQL, Oracle, MS-SQL, Firebird, Sybase and others, most of which support multiple DBAPIs. Other dialects are published as external projects.

What is SQLAlchemy schema?

SQLAlchemy schema metadata is a comprehensive system of describing and inspecting database schemas. The core of SQLAlchemy's query and object mapping operations is supported by database metadata.

What is the difference between psycopg2 and SQLAlchemy?

The psycopg2 is over 2x faster than SQLAlchemy on small table. This behavior is expected as psycopg2 is a database driver for postgresql while SQLAlchemy is general ORM library.


2 Answers

well there's a few ways to go at this and it depends on how your app is structured. Here is the most basic way:

meta = MetaData(schema="client1") 

If the way your app runs is one "client" at a time within the whole application, you're done.

But what may be wrong with that here is, every Table from that MetaData is on that schema. If you want one application to support multiple clients simultaneously (usually what "multitenant" means), this would be unwieldy since you'd need to create a copy of the MetaData and dupe out all the mappings for each client. This approach can be done, if you really want to, the way it works is you'd access each client with a particular mapped class like:

client1_foo = Client1Foo() 

and in that case you'd be working with the "entity name" recipe at http://www.sqlalchemy.org/trac/wiki/UsageRecipes/EntityName in conjunction with sometable.tometadata() (see http://docs.sqlalchemy.org/en/latest/core/metadata.html#sqlalchemy.schema.Table.tometadata).

So let's say the way it really works is multiple clients within the app, but only one at a time per thread. Well actually, the easiest way to do that in Postgresql would be to set the search path when you start working with a connection:

# start request  # new session sess = Session()  # set the search path sess.execute("SET search_path TO client1")  # do stuff with session  # close it.  if you're using connection pooling, the # search path is still set up there, so you might want to  # revert it first sess.close() 

The final approach would be to override the compiler using the @compiles extension to stick the "schema" name in within statements. This is doable, but would be tricky as there's not a consistent hook for everywhere "Table" is generated. Your best bet is probably setting the search path on each request.

like image 185
zzzeek Avatar answered Oct 03 '22 07:10

zzzeek


If you want to do this at the connection string level then use the following:

dbschema='schema1,schema2,public' # Searches left-to-right engine = create_engine(     'postgresql+psycopg2://dbuser@dbhost:5432/dbname',     connect_args={'options': '-csearch_path={}'.format(dbschema)}) 

But, a better solution for a multi-client (multi-tenant) application is to configure a different db user for each client, and configure the relevant search_path for each user:

alter role user1 set search_path = "$user", public 
like image 37
PPrice Avatar answered Oct 03 '22 08:10

PPrice