gOdoo-cli 0.16.0__tar.gz → 0.17.0__tar.gz

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 (107) hide show
  1. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/PKG-INFO +1 -1
  2. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/addons/godoo_test_succ_mod/__manifest__.py +1 -1
  3. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/src/godoo_cli/__about__.py +1 -1
  4. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/src/godoo_cli/cli.py +16 -2
  5. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/src/godoo_cli/commands/odoo_bin/shell.py +32 -11
  6. godoo_cli-0.17.0/src/godoo_cli/commands/odoo_bin/shell_scripts/pregenerate_assets.py +34 -0
  7. godoo_cli-0.17.0/src/godoo_cli/commands/odoo_bin/shell_scripts/render_report.py +122 -0
  8. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/src/godoo_cli/git/git_odoo_addons.py +1 -1
  9. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/src/godoo_cli/helpers/system.py +116 -3
  10. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/.codecovrc +0 -0
  11. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/.devcontainer/devcontainer.json +0 -0
  12. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/.env.sample +0 -0
  13. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/.eslintignore +0 -0
  14. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/.eslintrc.yml +0 -0
  15. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/.github/FUNDING.yml +0 -0
  16. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/.github/workflows/README.md +0 -0
  17. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/.github/workflows/quality.yml +0 -0
  18. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/.github/workflows/version-bump.yml +0 -0
  19. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/.github/workflows/version-publish.yml +0 -0
  20. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/.gitignore +0 -0
  21. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/.pre-commit-config.yaml +0 -0
  22. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/.prettierrc.yml +0 -0
  23. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/.vscode/launch.json +0 -0
  24. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/LICENSE +0 -0
  25. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/README.md +0 -0
  26. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/addons/.gitkeep +0 -0
  27. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/addons/godoo_test_succ_mod/__init__.py +0 -0
  28. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/addons/godoo_test_succ_mod/models/__init__.py +0 -0
  29. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/addons/godoo_test_succ_mod/tests/__init__.py +0 -0
  30. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/addons/godoo_test_succ_mod/tests/data.py +0 -0
  31. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/addons/godoo_test_succ_mod/tests/test_succ.py +0 -0
  32. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/assets/godoo-main-cli.png +0 -0
  33. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/assets/odoo_logo.png +0 -0
  34. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/config/nginx-proxy_headers.conf +0 -0
  35. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/config/nginx.conf +0 -0
  36. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/config/postgresql.conf +0 -0
  37. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/config/supervisord.conf +0 -0
  38. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/docker/Dockerfile +0 -0
  39. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/docker/cups/Dockerfile +0 -0
  40. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/docker/cups/config/cups-pdf.conf +0 -0
  41. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/docker/cups/config/cupsd.conf +0 -0
  42. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/docker/cups/cups_entrypoint.sh +0 -0
  43. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/docker/depends_build.sh +0 -0
  44. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/docker/depends_runtime.sh +0 -0
  45. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/docker/docker-compose.base.yml +0 -0
  46. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/docker/docker-compose.devcontainer-traefik.yml +0 -0
  47. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/docker/docker-compose.devcontainer.yml +0 -0
  48. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/docker/docker-compose.traefik.yml +0 -0
  49. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/docker/psql.gpg +0 -0
  50. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/godoo_src.code-workspace +0 -0
  51. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/makefile +0 -0
  52. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/odoo_manifest.yml +0 -0
  53. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/pyproject.toml +0 -0
  54. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/scripts/container_requirements.sh +0 -0
  55. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/scripts/launchodoo.sh +0 -0
  56. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/scripts/migrations/development/shrink_database.py +0 -0
  57. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/scripts/reset_devcontainer.sh +0 -0
  58. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/src/godoo_cli/__init__.py +0 -0
  59. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/src/godoo_cli/__main__.py +0 -0
  60. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/src/godoo_cli/cli_common.py +0 -0
  61. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/src/godoo_cli/commands/__init__.py +0 -0
  62. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/src/godoo_cli/commands/backup/__init__.py +0 -0
  63. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/src/godoo_cli/commands/backup/cli.py +0 -0
  64. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/src/godoo_cli/commands/backup/dump.py +0 -0
  65. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/src/godoo_cli/commands/backup/load.py +0 -0
  66. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/src/godoo_cli/commands/backup/pull.py +0 -0
  67. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/src/godoo_cli/commands/backup/util.py +0 -0
  68. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/src/godoo_cli/commands/config.py +0 -0
  69. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/src/godoo_cli/commands/db/__init__.py +0 -0
  70. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/src/godoo_cli/commands/db/cli.py +0 -0
  71. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/src/godoo_cli/commands/db/passwords.py +0 -0
  72. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/src/godoo_cli/commands/db/query.py +0 -0
  73. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/src/godoo_cli/commands/odoo_bin/__init__.py +0 -0
  74. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/src/godoo_cli/commands/odoo_bin/bootstrap.py +0 -0
  75. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/src/godoo_cli/commands/odoo_bin/cli_generate.py +0 -0
  76. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/src/godoo_cli/commands/odoo_bin/godoo_test/__init__.py +0 -0
  77. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/src/godoo_cli/commands/odoo_bin/godoo_test/cli.py +0 -0
  78. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/src/godoo_cli/commands/odoo_bin/godoo_test/test_load_data.py +0 -0
  79. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/src/godoo_cli/commands/odoo_bin/godoo_test/test_run.py +0 -0
  80. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/src/godoo_cli/commands/odoo_bin/launch.py +0 -0
  81. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/src/godoo_cli/commands/odoo_bin/shell_scripts/odoo_banner.py +0 -0
  82. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/src/godoo_cli/commands/rpc/__init__.py +0 -0
  83. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/src/godoo_cli/commands/rpc/cli.py +0 -0
  84. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/src/godoo_cli/commands/rpc/config_parameters.py +0 -0
  85. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/src/godoo_cli/commands/rpc/importer.py +0 -0
  86. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/src/godoo_cli/commands/rpc/modules.py +0 -0
  87. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/src/godoo_cli/commands/rpc/translations.py +0 -0
  88. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/src/godoo_cli/commands/source_get.py +0 -0
  89. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/src/godoo_cli/git/__init__.py +0 -0
  90. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/src/godoo_cli/git/git_repo.py +0 -0
  91. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/src/godoo_cli/git/git_url.py +0 -0
  92. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/src/godoo_cli/git/zip_download.py +0 -0
  93. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/src/godoo_cli/helpers/__init__.py +0 -0
  94. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/src/godoo_cli/helpers/cli.py +0 -0
  95. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/src/godoo_cli/helpers/modules_git.py +0 -0
  96. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/src/godoo_cli/helpers/modules_py.py +0 -0
  97. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/src/godoo_cli/helpers/odoo_files.py +0 -0
  98. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/src/godoo_cli/helpers/pip.py +0 -0
  99. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/src/godoo_cli/models/__init__.py +0 -0
  100. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/src/godoo_cli/models/db_connection.py +0 -0
  101. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/src/godoo_cli/models/godoo_git_repo.py +0 -0
  102. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/src/godoo_cli/models/godoo_manifest.py +0 -0
  103. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/src/godoo_cli/models/godoo_models.py +0 -0
  104. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/src/godoo_cli/models/godoo_modules.py +0 -0
  105. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/src/tests/test_git_clone.py +0 -0
  106. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/thirdparty/.gitkeep +0 -0
  107. {godoo_cli-0.16.0 → godoo_cli-0.17.0}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gOdoo-cli
