源码分析

# set server timezone in UTC before time module imported
__import__('os').environ['TZ'] = 'UTC'
import odoo

if __name__ == "__main__":
    odoo.cli.main()

odoo的启动入口,从这里跟踪到odoo模块下的cli包的__init__.py文件。

import logging
import sys
import os

import odoo

from .command import Command, main
...

在这里可以看到导入了command中的main函数,继续追踪main函数

def main():
	# ['D:\\odoo14\\odoo-bin', '-c', 'D:\\odoo14\\odoo.conf', '-u', 'service_request']
    # 获取除了脚本名称之外的命令行参数['-c', 'D:\\odoo14\\odoo.conf', '-u', 'service_request']
    args = sys.argv[1:]  
    if len(args) > 1 and args[0].startswith('--addons-path=') and not args[1].startswith("-"):
        odoo.tools.config._parse_config([args[0]])  # 解析--addons-path=参数
        args = args[1:]  # 从参数列表中移除已解析的参数

    command = "server"  # 默认命令设置为"server"

    #检查是否有子命令
    if len(args) and not args[0].startswith("-"):
        logging.disable(logging.CRITICAL)  # 禁用日志
        initialize_sys_path()  # 初始化系统路径
        # 遍历所有模块,检查是否有cli目录,如果有,则导入模块
        for module in get_modules():
            if isdir(joinpath(get_module_path(module), 'cli')):
                __import__('odoo.addons.' + module)
        logging.disable(logging.NOTSET)  # 恢复日志记录
        command = args[0]  # 设置实际的命令
        args = args[1:]  # 更新参数列表,移除已使用的命令

    # 检查命令是否存在
    if command in commands:
        o = commands[command]()  # 创建命令实例
        o.run(args)  # 执行命令
    else:
        sys.exit('Unknown command %r' % (command,))  # 退出程序并提示未知命令

command.py文件这里可以看到有用到了元类知识,定义了一个元类 CommandType,它的作用是在创建命令类时自动注册这些类。这通过修改类的 __init__ 方法实现,每当创建一个新的命令类时,我们都会将其添加到一个全局的 commands 字典中。这样,我们就可以通过命令名来查找和执行命令了。

接下来,我们定义了一个基础命令类 Command,使用 CommandType 作为其元类。这样做的目的是确保任何继承自 Command 的子类都会自动注册。

我简单了模仿写了个代码举例子,用来说明元类的用法

commands = {}


class CommandType(type):
    def __init__(cls, name, bases, attrs):
        super(CommandType, cls).__init__(name, bases, attrs)
        cls.name = attrs.get('name', name.lower())
        if cls.name != 'command':
            commands[cls.name] = cls


# Command = CommandType('Command', (object,), {'run': lambda self, args: None})
# 等同于以下的类定义
class Command(metaclass=CommandType):
    def run(self, args):
        pass


# 定义 CreateCommand
class CreateCommand(Command):
    def run(self, args):
        print("create,", ' '.join(args))


# 定义 ShellCommand
class ShellCommand(Command):
    def run(self, args):
        print("Shell,", ' '.join(args))


def execute_command(command_name, args):
    command_class = commands.get(command_name)
    if command_class is not None:
        command_instance = command_class()
        command_instance.run(args)
    else:
        print("Unknown command:", command_name)


user_input_command = "createcommand"
user_input_args = ["zuru"]
execute_command(user_input_command, user_input_args)

有了上面的铺垫,我自定义一个脚手架,模仿sacffold的子命令,我重命名了一个create的子命令,该类要继承Command, 重载了run函数,引用了自己的代码模版default2,里面可以编写自己想要的代码片段。模版路径存放于odoo\cli\templates

class Custom_Create(Command):
    """ Generates an Odoo module skeleton. """

    def run(self, cmdargs):
        # TODO: bash completion file
        parser = argparse.ArgumentParser(
            prog="%s costom_create" % sys.argv[0].split(os.path.sep)[-1],
            description=self.__doc__,
            epilog=self.epilog(),
        )
        parser.add_argument(
            '-t', '--template', type=template, default=template('default2'),
            help="Use a custom module template, can be a template name or the"
                 " path to a module template (default: %(default)s)")
        parser.add_argument('name', help="Name of the module to create")
        parser.add_argument(
            'dest', default='.', nargs='?',
            help="Directory to create the module in (default: %(default)s)")

        if not cmdargs:
            sys.exit(parser.print_help())
        args = parser.parse_args(args=cmdargs)

        args.template.render_to(
            snake(args.name),
            directory(args.dest, create=True),
            {'name': args.name})


使用自定义的脚手架

以下是如何执行一个命令的示例:

python D:\odoo14\odoo-bin custom_create  custom_module D:\odoo14\custom