From 1657e5058b536ff3eac0dbb2171f8b21ef639bf1 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 15 Jul 2025 15:07:00 +0200 Subject: [PATCH 1/4] [FIX] core: bump max supported python version Part-of: odoo/odoo#219270 Related: odoo/enterprise#90352 Signed-off-by: Xavier Morel (xmo) --- odoo/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/odoo/__init__.py b/odoo/__init__.py index 08082c11060fe..8839369ed4a13 100644 --- a/odoo/__init__.py +++ b/odoo/__init__.py @@ -17,7 +17,7 @@ import sys MIN_PY_VERSION = (3, 7) -MAX_PY_VERSION = (3, 12) +MAX_PY_VERSION = (3, 13) assert sys.version_info > MIN_PY_VERSION, f"Outdated python version detected, Odoo requires Python >= {'.'.join(map(str, MIN_PY_VERSION))} to run." #---------------------------------------------------------- From 0137af361029b46c28e83151591bd1a25ae90763 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 15 Jul 2025 08:22:47 +0200 Subject: [PATCH 2/4] [FIX] core: werkzeug 3.1 compatibility Werkzeug 3.0 deprecated `werkzeug.__version__` and 3.1 removed it. Trixie bundles werkzeug 3.1.3. So use `importlib.metadata.version` to retrieve the package's version. It was made final (non-provisional) in Python 3.10 which is the minver for odoo 17 so no need for a conditional. Also remove the unnecessary fallback in `test_misc`. Part-of: odoo/odoo#219270 Related: odoo/enterprise#90352 Signed-off-by: Xavier Morel (xmo) --- odoo/addons/test_http/tests/test_misc.py | 8 ++------ odoo/http.py | 4 ++-- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/odoo/addons/test_http/tests/test_misc.py b/odoo/addons/test_http/tests/test_misc.py index 4c06f70d989d9..d48e846693167 100644 --- a/odoo/addons/test_http/tests/test_misc.py +++ b/odoo/addons/test_http/tests/test_misc.py @@ -1,6 +1,7 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. import json +from importlib import metadata from io import StringIO from socket import gethostbyname from unittest.mock import patch @@ -14,12 +15,7 @@ from .test_common import TestHttpBase -try: - from importlib import metadata - werkzeug_version = metadata.version('werkzeug') -except ImportError: - import werkzeug - werkzeug_version = werkzeug.__version__ +werkzeug_version = metadata.version('werkzeug') @tagged('post_install', '-at_install') diff --git a/odoo/http.py b/odoo/http.py index 68c0eb5b6b121..c0980da87cc19 100644 --- a/odoo/http.py +++ b/odoo/http.py @@ -117,6 +117,7 @@ import glob import hashlib import hmac +import importlib.metadata import inspect import json import logging @@ -169,7 +170,6 @@ from .tools.geoipresolver import GeoIPResolver from .tools.facade import Proxy, ProxyAttr, ProxyFunc from .tools.func import filter_kwargs, lazy_property -from .tools.mimetypes import guess_mimetype from .tools.misc import pickle from .tools._vendor import sessions from .tools._vendor.useragents import UserAgent @@ -257,7 +257,7 @@ def get_default_session(): 'alias', 'host', 'methods', } -if parse_version(werkzeug.__version__) >= parse_version('2.0.2'): +if parse_version(importlib.metadata.version('werkzeug')) >= parse_version('2.0.2'): # Werkzeug 2.0.2 adds the websocket option. If a websocket request # (ws/wss) is trying to access an HTTP route, a WebsocketMismatch # exception is raised. On the other hand, Werkzeug 0.16 does not From 5290555ac44a9d440c61d591a25f7ac0e236c03a Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 15 Jul 2025 08:55:14 +0200 Subject: [PATCH 3/4] [IMP] requirements.txt: trixie package compat Some libraries need to be bumped to be compatible with Python 3.13 (as used in Trixie). In that case we update the requirements to the Trixie version if possible, even if a lower version would be compatible with 3.13 itself. - babel needs to be at least [2.11 to avoid usage of cgi][2] removed from 3.13 - freezegun needs to be [at least 1.5.0][3] to not call the now-removed `uuid._load_system_functions()` - trixie ships gevent 24.11.1 and greenlet 3.1.0, but upstream [gevent 24.11.1 requires greenlet 3.1.1][1] so basing the requirements off of trixie doesn't even install - zeep needs to be [at least 4.3.0][4] to not use the `cgi` module [1]: https://github.com/gevent/gevent/blob/24.11.1/setup.py#L200-L214 [2]: https://babel.pocoo.org/en/latest/changelog.html#version-2-11-0 [3]: https://github.com/spulec/freezegun/pull/534 [4]: https://github.com/mvantellingen/python-zeep/pull/1364 Part-of: odoo/odoo#219270 Related: odoo/enterprise#90352 Signed-off-by: Xavier Morel (xmo) --- requirements.txt | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/requirements.txt b/requirements.txt index 0fe1f8d584b39..32319b5344314 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,24 +1,33 @@ # The officially supported versions of the following packages are their # python3-* equivalent distributed in Ubuntu 22.04 and Debian 11 -Babel==2.9.1 # min version = 2.6.0 (Focal with security backports) -chardet==4.0.0 +Babel==2.9.1 ; python_version < '3.11' # min version = 2.6.0 (Focal with security backports) +Babel==2.10.3 ; python_version >= '3.11' and python_version < '3.13' +Babel==2.17.0 ; python_version >= '3.13' +chardet==4.0.0 ; python_version < '3.11' # (Jammy) +chardet==5.2.0 ; python_version >= '3.11' cryptography==3.4.8; python_version < '3.12' # incompatibility between pyopenssl 19.0.0 and cryptography>=37.0.0 cryptography==42.0.8 ; python_version >= '3.12' # (Noble) min 41.0.7, pinning 42.0.8 for security fixes -decorator==4.4.2 -docutils==0.16 +decorator==4.4.2 ; python_version < '3.11' # (Jammy) +decorator==5.1.1 ; python_version >= '3.11' +docutils==0.16 ; python_version < '3.11' # (Jammy) +docutils==0.20.1 ; python_version >= '3.11' ebaysdk==2.1.5 freezegun==0.3.11; python_version < '3.8' -freezegun==0.3.15; python_version >= '3.8' +freezegun==0.3.15; python_version >= '3.8' and python_version < '3.11' +freezegun==1.2.1 ; python_version >= '3.11' and python_version < '3.13' +freezegun==1.5.1 ; python_version >= '3.13' gevent==1.5.0 ; sys_platform != 'win32' and python_version == '3.7' gevent==20.9.0 ; sys_platform != 'win32' and python_version > '3.7' and python_version <= '3.9' gevent==21.8.0 ; sys_platform != 'win32' and python_version > '3.9' and python_version <= '3.10' # (Jammy) gevent==22.10.2; sys_platform != 'win32' and python_version > '3.10'and python_version < '3.12' # (Jammy) -gevent==24.2.1 ; sys_platform != 'win32' and python_version >= '3.12' # (Noble) +gevent==24.2.1 ; sys_platform != 'win32' and python_version >= '3.12' and python_version < '3.13' # (Noble) +gevent==24.11.1 ; sys_platform != 'win32' and python_version >= '3.13' # (Trixie) greenlet==0.4.15 ; sys_platform != 'win32' and python_version == '3.7' greenlet==0.4.17 ; sys_platform != 'win32' and python_version > '3.7' and python_version <= '3.9' greenlet==1.1.2 ; sys_platform != 'win32' and python_version > '3.9' and python_version <= '3.10' # (Jammy) greenlet==2.0.2 ; sys_platform != 'win32' and python_version > '3.10' and python_version < '3.12' # (Jammy) -greenlet==3.0.3 ; sys_platform != 'win32' and python_version >= '3.12' # (Noble) +greenlet==3.0.3 ; sys_platform != 'win32' and python_version >= '3.12' and python_version < '3.13' # (Noble) +greenlet==3.1.1 ; sys_platform != 'win32' and python_version >= '3.13' # (Trixie) idna==2.10 Jinja2==2.11.3 ; python_version <= '3.10' # min version = 2.10.1 (Focal - with security backports) Jinja2==3.1.2 ; python_version > '3.10' @@ -37,7 +46,8 @@ ofxparse==0.21; python_version > '3.9' # (Jammy) passlib==1.7.4 # min version = 1.7.2 (Focal with security backports) Pillow==9.0.1 ; python_version <= '3.10' # min version = 7.0.0 (Focal with security backports) Pillow==9.4.0 ; python_version > '3.10' and python_version < '3.12' -Pillow==10.2.0 ; python_version >= '3.12' # (Noble) Mostly to have a wheel package +Pillow==10.2.0 ; python_version >= '3.12' and python_version < '3.13' # (Noble) Mostly to have a wheel package +Pillow==11.1.0 ; python_version >= '3.13' # (Noble) Mostly to have a wheel package polib==1.1.0 psutil==5.8.0 ; python_version <= '3.10' psutil==5.9.4 ; python_version > '3.10' and python_version < '3.12' @@ -47,7 +57,8 @@ psycopg2==2.8.6 ; sys_platform != 'win32' and python_version >= '3.8' and python psycopg2==2.8.6 ; sys_platform == 'win32' and python_version < '3.10' # psycopg2==2.9.2 ; python_version == '3.10' psycopg2==2.9.5 ; python_version >= '3.11' and python_version < '3.12' -psycopg2==2.9.9 ; python_version >= '3.12' # (Noble) Mostly to have a wheel package +psycopg2==2.9.9 ; python_version >= '3.12' and python_version < '3.13' # (Noble) +psycopg2==2.9.10 ; python_version >= '3.13' # (Trixie) pydot==1.4.2 pyopenssl==20.0.1; python_version < '3.12' pyopenssl==24.1.0 ; python_version >= '3.12' # (Noble) min 23.2.0, pinned for compatibility with cryptography==42.0.8 and security patches @@ -79,4 +90,6 @@ xlrd==1.1.0; python_version < '3.8' xlrd==1.2.0; python_version >= '3.8' XlsxWriter==1.1.2 xlwt==1.3.0 -zeep==4.0.0 +zeep==4.1.0 ; python_version < '3.11' # (jammy) +zeep==4.2.1 ; python_version >= '3.11' and python_version < '3.13' +zeep==4.3.1 ; python_version >= '3.13' From 9d19353d752639fef68d49df7a52eec3c3d5f40a Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Wed, 16 Jul 2025 10:04:45 +0200 Subject: [PATCH 4/4] [REM] *: uses of OrderedMultiDict OrderedMultiDict is deprecated in Werkzeug 3.1 (pallets/werkzeug#2975) which is what Trixie bundles. While the entire thing was straight up removed in 5229cc8e92282018a1dc55faed1bd2c014e73156, pretty much keep the semantics by more or less inlining the way OrderedMultiDicts are compared. Part-of: odoo/odoo#219270 Related: odoo/enterprise#90352 Signed-off-by: Xavier Morel (xmo) --- addons/website/models/website.py | 14 ++++++++------ addons/website_event/controllers/main.py | 4 ++-- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/addons/website/models/website.py b/addons/website/models/website.py index 674524b94f62a..a700ae1c8603f 100644 --- a/addons/website/models/website.py +++ b/addons/website/models/website.py @@ -8,6 +8,8 @@ import logging import operator import re +from itertools import zip_longest + import requests import threading @@ -17,7 +19,7 @@ from lxml import etree, html from psycopg2 import sql from werkzeug import urls -from werkzeug.datastructures import OrderedMultiDict +from werkzeug.datastructures import MultiDict from werkzeug.exceptions import NotFound from markupsafe import Markup @@ -1400,11 +1402,11 @@ def _get_canonical_url(self, canonical_params): def _is_canonical_url(self, canonical_params): """Returns whether the current request URL is canonical.""" self.ensure_one() - # Compare OrderedMultiDict because the order is important, there must be - # only one canonical and not params permutations. - params = request.httprequest.args - canonical_params = canonical_params or OrderedMultiDict() - if params != canonical_params: + params = request.httprequest.args.items(multi=True) + canonical = iter([]) if not canonical_params\ + else canonical_params.items(multi=True) if isinstance(canonical_params, MultiDict)\ + else canonical_params.items() + if any(a != b for a, b in zip_longest(params, canonical)): return False # Compare URL at the first routing iteration because it's the one with # the language in the path. It is important to also test the domain of diff --git a/addons/website_event/controllers/main.py b/addons/website_event/controllers/main.py index ad854157a3ce8..f73c9712fad42 100644 --- a/addons/website_event/controllers/main.py +++ b/addons/website_event/controllers/main.py @@ -10,7 +10,7 @@ from datetime import datetime, timedelta from dateutil.parser import parse from dateutil.relativedelta import relativedelta -from werkzeug.datastructures import OrderedMultiDict +from collections import Counter from werkzeug.exceptions import NotFound from odoo import fields, http, _ @@ -132,7 +132,7 @@ def events(self, page=1, **searches): if searches['date'] == 'old': # the only way to display this content is to set date=old so it must be canonical - values['canonical_params'] = OrderedMultiDict([('date', 'old')]) + values['canonical_params'] = {'date': 'old'} return request.render("website_event.index", values)