Nous hébergeons une application multi-locataire avec SQLAlchemy et postgres. Je cherche à passer de bases de données distinctes pour chaque locataire à une base de données unique avec plusieurs schémas. SQLAlchemy prend-il cela en charge de manière native? En gros, je veux juste que chaque requête qui sort soit préfixée par un schéma prédéterminé ... par exemple
select * from client1.users
au lieu de juste
select * from users
Notez que je veux changer le schéma pour toutes les tables dans une requête/un ensemble de requêtes particulier, pas seulement une table ici ou là.
J'imagine que cela pourrait également être réalisé avec une classe de requête personnalisée, mais je ne peux pas imaginer que quelque chose n'ait pas déjà été fait dans ce sens.
eh bien, il existe quelques solutions et cela dépend de la structure de votre application. Voici le moyen le plus fondamental:
meta = MetaData(schema="client1")
Si votre application est exécutée un "client" à la fois dans l'ensemble de l'application, vous avez terminé.
Mais ce qui ne va pas avec ça ici, c'est que chaque table de cette méta-donnée se trouve sur ce schéma. Si vous souhaitez qu'une application prenne en charge plusieurs clients simultanément (généralement ce que signifie "multitenant"), cela serait compliqué, car vous devrez créer une copie de MetaData et dupliquer tous les mappages de chaque client. Cette approche peut être utilisée si vous le souhaitez vraiment. Si vous le souhaitez, vous accéderez à chaque client avec une classe mappée particulière, telle que:
client1_foo = Client1Foo()
et dans ce cas, vous utiliseriez la recette "Nom de l’entité" sur http://www.sqlalchemy.org/trac/wiki/UsageRecipes/EntityName en conjonction avec sometable.tometadata()
(voir http: // docs.sqlalchemy.org/fr/latest/core/metadata.html#sqlalchemy.schema.Table.tometadata ).
Supposons donc que cela fonctionne réellement avec plusieurs clients au sein de l'application, mais un seul à la fois par thread. En fait, le moyen le plus simple de procéder dans Postgresql consiste à définir le chemin de recherche lorsque vous commencez à utiliser une connexion:
# 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()
L'approche finale consisterait à remplacer le compilateur à l'aide de l'extension @compiles pour coller le nom "schema" dans les instructions. C'est faisable, mais serait délicat car il n'y a pas de hook cohérent pour tous les endroits où "Table" est générée. Votre meilleur pari est probablement de définir le chemin de recherche pour chaque demande.
Si vous voulez faire cela au niveau de la chaîne de connexion, utilisez ce qui suit:
dbschema='schema1,schema2,public' # Searches left-to-right
engine = create_engine(
'postgresql+psycopg2://dbuser@dbhost:5432/dbname',
connect_args={'options': '-csearch_path={}'.format(dbschema)})
Cependant, une meilleure solution pour une application multi-client (multi-locataire) consiste à configurer un utilisateur de base de données différent pour chaque client et à configurer le search_path correspondant à chaque utilisateur:
alter role user1 set search_path = "$user", public
Vous pourrez peut-être gérer cela à l'aide de l'interface d'événement sqlalchemy. Donc, avant de créer la première connexion, configurez un écouteur le long des lignes suivantes:
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 )
Ceci doit évidemment être exécuté avant la création de la première connexion (par exemple, lors de l'initialisation de l'application)
Le problème que je vois avec la solution session.execute (...) est qu'il s'exécute sur une connexion spécifique utilisée par la session. Cependant, je ne vois rien dans sqlalchemy qui garantisse que la session continuera à utiliser la même connexion indéfiniment. S'il sélectionne une nouvelle connexion dans le pool de connexions, il perd le paramètre de chemin de recherche.
J'ai besoin d'une approche comme celle-ci pour définir l'application search_path, qui est différente de la base de données ou du chemin de recherche de l'utilisateur. J'aimerais pouvoir définir ceci dans la configuration du moteur, mais je ne vois pas comment le faire. L'utilisation de l'événement connect fonctionne. Je serais intéressé par une solution plus simple si quelqu'un en a une.
D'autre part, si vous souhaitez gérer plusieurs clients au sein d'une application, cela ne fonctionnera pas - et je suppose que l'approche session.execute (...) peut être la meilleure approche.
Il existe une propriété de schéma dans Définitions de table
Je ne sais pas si cela fonctionne, mais vous pouvez essayer:
Table(CP.get('users', metadata, schema='client1',....)
J'ai trouvé aucune des réponses ci-dessus a fonctionné avec SqlAlchmeny 1.2.4
. C'est la solution qui a fonctionné pour moi.
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
Vous pouvez simplement changer votre chemin de recherche. Problème
set search_path=client9;
au début de votre session et gardez ensuite vos tables non qualifiées.
Vous pouvez également définir un chemin_recherche par défaut au niveau de la base de données ou de l'utilisateur. Je serais tenté de le définir par défaut sur un schéma vide afin que vous puissiez facilement détecter tout échec.
http://www.postgresql.org/docs/current/static/ddl-schemas.html#DDL-SCHEMAS-PATH
J'ai essayé:
con.execute('SET search_path TO {schema}'.format(schema='myschema'))
et cela n'a pas fonctionné pour moi. J'ai ensuite utilisé le paramètre schema = dans la fonction init:
# We then bind the connection to MetaData()
meta = sqlalchemy.MetaData(bind=con, reflect=True, schema='myschema')
Puis j'ai qualifié la table avec le nom du schéma
house_table = meta.tables['myschema.houses']
et tout a fonctionné.