From 8c43f2e6738c09980ce1e9f86f7ba06c23e64b80 Mon Sep 17 00:00:00 2001 From: Maksym Yankin Date: Tue, 31 Mar 2026 09:07:45 +0300 Subject: [PATCH 1/4] [REF] cloud_platform: server_environment should not be a required dependency --- cloud_platform/__manifest__.py | 10 ++-------- cloud_platform/models/cloud_platform.py | 3 +-- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/cloud_platform/__manifest__.py b/cloud_platform/__manifest__.py index fc63b366..1bfe3155 100644 --- a/cloud_platform/__manifest__.py +++ b/cloud_platform/__manifest__.py @@ -5,17 +5,11 @@ { "name": "Cloud Platform", "summary": "Addons required for the Camptocamp Cloud Platform", - "version": "16.0.2.0.0", + "version": "16.0.2.0.1", "author": "Camptocamp,Odoo Community Association (OCA)", "license": "AGPL-3", "category": "Extra Tools", - "depends": [ - "session_redis", - "monitoring_status", - "logging_json", - "server_environment", # OCA/server-tools - ], + "depends": ["session_redis", "monitoring_status", "logging_json"], "website": "https://github.com/camptocamp/odoo-cloud-platform", - "data": [], "installable": True, } diff --git a/cloud_platform/models/cloud_platform.py b/cloud_platform/models/cloud_platform.py index e55cfa51..f1cbfe5e 100644 --- a/cloud_platform/models/cloud_platform.py +++ b/cloud_platform/models/cloud_platform.py @@ -7,7 +7,6 @@ from collections import namedtuple from odoo import api, models -from odoo.tools.config import config from .strtobool import strtobool @@ -52,7 +51,7 @@ def _config_by_server_env(self, platform_kind, environment): return configs.get(environment) or self._default_config() def _get_running_env(self): - environment_name = config["running_env"] + environment_name = os.environ.get("RUNNING_ENV") if environment_name.startswith("labs"): # We allow to have environments such as 'labs-logistics' # or 'labs-finance', in order to have the matching ribbon. From 9981e029d42a25553937a2b9cadb7cf8cfc77774 Mon Sep 17 00:00:00 2001 From: Maksym Yankin Date: Tue, 31 Mar 2026 09:15:28 +0300 Subject: [PATCH 2/4] [REF] monitoring_prometheus: server_environment should not be a required dependency --- monitoring_prometheus/__manifest__.py | 11 +++-------- monitoring_prometheus/models/ir_http.py | 2 ++ 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/monitoring_prometheus/__manifest__.py b/monitoring_prometheus/__manifest__.py index 5745fa30..3dfa1d05 100644 --- a/monitoring_prometheus/__manifest__.py +++ b/monitoring_prometheus/__manifest__.py @@ -4,17 +4,12 @@ { "name": "Monitoring: Prometheus Metrics", - "version": "16.0.1.0.0", + "version": "16.0.1.0.1", "author": "Camptocamp,Odoo Community Association (OCA)", "license": "AGPL-3", - "category": "category", - "depends": [ - "base", - "web", - "server_environment", - ], + "category": "Extra Tools", + "depends": ["base", "web"], "website": "https://github.com/camptocamp/odoo-cloud-platform", - "data": [], "external_dependencies": { "python": ["prometheus_client"], }, diff --git a/monitoring_prometheus/models/ir_http.py b/monitoring_prometheus/models/ir_http.py index b2316c81..60a9ee34 100644 --- a/monitoring_prometheus/models/ir_http.py +++ b/monitoring_prometheus/models/ir_http.py @@ -17,6 +17,8 @@ class IrHttp(models.AbstractModel): @classmethod def _dispatch(cls, endpoint): + # httprequest environment is updated with WSGI environment variables in core + # REF: https://github.com/odoo/odoo/blob/16.0/addons/http_routing/models/ir_http.py#L529 path_info = request.httprequest.environ.get("PATH_INFO") if path_info.startswith("/longpolling/"): From 6d93fbffac17c9cc2b4093b13c65f8314e0f41c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Todorovich?= Date: Wed, 20 May 2026 09:50:40 -0300 Subject: [PATCH 3/4] dotfiles: update pre-commit config --- .github/workflows/pre-commit.yml | 2 +- .pre-commit-config.yaml | 181 +++++++++++-------------------- pyproject.toml | 54 +++++++++ 3 files changed, 121 insertions(+), 116 deletions(-) create mode 100644 pyproject.toml diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index c9a20a6e..ba409652 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -27,7 +27,7 @@ jobs: run: pre-commit run --all-files --show-diff-on-failure --color=always - name: Check that all files generated by pre-commit are in git run: | - newfiles="$(git ls-files --others --exclude-from=.gitignore)" + newfiles="$(git ls-files --others --exclude-from=.gitignore --exclude=.ruff_cache)" if [ "$newfiles" != "" ] ; then echo "Please check-in the following files:" echo "$newfiles" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b518033e..75677f17 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -21,119 +21,70 @@ exclude: | ^docs/_templates/.*\.html$| # You don't usually want a bot to modify your legal texts (LICENSE.*|COPYING.*) -default_language_version: - python: python3 - node: "16.17.0" + repos: - - repo: local - hooks: - # These files are most likely copier diff rejection junks; if found, - # review them manually, fix the problem (if needed) and remove them - - id: forbidden-files - name: forbidden files - entry: found forbidden files; remove them - language: fail - files: "\\.rej$" - - id: en-po-files - name: en.po files cannot exist - entry: found a en.po file - language: fail - files: '[a-zA-Z0-9_]*/i18n/en\.po$' - - repo: https://github.com/oca/maintainer-tools - rev: 4cd2b852214dead80822e93e6749b16f2785b2fe - hooks: - # update the NOT INSTALLABLE ADDONS section above - - id: oca-update-pre-commit-excluded-addons - - id: oca-fix-manifest-website - args: ["https://github.com/camptocamp/odoo-cloud-platform"] - - repo: https://github.com/myint/autoflake - rev: v1.6.1 - hooks: - - id: autoflake - args: - - --expand-star-imports - - --ignore-init-module-imports - - --in-place - - --remove-all-unused-imports - - --remove-duplicate-keys - - --remove-unused-variables - - repo: https://github.com/psf/black - rev: 22.8.0 - hooks: - - id: black - - repo: https://github.com/pre-commit/mirrors-prettier - rev: v2.7.1 - hooks: - - id: prettier - name: prettier (with plugin-xml) - additional_dependencies: - - "prettier@2.7.1" - - "@prettier/plugin-xml@2.2.0" - args: - - --plugin=@prettier/plugin-xml - files: \.(css|htm|html|js|json|jsx|less|md|scss|toml|ts|xml|yaml|yml)$ - - repo: https://github.com/pre-commit/mirrors-eslint - rev: v8.24.0 - hooks: - - id: eslint - verbose: true - args: - - --color - - --fix - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.3.0 - hooks: - - id: trailing-whitespace - # exclude autogenerated files - exclude: /README\.rst$|\.pot?$ - - id: end-of-file-fixer - # exclude autogenerated files - exclude: /README\.rst$|\.pot?$ - - id: debug-statements - - id: fix-encoding-pragma - args: ["--remove"] - - id: check-case-conflict - - id: check-docstring-first - - id: check-executables-have-shebangs - - id: check-merge-conflict - # exclude files where underlines are not distinguishable from merge conflicts - exclude: /README\.rst$|^docs/.*\.rst$ - - id: check-symlinks - - id: check-xml - - id: mixed-line-ending - args: ["--fix=lf"] - - repo: https://github.com/asottile/pyupgrade - rev: v2.38.2 - hooks: - - id: pyupgrade - args: ["--keep-percent-format"] - - repo: https://github.com/PyCQA/isort - rev: 5.12.0 - hooks: - - id: isort - name: isort except __init__.py - args: - - --settings=. - exclude: /__init__\.py$ - - repo: https://github.com/acsone/setuptools-odoo - rev: 3.1.8 - hooks: - - id: setuptools-odoo-make-default - - repo: https://github.com/PyCQA/flake8 - rev: 5.0.4 - hooks: - - id: flake8 - name: flake8 - additional_dependencies: ["flake8-bugbear==21.9.2"] - - repo: https://github.com/OCA/pylint-odoo - rev: v8.0.19 - hooks: - - id: pylint_odoo - name: pylint with optional checks - args: - - --rcfile=.pylintrc - - --exit-zero - verbose: true - - id: pylint_odoo - args: - - --rcfile=.pylintrc-mandatory + +# pylint +- repo: https://github.com/pycqa/pylint + rev: v4.0.5 + hooks: + - id: pylint + name: pylint odoo + # check only specific Odoo addons + files: odoo/local-src/ + types: ['file', 'python'] # to not check .csv and .conf files + args: ["--rcfile=.ci/pylintrc_odoo"] + additional_dependencies: [pylint-odoo==v10.0.1] + +# pre-commit-hooks +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v6.0.0 + hooks: + - id: check-yaml + - id: check-symlinks + - id: check-xml + - id: debug-statements + - id: end-of-file-fixer + - id: mixed-line-ending + args: ["--fix=lf"] + types_or: [python, javascript, css, xml, yaml] + - id: trailing-whitespace + types_or: [python, javascript, css, xml, yaml] + +# Odoo pre-commit hooks +- repo: https://github.com/OCA/odoo-pre-commit-hooks + rev: v0.2.20 + hooks: + - id: oca-checks-odoo-module + args: + - --disable= + xml-deprecated-tree-attribute, + xml-header-wrong, + xml-id-position-first, + - id: oca-checks-po + args: ["--fix"] + +# ruff (= flake8 + isort + pyupgrade) +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.15.4 + hooks: + - id: ruff-check + args: [--fix, --show-fixes] + exclude: __manifest__.py|__init__.py + - id: ruff-format + args: [--diff] + exclude: __manifest__.py|__init__.py + - id: ruff-format + exclude: __manifest__.py|__init__.py + +# rstcheck +- repo: https://github.com/rstcheck/rstcheck + rev: v6.2.5 + hooks: + - id: rstcheck + +# setuptools-odoo +- repo: https://github.com/acsone/setuptools-odoo + rev: 3.3.2 + hooks: + - id: setuptools-odoo-make-default diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..141835e5 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,54 @@ +[tool.ruff] +line-length = 88 +target-version = "py312" + + +[tool.ruff.lint] +select = [ + "E", # pycodestyle errors + "W", # pycodestyle warnings + "C90", # mccabe + "F", # pyflakes + "UP", # pyupgrade + "I", # isort +] +ignore = [ + "UP031", # pyupgrade: use format specifiers instead of percent format (no autofix) +] + +[tool.ruff.lint.pycodestyle] +# line-length is set in [tool.ruff], and it's used by the formatter +# in case the formatted can't autofix the line length, it will be reported as an error +# if it exceeds the max-line-length set here. We use 320 which is maximum allowed value. +max-line-length = 320 + +[tool.ruff.lint.isort] +combine-as-imports = true +force-wrap-aliases = true +known-third-party = [ + "anthem", + "git", + "invoke", + "marabunta", + "openupgradelib", + "pkg_resources", + "psycopg2", + "requests", + "setuptools", + "urllib2", + "yaml", +] +section-order = [ + "future", + "standard-library", + "third-party", + "odoo", + "first-party", + "local-folder", +] + +[tool.ruff.lint.isort.sections] +odoo = ["odoo"] + +[tool.ruff.lint.mccabe] +max-complexity = 16 From a9e4d6b1025ae6bf2de10795678d716883927223 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Todorovich?= Date: Wed, 20 May 2026 09:50:57 -0300 Subject: [PATCH 4/4] pre-commit run -a --- attachment_azure/models/ir_attachment.py | 8 ++++---- .../models/ir_attachment.py | 14 +++++++------- base_attachment_object_storage/models/strtobool.py | 2 +- cloud_platform/models/cloud_platform.py | 4 ++-- cloud_platform/models/strtobool.py | 2 +- cloud_platform_azure/{README.md => README.rst} | 3 ++- cloud_platform_azure/models/cloud_platform.py | 5 ++--- kwkhtmltopdf_assets/models/ir_qweb.py | 1 - logging_json/json_log.py | 2 +- logging_json/strtobool.py | 2 +- monitoring_status/controllers/main.py | 1 - session_redis/http.py | 5 ++--- session_redis/session.py | 4 ++-- session_redis/strtobool.py | 2 +- 14 files changed, 26 insertions(+), 29 deletions(-) rename cloud_platform_azure/{README.md => README.rst} (68%) diff --git a/attachment_azure/models/ir_attachment.py b/attachment_azure/models/ir_attachment.py index fd15a21e..2662a635 100644 --- a/attachment_azure/models/ir_attachment.py +++ b/attachment_azure/models/ir_attachment.py @@ -32,7 +32,7 @@ class IrAttachment(models.Model): _inherit = "ir.attachment" def _get_stores(self): - return ["azure"] + super(IrAttachment, self)._get_stores() + return ["azure"] + super()._get_stores() @api.model def _get_blob_service_client(self): @@ -162,7 +162,7 @@ def _store_file_read(self, fname, bin_size=False): _logger.info("Attachment '%s' missing on object storage", fname) return read else: - return super(IrAttachment, self)._store_file_read(fname, bin_size) + return super()._store_file_read(fname, bin_size) @api.model def _store_file_write(self, key, bin_data): @@ -189,7 +189,7 @@ def _store_file_write(self, key, bin_data): _("The file could not be stored: %s") % str(error) ) from None else: - _super = super(IrAttachment, self) + _super = super() filename = _super._store_file_write(key, bin_data) return filename @@ -215,4 +215,4 @@ def _store_file_delete(self, fname): # user _logger.exception("Error during deletion of the file %s" % fname) else: - super(IrAttachment, self)._store_file_delete(fname) + super()._store_file_delete(fname) diff --git a/base_attachment_object_storage/models/ir_attachment.py b/base_attachment_object_storage/models/ir_attachment.py index e8b17f54..c84530d1 100644 --- a/base_attachment_object_storage/models/ir_attachment.py +++ b/base_attachment_object_storage/models/ir_attachment.py @@ -33,7 +33,7 @@ def clean_fs(files): _logger.info( "_file_delete could not unlink %s", full_path, exc_info=True ) - except IOError: + except OSError: # Harmless and needed for race conditions _logger.info( "_file_delete could not unlink %s", full_path, exc_info=True @@ -124,7 +124,7 @@ def _store_in_db_instead_of_object_storage_domain(self): domain = [] storage_config = self._get_storage_force_db_config() for mimetype_key, limit in storage_config.items(): - part = [("mimetype", "=like", "{}%".format(mimetype_key))] + part = [("mimetype", "=like", f"{mimetype_key}%")] if limit: part = AND([part, [("file_size", "<=", limit)]]) domain = OR([domain, part]) @@ -236,7 +236,7 @@ def _file_delete(self, fname): # using SQL to include files hidden through unlink or due to record # rules cr.execute( - "SELECT COUNT(*) FROM ir_attachment " "WHERE store_fname = %s", (fname,) + "SELECT COUNT(*) FROM ir_attachment WHERE store_fname = %s", (fname,) ) count = cr.fetchone()[0] if not count: @@ -249,7 +249,7 @@ def _is_file_from_a_store(self, fname): for store_name in self._get_stores(): if self.is_storage_disabled(store_name): continue - uri = "{}://".format(store_name) + uri = f"{store_name}://" if fname.startswith(uri): return True return False @@ -340,7 +340,7 @@ def force_storage_to_db_for_special_fields(self, new_cr=False): ( normalize_domain( [ - ("store_fname", "=like", "{}://%".format(storage)), + ("store_fname", "=like", f"{storage}://%"), # for res_field, see comment in # _force_storage_to_object_storage "|", @@ -360,7 +360,7 @@ def force_storage_to_db_for_special_fields(self, new_cr=False): total = len(attachment_ids) start_time = time.time() _logger.info( - "Moving %d attachments from %s to" " DB for fast access", total, storage + "Moving %d attachments from %s to DB for fast access", total, storage ) current = 0 for attachment_id in attachment_ids: @@ -399,7 +399,7 @@ def _force_storage_to_object_storage(self, new_cr=False): domain = [ "!", - ("store_fname", "=like", "{}://%".format(storage)), + ("store_fname", "=like", f"{storage}://%"), "|", ("res_field", "=", False), ("res_field", "!=", False), diff --git a/base_attachment_object_storage/models/strtobool.py b/base_attachment_object_storage/models/strtobool.py index 12f4b828..1a7ad558 100644 --- a/base_attachment_object_storage/models/strtobool.py +++ b/base_attachment_object_storage/models/strtobool.py @@ -18,4 +18,4 @@ def strtobool(value): try: return _MAP[str(value).lower()] except KeyError as error: - raise ValueError('"{}" is not a valid bool value'.format(value)) from error + raise ValueError(f'"{value}" is not a valid bool value') from error diff --git a/cloud_platform/models/cloud_platform.py b/cloud_platform/models/cloud_platform.py index f1cbfe5e..e9b75c31 100644 --- a/cloud_platform/models/cloud_platform.py +++ b/cloud_platform/models/cloud_platform.py @@ -69,7 +69,7 @@ def _install(self, platform_kind): self.check() if configs.filestore.location == "remote": self.env["ir.attachment"].sudo().force_storage() - _logger.info("cloud platform configured for {}".format(platform_kind)) + _logger.info(f"cloud platform configured for {platform_kind}") @api.model def install(self): @@ -125,5 +125,5 @@ def check(self): self._check_redis(environment_name) def _register_hook(self): - super(CloudPlatform, self)._register_hook() + super()._register_hook() self.sudo().check() diff --git a/cloud_platform/models/strtobool.py b/cloud_platform/models/strtobool.py index 12f4b828..1a7ad558 100644 --- a/cloud_platform/models/strtobool.py +++ b/cloud_platform/models/strtobool.py @@ -18,4 +18,4 @@ def strtobool(value): try: return _MAP[str(value).lower()] except KeyError as error: - raise ValueError('"{}" is not a valid bool value'.format(value)) from error + raise ValueError(f'"{value}" is not a valid bool value') from error diff --git a/cloud_platform_azure/README.md b/cloud_platform_azure/README.rst similarity index 68% rename from cloud_platform_azure/README.md rename to cloud_platform_azure/README.rst index 449ab29b..1f7bd5d1 100644 --- a/cloud_platform_azure/README.md +++ b/cloud_platform_azure/README.rst @@ -1,4 +1,5 @@ -# Cloud Platform Azure +Cloud Platform Azure +==================== Install addons specific to the Azure setup. diff --git a/cloud_platform_azure/models/cloud_platform.py b/cloud_platform_azure/models/cloud_platform.py index b72d2294..9f6a7e75 100644 --- a/cloud_platform_azure/models/cloud_platform.py +++ b/cloud_platform_azure/models/cloud_platform.py @@ -5,7 +5,6 @@ import re from odoo import api, models - from odoo.addons.cloud_platform.models.cloud_platform import ( FilestoreKind, PlatformConfig, @@ -19,13 +18,13 @@ class CloudPlatform(models.AbstractModel): @api.model def _filestore_kinds(self): - kinds = super(CloudPlatform, self)._filestore_kinds() + kinds = super()._filestore_kinds() kinds["azure"] = AZURE_STORE_KIND return kinds @api.model def _platform_kinds(self): - kinds = super(CloudPlatform, self)._platform_kinds() + kinds = super()._platform_kinds() kinds.append("azure") return kinds diff --git a/kwkhtmltopdf_assets/models/ir_qweb.py b/kwkhtmltopdf_assets/models/ir_qweb.py index 9025b96c..702c4d41 100644 --- a/kwkhtmltopdf_assets/models/ir_qweb.py +++ b/kwkhtmltopdf_assets/models/ir_qweb.py @@ -6,7 +6,6 @@ class IrQweb(models.AbstractModel): - _inherit = "ir.qweb" def _generate_asset_nodes_cache( diff --git a/logging_json/json_log.py b/logging_json/json_log.py index 8c5821c0..2b7c5f78 100644 --- a/logging_json/json_log.py +++ b/logging_json/json_log.py @@ -35,7 +35,7 @@ def add_fields(self, log_record, record, message_dict): record.dbname = getattr(threading.current_thread(), "dbname", "?") record.request_id = getattr(threading.current_thread(), "request_uuid", None) record.uid = getattr(threading.current_thread(), "uid", None) - _super = super(OdooJsonFormatter, self) + _super = super() return _super.add_fields(log_record, record, message_dict) diff --git a/logging_json/strtobool.py b/logging_json/strtobool.py index 12f4b828..1a7ad558 100644 --- a/logging_json/strtobool.py +++ b/logging_json/strtobool.py @@ -18,4 +18,4 @@ def strtobool(value): try: return _MAP[str(value).lower()] except KeyError as error: - raise ValueError('"{}" is not a valid bool value'.format(value)) from error + raise ValueError(f'"{value}" is not a valid bool value') from error diff --git a/monitoring_status/controllers/main.py b/monitoring_status/controllers/main.py index 21b6893c..1ea77c3a 100644 --- a/monitoring_status/controllers/main.py +++ b/monitoring_status/controllers/main.py @@ -7,7 +7,6 @@ import werkzeug from odoo import http - from odoo.addons.web.controllers.main import ensure_db diff --git a/session_redis/http.py b/session_redis/http.py index 64595296..93145f0d 100644 --- a/session_redis/http.py +++ b/session_redis/http.py @@ -72,15 +72,14 @@ def purge_fs_sessions(path): if is_true(os.environ.get("ODOO_SESSION_REDIS")): if sentinel_host: _logger.debug( - "HTTP sessions stored in Redis with prefix '%s'. " - "Using Sentinel on %s:%s", + "HTTP sessions stored in Redis with prefix '%s'. Using Sentinel on %s:%s", prefix or "", sentinel_host, sentinel_port, ) else: _logger.debug( - "HTTP sessions stored in Redis with prefix '%s' on " "%s:%s", + "HTTP sessions stored in Redis with prefix '%s' on %s:%s", prefix or "", host, port, diff --git a/session_redis/session.py b/session_redis/session.py index d327474e..80649769 100644 --- a/session_redis/session.py +++ b/session_redis/session.py @@ -60,7 +60,7 @@ def save(self, session): else: user_msg = "anonymous user" _logger.debug( - "saving session with key '%s' and " "expiration of %s seconds for %s", + "saving session with key '%s' and expiration of %s seconds for %s", key, expiration, user_msg, @@ -80,7 +80,7 @@ def delete(self, session): def get(self, sid): if not self.is_valid_key(sid): _logger.debug( - "session with invalid sid '%s' has been asked, " "returning a new one", + "session with invalid sid '%s' has been asked, returning a new one", sid, ) return self.new() diff --git a/session_redis/strtobool.py b/session_redis/strtobool.py index 12f4b828..1a7ad558 100644 --- a/session_redis/strtobool.py +++ b/session_redis/strtobool.py @@ -18,4 +18,4 @@ def strtobool(value): try: return _MAP[str(value).lower()] except KeyError as error: - raise ValueError('"{}" is not a valid bool value'.format(value)) from error + raise ValueError(f'"{value}" is not a valid bool value') from error