python - 在连续的 pytests 中删除 sqlalchemy 列属性,在类声明之后添加
问题描述
要在数据库元数据中添加一列,在启动 alembic 迁移之前,我在列插入 python 脚本中执行以下操作:
class table(Base):
__tablename__ = "my_table"
id = Column(Integer, primary_key = True)
name = Column(String)
col_name = "nickname"
#also assume there's another class that has a foreign key that references table.id
related_class = list(inspect(table).relationships)[0].entity
related_tab = list(inspect(table).relationships)[0].entity.local_table
# I create two column objects, since the same one cannot be assigned to two tables
new_column = Column(col_name,type_ = new_type)
new_column2= Column(col_name,type_ = new_type)
table.__table__.append_column(new_column)
table.__mapper__.add_property(col_name,new_column)
#add the column to the related class/table
related_tab.append_column(new_column2)
related_class.add_property(col_name, new_column2)
#then I run my alembic auto revision and upgrade script
本质上,我创建了两个相同的列对象,然后将它们添加到表中,并作为映射器属性。
但是,我在测试数据库时遇到了问题。我的映射类(不是数据库)的状态被重用于下一个测试。但我希望在每次测试开始时都有一个干净的状态。
最后一个测试是向 Base.metadata 添加一个新列,然后创建一个自动生成的修订和升级。在测试结束时降级并不能解决问题。
这是具体情况。
我为每个测试类创建了一个新的引擎/数据库,但元数据/映射器(来自 Base 和类表)仍然包含来自前一组测试的额外列。作为我拆解的一部分,sqlite 数据库文件在测试集/类结束时被删除。
因此,下一组测试的 create_engine() 命令添加了我不想要的额外列。
使用 clear_mapper 不起作用,因为整个表都被删除了,并且无法在其他测试集中找到。
那么,作为拆解的一部分,如何从映射器中删除此列属性呢?
这是我发现几乎可以工作的东西
table.__mapper__.attrs=ImmutableProperties(
dict(list(Ref_sheet.__mapper__.attrs.items())[:size-1]
))
table.__table__.columns = ColumnCollection(
(list(table.__table__.columns.items())[:-1])).as_immutable()
如果我遍历列属性/键(),那么最新的列就消失了(在 pytest 类之间)。但是,如果我遍历表。mapper .all_ORM_descriptors(),应该删除的列仍然出现。
一个潜在的解决方案是为每组测试创建不同的类(即table1、table2)。但是,如果测试变得更大,这将无法扩展,并导致重复代码。
解决方案
这只是删除类属性的问题,以及曾经通过首先检索它们而关联的列对象。但是,仍然存在 ORM 描述符 - 作为 InstrumentedAttributes。
但是,删除类属性和关联的列就足以让 Base.metadata.create_all() 创建没有删除列的表。需要做的只是在应用迁移脚本之前但在 create_all() 命令之后反映数据库中的表。
._columns 之前的下划线(我也假设属性)是列对象,然后将其转换为不可变列集合
del table.__mapper__._props['new_column']
#col.name is the name of the column key as it appears in the table
cols = [col for col in table.__table__._columns if col.name == 'new_column'][0]
table.__table__._columns.remove(cols)
Base.metadata.create_all()
table.__table__ = Table(table.__tablename__, Base.metadata, autoload_with = engine)
因此,在测试数据库操作以及单个列添加(例如使用 alembic)的 pytest 环境中,只需将上述代码包装在 if 语句中作为引擎设置固定装置的一部分。
if list(table.__table__.columns.keys())[-1] == 'new_column':
del table.__mapper__._props['new_column']
cols = [col for col in table.__table__._columns if col.name == 'new_column'][0]
table.__table__._columns.remove(cols)
#do the same for related tables
Base.metadata.create_all()
table.__table__ = Table(table.__tablename__, Base.metadata, autoload_with = engine)
推荐阅读
- php - 根据 Woocommerce 中的特定城市显示货到付款 (COD)
- google-data-studio - 谷歌数据工作室关于计算字段的条件语句
- php - Composer 更新后更新包列表
- angular - 在重定向 Angular 6 上获取 HTTP 标头
- html - 带有下拉列表的表格主体列的位置粘性
- java - 如何异步将数据从一个类发送到另一个类?
- php - 当用户输入错误的 url 时如何禁止访问或重定向到另一个页面?
- javascript - 如何隐藏用户的响应?
- sql - PL SQL - 如何在带有条件返回的 for 循环中使用 select 语句?
- php - 如何使用 ajax 和 php 制作编辑模式