gOdoo-cli 0.0.2__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 +78 -0
- godoo_cli/commands/__init__.py +7 -0
- godoo_cli/commands/bootstrap.py +183 -0
- godoo_cli/commands/launch.py +188 -0
- godoo_cli/commands/makedev.py +138 -0
- godoo_cli/commands/rpc/__init__.py +8 -0
- godoo_cli/commands/rpc/cli.py +19 -0
- godoo_cli/commands/rpc/cli_callbacks.py +37 -0
- godoo_cli/commands/rpc/importer.py +70 -0
- godoo_cli/commands/rpc/modules.py +108 -0
- godoo_cli/commands/rpc/translations.py +94 -0
- godoo_cli/commands/shell.py +42 -0
- godoo_cli/commands/source_get.py +164 -0
- godoo_cli/commands/test.py +91 -0
- godoo_cli/git/__init__.py +4 -0
- godoo_cli/git/git_odoo.py +45 -0
- godoo_cli/git/git_odoo_addons.py +95 -0
- godoo_cli/git/git_repo.py +157 -0
- godoo_cli/git/git_url.py +152 -0
- godoo_cli/git/zip_download.py +47 -0
- godoo_cli/helpers/__init__.py +83 -0
- godoo_cli/helpers/cli.py +85 -0
- godoo_cli/helpers/odoo_files.py +105 -0
- godoo_cli/helpers/repospec.py +112 -0
- godoo_cli-0.0.2.dist-info/LICENSE +165 -0
- godoo_cli-0.0.2.dist-info/METADATA +169 -0
- godoo_cli-0.0.2.dist-info/RECORD +31 -0
- godoo_cli-0.0.2.dist-info/WHEEL +4 -0
- godoo_cli-0.0.2.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Import read_path into Odoo.
|
|
3
|
+
Will wait until Odoo is Online.
|
|
4
|
+
Places Systemparam to Track if import was already done.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
import typer
|
|
11
|
+
from godoo_rpc import import_data
|
|
12
|
+
from godoo_rpc.login import wait_for_odoo
|
|
13
|
+
|
|
14
|
+
from ...helpers.cli import typer_unpacker
|
|
15
|
+
from .cli import rpc_callback
|
|
16
|
+
|
|
17
|
+
app = typer.Typer(callback=rpc_callback, no_args_is_help=True)
|
|
18
|
+
LOGGER = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@typer_unpacker
|
|
22
|
+
def import_to_odoo(
|
|
23
|
+
ctx: typer.Context,
|
|
24
|
+
read_path: Path = typer.Argument(
|
|
25
|
+
...,
|
|
26
|
+
readable=True,
|
|
27
|
+
exists=True,
|
|
28
|
+
help="Folder in which to search for import",
|
|
29
|
+
),
|
|
30
|
+
file_regex: str = typer.Option(
|
|
31
|
+
r"(?P<module>.*)\.(csv|py|xlsx|json)$",
|
|
32
|
+
help="Regex for filesearch. Add group 'module' to set a Module for RPC import",
|
|
33
|
+
),
|
|
34
|
+
product_image_regex: str = typer.Option(
|
|
35
|
+
r"(?P<default_code>\d{6})\.(jpeg|png|jpg)$",
|
|
36
|
+
help="Regex to search for Product images. Add Fields as regex group for Matching.",
|
|
37
|
+
),
|
|
38
|
+
check_data_timestamp: bool = typer.Option(
|
|
39
|
+
True,
|
|
40
|
+
help="If true, Odoo remembers the Name of an uploaded File in a Serverparameter. Subsequent Imports will ignore the file if it hasnt changed.",
|
|
41
|
+
),
|
|
42
|
+
skip_existing_ids: bool = typer.Option(False, help="Will skip import of already existing External IDs."),
|
|
43
|
+
):
|
|
44
|
+
"""
|
|
45
|
+
Import Csv Files into Odoo.
|
|
46
|
+
Adds an ir.config_parameter for each imported file,
|
|
47
|
+
containing the modification time of the file.
|
|
48
|
+
Will Skip already matching Timestamps and only import files that are new.
|
|
49
|
+
"""
|
|
50
|
+
odoo_api = wait_for_odoo(
|
|
51
|
+
odoo_host=ctx.obj.odoo_rpc_host,
|
|
52
|
+
odoo_db=ctx.obj.odoo_main_db,
|
|
53
|
+
odoo_user=ctx.obj.odoo_rpc_user,
|
|
54
|
+
odoo_password=ctx.obj.odoo_rpc_password,
|
|
55
|
+
)
|
|
56
|
+
if not read_path.exists():
|
|
57
|
+
raise ValueError("Provided import Path: %s doesn't exist", read_path)
|
|
58
|
+
|
|
59
|
+
if read_path.is_file():
|
|
60
|
+
product_image_regex = ""
|
|
61
|
+
file_regex = ""
|
|
62
|
+
|
|
63
|
+
import_data(
|
|
64
|
+
odoo_api=odoo_api,
|
|
65
|
+
read_path=read_path.absolute(),
|
|
66
|
+
data_regex=file_regex,
|
|
67
|
+
product_image_regex=product_image_regex,
|
|
68
|
+
check_dataset_timestamp=check_data_timestamp,
|
|
69
|
+
skip_existing_ids=skip_existing_ids,
|
|
70
|
+
)
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Any, List
|
|
3
|
+
|
|
4
|
+
import typer
|
|
5
|
+
from godoo_rpc import OdooApiWrapper
|
|
6
|
+
from godoo_rpc.login import wait_for_odoo
|
|
7
|
+
|
|
8
|
+
from ...helpers.cli import typer_retuner
|
|
9
|
+
from .cli import rpc_callback
|
|
10
|
+
|
|
11
|
+
LOGGER = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def rpc_get_modules(odoo_api: OdooApiWrapper, module_query: str, valid_module_names: List[str] = None):
|
|
15
|
+
"""Get ir.module.module records by a query search string.
|
|
16
|
+
|
|
17
|
+
Parameters
|
|
18
|
+
----------
|
|
19
|
+
odoo_api : OdooApiWrapper
|
|
20
|
+
Odoo Wrapper
|
|
21
|
+
module_query : str
|
|
22
|
+
Custom query. Module name. Comma sep list of modules or % wildcards are supported
|
|
23
|
+
valid_module_names : List[str], optional
|
|
24
|
+
only allow certain model names to be returned, by default None
|
|
25
|
+
|
|
26
|
+
Returns
|
|
27
|
+
-------
|
|
28
|
+
_type_
|
|
29
|
+
_description_
|
|
30
|
+
"""
|
|
31
|
+
mod_env = odoo_api.session.env["ir.module.module"]
|
|
32
|
+
mod_env.update_list()
|
|
33
|
+
|
|
34
|
+
base_domain = []
|
|
35
|
+
if "," in module_query:
|
|
36
|
+
search_domain = [("name", "in", module_query.split(","))]
|
|
37
|
+
else:
|
|
38
|
+
if "%" in module_query:
|
|
39
|
+
search_domain = [("name", "=ilike", module_query)]
|
|
40
|
+
else:
|
|
41
|
+
search_domain = [("name", "=", module_query)]
|
|
42
|
+
|
|
43
|
+
if valid_module_names:
|
|
44
|
+
base_domain.insert(1, "&")
|
|
45
|
+
base_domain.append(("name", "in", valid_module_names))
|
|
46
|
+
|
|
47
|
+
if ids := mod_env.search(base_domain + search_domain):
|
|
48
|
+
return mod_env.browse(ids)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def rpc_install_modules(
|
|
52
|
+
modules: Any,
|
|
53
|
+
upgrade: bool = True,
|
|
54
|
+
):
|
|
55
|
+
"""Install and upgrade Modules to Database.
|
|
56
|
+
|
|
57
|
+
Parameters
|
|
58
|
+
----------
|
|
59
|
+
modules : Iterable RPC ir.module.module records
|
|
60
|
+
List of
|
|
61
|
+
upgrade : bool, optional
|
|
62
|
+
Upgrade module if already installed, by default True
|
|
63
|
+
"""
|
|
64
|
+
did_something = False
|
|
65
|
+
|
|
66
|
+
if install_module_ids := [m.id for m in modules if not m.installed]:
|
|
67
|
+
install_modules = modules.browse(install_module_ids)
|
|
68
|
+
LOGGER.info("Installing Module: " + ", ".join(install_modules.mapped("name")))
|
|
69
|
+
install_modules.button_immediate_install()
|
|
70
|
+
did_something = True
|
|
71
|
+
|
|
72
|
+
if upgrade and (up_module_ids := [m.id for m in modules if m.installed]):
|
|
73
|
+
up_modules = modules.browse(up_module_ids)
|
|
74
|
+
LOGGER.info("Updating Module: " + ", ".join(up_modules.mapped("name")))
|
|
75
|
+
up_modules.button_immediate_upgrade()
|
|
76
|
+
did_something = True
|
|
77
|
+
return did_something
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def install_modules(
|
|
81
|
+
ctx: typer.Context,
|
|
82
|
+
module_name_query: str = typer.Argument(..., help=r"Module Internal name. Will use ilike Match if \% is present"),
|
|
83
|
+
upgrade: bool = typer.Option(True, help="Upgrae Module if already installed"),
|
|
84
|
+
):
|
|
85
|
+
"""Upgrades or Installs Modules in Odoo via RPC."""
|
|
86
|
+
|
|
87
|
+
odoo_api = wait_for_odoo(
|
|
88
|
+
odoo_host=ctx.obj.odoo_rpc_host,
|
|
89
|
+
odoo_db=ctx.obj.odoo_main_db,
|
|
90
|
+
odoo_user=ctx.obj.odoo_rpc_user,
|
|
91
|
+
odoo_password=ctx.obj.odoo_rpc_password,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
if modules := rpc_get_modules(odoo_api, module_name_query):
|
|
95
|
+
if rpc_install_modules(modules):
|
|
96
|
+
return
|
|
97
|
+
else:
|
|
98
|
+
LOGGER.warn("Found Modules, but didn't do anything on DB.")
|
|
99
|
+
else:
|
|
100
|
+
LOGGER.warn("Could not find modules with Query: '%s'", module_name_query)
|
|
101
|
+
return typer_retuner(1)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def modules_cli_app():
|
|
105
|
+
app = typer.Typer(callback=rpc_callback, no_args_is_help=True)
|
|
106
|
+
|
|
107
|
+
app.command(name="install")(install_modules)
|
|
108
|
+
return app
|
|
@@ -0,0 +1,94 @@
|
|
|
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 ...helpers.cli import typer_retuner
|
|
9
|
+
from ...helpers.odoo_files import get_odoo_addons_in_folder
|
|
10
|
+
from .cli import rpc_callback
|
|
11
|
+
from .modules import rpc_get_modules
|
|
12
|
+
|
|
13
|
+
app = typer.Typer(callback=rpc_callback, no_args_is_help=True)
|
|
14
|
+
LOGGER = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def dump_translation(module, target_path: Path):
|
|
18
|
+
"""Dump Translation of a module into POT file.
|
|
19
|
+
|
|
20
|
+
Parameters
|
|
21
|
+
----------
|
|
22
|
+
module : _type_
|
|
23
|
+
_description_
|
|
24
|
+
target_path : Path
|
|
25
|
+
_description_
|
|
26
|
+
"""
|
|
27
|
+
trans_exp_mod = module.env["base.language.export"]
|
|
28
|
+
|
|
29
|
+
LOGGER.info("Exporting: %s --> %s", module.name, str(target_path))
|
|
30
|
+
trans_wiz_id = trans_exp_mod.create({"format": "po", "modules": [module.id]})
|
|
31
|
+
trans_wiz = trans_exp_mod.browse([trans_wiz_id])
|
|
32
|
+
trans_wiz.act_getfile()
|
|
33
|
+
trans_wiz = trans_exp_mod.browse([trans_wiz_id])
|
|
34
|
+
target_path.parent.mkdir(exist_ok=True)
|
|
35
|
+
target_path.write_bytes(b64decode(trans_wiz.data))
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _dump_translations(
|
|
39
|
+
modules,
|
|
40
|
+
workspace_addon_path: Path,
|
|
41
|
+
upgrade_modules: bool = True,
|
|
42
|
+
):
|
|
43
|
+
"""Dump translations of given Modules into their addon folders.
|
|
44
|
+
|
|
45
|
+
Parameters
|
|
46
|
+
----------
|
|
47
|
+
odoo_api : OdooApiWrapper
|
|
48
|
+
Odoo Api Wrapper
|
|
49
|
+
modules : _type_
|
|
50
|
+
Api Models of modules
|
|
51
|
+
workspace_addon_path : Path
|
|
52
|
+
path where those modules are installed
|
|
53
|
+
upgrade_modules : bool, optional
|
|
54
|
+
Wether to upgrade modules before dumping, by default True
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
if upgrade_modules:
|
|
58
|
+
LOGGER.info("Upgrading Modules: '%s'", ", ".join(modules.mapped("name")))
|
|
59
|
+
modules.button_immediate_upgrade()
|
|
60
|
+
|
|
61
|
+
for mod in modules:
|
|
62
|
+
ex_path: Path = workspace_addon_path / mod.name / "i18n"
|
|
63
|
+
pot_path: Path = ex_path / (mod.name + ".pot")
|
|
64
|
+
dump_translation(mod, pot_path)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def dump_translations(
|
|
68
|
+
ctx: typer.Context,
|
|
69
|
+
module_query=typer.Argument(..., help="Module Name. Add % to force =ilike match. Only valid for Workspace Addons."),
|
|
70
|
+
upgrade_modules: bool = typer.Option(True, help="Upgrade modules before exporting"),
|
|
71
|
+
):
|
|
72
|
+
|
|
73
|
+
addon_path = ctx.obj.workspace_addon_path
|
|
74
|
+
addon_folders = get_odoo_addons_in_folder(addon_path)
|
|
75
|
+
valid_module_names = [str(p.stem) for p in addon_folders]
|
|
76
|
+
LOGGER.debug("Found modules:\n%s", "\n".join(["\t" + mn for mn in valid_module_names]))
|
|
77
|
+
|
|
78
|
+
odoo_api = wait_for_odoo(
|
|
79
|
+
odoo_host=ctx.obj.odoo_rpc_host,
|
|
80
|
+
odoo_db=ctx.obj.odoo_main_db,
|
|
81
|
+
odoo_user=ctx.obj.odoo_rpc_user,
|
|
82
|
+
odoo_password=ctx.obj.odoo_rpc_password,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
modules = rpc_get_modules(odoo_api, module_query, valid_module_names)
|
|
86
|
+
if not modules:
|
|
87
|
+
LOGGER.warning("No installed Modules found for Query string: '%s'", module_query)
|
|
88
|
+
return typer_retuner(1)
|
|
89
|
+
|
|
90
|
+
_dump_translations(
|
|
91
|
+
modules=modules,
|
|
92
|
+
workspace_addon_path=addon_path,
|
|
93
|
+
upgrade_modules=upgrade_modules,
|
|
94
|
+
)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
from typing import List
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
from ..helpers.cli import typer_retuner, typer_unpacker
|
|
8
|
+
|
|
9
|
+
LOGGER = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@typer_unpacker
|
|
13
|
+
def uninstall_modules(
|
|
14
|
+
ctx: typer.Context,
|
|
15
|
+
module_list: List[str] = typer.Argument(..., help="List of Modules to uninstall"),
|
|
16
|
+
):
|
|
17
|
+
"""
|
|
18
|
+
Uninstall Given Modules from Odoo via shell.
|
|
19
|
+
"""
|
|
20
|
+
module_list_str = str(list(module_list))
|
|
21
|
+
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()"
|
|
22
|
+
uninstall_shell = f'echo "{uninstall_cmd}" | {str(ctx.obj.odoo_main_path.absolute())}/odoo-bin shell -c {str(ctx.obj.odoo_conf_path.absolute())} --no-http'
|
|
23
|
+
LOGGER.info("Launching Uninstaller: '%s'", uninstall_shell)
|
|
24
|
+
ret = os.system(uninstall_shell)
|
|
25
|
+
return typer_retuner(ret)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@typer_unpacker
|
|
29
|
+
def odoo_shell(
|
|
30
|
+
ctx: typer.Context,
|
|
31
|
+
pipe_in_command: str = typer.Argument(""),
|
|
32
|
+
):
|
|
33
|
+
"""
|
|
34
|
+
Start Odoo session in an Interactive shell.
|
|
35
|
+
"""
|
|
36
|
+
shell_cmd = (
|
|
37
|
+
f"{str(ctx.obj.odoo_main_path.absolute())}/odoo-bin shell -c {str(ctx.obj.odoo_conf_path.absolute())} --no-http"
|
|
38
|
+
)
|
|
39
|
+
if pipe_in_command:
|
|
40
|
+
shell_cmd = f'echo "{pipe_in_command}" |' + shell_cmd
|
|
41
|
+
LOGGER.debug("Running Command: %s", shell_cmd)
|
|
42
|
+
os.system(shell_cmd)
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import configparser
|
|
2
|
+
import logging
|
|
3
|
+
import shutil
|
|
4
|
+
import zipfile
|
|
5
|
+
from enum import Enum
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import List
|
|
8
|
+
|
|
9
|
+
import typer
|
|
10
|
+
from ruamel.yaml import YAML
|
|
11
|
+
|
|
12
|
+
from ..git import GitUrl, git_ensure_addon_repos, git_ensure_odoo_repo
|
|
13
|
+
from ..helpers import download_file
|
|
14
|
+
from ..helpers.cli import typer_unpacker
|
|
15
|
+
from ..helpers.odoo_files import get_odoo_addons_in_folder
|
|
16
|
+
from ..helpers.repospec import remove_unused_folders
|
|
17
|
+
|
|
18
|
+
LOGGER = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class UpdateMode(str, Enum):
|
|
22
|
+
all = "all"
|
|
23
|
+
zip = "zip"
|
|
24
|
+
odoo = "odoo"
|
|
25
|
+
thirdparty = "thirdparty"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def unzip_addons(zip_folder: Path, target_addon_folder: Path, remove_excess: bool = False):
|
|
29
|
+
"""Take .zip files from zip_folder and extracts them into the odoo addons folder.
|
|
30
|
+
|
|
31
|
+
Parameters
|
|
32
|
+
----------
|
|
33
|
+
zip_folder : Path
|
|
34
|
+
Where to look for zip files
|
|
35
|
+
target_addon_folder : Path
|
|
36
|
+
where to place them
|
|
37
|
+
remove_excess : bool , optional
|
|
38
|
+
remove all and then unzip, by default False
|
|
39
|
+
"""
|
|
40
|
+
if remove_excess:
|
|
41
|
+
for folder in target_addon_folder.iterdir():
|
|
42
|
+
shutil.rmtree(folder)
|
|
43
|
+
LOGGER.info("Extracting Zip Addons to: %s", target_addon_folder)
|
|
44
|
+
for zip_file in zip_folder.glob("*.zip"):
|
|
45
|
+
target_addon_folder.mkdir(exist_ok=True, parents=True)
|
|
46
|
+
LOGGER.info("Extracting Zip Addon: %s", zip_file)
|
|
47
|
+
with zipfile.ZipFile(zip_file, "r") as zip_ref:
|
|
48
|
+
zip_ref.extractall(target_addon_folder)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def update_odoo_conf_addon_paths(odoo_conf: Path, addon_paths: List[Path]):
|
|
52
|
+
"""Update Odoo.Conf with Addon Paths
|
|
53
|
+
|
|
54
|
+
Parameters
|
|
55
|
+
----------
|
|
56
|
+
odoo_conf : Path
|
|
57
|
+
odoo.conf location
|
|
58
|
+
addon_paths : List[Path]
|
|
59
|
+
list of paths
|
|
60
|
+
"""
|
|
61
|
+
config = configparser.ConfigParser()
|
|
62
|
+
config.read(odoo_conf)
|
|
63
|
+
addon_paths = ",".join([str(p.absolute()) for p in addon_paths])
|
|
64
|
+
config["options"]["addons_path"] = addon_paths
|
|
65
|
+
LOGGER.info("Writing Addon Paths to Odoo Config.")
|
|
66
|
+
LOGGER.debug(addon_paths)
|
|
67
|
+
config.write(odoo_conf.open("w"))
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@typer_unpacker
|
|
71
|
+
def get_source_file(
|
|
72
|
+
repospec_yml: Path = typer.Option(
|
|
73
|
+
"", envvar="ODOO_GITSPEC", help="godoo Repospec path, when downloading odoo source (skip repo_url)"
|
|
74
|
+
),
|
|
75
|
+
repo_url: str = typer.Option("", help="git repo url, for specific repo (skip repospec_yml)"),
|
|
76
|
+
file_ref: str = typer.Option("", help="When not using repospec. File Branch, Commit, Tag..."),
|
|
77
|
+
file_path: str = typer.Option(..., help="Relative Filepath in Repository"),
|
|
78
|
+
save_path: Path = typer.Option(..., file_okay=True, dir_okay=False, help="Where to write the file"),
|
|
79
|
+
):
|
|
80
|
+
"""Get Raw file from git remote.
|
|
81
|
+
Either from the Odoo Repo defined in repospec file or from a custom Git remote.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
if not repo_url and not repospec_yml:
|
|
85
|
+
raise ValueError("Need to provide either repospec_yml or repo_url")
|
|
86
|
+
if repospec_yml and not repo_url:
|
|
87
|
+
repospec = YAML().load(repospec_yml.resolve())
|
|
88
|
+
odoo_spec = repospec["odoo"]
|
|
89
|
+
repo_url = odoo_spec["url"]
|
|
90
|
+
file_ref = odoo_spec.get("commit") or odoo_spec.get("branch")
|
|
91
|
+
if not file_ref:
|
|
92
|
+
raise ValueError(
|
|
93
|
+
"Need to provide file ref. If you provided a repospec, make sure there is a branch or commit key in the odoo section"
|
|
94
|
+
)
|
|
95
|
+
git_url = GitUrl(repo_url)
|
|
96
|
+
file_url = git_url.get_file_raw_url(ref=file_ref, file_path=file_path)
|
|
97
|
+
|
|
98
|
+
return download_file(url=file_url, save_path=save_path)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@typer_unpacker
|
|
102
|
+
def get_source(
|
|
103
|
+
ctx: typer.Context,
|
|
104
|
+
repospec_yml: Path = typer.Option(
|
|
105
|
+
..., envvar="ODOO_GITSPEC", help="Git.yml file, that specified what to download with wich prefix"
|
|
106
|
+
),
|
|
107
|
+
thirdparty_addon_path: Path = typer.Option(
|
|
108
|
+
..., envvar="ODOO_THIRDPARTY_LOCATION", help="Root folder of the Thirdparty addon repos"
|
|
109
|
+
),
|
|
110
|
+
thirdparty_zip_source: Path = typer.Option(
|
|
111
|
+
..., envvar="ODOO_THIRDPARTY_ZIP_LOCATION", help="Source folder, where to look for Addon zips"
|
|
112
|
+
),
|
|
113
|
+
update_mode: UpdateMode = typer.Option(UpdateMode.all, help="What to Update"),
|
|
114
|
+
add_compare_comments: bool = typer.Option(
|
|
115
|
+
False, help="Wether to add github.com three dot compare links as comments."
|
|
116
|
+
),
|
|
117
|
+
remove_unspecified_addons: bool = typer.Option(
|
|
118
|
+
False, help="Remove Addon folders that are not in YML or thirdparty.zip"
|
|
119
|
+
),
|
|
120
|
+
force_fetch: bool = typer.Option(
|
|
121
|
+
False,
|
|
122
|
+
help="Forces origin fetch, regardless of current branch or commit sha (may be slow)",
|
|
123
|
+
),
|
|
124
|
+
):
|
|
125
|
+
"""Update Odoo Source and Thirdparty source."""
|
|
126
|
+
LOGGER.info("Updating Addon Repos")
|
|
127
|
+
zip_addon_path = thirdparty_addon_path / "custom"
|
|
128
|
+
|
|
129
|
+
if update_mode in ["all", "zip"]:
|
|
130
|
+
unzip_addons(thirdparty_zip_source, zip_addon_path)
|
|
131
|
+
|
|
132
|
+
if update_mode in ["all", "odoo"]:
|
|
133
|
+
git_ensure_odoo_repo(
|
|
134
|
+
target_folder=ctx.obj.odoo_main_path,
|
|
135
|
+
repo_spec_file=repospec_yml,
|
|
136
|
+
force_fetch=force_fetch,
|
|
137
|
+
add_compare_comment=add_compare_comments,
|
|
138
|
+
download_archive=ctx.obj.source_download_archive,
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
if update_mode in ["all", "thirdparty"]:
|
|
142
|
+
git_repos = git_ensure_addon_repos(
|
|
143
|
+
root_folder=thirdparty_addon_path,
|
|
144
|
+
git_yml_path=repospec_yml,
|
|
145
|
+
generate_yml_compare_comments=add_compare_comments,
|
|
146
|
+
download_archive=ctx.obj.source_download_archive,
|
|
147
|
+
)
|
|
148
|
+
if remove_unspecified_addons:
|
|
149
|
+
remove_unused_folders(
|
|
150
|
+
thirdparty_addon_path=thirdparty_addon_path,
|
|
151
|
+
thirdparty_repos=git_repos.get("thirdparty", []),
|
|
152
|
+
keep_folders=[zip_addon_path],
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
if (conf_path := ctx.obj.odoo_conf_path).exists():
|
|
156
|
+
odoo_addon_paths = [ctx.obj.odoo_main_path / "addons"]
|
|
157
|
+
if get_odoo_addons_in_folder(ctx.obj.workspace_addon_path):
|
|
158
|
+
odoo_addon_paths.append(ctx.obj.workspace_addon_path)
|
|
159
|
+
if get_odoo_addons_in_folder(zip_addon_path):
|
|
160
|
+
odoo_addon_paths.append(zip_addon_path)
|
|
161
|
+
odoo_addon_paths += [
|
|
162
|
+
p for p in thirdparty_addon_path.iterdir() if p.is_dir() and not p.resolve() == zip_addon_path.resolve()
|
|
163
|
+
]
|
|
164
|
+
update_odoo_conf_addon_paths(odoo_conf=conf_path, addon_paths=odoo_addon_paths)
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import re
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import List
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
|
|
8
|
+
from ..helpers.cli import typer_retuner
|
|
9
|
+
from ..helpers.odoo_files import get_changed_modules, get_depends_of_modules, get_odoo_addons_in_folder
|
|
10
|
+
from .launch import launch_odoo as launch_odoo
|
|
11
|
+
|
|
12
|
+
LOGGER = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _test_modules_special_cases(in_modules: List[str], workspace_addon_path: Path):
|
|
16
|
+
if len(in_modules) == 1:
|
|
17
|
+
command = in_modules[0]
|
|
18
|
+
out_modules = []
|
|
19
|
+
if command == "all":
|
|
20
|
+
out_modules = get_odoo_addons_in_folder(workspace_addon_path)
|
|
21
|
+
|
|
22
|
+
if re_match := re.match(r"changes\:(.*)", command):
|
|
23
|
+
compare_branch = re_match.group(1)
|
|
24
|
+
changed_modules = get_changed_modules(addon_path=workspace_addon_path, diff_branch=compare_branch)
|
|
25
|
+
if not changed_modules:
|
|
26
|
+
return []
|
|
27
|
+
out_modules = get_depends_of_modules(workspace_addon_path, changed_modules)
|
|
28
|
+
if out_modules:
|
|
29
|
+
out_modules_with_tests = [p for p in out_modules if any(p.rglob("tests/__init__.py"))]
|
|
30
|
+
return [p.stem for p in out_modules_with_tests]
|
|
31
|
+
return in_modules
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def odoo_test(
|
|
35
|
+
ctx: typer.Context,
|
|
36
|
+
test_modules: List[str] = typer.Argument(
|
|
37
|
+
...,
|
|
38
|
+
help="Modules to install and test (Use 'all' for all Workspace modules), ('changes:<branch> to compare git changes)",
|
|
39
|
+
),
|
|
40
|
+
skip_test_modules: List[str] = typer.Option(
|
|
41
|
+
[], envvar="ODOO_TEST_SKIP_MODULES", help="Modules not to Test even if specified in test_modules"
|
|
42
|
+
),
|
|
43
|
+
odoo_log_level: str = typer.Option("test", help="Log level"),
|
|
44
|
+
):
|
|
45
|
+
workspace_addon_path = ctx.obj.workspace_addon_path
|
|
46
|
+
|
|
47
|
+
test_modules = _test_modules_special_cases(test_modules, workspace_addon_path)
|
|
48
|
+
|
|
49
|
+
skip_test_modules = [m for m in skip_test_modules if m in test_modules] # Filter out skip mods that arent requested
|
|
50
|
+
if skip_test_modules:
|
|
51
|
+
LOGGER.info("Skipping Tests for Modules:\n%s", ", ".join(["\t" + m for m in skip_test_modules]))
|
|
52
|
+
test_modules = [m for m in test_modules if m not in skip_test_modules]
|
|
53
|
+
|
|
54
|
+
if not test_modules:
|
|
55
|
+
LOGGER.info("Nothing to Test. Skipping.")
|
|
56
|
+
return
|
|
57
|
+
|
|
58
|
+
test_module_list = ",".join(["/" + m for m in test_modules])
|
|
59
|
+
module_list = ",".join(test_modules)
|
|
60
|
+
|
|
61
|
+
LOGGER.info("Testing Odoo Modules:\n%s", "\n".join(sorted(["\t" + m for m in test_modules])))
|
|
62
|
+
|
|
63
|
+
bootstrap_args = [
|
|
64
|
+
"--test-enable",
|
|
65
|
+
f"--init {module_list}",
|
|
66
|
+
]
|
|
67
|
+
if re.search("(sale|account)", test_module_list, re.IGNORECASE):
|
|
68
|
+
bootstrap_args[-1] += ",l10n_generic_coa"
|
|
69
|
+
|
|
70
|
+
bootstrap_args.append(f"--test-tags {test_module_list}")
|
|
71
|
+
bootstrap_args.append("--load-language en_US")
|
|
72
|
+
launch_args = [
|
|
73
|
+
f"-u {module_list}",
|
|
74
|
+
"--test-enable",
|
|
75
|
+
f"--log-level {odoo_log_level}",
|
|
76
|
+
"--stop-after-init",
|
|
77
|
+
"--no-http",
|
|
78
|
+
f"--test-tags {test_module_list}",
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
ret = launch_odoo(
|
|
82
|
+
ctx=ctx,
|
|
83
|
+
install_workspace_addons=False,
|
|
84
|
+
extra_args=launch_args,
|
|
85
|
+
extra_bootstrap_args=bootstrap_args,
|
|
86
|
+
launch=False,
|
|
87
|
+
multithread_worker_count=0,
|
|
88
|
+
odoo_demo=True,
|
|
89
|
+
update_source=False,
|
|
90
|
+
)
|
|
91
|
+
return typer_retuner(ret)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from ..helpers.repospec import yaml_add_compare_commit, yaml_remove_compare_commit, yaml_roundtrip_loader
|
|
5
|
+
from .git_repo import git_ensure_repo
|
|
6
|
+
|
|
7
|
+
LOGGER = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def git_ensure_odoo_repo(
|
|
11
|
+
target_folder: Path,
|
|
12
|
+
repo_spec_file: Path,
|
|
13
|
+
force_fetch: bool,
|
|
14
|
+
add_compare_comment: bool,
|
|
15
|
+
download_archive: bool,
|
|
16
|
+
):
|
|
17
|
+
yaml = yaml_roundtrip_loader()
|
|
18
|
+
git_repos = yaml.load(repo_spec_file.resolve())
|
|
19
|
+
if not git_repos:
|
|
20
|
+
raise FileNotFoundError(f"Couldnt load yml file from: {str(repo_spec_file)}")
|
|
21
|
+
|
|
22
|
+
odoo_data = git_repos.get("odoo")
|
|
23
|
+
if not odoo_data:
|
|
24
|
+
raise KeyError(f"Could not find key 'odoo' in {str(repo_spec_file)}")
|
|
25
|
+
|
|
26
|
+
odoo_url = odoo_data["url"]
|
|
27
|
+
odoo_branch = odoo_data.get("branch", "master")
|
|
28
|
+
odoo_commit = odoo_data.get("commit", "")
|
|
29
|
+
if add_compare_comment:
|
|
30
|
+
yaml_add_compare_commit(odoo_data, odoo_branch)
|
|
31
|
+
else:
|
|
32
|
+
yaml_remove_compare_commit(odoo_data)
|
|
33
|
+
|
|
34
|
+
git_ensure_repo(
|
|
35
|
+
target_folder=target_folder,
|
|
36
|
+
repo_src=odoo_url,
|
|
37
|
+
branch=odoo_branch,
|
|
38
|
+
commit=odoo_commit,
|
|
39
|
+
pull=force_fetch,
|
|
40
|
+
zip_mode=download_archive,
|
|
41
|
+
filter="blob:none",
|
|
42
|
+
single_branch=True,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
yaml.dump(git_repos, repo_spec_file)
|