SQLAlchemy对Postgres Schemas的支持

时间:2012-02-15 17:35:18

标签: postgresql sqlalchemy

我们使用SQLAlchemy和postgres托管多租户应用。我正在考虑从每个租户的单独数据库迁移到具有多个模式的单个数据库。 SQLAlchemy本机支持吗?我基本上只是希望每个查询都以预定的模式为前缀......例如

select * from client1.users

而不仅仅是

select * from users

请注意,我想切换特定请求/请求集中的所有表的架构,而不仅仅是单个表。

我想这也可以通过自定义查询类完成,但我无法想象已经有了这样的事情。

10 个答案:

答案 0 :(得分:46)

有很多方法可以解决这个问题,这取决于您的应用的结构。这是最基本的方式:

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”名称粘贴在语句中。这是可行的,但是因为没有一致的钩子可以生成“Table”,所以这很麻烦。您最好的选择可能是在每个请求上设置搜索路径。

答案 1 :(得分:18)

如果要在连接字符串级别执行此操作,请使用以下命令:

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

答案 2 :(得分:9)

您可以使用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,它与数据库或用户搜索路径不同。我希望能够在引擎配置中设置它,但是看不到这样做的方法。使用connect事件确实有效。如果有人有一个更简单的解决方案,我会感兴趣。

另一方面,如果您想要在应用程序中处理多个客户端,那么这将无法工作 - 我想session.execute(...)方法可能是最好的方法。

答案 3 :(得分:5)

Table definitions

中有一个架构属性

我不确定它是否有效,但您可以尝试:

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

答案 4 :(得分:0)

您只需更改search_path即可。问题

set search_path=client9;

在会话开始时,然后保持你的桌子不合格。

您还可以在每个数据库或每个用户级别设置默认search_path。我很想在默认情况下将其设置为空模式,这样您就可以轻松捕获任何设置失败。

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

答案 5 :(得分:0)

我尝试过:

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']

一切正常。

答案 6 :(得分:0)

现在可以使用Sqlalchemy 1.1中的模式转换映射来完成。

{
  "name": "reactapp",
  "version": "1.0.0",
  "description": "Learning ReactJs with AK",
  "main": "index.js",
  "scripts": {
    "start": "webpack-dev-server --hot"
  },
  "keywords": [
    "npm",
    "learn",
    "react"
  ],
  "author": "AK",
  "license": "UNLICENSED",
  "dependencies": {
    "babel-core": "^6.26.3",
    "babel-loader": "^8.0.6",
    "babel-preset-es2015": "^6.24.1",
    "babel-preset-react": "^6.24.1",
    "react": "^16.8.6",
    "react-dom": "^16.8.6",
    "webpack": "^4.33.0",
    "webpack-dev-server": "^3.7.0"
  }
}

对于每个请求,可以将会话设置为每次都引用不同的模式:

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

    __table_args__ = {'schema': 'per_user'}

从SO答案here中引用了它。

链接到Sqlalchemy docs

答案 7 :(得分:0)

来自sqlalchemy 1.1, 使用schema_translation_map可以很容易地做到这一点。

https://docs.sqlalchemy.org/en/11/changelog/migration_11.html#multi-tenancy-schema-translation-for-table-objects

connection = engine.connect().execution_options(
    schema_translate_map={None: "user_schema_one"})

result = connection.execute(user_table.select())

这里是所有可用选项的详细评论: https://github.com/sqlalchemy/sqlalchemy/issues/4081

答案 8 :(得分:0)

我使用以下模式。

engine = sqlalchemy.create_engine("postgresql://postgres:mypass@172.17.0.2/mydb")

for schema in ['schema1', 'schema2']:
    engine.execute(CreateSchema(schema))
    tmp_engine = engine.execution_options(schema_translate_map = { None: schema } )
    Base.metadata.create_all(tmp_engine)

答案 9 :(得分:0)

可以在数据库级别解决这个问题。我想您的应用程序有一个专门的用户,该用户被授予对架构的一些权限。只需为他设置 search_path 到此架构:

ALTER ROLE your_user IN DATABASE your_db SET search_path TO your_schema;