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

1import argparse 

2import contextlib 

3import re 

4import sys 

5from inspect import cleandoc 

6from pathlib import Path 

7 

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 

12 

13 

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""" 

18 

19 

20class Command: 

21 name = None 

22 description = None 

23 epilog = None 

24 _parser = None 

25 

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 

38 

39 @property 

40 def prog(self): 

41 return f"{PROG_NAME} [--addons-path=PATH,...] {self.name}" 

42 

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 

53 

54 @classmethod 

55 def is_valid_name(cls, name): 

56 return re.match(COMMAND_NAME_RE, name) 

57 

58 

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}') 

66 

67 

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 

77 

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 

86 

87 for fq_name, fullpath in mapping.items(): 

88 with contextlib.suppress(ImportError): 

89 load_script(fullpath, fq_name) 

90 

91 

92def find_command(name: str) -> Command | None: 

93 """ Get command by name. """ 

94 

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 

98 

99 # import from odoo.cli 

100 with contextlib.suppress(ImportError): 

101 __import__(f'odoo.cli.{name}') 

102 return commands[name] 

103 

104 # import from odoo.addons.*.cli 

105 load_addons_commands(command=name) 

106 return commands.get(name) 

107 

108 

109def main(): 

110 args = sys.argv[1:] 

111 

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:] 

118 

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' 

130 

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)