gOdoo-cli 0.7.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. godoo_cli/__init__.py +2 -0
  2. godoo_cli/__main__.py +5 -0
  3. godoo_cli/cli.py +87 -0
  4. godoo_cli/cli_common.py +183 -0
  5. godoo_cli/commands/__init__.py +9 -0
  6. godoo_cli/commands/backup/__init__.py +1 -0
  7. godoo_cli/commands/backup/cli.py +17 -0
  8. godoo_cli/commands/backup/load.py +110 -0
  9. godoo_cli/commands/backup/pull.py +134 -0
  10. godoo_cli/commands/backup/util.py +35 -0
  11. godoo_cli/commands/bootstrap.py +187 -0
  12. godoo_cli/commands/config.py +43 -0
  13. godoo_cli/commands/db/__init__.py +1 -0
  14. godoo_cli/commands/db/cli.py +18 -0
  15. godoo_cli/commands/db/connection.py +94 -0
  16. godoo_cli/commands/db/passwords.py +56 -0
  17. godoo_cli/commands/db/query.py +41 -0
  18. godoo_cli/commands/launch.py +325 -0
  19. godoo_cli/commands/rpc/__init__.py +8 -0
  20. godoo_cli/commands/rpc/cli.py +34 -0
  21. godoo_cli/commands/rpc/config_parameters.py +48 -0
  22. godoo_cli/commands/rpc/importer.py +70 -0
  23. godoo_cli/commands/rpc/modules.py +144 -0
  24. godoo_cli/commands/rpc/translations.py +129 -0
  25. godoo_cli/commands/shell.py +45 -0
  26. godoo_cli/commands/source_get.py +279 -0
  27. godoo_cli/commands/test.py +126 -0
  28. godoo_cli/git/__init__.py +4 -0
  29. godoo_cli/git/git_odoo.py +89 -0
  30. godoo_cli/git/git_odoo_addons.py +95 -0
  31. godoo_cli/git/git_repo.py +207 -0
  32. godoo_cli/git/git_url.py +152 -0
  33. godoo_cli/git/zip_download.py +49 -0
  34. godoo_cli/helpers/__init__.py +1 -0
  35. godoo_cli/helpers/bootstrap.py +61 -0
  36. godoo_cli/helpers/cli.py +15 -0
  37. godoo_cli/helpers/odoo_files.py +278 -0
  38. godoo_cli/helpers/odoo_manifest.py +112 -0
  39. godoo_cli/helpers/system.py +176 -0
  40. godoo_cli/version.py +1 -0
  41. godoo_cli-0.7.0.dist-info/LICENSE +165 -0
  42. godoo_cli-0.7.0.dist-info/METADATA +197 -0
  43. godoo_cli-0.7.0.dist-info/RECORD +45 -0
  44. godoo_cli-0.7.0.dist-info/WHEEL +4 -0
  45. godoo_cli-0.7.0.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,129 @@
