web-dev-qa-db-ja.com

PostgresスキーマのSQLAlchemyサポート

SQLAlchemyとpostgresを使用してマルチテナントアプリをホストします。私は、テナントごとに個別のデータベースを持つことから、複数のスキーマを持つ単一のデータベースに移行することを検討しています。 SQLAlchemyはこれをネイティブにサポートしていますか?基本的には、出てくるすべてのクエリに事前定義されたスキーマをプレフィックスとして追加するだけです...

select * from client1.users

ただではなく

select * from users

ここでは、1つのテーブルだけでなく、特定のリクエスト/リクエストセット内のすべてのテーブルのスキーマを切り替えたいことに注意してください。

これは、カスタムクエリクラスでも実現できると思いますが、この方法で何かがまだ行われていないとは想像できません。

46
eleddy

これにはいくつかの方法がありますが、それはアプリの構造によって異なります。最も基本的な方法は次のとおりです。

meta = MetaData(schema="client1")

アプリの実行方法が、アプリケーション全体で一度に1つの「クライアント」であれば、これで完了です。

しかし、ここで間違っている可能性があるのは、そのメタデータのすべてのテーブルがそのスキーマ上にあるということです。 1つのアプリケーションで複数のクライアントを同時にサポートする場合(通常は「マルチテナント」の意味)、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 を参照)。

そのため、実際に機能する方法はアプリ内の複数のクライアントですが、スレッドごとに一度に1つだけです。実際、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拡張を使用してコンパイラーをオーバーライドし、ステートメント内に「スキーマ」名を固定することです。これは実行可能ですが、「テーブル」が生成されるすべての場所に一貫したフックがないため、注意が必要です。最善の策は、おそらく各リクエストで検索パスを設定することです。

49
zzzeek

接続文字列レベルでこれを行うには、次を使用します。

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

ただし、マルチクライアント(マルチテナント)アプリケーションのより良いソリューションは、クライアントごとに異なるdbユーザーを構成し、各ユーザーに関連するsearch_pathを構成することです。

alter role user1 set search_path = "$user", public
23
PPrice

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 )

最初の接続が作成される前に、これを実行する必要があることは明らかです(たとえば、アプリケーションの初期化で)

Session.execute(...)ソリューションで見られる問題は、これがセッションで使用される特定の接続で実行されることです。ただし、セッションが無期限に同じ接続を使用し続けることを保証するsqlalchemyには何も表示されません。接続プールから新しい接続を選択すると、検索パスの設定が失われます。

データベースまたはユーザー検索パスとは異なるアプリケーションsearch_pathを設定するには、このようなアプローチが必要です。エンジン設定でこれを設定できるようにしたいのですが、これを行う方法がわかりません。接続イベントの使用は機能します。誰かが持っているなら、もっと簡単な解決策に興味があります。

一方、アプリケーション内で複数のクライアントを処理したい場合、これは機能しません-私はsession.execute(...)アプローチが最良のアプローチであると思います。

9
Chris Crook

テーブル定義 にスキーマプロパティがあります

うまくいくかどうかはわかりませんが、試してみてください:

Table(CP.get('users', metadata, schema='client1',....)
5
christian

これは、Sqlalchemy 1.1のスキーマ変換マップを使用して実行できるようになりました。

class User(Base):
    __table= 'user'
    id = Column(Integer, primary_key=True)

    __table_args__ = {'schema': 'per_user'}

各リクエストで、セッションは毎回異なるスキーマを参照するように設定できます。

session = Session()
session.connection(execution_options={
    "schema_translate_map": {"per_user": "account_one"}})

# will query from the ``account_one.user`` table

session.query(User).get(5)

SO answer here

Sqlalchemy docs へのリンク。

1

上記の回答がいずれも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
0
Joseph Lust

Search_pathを変更するだけです。問題

set search_path=client9;

セッションの開始時に、テーブルを非修飾のままにしておきます。

データベースごとまたはユーザーごとにデフォルトのsearch_pathを設定することもできます。デフォルトで空のスキーマに設定したいので、設定の失敗を簡単にキャッチできます。

http://www.postgresql.org/docs/current/static/ddl-schemas.html#DDL-SCHEMAS-PATH

0
Richard Huxton

私は試した:

con.execute('SET search_path TO {schema}'.format(schema='myschema'))

それは私にとってはうまくいきませんでした。次に、init関数でschema =パラメーターを使用しました。

# We then bind the connection to MetaData()
meta = sqlalchemy.MetaData(bind=con, reflect=True, schema='myschema')

次に、スキーマ名でテーブルを修飾しました

house_table = meta.tables['myschema.houses']

そしてすべてが働いた。

0
crankymax