3
- Version: 0.16.0
3
+ Version: 0.17.0
4
4
  Summary: Wrapper around Odoo-Bin with some convinience RPC functions.
5
5
  Project-URL: repository, https://github.com/OpenJKSoftware/gOdoo
6
6
  Author-email: Joshua Kreuder <Joshua_Kreuder@outlook.com>
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "godoo Test Succ Module",
3
- "version": "16.0.1.0.0",
3
+ "version": "19.0.1.0.0",
4
4
  "author": "Joshua Kreuder",
5
5
  "license": "AGPL-3",
6
6
  "depends": ["base"],
@@ -2,7 +2,7 @@
2
2
 
3
3
  from packaging import version
4
4
 
5
- __version__ = "0.16.0" # Initial version, will be managed by Hatch
5
+ __version__ = "0.17.0" # Initial version, will be managed by Hatch
6
6
 
7
7
 
8
8
  def get_version_tuple() -> tuple[int, int, int]:
@@ -38,14 +38,28 @@ def main_callback(
38
38
  help="Verbose Logging with Error stacktraces",
39
39
  ),
40
40
  ] = False,
41
+ log_filter: Annotated[
42
+ Optional[str],
43
+ typer.Option(
44
+ "--log-filter",
45
+ "-lf",
46
+ envvar="GODOO_LOG_FILTER",
47
+ help="Regex pattern to filter log output by logger name (e.g. 'odoo.test.suite')",
48
+ ),
49
+ ] = None,
41
50
  ):
