2012-02-15 35 views
32

Chúng tôi lưu trữ một ứng dụng đa nhiệm với SQLAlchemy và postgres. Tôi đang xem xét việc chuyển từ việc có các cơ sở dữ liệu riêng biệt cho từng đối tượng thuê đến một cơ sở dữ liệu đơn lẻ với nhiều lược đồ. SQLAlchemy có hỗ trợ điều này không? Về cơ bản tôi chỉ muốn mọi truy vấn mà đi ra để được bắt đầu bằng một sơ đồ định trước ... ví dụHỗ trợ SQLAlchemy của các lược đồ Postgres

select * from client1.users 

thay vì chỉ

select * from users 

Lưu ý rằng tôi muốn chuyển sang sơ đồ cho tất cả các bảng trong một yêu cầu/tập hợp yêu cầu cụ thể, không chỉ là một bảng duy nhất ở đây và ở đó.

Tôi tưởng tượng rằng điều này có thể được thực hiện với một lớp truy vấn tùy chỉnh là tốt nhưng tôi không thể tưởng tượng rằng một cái gì đó đã không được thực hiện trong tĩnh mạch này rồi.

Trả lời

35

cũng có một vài cách để đi ở này và nó phụ thuộc vào cách bạn ứng dụng được cấu trúc. Đây là cách cơ bản nhất:

meta = MetaData(schema="client1") 

Nếu cách ứng dụng của bạn chạy là một "khách hàng" tại một thời điểm trong toàn bộ ứng dụng, bạn đã hoàn tất.

Nhưng điều có thể sai với điều đó ở đây, mọi Bảng từ MetaData đó đều nằm trong lược đồ đó. Nếu bạn muốn một ứng dụng hỗ trợ nhiều khách hàng cùng một lúc (thường là những gì "đa nhiệm" có nghĩa là), điều này sẽ khó sử dụng vì bạn cần tạo một bản sao của MetaData và loại bỏ tất cả các ánh xạ cho mỗi máy khách. Cách tiếp cận này có thể được thực hiện, nếu bạn thực sự muốn, cách nó hoạt động là bạn muốn truy cập vào từng khách hàng với một lớp ánh xạ cụ thể như:

client1_foo = Client1Foo() 

và trong trường hợp đó bạn muốn được làm việc với những "thực thể tên "công thức tại http://www.sqlalchemy.org/trac/wiki/UsageRecipes/EntityName kết hợp với sometable.tometadata() (xem http://docs.sqlalchemy.org/en/latest/core/metadata.html#sqlalchemy.schema.Table.tometadata).

Vì vậy, giả sử cách nó thực sự hoạt động là nhiều ứng dụng khách trong ứng dụng, nhưng mỗi lần chỉ có một khách hàng trong một chủ đề. Vâng trên thực tế, cách dễ nhất để làm điều đó trong PostgreSQL sẽ được thiết lập các đường dẫn tìm kiếm khi bạn bắt đầu làm việc với một kết nối:

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

Cách tiếp cận chính thức sẽ được ghi đè lên các trình biên dịch sử dụng phần mở rộng @compiles để dính tên "lược đồ" trong các câu lệnh trong. Điều này là có thể thực hiện được, nhưng sẽ phức tạp vì không có một móc phù hợp cho mọi nơi "Bảng" được tạo ra. Đặt cược tốt nhất của bạn có thể là đặt đường dẫn tìm kiếm trên mỗi yêu cầu.

+0

Cảm ơn! Tôi sẽ thử một vài điều và sau đó xem cái nào hoạt động tốt nhất và báo cáo lại nhưng tôi nghĩ con đường là con đường để đi. – eleddy

+0

@zzzeek Tôi có câu hỏi về vấn đề này nhưng đối với Alembic, thực sự có thể sử dụng thông tin đầu vào của bạn: http://stackoverflow.com/questions/21109218/alembic-support-for-multiple-postgres-schemas – dtheodor

+4

Ngẫu nhiên, tôi đã cố gắng thực hiện điều này cho cú pháp khai báo như: '' Base = declarative_base(); Base.metadata.schema = 'ebay'''. Có lẽ là một cách tốt hơn, mặc dù. –

2

Có một tài sản schema trong Table definitions

Tôi không chắc chắn nếu nó hoạt động nhưng bạn có thể thử:

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

Tôi đang tìm thứ gì đó trên quy mô toàn cầu hơn để tôi có thể chuyển tất cả truy vấn trong tất cả các bảng cho một yêu cầu. Tôi sẽ cập nhật câu hỏi để phản ánh điều đó. – eleddy

0

Bạn chỉ có thể thay đổi search_path của mình. Vấn đề

set search_path=client9; 

khi bắt đầu phiên và sau đó chỉ giữ cho bảng của bạn không đủ điều kiện.

Bạn cũng có thể đặt search_path mặc định ở cấp cho mỗi cơ sở dữ liệu hoặc cho mỗi người dùng. Tôi sẽ bị cám dỗ để đặt nó vào một lược đồ trống theo mặc định, do đó bạn có thể dễ dàng bắt bất kỳ thất bại để thiết lập nó.

+0

đó là ... một ý tưởng tuyệt vời. đập tay! – eleddy

+0

Và hãy nhớ rằng sau khi session.commit() một giao dịch mới được bắt đầu để search_path sẽ được đặt lại. Các sự kiện phiên SA hoạt động tốt cho việc thiết lập search_path cho mỗi giao dịch mới. – Brett

5

Bạn có thể quản lý này sử dụng giao diện sự kiện SQLAlchemy. Vì vậy, trước khi bạn tạo kết nối đầu tiên, thiết lập một người biết lắng nghe dọc theo dòng của

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) 

Rõ ràng điều này cần phải được thực hiện trước khi kết nối đầu tiên được tạo ra (ví dụ như trong initiallization ứng dụng)

Vấn đề tôi thấy với giải pháp session.execute (...) là điều này thực hiện trên một kết nối cụ thể được sử dụng bởi phiên. Tuy nhiên tôi không thể nhìn thấy bất cứ điều gì trong sqlalchemy đảm bảo rằng phiên sẽ tiếp tục sử dụng cùng một kết nối vô thời hạn. Nếu nó chọn một kết nối mới từ nhóm kết nối thì nó sẽ mất cài đặt đường dẫn tìm kiếm.

Tôi cần một cách tiếp cận như thế này để đặt search_path ứng dụng, khác với cơ sở dữ liệu hoặc đường dẫn tìm kiếm của người dùng. Tôi muốn có thể thiết lập điều này trong cấu hình động cơ, nhưng không thể nhìn thấy một cách để làm điều này. Sử dụng sự kiện kết nối không hoạt động. Tôi muốn được quan tâm trong một giải pháp đơn giản nếu có ai có.

Mặt khác, nếu bạn muốn xử lý nhiều ứng dụng trong một ứng dụng, điều này sẽ không hoạt động - và tôi đoán phương pháp session.execute (...) có thể là cách tiếp cận tốt nhất.

+1

Bạn có một cách thanh lịch để vượt qua 'client9' như một đối số thay vì hardcoding nó? Cách khắc phục hiện tại (hacky) của tôi là chuyển truy vấn 'application_name' arg tới db-url ('? Application_name = bla') và sau đó kiểm tra nó trong 'set_search_path' với' db_conn.dsn.split ('application_name =') [ 1]) '. – rkrzr

0

Tôi không tìm thấy câu trả lời nào ở trên làm việc với SqlAlchmeny 1.2.4. Đây là giải pháp làm việc cho tôi.

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 
Các vấn đề liên quan