19.0 vanilla

This commit is contained in:
Ernad Husremovic 2025-10-03 18:07:25 +02:00
parent 0a7ae8db93
commit 991d2234ca
416 changed files with 646602 additions and 300844 deletions

View file

@ -1,39 +1,67 @@
# ruff: noqa: F401, PLC0415
# ignore import not at top of the file
"""Lazy module monkeypatcher
Submodules should be named after the module (stdlib or third-party) they need
to patch, and should define a `patch_module` function.
This function will be called either immediately if the module to patch is
already imported when the monkey patcher runs, or right after that module is
imported otherwise.
"""
import importlib
import os
import pkgutil
import sys
import time
from .evented import patch_evented
from types import ModuleType, SimpleNamespace
def set_timezone_utc():
class PatchImportHook:
"""Register hooks that are run on import."""
def __init__(self):
self.hooks = set()
def add_hook(self, fullname: str) -> None:
"""Register a hook after a module is loaded.
If already loaded, run hook immediately."""
self.hooks.add(fullname)
if fullname in sys.modules:
patch_module(fullname)
def find_spec(self, fullname, path=None, target=None):
if fullname not in self.hooks:
return None # let python use another import hook to import this fullname
# skip all finders before this one
idx = sys.meta_path.index(self)
for finder in sys.meta_path[idx + 1:]:
spec = finder.find_spec(fullname, path, target)
if spec is not None:
# we found a spec, change the loader
def exec_module(module: ModuleType, exec_module=spec.loader.exec_module) -> None:
exec_module(module)
patch_module(module.__name__)
spec.loader = SimpleNamespace(create_module=spec.loader.create_module, exec_module=exec_module)
return spec
raise ImportError(f"Could not load the module {fullname!r} to patch")
HOOK_IMPORT = PatchImportHook()
sys.meta_path.insert(0, HOOK_IMPORT)
def patch_init() -> None:
os.environ['TZ'] = 'UTC' # Set the timezone
if hasattr(time, 'tzset'):
time.tzset()
for submodule in pkgutil.iter_modules(__path__):
HOOK_IMPORT.add_hook(submodule.name)
def patch_all():
patch_evented()
set_timezone_utc()
from .codecs import patch_codecs
patch_codecs()
from .email import patch_email
patch_email()
from .mimetypes import patch_mimetypes
patch_mimetypes()
from .pytz import patch_pytz
patch_pytz()
from .literal_eval import patch_literal_eval
patch_literal_eval()
from .lxml import patch_lxml
patch_lxml()
from .num2words import patch_num2words
patch_num2words()
from .stdnum import patch_stdnum
patch_stdnum()
from .urllib3 import patch_urllib3
patch_urllib3()
from .werkzeug_urls import patch_werkzeug
patch_werkzeug()
from .zeep import patch_zeep
patch_zeep()
def patch_module(name: str) -> None:
module = importlib.import_module(f'.{name}', __name__)
module.patch_module()

View file

@ -28,5 +28,5 @@ def literal_eval(expr):
return orig_literal_eval(expr)
def patch_literal_eval():
def patch_module():
ast.literal_eval = literal_eval

View file

@ -0,0 +1,9 @@
import bs4
import warnings
def patch_module():
if hasattr(bs4, 'XMLParsedAsHTMLWarning'):
# ofxparse use an html parser to parse ofx xml files and triggers a
# warning since bs4 4.11.0 https://github.com/jseutter/ofxparse/issues/170
warnings.filterwarnings('ignore', category=bs4.XMLParsedAsHTMLWarning)

View file

@ -1,26 +0,0 @@
import codecs
import encodings.aliases
import re
import babel.core
def patch_codecs():
# ---------------------------------------------------------
# some charset are known by Python under a different name
# ---------------------------------------------------------
encodings.aliases.aliases['874'] = 'cp874'
encodings.aliases.aliases['windows_874'] = 'cp874'
# ---------------------------------------------------------
# alias hebrew iso-8859-8-i and iso-8859-8-e on iso-8859-8
# https://bugs.python.org/issue18624
# ---------------------------------------------------------
iso8859_8 = codecs.lookup('iso8859_8')
iso8859_8ie_re = re.compile(r'iso[-_]?8859[-_]8[-_]?[ei]', re.IGNORECASE)
codecs.register(lambda charset: iso8859_8 if iso8859_8ie_re.match(charset) else None)
# To remove when corrected in Babel
babel.core.LOCALE_ALIASES['nb'] = 'nb_NO'

View file

@ -0,0 +1,13 @@
import csv
def patch_module():
""" The default limit for CSV fields in the module is 128KiB,
which is not quite sufficient to import images to store
in attachment. 500MiB is a bit overkill, but better safe
than sorry I guess
"""
class UNIX_LINE_TERMINATOR(csv.excel):
lineterminator = '\n'
csv.field_size_limit(500 * 1024 * 1024)
csv.register_dialect("UNIX", UNIX_LINE_TERMINATOR)

