2012-02-15 5 views
32

У нас есть многопользовательское приложение с SQLAlchemy и postgres. Я рассматриваю возможность перехода от отдельных баз данных для каждого арендатора к одной базе данных с несколькими схемами. Поддерживает ли SQLAlchemy это изначально? Я в основном просто хочу, чтобы каждый запрос, который приходит к предваряться с заданной схемой ... напримерПоддержка SQLAlchemy схем Postgres

select * from client1.users 

вместо просто

select * from users 

Обратите внимание, что я хочу, чтобы переключить схему для всех таблиц в конкретный запрос/набор запросов, а не только один стол здесь и там.

Я предполагаю, что это может быть выполнено с помощью собственного класса запросов, но я не могу себе представить, что в этом ключе что-то еще не было сделано.

ответ

35

также есть несколько способов, чтобы идти на это, и это зависит от того, как приложение структурировано. Вот самый основной способ:

meta = MetaData(schema="client1") 

Если путь работает ваше приложение один «клиент» в то время, в течение всего приложения, вы сделали.

Но что может быть неправильно с этим, каждая таблица из этой MetaData находится на этой схеме. Если вы хотите, чтобы одно приложение поддерживало несколько клиентов одновременно (обычно это означает «многопользовательский»), это было бы громоздким, так как вам нужно было бы создать копию MetaData и обмануть все сопоставления для каждого клиента. Такой подход может быть сделан, если вы действительно хотите, то, как это работает, вы обращаетесь к каждому клиенту с определенным отображенным классом, как:

client1_foo = Client1Foo() 

и в том случае, если вы хотите работать с «лицом название "рецепта на http://www.sqlalchemy.org/trac/wiki/UsageRecipes/EntityName в сочетании с sometable.tometadata() (см. http://docs.sqlalchemy.org/en/latest/core/metadata.html#sqlalchemy.schema.Table.tometadata).

Итак, скажем так, как это работает, это несколько клиентов в приложении, но только по одному за потоком. Ну на самом деле, самый простой способ сделать это в Postgresql бы установить путь поиска, когда вы начинаете работать с подключением:

# 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() 

Окончательный подход будет переопределить компилятор с помощью расширения @compiles, чтобы наклеить имя "schema" внутри операторов. Это выполнимо, но было бы сложно, потому что везде не было согласованного крючка «Таблица». Ваш лучший выбор, вероятно, задает путь поиска по каждому запросу.

+0

Спасибо! Я попробую пару вещей, а затем посмотрю, какие из них лучше всего работают и отчитываются, но я думаю, что путь - это путь. – eleddy

+0

@zzzeek У меня есть зеркальный вопрос об этом, но для Alembic можно реально использовать ваш вход: http://stackoverflow.com/questions/21109218/alembic-support-for-multiple-postgres-schemas – dtheodor

+4

Кстати, мне удалось сделать это для декларативного синтаксиса типа: '' Base = declarative_base(); Base.metadata.schema = 'ebay'''. Однако, вероятно, лучший способ. –

2

Существует свойство схемы в Table definitions

Я не уверен, если он работает, но вы можете попробовать:

Table(CP.get('users', metadata, schema='client1',....) 
+0

Я ищу что-то в более глобальном масштабе, чтобы я мог переключать все запросы во всех таблицах для одного запроса. Я обновлю этот вопрос, чтобы это отразить. – eleddy

0

Вы можете просто изменить свой поиск. Выпуск

set search_path=client9; 

в начале вашей сессии, а затем просто держите свои столы в неквалифицированных условиях.

Вы также можете установить путь по умолчанию по умолчанию для каждого пользователя или для каждого пользователя. У меня возникнет соблазн установить его по пустой схеме по умолчанию, поэтому вы можете легко поймать любую неспособность установить ее.

+0

это ... отличная идея. дай пять! – eleddy

+0

И помните, что после сеанса session.commit() запускается новая транзакция, поэтому поиск будет сбрасываться. События сеанса SA отлично подходят для установки пути search_path для каждой новой транзакции. – Brett

5

Вы можете быть в состоянии справиться с этим с помощью интерфейса SQLAlchemy событий. Поэтому, прежде чем создать первое соединение, настроить слушателя вдоль линий

from sqlalchemy import event 
from sqlalchemy.pool import Pool 

def set_search_path(db_conn, conn_proxy): 
    print "Setting search path..." 
    db_conn.cursor().execute('set search_path=client9, public') 

event.listen(Pool,'connect', set_search_path) 

Очевидно, что это должно быть выполнено до создания первого соединения (например, в initiallization приложения)

Проблема, которую я вижу с решением session.execute (...) заключается в том, что это выполняется по определенному соединению, используемому сеансом. Однако я ничего не вижу в sqlalchemy, который гарантирует, что сеанс будет продолжать использовать одно и то же соединение неограниченно. Если он забирает новое соединение из пула соединений, он потеряет настройку пути поиска.

Мне нужен такой подход, чтобы задать путь поиска приложения, который отличается от базы данных или пути поиска пользователя. Я хотел бы иметь возможность установить это в конфигурации двигателя, но не вижу способа сделать это. Использование события connect работает. Меня бы интересовало более простое решение, если у кого-то есть.

С другой стороны, если вы хотите обрабатывать несколько клиентов в приложении, то это не сработает - и я думаю, что подход session.execute (...) может быть лучшим подходом.

+1

У вас есть элегантный способ передать «client9» в качестве аргумента, а не hardcoding? Мое текущее (хакерское) обходное решение заключается в том, чтобы передать запрос 'application_name' arg к db-url ('? Application_name = bla'), а затем проверить его в 'set_search_path' с помощью' db_conn.dsn.split ('application_name =') [ 1]) '. – rkrzr

0

Я нашел, что ни один из вышеперечисленных ответов не работал с SqlAlchmeny 1.2.4. Это решение, которое сработало для меня.

from sqlalchemy import MetaData, Table 
from sqlalchemy import create_engine  

def table_schemato_psql(schema_name, table_name): 

     conn_str = 'postgresql://{username}:{password}@localhost:5432/{database}'.format(
      username='<username>', 
      password='<password>', 
      database='<database name>' 
     ) 

     engine = create_engine(conn_str) 

     with engine.connect() as conn: 
      conn.execute('SET search_path TO {schema}'.format(schema=schema_name)) 

      meta = MetaData() 

      table_data = Table(table_name, meta, 
           autoload=True, 
           autoload_with=conn, 
           postgresql_ignore_search_path=True) 

      for column in table_data.columns: 
       print column.name 
Смежные вопросы