1
+ import logging
2
+ from base64 import b64decode
3
+ from pathlib import Path
4
+
5
+ import typer
6
+ from godoo_rpc.login import wait_for_odoo
7
+
8
+ from ...cli_common import CommonCLI
9
+ from ...helpers.odoo_files import get_odoo_module_paths
10
+ from .modules import rpc_get_modules
11
+
12
+ CLI = CommonCLI()
13
+ LOGGER = logging.getLogger(__name__)
14
+
15
+
16
+ def _dump_translation_for_module(module, target_path: Path):
17
+ """Dump Translation of a module into POT file.
18
+
19
+ Parameters
20
+ ----------
21
+ module : _type_
22
+ rpc record of module to export
23
+ target_path : Path
24
+ target pot path
25
+ """
26
+ trans_exp_mod = module.env["base.language.export"]
27
+
28
+ LOGGER.info("Exporting: %s --> %s", module.name, str(target_path))
29
+ trans_wiz_id = trans_exp_mod.create({"format": "po", "modules": [module.id]})
30
+ trans_wiz = trans_exp_mod.browse([trans_wiz_id])
31
+ trans_wiz.act_getfile()
32
+ trans_wiz = trans_exp_mod.browse([trans_wiz_id])
33
+ target_path.parent.mkdir(exist_ok=True)
34
+ target_path.write_bytes(b64decode(trans_wiz.data))
35
+
36
+
37
+ def _dump_translations(
38
+ modules,
39
+ workspace_addon_path: Path,
40
+ upgrade_modules: bool = True,
41
+ ):
42
+ """Dump translations of given Modules into their addon folders.
43
+
44
+ Parameters
45
+ ----------
46
+ odoo_api : OdooApiWrapper
47
+ Odoo Api Wrapper
48
+ modules : _type_
49
+ Api Models of modules
50
+ workspace_addon_path : Path
51
+ path where those modules are installed
52
+ upgrade_modules : bool, optional
53
+ Wether to upgrade modules before dumping, by default True
54
+ """
55
+
56
+ if upgrade_modules:
57
+ LOGGER.info("Upgrading Modules: '%s'", ", ".join(modules.mapped("name")))
58
+ modules.button_immediate_upgrade()
59
+
60
+ for mod in modules:
61
+ ex_path: Path = workspace_addon_path / mod.name / "i18n"
62
+ pot_path: Path = ex_path / (mod.name + ".pot")
63
+ _dump_translation_for_module(mod, pot_path)
64
+
65
+
66
+ def complete_workspace_addon_names(ctx: typer.Context, incomplete: str):
67
+ """Autocomplete handler that searches modules in Workspace_addon_path
68
+
69
+ Parameters
70
+ ----------
71
+ ctx : typer.Context
72
+ Contains calling parameters
73
+ incomplete : str
74
+ Incomplete current entry
75
+
76
+ Yields
77
+ ------
78
+ str
79
+ folder name
80
+ """
81
+ workspace_folder = ctx.params.get("workspace_addon_path")
82
+ if not workspace_folder:
83
+ return
84
+ workspace_folder = Path(workspace_folder)
85
+ addon_folders = get_odoo_module_paths(workspace_folder)
86
+ for fold in addon_folders:
87
+ if not incomplete or fold.name.startswith(incomplete):
88
+ yield fold.name
89
+
90
+
91
+ @CLI.arg_annotator
92
+ def dump_translations(
93
+ module_query=typer.Argument(
94
+ ...,
95
+ help="Module Name(s) comma Seperated. Add % to force =ilike match. Only works in workspace addon path",
96
+ autocompletion=complete_workspace_addon_names,
97
+ ),
98
+ workspace_addon_path=CLI.odoo_paths.workspace_addon_path,
99
+ rpc_host=CLI.rpc.rpc_host,
100
+ rpc_database=CLI.rpc.rpc_db_name,
101
+ rpc_user=CLI.rpc.rpc_user,
102
+ rpc_password=CLI.rpc.rpc_password,
103
+ no_upgrade_modules: bool = typer.Option(
104
+ False, "--no-upgrade-modules", help="don't upgrade modules before exporting"
105
+ ),
106
+ ):
107
+ """Dump Translations of module to <module_folder>/i18n/<module_name>.pot"""
108
+ addon_path = workspace_addon_path
109
+ addon_folders = get_odoo_module_paths(addon_path)
110
+ valid_module_names = [str(p.stem) for p in addon_folders]
111
+ LOGGER.debug("Found modules:\n%s", "\n".join(["\t" + mn for mn in valid_module_names]))
112
+
113
+ odoo_api = wait_for_odoo(
114
+ odoo_host=rpc_host,
115
+ odoo_db=rpc_database,
116
+ odoo_user=rpc_user,
117
+ odoo_password=rpc_password,
118
+ )
119
+
120
+ modules = rpc_get_modules(odoo_api, module_query, valid_module_names)
121
+ if not modules:
122
+ LOGGER.warning("No installed Modules found for Query string: '%s'", module_query)
123
+ return CLI.returner(1)
124
+
125
+ _dump_translations(
126
+ modules=modules,
127
+ workspace_addon_path=addon_path,
128
+ upgrade_modules=not no_upgrade_modules,
129
+ )
@@ -0,0 +1,45 @@
1
+ import logging
2
+ from typing import List
3
+
4
+ import typer
5
+
6
+ from ..cli_common import CommonCLI
7
+ from ..helpers.system import run_cmd
8
+
9
+ CLI = CommonCLI()
10
+ LOGGER = logging.getLogger(__name__)
11
+
12
+
13
+ @CLI.unpacker
14
+ @CLI.arg_annotator
15
+ def uninstall_modules(
16
+ module_list: List[str] = typer.Argument(..., help="List of Modules to uninstall"),
17
+ odoo_main_path=CLI.odoo_paths.bin_path,
18
+ odoo_conf_path=CLI.odoo_paths.conf_path,
19
+ ):
20
+ """
21
+ Uninstall given Modules from Odoo via shell.
22
+ """
23
+ module_list_str = str(list(module_list))
24
+ uninstall_cmd = f"env['ir.module.module'].search([('name','in',{module_list_str})]).filtered(lambda m: m.state not in ['uninstallable','uninstalled']).button_immediate_uninstall()"
25
+ uninstall_shell = f'echo "{uninstall_cmd}" | {str(odoo_main_path.absolute())}/odoo-bin shell -c {str(odoo_conf_path.absolute())} --no-http'
26
+ LOGGER.info("Launching Uninstaller: '%s'", uninstall_shell)
27
+ ret = run_cmd(uninstall_shell).returncode
28
+ return CLI.returner(ret)
29
+
30
+
31
+ @CLI.unpacker
32
+ @CLI.arg_annotator
33
+ def odoo_shell(
34
+ pipe_in_command: str = typer.Argument("", help="Python command, that will be piped into odoo-bin shell"),
35
+ odoo_main_path=CLI.odoo_paths.bin_path,
36
+ odoo_conf_path=CLI.odoo_paths.conf_path,
37
+ ):
38
+ """
39
+ Start Odoo session in an Interactive shell.
40
+ """
41
+ shell_cmd = f"{str(odoo_main_path.absolute())}/odoo-bin shell -c {str(odoo_conf_path.absolute())} --no-http"
42
+ if pipe_in_command:
43
+ shell_cmd = f'echo "{pipe_in_command}" |' + shell_cmd
44
+ LOGGER.debug("Running Command: %s", shell_cmd)
45
+ return CLI.returner(run_cmd(shell_cmd).returncode)
@@ -0,0 +1,279 @@
1
+ """Commands to clone Odoo and addon source code"""
2
+ import configparser
3
+ import logging
4
+ import shutil
5
+ import tempfile
6
+ from enum import Enum
7
+ from pathlib import Path
8
+ from typing import List
9
+
10
+ import typer
11
+ from ruamel.yaml import YAML
12
+
13
+ from ..cli_common import CommonCLI
14
+ from ..git import GitUrl, git_ensure_addon_repos, git_ensure_odoo_repo
15
+ from ..helpers.bootstrap import _install_py_reqs_for_modules
16
+ from ..helpers.odoo_files import get_addon_paths, get_odoo_module_paths, get_zip_addon_path
17
+ from ..helpers.odoo_manifest import remove_unused_folders
18
+ from ..helpers.system import download_file
19
+ from .db.connection import DBConnection
20
+
21
+ LOGGER = logging.getLogger(__name__)
22
+ CLI = CommonCLI()
23
+
24
+
25
+ class UpdateMode(str, Enum):
26
+ all = "all"
27
+ zip = "zip"
28
+ odoo = "odoo"
29
+ thirdparty = "thirdparty"
30
+
31
+
32
+ def unpack_addon_archives(
33
+ archive_folder: Path,
34
+ target_addon_folder: Path,
35
+ remove_excess: bool = False,
36
+ ):
37
+ """Take archive files from archive_folder and extract them into target_addon_folder.
38
+
39
+ Parameters
40
+ ----------
41
+ archive_folder : Path
42
+ Where to look for zip files
43
+ target_addon_folder : Path
44
+ where to place them
45
+ remove_excess : bool , optional
46
+ remove all and then unzip, by default False
47
+ """
48
+ target_addon_folder.mkdir(exist_ok=True, parents=True)
49
+ if remove_excess:
50
+ LOGGER.debug("Clearing out unarchive folder: %s", target_addon_folder)
51
+ for folder in target_addon_folder.iterdir():
52
+ shutil.rmtree(folder)
53
+ LOGGER.info("Extracting archive addons to: %s", target_addon_folder)
54
+ for zip_file in archive_folder.glob("*.zip"):
55
+ LOGGER.info("Extracting addon archive: %s", zip_file)
56
+ with tempfile.TemporaryDirectory() as td:
57
+ td = Path(td)
58
+ shutil.unpack_archive(zip_file, td)
59
+ # We can have zip files with one or more modules.
60
+ # Either the first folder contains multiple or its a module by itself
61
+ # So first get the real modules form the zip root or one level down and then move them to subpaths
62
+ zip_module_paths = get_odoo_module_paths([td] + list(td.glob("*/")))
63
+ if not zip_module_paths:
64
+ LOGGER.warning("Could not find valid modules in thirdparty zip: %s", zip_file)
65
+ continue
66
+ LOGGER.debug(
67
+ "Found modules in Zipfile:\n%s",
68
+ "\n".join([str(f.relative_to(td)) for f in zip_module_paths]),
69
+ )
70
+ target_folder = target_addon_folder / ("single_mods" if len(zip_module_paths) == 1 else zip_file.stem)
71
+ target_folder.mkdir(exist_ok=True)
72
+ for m in zip_module_paths:
73
+ module_target = target_folder / m.stem
74
+ shutil.rmtree(module_target, ignore_errors=True)
75
+ shutil.move(m, module_target)
76
+
77
+
78
+ def update_odoo_conf_addon_paths(odoo_conf: Path, addon_paths: List[Path]):
79
+ """Update Odoo.Conf with Addon Paths
80
+
81
+ Parameters
82
+ ----------
83
+ odoo_conf : Path
84
+ odoo.conf location
85
+ addon_paths : List[Path]
86
+ list of paths
87
+ """
88
+ if not odoo_conf.exists():
89
+ raise FileNotFoundError("Odoo.conf not found at: %s" % odoo_conf)
90
+ config = configparser.ConfigParser()
91
+ config.read(odoo_conf)
92
+ addon_paths = ",".join([str(p.absolute()) for p in addon_paths])
93
+ config["options"]["addons_path"] = addon_paths
94
+ LOGGER.info("Writing Addon Paths to Odoo Config.")
95
+ LOGGER.debug(addon_paths)
96
+ config.write(odoo_conf.open("w"))
97
+
98
+
99
+ @CLI.unpacker
100
+ @CLI.arg_annotator
101
+ def py_depends_by_db(
102
+ odoo_main_path=CLI.odoo_paths.bin_path,
103
+ workspace_addon_path=CLI.odoo_paths.workspace_addon_path,
104
+ thirdparty_addon_path=CLI.odoo_paths.thirdparty_addon_path,
105
+ db_host=CLI.database.db_host,
106
+ db_port=CLI.database.db_port,
107
+ db_name=CLI.database.db_name,
108
+ db_user=CLI.database.db_user,
109
+ db_password=CLI.database.db_password,
110
+ ):
111
+ """Insall Python Dependencies for all installed modules in DB."""
112
+ connection = DBConnection(
113
+ hostname=db_host,
114
+ port=db_port,
115
+ username=db_user,
116
+ password=db_password,
117
+ db_name=db_name,
118
+ )
119
+ with connection.connect() as conn:
120
+ try:
121
+ conn.execute("SELECT name FROM ir_module_module WHERE state='installed'")
122
+ except UnboundLocalError as e:
123
+ LOGGER.error("Could not connect to DB. Check your DB Connection settings.")
124
+ raise typer.Exit(1) from e
125
+ module_list = [r[0] for r in conn.fetchall()]
126
+ LOGGER.info("Installing Py Reqiurements for Modules: %s", module_list)
127
+ odoo_addon_paths = get_addon_paths(
128
+ odoo_main_repo=odoo_main_path,
129
+ workspace_addon_path=workspace_addon_path,
130
+ thirdparty_addon_path=thirdparty_addon_path,
131
+ )
132
+ _install_py_reqs_for_modules(odoo_addon_paths, module_list)
133
+
134
+
135
+ @CLI.unpacker
136
+ @CLI.arg_annotator
137
+ def py_depends_by_modules(
138
+ module_list: List[str] = typer.Argument(
139
+ ...,
140
+ help="Modules to check for dependencies (can use all for all available addons)",
141
+ ),
142
+ thirdparty_addon_path=CLI.odoo_paths.thirdparty_addon_path,
143
+ odoo_main_path=CLI.odoo_paths.bin_path,
144
+ workspace_addon_path=CLI.odoo_paths.workspace_addon_path,
145
+ ):
146
+ """Install dependencies from __manifest__.py in specified modules."""
147
+ odoo_addon_paths = get_addon_paths(
148
+ odoo_main_repo=odoo_main_path,
149
+ workspace_addon_path=workspace_addon_path,
150
+ thirdparty_addon_path=thirdparty_addon_path,
151
+ )
152
+ if len(module_list) == 1 and module_list[0] == "all":
153
+ search_addon_paths = [p for p in odoo_addon_paths if odoo_main_path not in p.parents]
154
+ module_list = [p.stem for p in get_odoo_module_paths(search_addon_paths)]
155
+ _install_py_reqs_for_modules(odoo_addon_paths, module_list)
156
+
157
+
158
+ @CLI.unpacker
159
+ @CLI.arg_annotator
160
+ def get_source_file(
161
+ manifest_path=CLI.source.mainfest_path,
162
+ repo_url: str = typer.Option("", help="git repo url, for specific repo (skip manifest_yml)"),
163
+ file_ref: str = typer.Option("", help="When not using manifest. File Branch, Commit, Tag..."),
164
+ file_path: str = typer.Option(..., help="Relative Filepath in Repository"),
165
+ save_path: Path = typer.Option(..., file_okay=True, dir_okay=False, help="Where to write the file"),
166
+ ):
167
+ """Get Raw file from manifest git remotes or specific git remote."""
168
+
169
+ if not repo_url and not manifest_path:
170
+ raise ValueError("Need to provide either manifest_yml or repo_url")
171
+ if manifest_path and not repo_url:
172
+ manifest = YAML().load(manifest_path.resolve())
173
+ odoo_spec = manifest["odoo"]
174
+ repo_url = odoo_spec["url"]
175
+ file_ref = odoo_spec.get("commit") or odoo_spec.get("branch")
176
+ if not file_ref:
177
+ raise ValueError(
178
+ "Need to provide file ref. If you provided a manifest, make sure there is a branch or commit key in the odoo section"
179
+ )
180
+ git_url = GitUrl(repo_url)
181
+ file_url = git_url.get_file_raw_url(ref=file_ref, file_path=file_path)
182
+
183
+ return download_file(url=file_url, save_path=save_path)
184
+
185
+
186
+ @CLI.unpacker
187
+ @CLI.arg_annotator
188
+ def get_source(
189
+ update_mode: UpdateMode = typer.Argument(UpdateMode.all, help="What to Update"),
190
+ odoo_main_path=CLI.odoo_paths.bin_path,
191
+ odoo_conf_path=CLI.odoo_paths.conf_path,
192
+ workspace_addon_path=CLI.odoo_paths.workspace_addon_path,
193
+ manifest_path=CLI.source.mainfest_path,
194
+ thirdparty_addon_path=CLI.odoo_paths.thirdparty_addon_path,
195
+ download_zipmode=CLI.source.source_download_archive,
196
+ thirdparty_zip_source: Path = typer.Option(
197
+ ..., envvar="ODOO_THIRDPARTY_ZIP_LOCATION", help="Source folder, where to look for Addon zips"
198
+ ),
199
+ add_compare_comments: bool = typer.Option(
200
+ False, "--add-compare-comments", help="Wether to add github.com three dot compare links as comments."
201
+ ),
202
+ remove_unspecified_addons: bool = typer.Option(
203
+ False, "--remove-unspecified-addons", help="Remove Addon folders that are not in YML or thirdparty.zip"
204
+ ),
205
+ force_fetch: bool = typer.Option(
206
+ False,
207
+ "--force-fetch",
208
+ help="Forces origin fetch, regardless of current branch or commit sha (may be slow)",
209
+ ),
210
+ ):
211
+ """Download/Unzip Odoo Source and thirdparty addons."""
212
+ LOGGER.info("Updating Source Repos")
213
+ zip_addon_path = get_zip_addon_path(thirdparty_addon_path)
214
+
215
+ if update_mode in ["all", "zip"]:
216
+ unpack_addon_archives(thirdparty_zip_source, zip_addon_path, remove_excess=remove_unspecified_addons)
217
+
218
+ if update_mode in ["all", "odoo"]:
219
+ git_ensure_odoo_repo(
220
+ target_folder=odoo_main_path,
221
+ manifest_file=manifest_path,
222
+ force_fetch=force_fetch,
223
+ add_compare_comment=add_compare_comments,
224
+ download_archive=download_zipmode,
225
+ )
226
+
227
+ if update_mode in ["all", "thirdparty"]:
228
+ git_repos = git_ensure_addon_repos(
229
+ root_folder=thirdparty_addon_path,
230
+ git_yml_path=manifest_path,
231
+ generate_yml_compare_comments=add_compare_comments,
232
+ download_archive=download_zipmode,
233
+ )
234
+ if remove_unspecified_addons:
235
+ remove_unused_folders(
236
+ thirdparty_addon_path=thirdparty_addon_path,
237
+ thirdparty_repos=git_repos.get("thirdparty", []),
238
+ keep_folders=[zip_addon_path],
239
+ )
240
+
241
+ if (conf_path := odoo_conf_path).exists():
242
+ odoo_addon_paths = get_addon_paths(
243
+ odoo_main_repo=odoo_main_path,
244
+ workspace_addon_path=workspace_addon_path,
245
+ thirdparty_addon_path=thirdparty_addon_path,
246
+ )
247
+ update_odoo_conf_addon_paths(odoo_conf=conf_path, addon_paths=odoo_addon_paths)
248
+
249
+
250
+ @CLI.unpacker
251
+ @CLI.arg_annotator
252
+ def update_odoo_conf(
253
+ odoo_conf=CLI.odoo_paths.conf_path,
254
+ odoo_main_path=CLI.odoo_paths.bin_path,
255
+ workspace_addon_path=CLI.odoo_paths.workspace_addon_path,
256
+ thirdparty_addon_path=CLI.odoo_paths.thirdparty_addon_path,
257
+ ):
258
+ """Update Odoo.conf with Addon Paths."""
259
+ odoo_addon_paths = get_addon_paths(
260
+ odoo_main_repo=odoo_main_path,
261
+ workspace_addon_path=workspace_addon_path,
262
+ thirdparty_addon_path=thirdparty_addon_path,
263
+ )
264
+ update_odoo_conf_addon_paths(odoo_conf=odoo_conf, addon_paths=odoo_addon_paths)
265
+
266
+
267
+ def source_cli_app():
268
+ app = typer.Typer(
269
+ no_args_is_help=True,
270
+ help="Functions concerning with Odoo Source code",
271
+ )
272
+
273
+ app.command("get")(get_source)
274
+ app.command("sync-conf")(update_odoo_conf)
275
+ app.command("get-file")(get_source_file)
276
+ app.command("get-dependencies")(py_depends_by_modules)
277
+ app.command("get-dependencies-db")(py_depends_by_db)
278
+
279
+ return app
@@ -0,0 +1,126 @@
1
+ import logging
2
+ import re
3
+ from pathlib import Path
4
+ from typing import List
5
+
6
+ import typer
7
+
8
+ from ..cli_common import CommonCLI
9
+ from ..helpers.odoo_files import get_changed_modules, get_depends_of_module, get_odoo_module_paths
10
+ from ..helpers.system import run_cmd
11
+ from .launch import pre_launch
12
+
13
+ CLI = CommonCLI()
14
+ LOGGER = logging.getLogger(__name__)
15
+
16
+
17
+ def _test_modules_special_cases(in_modules: List[str], workspace_addon_path: Path):
18
+ if len(in_modules) == 1:
19
+ command = in_modules[0]
20
+ out_modules = []
21
+ if command == "all":
22
+ out_modules = get_odoo_module_paths(workspace_addon_path)
23
+
24
+ if re_match := re.match(r"changes\:(.*)", command):
25
+ compare_branch = re_match.group(1)
26
+ changed_modules = get_changed_modules(addon_path=workspace_addon_path, diff_branch=compare_branch)
27
+ if not changed_modules:
28
+ return []
29
+ change_modules_depends = []
30
+ for module in changed_modules:
31
+ change_modules_depends += get_depends_of_module(
32
+ out_modules, module, already_done_modules=change_modules_depends
33
+ )
34
+ out_modules = changed_modules + change_modules_depends
35
+ if out_modules:
36
+ out_modules_with_tests = [p for p in out_modules if any(p.rglob("tests/__init__.py"))]
37
+ return [p.stem for p in out_modules_with_tests]
38
+ return in_modules
39
+
40
+
41
+ @CLI.arg_annotator
42
+ def odoo_test(
43
+ test_modules: List[str] = typer.Argument(
44
+ ...,
45
+ help="Modules to install and test (Use 'all' for all Workspace modules), ('changes:<branch> to compare git changes)",
46
+ ),
47
+ odoo_main_path=CLI.odoo_paths.bin_path,
48
+ workspace_addon_path=CLI.odoo_paths.workspace_addon_path,
49
+ thirdparty_addon_path=CLI.odoo_paths.thirdparty_addon_path,
50
+ odoo_conf_path=CLI.odoo_paths.conf_path,
51
+ extra_launch_args=CLI.odoo_launch.extra_cmd_args,
52
+ db_filter=CLI.database.db_filter,
53
+ db_host=CLI.database.db_host,
54
+ db_port=CLI.database.db_port,
55
+ db_name=CLI.database.db_name,
56
+ db_user=CLI.database.db_user,
57
+ db_password=CLI.database.db_password,
58
+ skip_test_modules: List[str] = typer.Option(
59
+ [], envvar="ODOO_TEST_SKIP_MODULES", help="Modules not to Test even if specified in test_modules"
60
+ ),
61
+ odoo_log_level: str = typer.Option("test", help="Log level"),
62
+ ):
63
+ """Bootstrap or Launch odoo in Testing Mode."""
64
+
65
+ test_modules = _test_modules_special_cases(test_modules, workspace_addon_path)
66
+
67
+ skip_test_modules = [m for m in skip_test_modules if m in test_modules] # Filter out skip mods that arent requested
68
+ if skip_test_modules:
69
+ LOGGER.info("Skipping Tests for Modules:\n%s", ", ".join(["\t" + m for m in skip_test_modules]))
70
+ test_modules = [m for m in test_modules if m not in skip_test_modules]
71
+
72
+ if not test_modules:
73
+ LOGGER.info("Nothing to Test. Skipping.")
74
+ return
75
+
76
+ test_module_list = ",".join(["/" + m for m in test_modules])
77
+ module_list = ",".join(test_modules)
78
+
79
+ LOGGER.info("Testing Odoo Modules:\n%s", "\n".join(sorted(["\t" + m for m in test_modules])))
80
+
81
+ bootstrap_args = [
82
+ "--test-enable",
83
+ f"--init {module_list}",
84
+ ]
85
+ if re.search("(sale|account)", test_module_list, re.IGNORECASE):
86
+ bootstrap_args[-1] += ",l10n_generic_coa"
87
+
88
+ bootstrap_args.append(f"--test-tags {test_module_list}")
89
+ bootstrap_args.append("--load-language en_US")
90
+ launch_args = [
91
+ f"-u {module_list}",
92
+ "--test-enable",
93
+ f"--log-level {odoo_log_level}",
94
+ "--stop-after-init",
95
+ "--no-http",
96
+ f"--test-tags {test_module_list}",
97
+ ]
98
+
99
+ if extra_launch_args:
100
+ launch_args = extra_launch_args + launch_args
101
+
102
+ launch_cmd = pre_launch(
103
+ odoo_main_path=odoo_main_path,
104
+ workspace_addon_path=workspace_addon_path,
105
+ thirdparty_addon_path=thirdparty_addon_path,
106
+ odoo_conf_path=odoo_conf_path,
107
+ db_filter=db_filter,
108
+ db_host=db_host,
109
+ db_port=db_port,
110
+ db_name=db_name,
111
+ db_user=db_user,
112
+ db_password=db_password,
113
+ dev_mode=False,
114
+ install_base=True,
115
+ install_workspace_addons=False,
116
+ extra_launch_args=launch_args,
117
+ extra_bootstrap_args=bootstrap_args,
118
+ multithread_worker_count=0,
119
+ odoo_demo=True,
120
+ launch_or_bootstrap=True,
121
+ )
122
+ if isinstance(launch_cmd, str):
123
+ LOGGER.info("Launching Odoo Tests")
124
+ return CLI.returner(run_cmd(launch_cmd).returncode)
125
+
126
+ return CLI.returner(launch_cmd)
@@ -0,0 +1,4 @@
1
+ from .git_odoo import git_ensure_odoo_repo
2
+ from .git_odoo_addons import git_ensure_addon_repos
3
+ from .git_repo import git_ensure_repo
4
+ from .git_url import GitUrl
@@ -0,0 +1,89 @@
1
+ import json
2
+ import logging
3
+ from pathlib import Path
4
+
5
+ from ..helpers.odoo_manifest import yaml_add_compare_commit, yaml_remove_compare_commit, yaml_roundtrip_loader
6
+ from .git_repo import git_ensure_repo
7
+
8
+ LOGGER = logging.getLogger(__name__)
9
+
10
+
11
+ def git_ensure_odoo_repo(
12
+ target_folder: Path,
13
+ manifest_file: Path,
14
+ force_fetch: bool,
15
+ add_compare_comment: bool,
16
+ download_archive: bool,
17
+ ):
18
+ yaml = yaml_roundtrip_loader()
19
+ git_repos = yaml.load(manifest_file.resolve())
20
+ if not git_repos:
21
+ raise FileNotFoundError(f"Couldnt load yml file from: {str(manifest_file)}")
22
+
23
+ odoo_data = git_repos.get("odoo")
24
+ if not odoo_data:
25
+ raise KeyError(f"Could not find key 'odoo' in {str(manifest_file)}")
26
+
27
+ odoo_url = odoo_data["url"]
28
+ odoo_branch = odoo_data.get("branch", "master")
29
+ odoo_commit = odoo_data.get("commit", "")
30
+
31
+ download_stubs(target_folder, force_fetch, download_archive, odoo_data, odoo_branch)
32
+
33
+ if add_compare_comment:
34
+ yaml_add_compare_commit(odoo_data, odoo_branch)
35
+ else:
36
+ yaml_remove_compare_commit(odoo_data)
37
+
38
+ git_ensure_repo(
39
+ target_folder=target_folder,
40
+ repo_src=odoo_url,
41
+ branch=odoo_branch,
42
+ commit=odoo_commit,
43
+ pull=force_fetch,
44
+ zip_mode=download_archive,
45
+ filter="blob:none",
46
+ single_branch=True,
47
+ )
48
+
49
+ yaml.dump(git_repos, manifest_file)
50
+
51
+
52
+ def download_stubs(
53
+ target_folder: Path,
54
+ force_fetch: bool,
55
+ download_archive: bool,
56
+ odoo_data,
57
+ odoo_branch: str,
58
+ ):
59
+ """Download Odoo stubs for https://github.com/odoo-ide/vscode-odoo
60
+
61
+ Hopefully this Method will become obsolete in the future, when the vscode-odoo extension includes stubs in the extension itself.
62
+
63
+ Parameters
64
+ ----------
65
+ target_folder : Path
66
+ Stubs will be downloaded to target_folder.parent/odoo-stubs
67
+ force_fetch : bool
68
+ passed down to git_ensure_repo
69
+ download_archive : bool
70
+ passed down to git_ensure_repo
71
+ odoo_data : Dict
72
+ odoo_data from manifest.yml
73
+ odoo_branch : str
74
+ branch to download stubs from
75
+ """
76
+ odoo_stubs = odoo_data.get("stubs")
77
+ if odoo_stubs:
78
+ stub_target_path = target_folder.parent / "odoo-stubs"
79
+ git_ensure_repo(
80
+ target_folder=stub_target_path,
81
+ repo_src=odoo_stubs["url"],
82
+ branch=odoo_branch,
83
+ pull=force_fetch,
84
+ zip_mode=download_archive,
85
+ filter="blob:none",
86
+ single_branch=True,
87
+ )
88
+ if pyright_path := odoo_stubs.get("pyright_conf"):
89
+ json.dump({"stubPath": str(stub_target_path.absolute())}, Path(pyright_path).open("w"))