View file

@ -0,0 +1,29 @@
"""
The docstrings can use many more roles and directives than the one
present natively in docutils. That's because we use Sphinx to render
them in the documentation, and Sphinx defines the "Python Domain", a set
of additional rules and directive to understand the python language.
It is not desirable to add a dependency on Sphinx in community, as it is
a *too big* dependency.
The following code adds a bunch of dummy elements for the missing roles
and directives, so docutils is able to parse them with no warning.
"""
import docutils.nodes
import docutils.parsers.rst.directives.admonitions
def _role_literal(name, rawtext, text, lineno, inliner, options=None, content=None):
literal = docutils.nodes.literal(rawtext, text)
return [literal], []
def patch_module():
for role in ('attr', 'class', 'func', 'meth', 'ref', 'const', 'samp', 'term'):
docutils.parsers.rst.roles.register_local_role(role, _role_literal)
for directive in ('attribute', 'deprecated'):
docutils.parsers.rst.directives.register_directive(
directive, docutils.parsers.rst.directives.admonitions.Note)

View file

@ -1,7 +1,7 @@
from email._policybase import _PolicyBase
def patch_email():
def patch_module():
def policy_clone(self, **kwargs):
for arg in kwargs:
if arg.startswith("_") or "__" in arg:

View file

@ -0,0 +1,31 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import locale
import time
import datetime
def patch_module():
if not hasattr(locale, 'D_FMT'):
locale.D_FMT = 1
if not hasattr(locale, 'T_FMT'):
locale.T_FMT = 2
if not hasattr(locale, 'nl_langinfo'):
def nl_langinfo(param):
if param == locale.D_FMT:
val = time.strptime('30/12/2004', '%d/%m/%Y')
dt = datetime.datetime(*val[:-2])
format_date = dt.strftime('%x')
for x, y in [('30', '%d'), ('12', '%m'), ('2004', '%Y'), ('04', '%Y')]:
format_date = format_date.replace(x, y)
return format_date
if param == locale.T_FMT:
val = time.strptime('13:24:56', '%H:%M:%S')
dt = datetime.datetime(*val[:-2])
format_time = dt.strftime('%X')
for x, y in [('13', '%H'), ('24', '%M'), ('56', '%S')]:
format_time = format_time.replace(x, y)
return format_time
locale.nl_langinfo = nl_langinfo

View file

@ -6,7 +6,7 @@ from importlib.metadata import version
from odoo.tools import parse_version
def patch_lxml():
def patch_module():
# between these versions having a couple data urls in a style attribute
# or style node removes the attribute or node erroneously
if parse_version("4.6.0") <= parse_version(version('lxml')) < parse_version("5.2.0"):

View file

@ -1,7 +1,7 @@
import mimetypes
def patch_mimetypes():
def patch_module():
# if extension is already knows, the new definition will remplace the existing one
# Add potentially missing (older ubuntu) font mime types
mimetypes.add_type('application/font-woff', '.woff')

View file

@ -6,7 +6,7 @@ from collections import OrderedDict
from decimal import ROUND_HALF_UP, Decimal
from math import floor
from odoo import MIN_PY_VERSION
from odoo.release import MIN_PY_VERSION
# The following section of the code is used to monkey patch
# the Arabic class of num2words package as there are some problems
@ -145,7 +145,7 @@ class Num2Word_Base:
def to_cardinal_float(self, value):
try:
float(value) == value
_ = float(value) == value
except (ValueError, TypeError, AssertionError, AttributeError):
raise TypeError(self.errmsg_nonnum % value)
@ -971,7 +971,7 @@ class NumberToWords_BG(Num2Word_Base):
return ret_minus + ''.join(ret)
def patch_num2words():
def patch_module():
try:
import num2words # noqa: PLC0415
except ImportError:

View file

@ -122,7 +122,7 @@ _tz_mapping = {
original_pytz_timezone = pytz.timezone
def patch_pytz():
def patch_module():
def timezone(name):
if name not in pytz.all_timezones_set and name in _tz_mapping:
name = _tz_mapping[name]

View file

@ -0,0 +1,6 @@
import re
def patch_module():
""" Default is 512, a little too small for odoo """
re._MAXCACHE = 4096

View file

@ -1,16 +1,30 @@
"""
Running mode flags (gevent, prefork)
"""Patcher for any change not strictly related to an stdlib module
This should be imported as early as possible.
It will initialize the `odoo.evented` variable.
"""
import odoo
import codecs
import encodings.aliases
import re
import sys
import babel.core
import odoo
def patch_module():
patch_evented()
patch_codecs()
odoo.evented = False
def patch_evented():
"""Running mode flags (gevent, prefork)
This should be executed early. It will initialize the `odoo.evented` variable.
"""
if odoo.evented or not (len(sys.argv) > 1 and sys.argv[1] == 'gevent'):
return
sys.argv.remove('gevent')
@ -35,5 +49,27 @@ def patch_evented():
else:
raise psycopg2.OperationalError(
"Bad result from poll: %r" % state)
psycopg2.extensions.set_wait_callback(gevent_wait_callback)
odoo.evented = True
def patch_codecs():
# ---------------------------------------------------------
# some charset are known by Python under a different name
# ---------------------------------------------------------
encodings.aliases.aliases['874'] = 'cp874'
encodings.aliases.aliases['windows_874'] = 'cp874'
# ---------------------------------------------------------
# alias hebrew iso-8859-8-i and iso-8859-8-e on iso-8859-8
# https://bugs.python.org/issue18624
# ---------------------------------------------------------
iso8859_8 = codecs.lookup('iso8859_8')
iso8859_8ie_re = re.compile(r'iso[-_]?8859[-_]8[-_]?[ei]', re.IGNORECASE)
codecs.register(lambda charset: iso8859_8 if iso8859_8ie_re.match(charset) else None)
# To remove when corrected in Babel
babel.core.LOCALE_ALIASES['nb'] = 'nb_NO'

View file

@ -48,7 +48,7 @@ def new_get_soap_client(wsdlurl, timeout=30):
return _soap_clients[(wsdlurl, timeout)]
def patch_stdnum():
def patch_module():
try:
from stdnum import util
except ImportError:

View file

@ -8,5 +8,5 @@ def pool_init(self, *args, **kwargs):
self.pool_classes_by_scheme = {**self.pool_classes_by_scheme}
def patch_urllib3():
def patch_module():
PoolManager.__init__ = pool_init

View file

@ -1040,8 +1040,8 @@ def url_join(
return url_unparse((scheme, netloc, path, query, fragment))
def patch_werkzeug():
from ..tools.json import scriptsafe # noqa: PLC0415
def patch_module():
from odoo.tools.json import scriptsafe
Request.json_module = Response.json_module = scriptsafe
FileStorage.save = lambda self, dst, buffer_size=(1 << 20): copyfileobj(self.stream, dst, buffer_size)

View file

@ -0,0 +1,20 @@
def patch_module():
try:
from xlrd import xlsx # noqa: PLC0415
except ImportError:
xlsx = None
else:
from lxml import etree # noqa: PLC0415
# xlrd.xlsx supports defusedxml, defusedxml's etree interface is broken
# (missing ElementTree and thus ElementTree.iter) which causes a fallback to
# Element.getiterator(), triggering a warning before 3.9 and an error from 3.9.
#
# Historically we had defusedxml installed because zeep had a hard dep on
# it. They have dropped it as of 4.1.0 which we now require (since 18.0),
# but keep this patch for now as Odoo might get updated in a legacy env
# which still has defused.
#
# Directly instruct xlsx to use lxml as we have a hard dependency on that.
xlsx.ET = etree
xlsx.ET_has_iterparse = True
xlsx.Element_has_iter = True

View file

@ -0,0 +1,22 @@
"""
Patch xlsxwriter to add some sanitization to respect the excel sheet name
restrictions as the sheet name is often translatable, can not control the input
"""
import re
import xlsxwriter
class PatchedXlsxWorkbook(xlsxwriter.Workbook):
def add_worksheet(self, name=None, worksheet_class=None):
if name:
# invalid Excel character: []:*?/\
name = re.sub(r'[\[\]:*?/\\]', '', name)
# maximum size is 31 characters
name = name[:31]
return super().add_worksheet(name, worksheet_class=worksheet_class)
def patch_module():
xlsxwriter.Workbook = PatchedXlsxWorkbook

View file

@ -0,0 +1,21 @@
"""
Patch xlwt to add some sanitization to respect the excel sheet name
restrictions as the sheet name is often translatable, can not control the input
"""
import re
import xlwt
class PatchedWorkbook(xlwt.Workbook):
def add_sheet(self, name, cell_overwrite_ok=False):
# invalid Excel character: []:*?/\
name = re.sub(r'[\[\]:*?/\\]', '', name)
# maximum size is 31 characters
name = name[:31]
return super().add_sheet(name, cell_overwrite_ok=cell_overwrite_ok)
def patch_module():
xlwt.Workbook = PatchedWorkbook

View file

@ -2,7 +2,7 @@ from zeep.xsd import visitor
from zeep.xsd.const import xsd_ns
def patch_zeep():
def patch_module():
# see https://github.com/mvantellingen/python-zeep/issues/1185
if visitor.tags.notation.localname != 'notation':
visitor.tags.notation = xsd_ns('notation')