42
- """Configure the CLI's logging level.
51
+ """Configure the CLI's logging level and optional log filter.
43
52
 
44
53
  Args:
45
54
  verbose: If True, enables verbose logging with error stacktraces.
46
55
  Can be set via --verbose flag or GODOO_VERBOSE environment variable.
56
+ log_filter: Optional regex pattern to filter log records by logger name.
57
+ Can be set via --log-filter flag or GODOO_LOG_FILTER environment variable.
47
58
  """
48
- set_logging(verbose=bool(verbose) if verbose is not None else False)
59
+ set_logging(
60
+ verbose=bool(verbose) if verbose is not None else False,
61
+ log_filter=log_filter,
62
+ )
49
63
 
50
64
 
51
65
  def main_cli():
@@ -6,6 +6,7 @@ Odoo environment. It supports both interactive and script-based operations.
6
6
  """
7
7
 
8
8
  import logging
9
+ import subprocess
9
10
  import sys
10
11
  from pathlib import Path
11
12
  from typing import Annotated, Optional
@@ -103,6 +104,10 @@ def odoo_shell_run_script(
103
104
  odoo_conf_path: Annotated[Path, CLI.odoo_paths.conf_path],
104
105
  db_name: Annotated[str, CLI.database.db_name],
105
106
  db_user: Annotated[str, CLI.database.db_user],
107
+ script_args: Annotated[
108
+ Optional[list[str]],
109
+ typer.Argument(help="Arguments to pass to the script"),
110
+ ] = None,
106
111
  db_host: Annotated[str, CLI.database.db_host] = "",
107
112
  db_port: Annotated[int, CLI.database.db_port] = 0,
108
113
  db_password: Annotated[str, CLI.database.db_password] = "",
@@ -111,6 +116,7 @@ def odoo_shell_run_script(
111
116
 
112
117
  This function executes a Python script in the Odoo shell environment,
113
118
  supporting both configuration file and direct database connection parameters.
119
+ Additional arguments after the script name are passed to the script as script_args list.
114
120
 
115
121
  Returns:
116
122
  int: 0 for success, non-zero for failure.
@@ -121,7 +127,7 @@ def odoo_shell_run_script(
121
127
  return CLI.returner(1)
122
128
 
123
129
  shell_cmd = f"{odoo_main_path.absolute()!s}/odoo-bin shell --no-http"
124
- if odoo_conf_path.exists():
130
+ if odoo_conf_path and odoo_conf_path.exists():
125
131
  shell_cmd += f" -c {odoo_conf_path.absolute()!s}"
126
132
  else:
127
133
  LOGGER.warning("No Odoo Config File found at %s", odoo_conf_path)
@@ -130,8 +136,28 @@ def odoo_shell_run_script(
130
136
  return CLI.returner(1)
131
137
  shell_cmd += f" --db_host={db_host} --db_port={db_port} --database={db_name} --db_user={db_user} --db_password={db_password}"
132
138
 
133
- LOGGER.info("Running Script: %s", script_path)
134
- run_cmd(shell_cmd, stdin=script_path.open("r"))
139
+ # Prepare script with arguments
140
+ script_content = script_path.read_text()
141
+
142
+ # If script_args provided, prepend them as a list variable
143
+ if script_args:
144
+ args_str = repr(list(script_args)) # Safely convert to Python list literal
145
+ script_with_args = f"script_args = {args_str}\n\n{script_content}"
146
+ LOGGER.info("Running Script: %s with args: %s", script_path, script_args)
147
+ else:
148
+ script_with_args = f"script_args = []\n\n{script_content}"
149
+ LOGGER.info("Running Script: %s", script_path)
150
+
151
+ proc = subprocess.Popen(
152
+ shell_cmd,
153
+ shell=True,
154
+ stdin=subprocess.PIPE,
155
+ stdout=sys.stdout,
156
+ stderr=sys.stderr,
157
+ text=True,
158
+ )
159
+ proc.communicate(input=script_with_args)
160
+ return CLI.returner(proc.returncode)
135
161
 
136
162
 
137
163
  def odoo_pregenerate_assets(godoo_conf: GodooConfig):
@@ -144,15 +170,10 @@ def odoo_pregenerate_assets(godoo_conf: GodooConfig):
144
170
  NotImplementedError: If the Odoo version is not supported.
145
171
  """
146
172
  odoo_version = odoo_bin_get_version(odoo_main_repo_path=godoo_conf.odoo_install_folder)
147
- if odoo_version.major == 16:
148
- pregen_command = "env['ir.qweb']._pregenerate_assets_bundles();env.cr.commit()"
149
- else:
150
- msg = f"Odoo Version {odoo_version.raw} not supported in gOdoo for pregenerate_assets"
151
- LOGGER.error(msg)
152
- raise NotImplementedError(msg)
153
173
  LOGGER.info("Pregenerating Assets for Odoo version %s", odoo_version.raw)
154
- odoo_shell(
155
- pipe_in_command=pregen_command,
174
+ # Use the pregenerate_assets script which attempts multiple internal APIs.
175
+ odoo_shell_run_script(
176
+ script_name="pregenerate_assets",
156
177
  odoo_main_path=godoo_conf.odoo_install_folder,
157
178
  odoo_conf_path=godoo_conf.odoo_conf_path,
158
179
  db_name=godoo_conf.db_name,
@@ -0,0 +1,34 @@
1
+ """Script used by gOdoo to pregenerate asset bundles in the Odoo filestore.
2
+
3
+ The script tries several internal APIs to be tolerant across multiple
4
+ Odoo versions (16+). It prints which method succeeded or prints
5
+ diagnostic information if all attempts fail.
6
+ """
7
+
8
+ import contextlib
9
+
10
+ from odoo import api
11
+
12
+ env: api.Environment = env # Just to silence pyright # pyright: ignore # NOQA
13
+
14
+ try:
15
+ env["ir.qweb"]._pregenerate_assets_bundles()
16
+ print("pregenerated: ir.qweb._pregenerate_assets_bundles")
17
+ except Exception:
18
+ try:
19
+ env["ir.qweb"]._pregenerate_assets()
20
+ print("pregenerated: ir.qweb._pregenerate_assets")
21
+ except Exception:
22
+ try:
23
+ env["ir.asset"]._pregenerate_bundles()
24
+ print("pregenerated: ir.asset._pregenerate_bundles")
25
+ except Exception:
26
+ print("pregenerate failed; exceptions:")
27
+ import traceback
28
+
29
+ with contextlib.suppress(Exception):
30
+ traceback.print_exc()
31
+
32
+ # Commit any DB changes
33
+ with contextlib.suppress(Exception):
34
+ env.cr.commit()
@@ -0,0 +1,122 @@
1
+ """Render an Odoo report to HTML and print to stdout.
2
+
3
+ This script renders a QWeb report for given record IDs and outputs the HTML.
4
+ It is executed in the Odoo shell environment via:
5
+
6
+ godoo shell-script render_report <report_name> <record_id> [<record_id> ...]
7
+
8
+ Example:
9
+ godoo shell-script render_report sale.report_saleorder 1
10
+ godoo shell-script render_report sale.report_saleorder 1 2 3
11
+
12
+ Flags:
13
+ --keep-base64 Keep base64 content instead of replacing with placeholder
14
+
15
+ """
16
+
17
+ import logging
18
+ import re
19
+ import sys
20
+ from xml.dom import minidom
21
+
22
+ from odoo import api
23
+
24
+ LOGGER = logging.getLogger(__name__)
25
+
26
+ env: api.Environment = env # Just to silence pyright # pyright: ignore # NOQA
27
+
28
+ # script_args is injected by godoo shell-script command
29
+ script_args: list[str] = script_args # pyright: ignore # NOQA
30
+
31
+
32
+ def replace_base64_with_placeholder(html_string: str) -> str:
33
+ """Replace base64 content with a placeholder.
34
+
35
+ Replaces long base64 strings (data URIs, embedded images, etc.) with
36
+ a placeholder to make output more readable.
37
+
38
+ Parameters
39
+ ----------
40
+ html_string : str
41
+ HTML string potentially containing base64 content
42
+
43
+ Returns:
44
+ -------
45
+ str
46
+ HTML string with base64 content replaced by placeholder
47
+ """
48
+ # Match data URIs with base64 content and replace with placeholder
49
+ # Matches patterns like: data:image/png;base64,... or similar
50
+ pattern = r"data:[^/]+/[^;]+;base64,[A-Za-z0-9+/]+={0,2}"
51
+ replacement = "data:[MIME];base64,[BASE64_CONTENT_PLACEHOLDER]"
52
+ return re.sub(pattern, replacement, html_string)
53
+
54
+
55
+ def render_report(report_name: str, record_ids: list[int], replace_base64: bool = True):
56
+ """Render an Odoo report to HTML.
57
+
58
+ Parameters
59
+ ----------
60
+ report_name : str
61
+ Name of the report to render (e.g., 'sale.report_saleorder')
62
+ record_ids : list[int]
63
+ List of record IDs to render
64
+ replace_base64 : bool, optional
65
+ Replace base64 content with placeholder (True by default)
66
+ """
67
+ try:
68
+ report_model = env["ir.actions.report"]
69
+ html_content, _ = report_model._render_qweb_html(report_name, record_ids)
70
+
71
+ if html_content:
72
+ # Decode bytes to string
73
+ html_string = html_content.decode("utf-8") if isinstance(html_content, bytes) else html_content
74
+
75
+ # Replace base64 content with placeholder if enabled
76
+ if replace_base64:
77
+ html_string = replace_base64_with_placeholder(html_string)
78
+
79
+ # Try to pretty print the HTML
80
+ try:
81
+ dom = minidom.parseString(html_string)
82
+ pretty_html = dom.toprettyxml(indent=" ")
83
+ # Remove extra blank lines that minidom adds
84
+ print("\n".join([line for line in pretty_html.split("\n") if line.strip()]))
85
+ except Exception:
86
+ # If pretty printing fails, just print the raw HTML
87
+ print(html_string)
88
+ else:
89
+ LOGGER.error("Failed to render report: %s", report_name)
90
+ sys.exit(1)
91
+
92
+ except ValueError:
93
+ LOGGER.exception("Report not found")
94
+ sys.exit(1)
95
+ except Exception:
96
+ LOGGER.exception("Error rendering report %s", report_name)
97
+ sys.exit(1)
98
+
99
+
100
+ # Main execution: Parse script_args and call render_report
101
+ if not script_args:
102
+ LOGGER.error("Usage: godoo shell-script render_report <report_name> <record_id> [<record_id> ...] [--keep-base64]")
103
+ sys.exit(1)
104
+
105
+ if len(script_args) < 2:
106
+ LOGGER.error("Missing arguments. Need at least report_name and one record_id")
107
+ sys.exit(1)
108
+
109
+ # Check for --keep-base64 flag
110
+ keep_base64_flag = "--keep-base64" in script_args
111
+ filtered_args = [arg for arg in script_args if arg != "--keep-base64"]
112
+
113
+ # Now extract report_name and record_ids from filtered args
114
+ if len(filtered_args) < 2:
115
+ LOGGER.error("Missing arguments. Need at least report_name and one record_id")
116
+ sys.exit(1)
117
+
118
+ report_name = filtered_args[0]
119
+ record_ids = [int(rid) for rid in filtered_args[1:]]
120
+
121
+ # render_report with replace_base64 being False if --keep-base64 was passed
122
+ render_report(report_name, record_ids, replace_base64=not keep_base64_flag)
@@ -69,9 +69,9 @@ def _merge_sources_into_repo(godoo_repo: GodooGitRepo) -> None:
69
69
  remote_name = f"merge_from_{merge_spec.url.split('/')[-1].replace('.git', '')}"
70
70
  remote = _ensure_remote(repo, remote_name, merge_spec.url)
71
71
  fetch_ref = merge_spec.ref
72
- LOGGER.info("Merging ref %s from %s into %s", fetch_ref, merge_spec.url, godoo_repo.ref)
73
72
  remote.fetch(fetch_ref)
74
73
  merge_target = merge_spec.commit or f"{remote.name}/{merge_spec.branch or fetch_ref}"
74
+ LOGGER.info("Merging ref %s from %s into %s", fetch_ref, merge_spec.url, merge_target)
75
75
  repo.git.merge(
76
76
  merge_target,
77
77
  "--no-ff",
@@ -1,11 +1,16 @@
1
1
  """Helper functions around the host system."""
2
2
 
3
+ import atexit
4
+ import contextlib
3
5
  import datetime
4
6
  import logging
5
7
  import os
8
+ import re
6
9
  import subprocess
10
+ import sys
11
+ import threading
7
12
  from pathlib import Path
8
- from typing import Any, Union
13
+ from typing import Any, Optional, Union
9
14
 
10
15
  import click
11
16
  import requests
@@ -20,6 +25,100 @@ from . import cli as godoo_cli_helpers
20
25
  LOGGER = logging.getLogger(__name__)
21
26
 
22
27
 
28
+ class RegexLogFilter(logging.Filter):
29
+ """A logging filter that only passes records whose logger name matches a regex pattern."""
30
+
31
+ def __init__(self, pattern: str) -> None:
32
+ """Compile and store the regex pattern used for filtering."""
33
+ super().__init__()
34
+ self._pattern = re.compile(pattern)
35
+
36
+ def filter(self, record: logging.LogRecord) -> bool:
37
+ """Return True only if the record's logger name matches the pattern."""
38
+ return bool(self._pattern.search(record.name))
39
+
40
+
41
+ def _filter_thread_main(pattern: re.Pattern, r_fd: int, orig_fd: int) -> None:
42
+ """Read lines from r_fd and write only those matching pattern to orig_fd.
43
+
44
+ Lines that are part of a traceback block (starting with ``Traceback``) are
45
+ always emitted in full, regardless of the primary pattern, so that error
46
+ context is never silently dropped.
47
+ """
48
+ traceback_start_re = re.compile(rb"^Traceback ")
49
+ in_traceback = False
50
+
51
+ def should_emit(line_bytes: bytes) -> bool:
52
+ nonlocal in_traceback
53
+ if in_traceback:
54
+ # Tracebacks are delimited by an empty line; emit it, then leave traceback mode.
55
+ if not line_bytes.strip():
56
+ in_traceback = False
57
+ return True
58
+ if traceback_start_re.match(line_bytes):
59
+ in_traceback = True
60
+ return True
61
+ return bool(pattern.search(line_bytes.decode("utf-8", errors="replace")))
62
+
63
+ buf = b""
64
+ with os.fdopen(r_fd, "rb") as reader, os.fdopen(orig_fd, "wb") as writer:
65
+ while True:
66
+ chunk = reader.read(4096)
67
+ if not chunk:
68
+ # EOF - flush any unterminated line
69
+ if buf and should_emit(buf):
70
+ writer.write(buf)
71
+ writer.flush()
72
+ break
73
+ buf += chunk
74
+ while b"\n" in buf:
75
+ line, buf = buf.split(b"\n", 1)
76
+ if should_emit(line):
77
+ writer.write(line + b"\n")
78
+ writer.flush()
79
+
80
+
81
+ def _install_fd_filter(pattern: re.Pattern) -> None:
82
+ """Redirect fd 1 and fd 2 through a pipe and filter each line by regex.
83
+
84
+ Saves the original stdout fd, creates a pipe, points fd 1 and fd 2 at the
85
+ pipe's write end, then starts a thread that reads from the read end
86
+ and forwards only lines matching *pattern* to the original stdout fd.
87
+ This intercepts output from both Python code and child subprocesses.
88
+ An atexit handler closes the write end and joins the thread so the last
89
+ lines are never lost.
90
+ """
91
+ orig_fd = os.dup(sys.stdout.fileno()) # save original stdout (e.g. fd 1 -> terminal)
92
+ r_fd, w_fd = os.pipe()
93
+ os.dup2(w_fd, 1) # fd 1 now -> pipe write end
94
+ os.dup2(w_fd, 2) # fd 2 now -> pipe write end
95
+ os.close(w_fd)
96
+
97
+ # Rebind Python's sys.stdout/stderr so buffered I/O goes to the new fd too
98
+ sys.stdout = os.fdopen(1, "w", buffering=1, closefd=False)
99
+ sys.stderr = sys.stdout
100
+
101
+ t = threading.Thread(
102
+ target=_filter_thread_main,
103
+ args=(pattern, r_fd, orig_fd),
104
+ daemon=True,
105
+ name="godoo-log-filter",
106
+ )
107
+ t.start()
108
+
109
+ def _cleanup() -> None:
110
+ # Flush Python-buffered output into the pipe, then close the write end.
111
+ # Closing fd 1 AND fd 2 sends EOF to the reader thread, letting it drain and exit.
112
+ with contextlib.suppress(Exception):
113
+ sys.stdout.flush()
114
+ for fd in (1, 2):
115
+ with contextlib.suppress(OSError):
116
+ os.close(fd)
117
+ t.join(timeout=10)
118
+
119
+ atexit.register(_cleanup)
120
+
121
+
23
122
  def run_cmd(command: str, **kwargs: dict[str, Any]) -> subprocess.CompletedProcess:
24
123
  """Runs command via subprocess.run."""
25
124
  LOGGER.debug("Running shell:\n%s", command)
@@ -40,8 +139,14 @@ def ensure_dotenv(varname: str) -> str:
40
139
  return var
41
140
 
42
141
 
43
- def set_logging(verbose: bool = False) -> None:
44
- """Set the logging configuration according to passed arguments."""
142
+ def set_logging(verbose: bool = False, log_filter: Optional[str] = None) -> None:
143
+ """Set the logging configuration according to passed arguments.
144
+
145
+ Args:
146
+ verbose: Enable DEBUG-level logging with rich tracebacks.
147
+ log_filter: Optional regex pattern; only log records whose logger name
148
+ matches the pattern will be emitted.
149
+ """
45
150
  if verbose:
46
151
  install_rich_traceback(suppress=[click, godoo_cli_helpers])
47
152
  logging.basicConfig(
@@ -68,6 +173,14 @@ def set_logging(verbose: bool = False) -> None:
68
173
  datefmt="%Y-%m-%d %H:%M:%S",
69
174
  )
70
175
 
176
+ if log_filter:
177
+ pattern = re.compile(log_filter)
178
+ regex_filter = RegexLogFilter(log_filter)
179
+ for handler in logging.root.handlers:
180
+ handler.addFilter(regex_filter)
181
+ _install_fd_filter(pattern)
182
+ LOGGER.debug("Log filter active: '%s'", log_filter)
183
+
71
184
 
72
185
  def download_file(url: str, save_path: Path, chunk_size: int = 128) -> None:
73
186
  """Download file from URL to specified path."""
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes