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.
- godoo_cli/__init__.py +2 -0
- godoo_cli/__main__.py +5 -0
- godoo_cli/cli.py +87 -0
- godoo_cli/cli_common.py +183 -0
- godoo_cli/commands/__init__.py +9 -0
- godoo_cli/commands/backup/__init__.py +1 -0
- godoo_cli/commands/backup/cli.py +17 -0
- godoo_cli/commands/backup/load.py +110 -0
- godoo_cli/commands/backup/pull.py +134 -0
- godoo_cli/commands/backup/util.py +35 -0
- godoo_cli/commands/bootstrap.py +187 -0
- godoo_cli/commands/config.py +43 -0
- godoo_cli/commands/db/__init__.py +1 -0
- godoo_cli/commands/db/cli.py +18 -0
- godoo_cli/commands/db/connection.py +94 -0
- godoo_cli/commands/db/passwords.py +56 -0
- godoo_cli/commands/db/query.py +41 -0
- godoo_cli/commands/launch.py +325 -0
- godoo_cli/commands/rpc/__init__.py +8 -0
- godoo_cli/commands/rpc/cli.py +34 -0
- godoo_cli/commands/rpc/config_parameters.py +48 -0
- godoo_cli/commands/rpc/importer.py +70 -0
- godoo_cli/commands/rpc/modules.py +144 -0
- godoo_cli/commands/rpc/translations.py +129 -0
- godoo_cli/commands/shell.py +45 -0
- godoo_cli/commands/source_get.py +279 -0
- godoo_cli/commands/test.py +126 -0
- godoo_cli/git/__init__.py +4 -0
- godoo_cli/git/git_odoo.py +89 -0
- godoo_cli/git/git_odoo_addons.py +95 -0
- godoo_cli/git/git_repo.py +207 -0
- godoo_cli/git/git_url.py +152 -0
- godoo_cli/git/zip_download.py +49 -0
- godoo_cli/helpers/__init__.py +1 -0
- godoo_cli/helpers/bootstrap.py +61 -0
- godoo_cli/helpers/cli.py +15 -0
- godoo_cli/helpers/odoo_files.py +278 -0
- godoo_cli/helpers/odoo_manifest.py +112 -0
- godoo_cli/helpers/system.py +176 -0
- godoo_cli/version.py +1 -0
- godoo_cli-0.7.0.dist-info/LICENSE +165 -0
- godoo_cli-0.7.0.dist-info/METADATA +197 -0
- godoo_cli-0.7.0.dist-info/RECORD +45 -0
- godoo_cli-0.7.0.dist-info/WHEEL +4 -0
- 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,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"))
|