如何正确组织SQLAlchemy(声明式风格)Python项目及其单元测试

10 投票
2 回答
7100 浏览
提问于 2025-04-17 12:41

我正在为一些网页应用开发一个大型的后端。这是我第一次使用Python和SQLAlchemy,所以有些地方我感到困惑。而且相比于Java的编程工具和IDE,我觉得Python的工具有点不够好(不过我还是在Eclipse里用pydev)。我需要帮助来整理项目结构和编写测试。首先,我先描述一下我的情况。

在PyDev中,我把我的项目命名为“ProjectName”,下面是我当前的文件夹/包和文件结构。

  • ProjectName
    • projectname
      • __init__.py
      • some_package
        • __init__.py
        • Foo.py
        • Bar.py
    • tests
      • unit_tests
        • __init__.py
        • some_package
          • __init__.py
          • TestFoo.py
          • TestBar.py
      • load_tests
      • integration_tests
      • __init__.py

我在SQLAlchemy中使用声明式风格。Foo和Bar是一些类,Foo扩展了SQLAlchemy的声明式基础类,Bar则扩展了Foo。在'projectname.some_package'的__init__.py里,我有以下代码:

engine = create_engine('mysql+mysqldb://user:pass@localhost:3306/SomeDataBase', pool_recycle=3600)
Session = sessionmaker(bind=engine)
Base = declarative_base()

所以,Foo导入了这个基础类并进行了扩展,Bar导入了Foo并进行了扩展。我的第一个问题是,我应该把基础类存放在这个__init__.py里,并像我开始时那样使用这两个类吗?这个create_engine只是暂时放在那里的,我想有一个配置文件并从那里加载设置,应该怎么做?我应该在哪里调用Base.metadata.create_all(),这样才能一次性创建所有数据库表?

接下来,在测试类中,比如在TestFoo里,我有以下代码:

def setUp(self):
    #create database tables and session object
    self.engine = create_engine('mysql+mysqldb://user:pass@localhost:3306/SomeDatabase', pool_recycle=3600)
    Session = sessionmaker(bind=self.engine)
    Foo.metadata.create_all(bind=self.engine)
    self.session = Session()

def tearDown(self):
    #drop all tables and close session object
    self.session.close()
    meta = MetaData(self.engine)
    meta.reflect()
    meta.drop_all()

然后我在这个测试类里有一些测试方法,运行得很好。在TestBar类中,区别在于

Foo.metadata.create_all(bind=self.engine)

是:

Bar.metadata.create_all(bind=self.engine)

当我运行TestBar时,它也运行得很好。但是,当我选择两个测试类并一起运行时,我遇到了错误:

/usr/local/lib/python2.7/dist-packages/sqlalchemy/ext/declarative.py:1336: SAWarning: The classname 'Foo' is already in the registry of this declarative base, mapped to <class 'projectname.some_package.Foo.Foo'>
  _as_declarative(cls, classname, cls.__dict__)
/usr/local/lib/python2.7/dist-packages/sqlalchemy/engine/default.py:330: Warning: Field 'id' doesn't have a default value
 cursor.execute(statement, parameters)

这里的问题是什么?我尝试用nose和pydev的测试运行器来运行测试,结果也出现了相同的错误。然后我尝试把数据库表的创建移动到unit_tests下的__init__.py里,但我无法让它工作。此外,我对Python的导入机制感到困惑。例如,如果我在TestBar类中添加Foo的导入,我也会遇到类似的错误。我该如何一次性运行多个测试,来测试SQLAlchemy的类呢?

所以,再次提炼出最重要的问题:

  1. 如何正确地构建一个使用SQLAlchemy声明式风格和单元测试的Python项目?顺便说一下,我在Foo和Bar中有很多与数据库交互的类方法,这样做可以吗?
  2. 应该把基础声明类存放在哪里,如何在整个项目中正确使用它,以及如何在项目的任何地方提取我在类中声明的所有数据库模式并使用它?
  3. 如何最好地使用单元测试与SQLAlchemy,并一次性运行多个单元测试?
  4. 如果你有其他建议,请随时补充。

非常感谢你的帮助。

2 个回答

0

根据zzzeek的评论,模式/schema包里可以用一个名为"__init__.py"的文件,代替普通的Python文件。这在SQLAlchemy 1.4.32版本中是有效的。

4

简单回答一下(时间有限,抱歉):用一个 MetaData 实例就可以了,不需要为 Foo 和 Bar 各自创建一个。一般来说,多个 MetaData 实例是个高级技巧,几乎用不到。

撰写回答