在sqlalchemy中管理用户权限

5 投票
2 回答
9984 浏览
提问于 2025-04-17 21:33

我有一个使用sqlalchemy的脚本,它创建并使用了很多引擎实例,这些实例代表了许多用户的连接。所有的引擎都配置为指向同一个postgres数据库。

我有一个引擎,叫做oSuperEngine,它可以做一些超级的事情。还有另一个引擎,叫做oBobsEngine,是给Bob用的。

现在我在做这样的操作:

sSQL = "GRANT ALL PRIVILEGES ON TABLE \"NICE_TABLE\" to bob;"
oSuperEngine.execute(sSQL)

sSQL = "insert into \"NICE_TABLE\" (foo) values (bar)"
oBobsEngine.execute(sSQL) # ERROR HERE

结果是:

ProgrammingError: (ProgrammingError) permission denied for relation NICE_TABLE

这是为什么呢?

在psql中,输入\dp命令告诉我,Bob从来没有被授予权限。如果我用超级用户登录psql,并手动给Bob授予权限,那么一切就都正常了。在这种情况下,我使用的命令和通过sqlalchemy执行的完全一样。

是不是有什么东西没有正确刷新?sqlalchemy是否因为某种原因在尝试授予权限时喜欢静默失败?我该如何让这个工作正常呢?

抱歉没有提供更多代码,代码库有点复杂。事件的顺序已经通过日志确认过了……如果你需要更多代码来理解我的问题,请告诉我。

一些日志信息

2014-03-10 10:07:24,767 - common.sqlalchemy_tools - DEBUG - connection string = "postgresql+psycopg2://super:password@localhost/db_name"
2014-03-10 10:07:24,767 - sqlalchemy.engine.base.Engine - INFO - BEGIN;
2014-03-10 10:07:24,767 - sqlalchemy.engine.base.Engine - INFO - {}
2014-03-10 10:07:24,767 - sqlalchemy.engine.base.Engine - INFO - GRANT ALL PRIVILEGES ON TABLE "MY_TABLE" to bob;
2014-03-10 10:07:24,767 - sqlalchemy.engine.base.Engine - INFO - {}
2014-03-10 10:07:24,768 - sqlalchemy.engine.base.Engine - INFO - COMMIT;
2014-03-10 10:07:24,768 - sqlalchemy.engine.base.Engine - INFO - {}
 ...
2014-03-10 10:07:24,804 - common.sqlalchemy_tools - DEBUG - connection string = "postgresql+psycopg2://bob:password@localhost/db_name"
2014-03-10 10:07:24,814 - sqlalchemy.engine.base.Engine - INFO - BEGIN;
2014-03-10 10:07:24,815 - sqlalchemy.engine.base.Engine - INFO - {}
2014-03-10 10:07:24,827 - sqlalchemy.engine.base.Engine - INFO - insert into "MY_TABLE" (stuff) values (other stuff);
2014-03-10 10:07:24,827 - sqlalchemy.engine.base.Engine - INFO - {}
2014-03-10 10:07:24,828 - sqlalchemy.engine.base.Engine - INFO - ROLLBACK

第一部分的操作是用oSuperEngine进行的,第二部分是用Bob的引擎进行的。错误信息看起来是:

ProgrammingError: (ProgrammingError) permission denied for relation MY_TABLE
 'insert into "MY_TABLE" (stuff) values (stuff);' {}

2 个回答

8

我也遇到过完全一样的问题。通过这样做解决了:

sSQL = "GRANT ALL PRIVILEGES ON TABLE \"NICE_TABLE\" to bob;"
with oSuperEngine.begin() as conn:
    conn.execute(sSQL)

http://docs.sqlalchemy.org/en/latest/orm/session_basics.html#adding-additional-configuration-to-an-existing-sessionmaker

4

如果你运行上面的脚本,你会遇到一个关于事务隔离的问题。我之前也遇到过类似的情况。这里要记住的是,Bob的事务看不到超级引擎所做的更改,因为这个事务还在进行中,而隔离性不允许读取未提交的数据。Postgres有很多关于这个的文档,简单来说就是:在Postgres中,无法读取未提交的数据

所以,你的解决办法显然是先从超级引擎提交你的更改,然后再在另一个事务中使用这些更改。我没有尝试过,但我猜测你必须保持默认的已提交读取隔离级别(因为更高的隔离级别会让你无法检测到自从事务开始(在这个例子中是Bob的事务)以来的更改)。

因此,在执行Bob的查询之前:

oSuperEngine.execute("COMMIT")

不过,这引出了一个副问题,实际上破坏了事务带来的好处:你无法轻松地回滚,因为更改已经被提交。你在这里基本上想要的是与保存点相反的东西:

保存点不会立即将更改存入数据库,而是确保回滚只会回到某个特定的点,而不是回到最开始。你想要的是:将更改存入数据库,但又能回滚并将其移除。我不知道有没有这样的东西,我非常怀疑它的存在,因为这会违反事务隔离的原则。

我解决这个问题的方法是编写自定义的回滚程序,当出现异常时调用它,这样我就可以手动撤销更改。不过,这需要不少工作,而且从长远来看维护起来也比较麻烦。最后,在我的案例中,我重新考虑了我的解决方案,放弃了多引擎的方法(但这个决定你得自己做)。

撰写回答