如何在setuptools中使用选项运行命令?

8 投票
2 回答
2372 浏览
提问于 2025-04-18 10:41

我在我的 setup.py 文件 里有两个自定义命令:create_tablesdrop_tables

class create_tables(command):
    description = 'create DB tables'

    user_options = [
        ('database=', 'd', 'which database configuration use'),
        ('reset', 'r', 'reset all data previously'),
    ]

    def initialize_options(self):
        command.initialize_options(self)
        self.reset = False

    def run(self):
        if self.reset:
            self.run_command('drop_tables')
        else:
            command.run(self)
        from vk_relations import models
        models.create_tables()
        print 'Tables were created successfully'


class drop_tables(command):
    description = 'drop all created DB tables'

    user_options = [
        ('database=', 'd', 'which database configuration use'),
    ]

    def run(self):
        command.run(self)
        answer = raw_input('Are you sure you want to clear all VK Relations data? (y/n): ')
        if 'y' == answer:
            from vk_relations import models
            models.drop_tables()
            print 'Tables were dropped successfully'
        elif 'n' == answer:
            quit()
        else:
            sys.exit()

命令 $ setup.py create_tables -r -dmain 应该先运行 drop_tables 命令,然后在 main 数据库里创建新表格,但 run_command 方法不允许给命令提供选项。我该如何在 create_tables 命令里为 drop_tables 指定 database 选项呢?

2 个回答

2

“正确”的解决方案

在对象上设置属性会对一些预定义的目标(比如 build)失败。你能找到的最接近“正确”解决方案的方法是:

class drop_tables(command): # <-- Note this should come from drop_tables command

    def finalize_options(self):
        self.set_undefined_options("create_tables", ("database", "database"))

这个方法用于将参数从 build 传递到 build_py 和其他子命令。

关于 build 命令

我不太喜欢 distutils 包的作者在 build 命令中引入的循环引用。执行顺序是这样的:build 命令调用 build_py 子命令。然后这个子命令又回到 build 命令,获取那些没有定义的参数。这种做法让两个命令紧密耦合,因为它们需要互相了解。如果再添加一个聚合命令,就会引入模糊性——build_py 将会有两个参数来源。

减少耦合的做法应该有所不同。如果 build 命令是一个聚合命令,那么它应该处理所有参数传递给它的子命令。

class build(command):
    ...
    def finalize_options(self):
        for cmd_name in self.get_sub_commands():
            cmd_obj = self.distribution.get_command_obj(cmd_name)
            cmd_obj.set_undefined_options("build", ("build_lib", "build_lib"), ...)

现在不需要通过名称传递命令,我们可以使用实例来代替。这也能解决在 set_undefined_options > ensure_finalized > finalize_options > set_undefined_options 中出现的无限递归问题。

正确的解决方案

考虑到目前的情况,你问题的更好解决方案是:

class create_tables(command):

    def run(self):
        cmd_obj = self.distribution.get_command_obj("drop_tables")
        cmd_obj.set_undefined_options("create_tables", ("database", "database"))
        self.run_command("drop_tables")
4

现在我用的是这个小技巧:

cmd_obj = self.distribution.get_command_obj('drop_tables')
cmd_obj.database = self.database
self.run_command('drop_tables')

撰写回答