Coverage for adhoc-cicd-odoo-odoo / odoo / cli / command.py: 48%
83 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-09 18:05 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-09 18:05 +0000
1import argparse
2import contextlib
3import re
4import sys
5from inspect import cleandoc
6from pathlib import Path
8import odoo.init # import first for core setup
9import odoo.cli
10from odoo.modules import initialize_sys_path, load_script
11from odoo.tools import config
14COMMAND_NAME_RE = re.compile(r'^[a-z][a-z0-9_]*$', re.I)
15PROG_NAME = Path(sys.argv[0]).name
16commands = {}
17"""All loaded commands"""
20class Command:
21 name = None
22 description = None
23 epilog = None
24 _parser = None
26 def __init_subclass__(cls):
27 cls.name = cls.name or cls.__name__.lower()
28 module = cls.__module__.rpartition('.')[2]
29 if not cls.is_valid_name(cls.name): 29 ↛ 30line 29 didn't jump to line 30 because the condition on line 29 was never true
30 raise ValueError(
31 f"Command name {cls.name!r} "
32 f"must match {COMMAND_NAME_RE.pattern!r}")
33 if cls.name != module: 33 ↛ 34line 33 didn't jump to line 34 because the condition on line 33 was never true
34 raise ValueError(
35 f"Command name {cls.name!r} "
36 f"must match Module name {module!r}")
37 commands[cls.name] = cls
39 @property
40 def prog(self):
41 return f"{PROG_NAME} [--addons-path=PATH,...] {self.name}"
43 @property
44 def parser(self):
45 if not self._parser:
46 self._parser = argparse.ArgumentParser(
47 formatter_class=argparse.RawDescriptionHelpFormatter,
48 prog=self.prog,
49 description=cleandoc(self.description or self.__doc__ or ""),
50 epilog=cleandoc(self.epilog or ""),
51 )
52 return self._parser
54 @classmethod
55 def is_valid_name(cls, name):
56 return re.match(COMMAND_NAME_RE, name)
59def load_internal_commands():
60 """ Load ``commands`` from ``odoo.cli`` """
61 for path in odoo.cli.__path__:
62 for module in Path(path).iterdir():
63 if module.suffix != '.py':
64 continue
65 __import__(f'odoo.cli.{module.stem}')
68def load_addons_commands(command=None):
69 """
70 Search the addons path for modules with a ``cli/{command}.py`` file.
71 In case no command is provided, discover and load all the commands.
72 """
73 if command is None:
74 command = '*'
75 elif not Command.is_valid_name(command):
76 return
78 mapping = {}
79 initialize_sys_path()
80 for path in odoo.addons.__path__:
81 for fullpath in Path(path).glob(f'*/cli/{command}.py'):
82 if (found_command := fullpath.stem) and Command.is_valid_name(found_command):
83 # loading as odoo.cli and not odoo.addons.{module}.cli
84 # so it doesn't load odoo.addons.{module}.__init__
85 mapping[f'odoo.cli.{found_command}'] = fullpath
87 for fq_name, fullpath in mapping.items():
88 with contextlib.suppress(ImportError):
89 load_script(fullpath, fq_name)
92def find_command(name: str) -> Command | None:
93 """ Get command by name. """
95 # built-in commands
96 if command := commands.get(name): 96 ↛ 97line 96 didn't jump to line 97 because the condition on line 96 was never true
97 return command
99 # import from odoo.cli
100 with contextlib.suppress(ImportError):
101 __import__(f'odoo.cli.{name}')
102 return commands[name]
104 # import from odoo.addons.*.cli
105 load_addons_commands(command=name)
106 return commands.get(name)
109def main():
110 args = sys.argv[1:]
112 # The only shared option is '--addons-path=' needed to discover additional
113 # commands from modules
114 if len(args) > 1 and args[0].startswith('--addons-path=') and not args[1].startswith('-'): 114 ↛ 116line 114 didn't jump to line 116 because the condition on line 114 was never true
115 # parse only the addons-path, do not setup the logger...
116 config._parse_config([args[0]])
117 args = args[1:]
119 if len(args) and not args[0].startswith('-'): 119 ↛ 121line 119 didn't jump to line 121 because the condition on line 119 was never true
120 # Command specified, search for it
121 command_name = args[0]
122 args = args[1:]
123 elif '-h' in args or '--help' in args: 123 ↛ 125line 123 didn't jump to line 125 because the condition on line 123 was never true
124 # No command specified, but help is requested
125 command_name = 'help'
126 args = [x for x in args if x not in ('-h', '--help')]
127 else:
128 # No command specified, default command used
129 command_name = 'server'
131 if command := find_command(command_name): 131 ↛ 135line 131 didn't jump to line 135 because the condition on line 131 was always true
132 odoo.cli.COMMAND = command_name
133 command().run(args)
134 else:
135 message = (
136 f"Unknown command {command_name!r}.\n"
137 f"Use '{PROG_NAME} --help' to see the list of available commands."
138 )
139 sys.exit(message)