使用SQLAlchemy反射功能时如何合理构建代码结构?
核心总结
- 难以理解SQLAlchemy ORM反射功能的代码结构构建方式
- 构建「表类型实例」似乎必须传递
metadata对象,而非像其他Python类型那样直接使用 - 推测这是因为类型无法在编写代码时静态确定,但对此编程风格不熟悉,希望得到更多建议
问题详情
使用SQLAlchemy反射功能时,Python会根据SQL数据库中的表结构在运行时动态生成类型,由于这些类型仅在代码执行时可用,编写代码时无法获取,导致在结构设计上遇到概念障碍。
反射的基本使用
实现反射并获取表类型对象的最简代码如下:
db_url = "postgresql://postgres:password@localhost/postgresdb" engine = sqlalchemy.create_engine(url=db_url) metadata = sqlalchemy.MetaData() my_table_object = sqlalchemy.Table('my_table', metadata, autoload_with=engine) with engine.connect() as connection: # rest of code
此时my_table_object可用于执行CRUD操作:
query = sqlalchemy.select(my_table_object) result_proxy = connection.execute(query) result_set = result_proxy.fetchall() print(result_set[0])
现有代码情况
现有代码大多未使用SQLAlchemy的ORM功能,仅使用text对象和SQL领域特定语言(DSL)编写,示例函数如下:
def select_id_from_my_table(connection: sqlalchemy.Connection): query_string = text( ''' select id from my_table ''' ) query_params = { } result = connection.execute(query_string, query_params) # do something with result and return value(s)
选择传递单个connection对象而非engine的原因:
- 程序为单线程(Python编写,未使用multiprocess等库)
- 无需反复打开关闭连接增加延迟,可在main函数顶层打开连接并在调用栈中传递,贯穿程序生命周期
使用ORM的疑问
通过sqlalchemy.Table('my_table', metadata, autoload_with=engine)生成的表类型对象,该如何合理使用?
之前仅用SQL的代码只需传递connection对象,若要使用ORM功能,有两种选择:
- 在代码开头通过反射创建所有需要的表类型
- 延迟创建——仅在需要时生成
已知反射得到的表的实际信息会存储在metadata对象中,同时也会由sqlalchemy.Table()调用返回。若选择在开头创建所有所需表类型,有两种实现方式:
- 创建新结构(如字典)存储反射得到的表类型
- 传递
metadata对象,通过其tables属性获取表类型:
my_table_object = metadata.tables.get('my_table')
第二种方式似乎更合理,但延迟创建需要额外代码检查表类型是否已生成,不如在开头就反射所有所需表。
核心困惑
目前构思的使用方式感觉有些怪异,原因如下:
- 与数据库交互需要传递
connection对象 - 使用ORM功能需要传递
metadata对象才能获取「类型」 - 若要创建新连接或执行更多反射操作,还需传递
engine对象 - 似乎无法使用
connection对象执行反射操作 - 这导致代码中至少需要在调用栈各层传递3个对象,以备不时之需
- 虽可以将它们封装成元组或更结构化的类型,但会增加大量代码,导致整体结构更僵化,不符合Python的灵活性
特别强调:这种从metadata对象中获取类型的方式非常怪异,通常Python类型是全局可访问的,若要创建MyClass实例,只需正确导入即可直接使用,无需从某个对象中获取。可能只是不熟悉这种编程风格所以觉得奇怪,本身没问题。
补充说明
已知SQLAlchemy ORM可以在编写代码时静态定义表,但不想这么做,希望利用SQL数据库中已有的元数据/结构来定义表结构,以此减少维护成本:若数据库结构变更,通常无需修改代码,除非逻辑需要调整。使用Python这类动态语言却不利用其动态特性毫无意义(不过若用Python代码驱动数据库创建,反向方式会更合理)。
内容的提问来源于stack exchange,提问作者user2138149

