diff --git a/addons/crm/models/crm_lead.py b/addons/crm/models/crm_lead.py index a43de43ebe1..249c007a77d 100644 --- a/addons/crm/models/crm_lead.py +++ b/addons/crm/models/crm_lead.py @@ -2156,7 +2156,7 @@ class Lead(models.Model): # - avoid blocking the table for too long with a too big transaction transactions_count, transactions_failed_count = 0, 0 cron_update_lead_start_date = datetime.now() - auto_commit = not getattr(threading.currentThread(), 'testing', False) + auto_commit = not getattr(threading.current_thread(), 'testing', False) for probability, probability_lead_ids in probability_leads.items(): for lead_ids_current in tools.split_every(PLS_UPDATE_BATCH_STEP, probability_lead_ids): transactions_count += 1 diff --git a/addons/crm/models/crm_team.py b/addons/crm/models/crm_team.py index c0f0d5bce8e..3e622756edb 100644 --- a/addons/crm/models/crm_team.py +++ b/addons/crm/models/crm_team.py @@ -445,7 +445,7 @@ class Team(models.Model): BUNDLE_HOURS_DELAY = int(self.env['ir.config_parameter'].sudo().get_param('crm.assignment.delay', default=0)) BUNDLE_COMMIT_SIZE = int(self.env['ir.config_parameter'].sudo().get_param('crm.assignment.commit.bundle', 100)) - auto_commit = not getattr(threading.currentThread(), 'testing', False) + auto_commit = not getattr(threading.current_thread(), 'testing', False) # leads max_create_dt = self.env.cr.now() - datetime.timedelta(hours=BUNDLE_HOURS_DELAY) diff --git a/addons/crm/models/crm_team_member.py b/addons/crm/models/crm_team_member.py index bf6f44ae37f..d25787d8d50 100644 --- a/addons/crm/models/crm_team_member.py +++ b/addons/crm/models/crm_team_member.py @@ -154,7 +154,7 @@ class TeamMember(models.Model): leads_done_ids = set() counter = 0 # auto-commit except in testing mode - auto_commit = not getattr(threading.currentThread(), 'testing', False) + auto_commit = not getattr(threading.current_thread(), 'testing', False) commit_bundle_size = int(self.env['ir.config_parameter'].sudo().get_param('crm.assignment.commit.bundle', 100)) while population: counter += 1 diff --git a/addons/event/models/event_mail.py b/addons/event/models/event_mail.py index 2272f030c33..78e2c736942 100644 --- a/addons/event/models/event_mail.py +++ b/addons/event/models/event_mail.py @@ -242,7 +242,7 @@ You receive this email because you are: self.invalidate_cache() self._warn_template_error(scheduler, e) else: - if autocommit and not getattr(threading.currentThread(), 'testing', False): + if autocommit and not getattr(threading.current_thread(), 'testing', False): self.env.cr.commit() return True diff --git a/addons/hr/models/res_config_settings.py b/addons/hr/models/res_config_settings.py index 457188e9bb8..5308b036a14 100644 --- a/addons/hr/models/res_config_settings.py +++ b/addons/hr/models/res_config_settings.py @@ -23,7 +23,7 @@ class ResConfigSettings(models.TransientModel): @api.constrains('module_hr_presence', 'hr_presence_control_email', 'hr_presence_control_ip') def _check_advanced_presence(self): - test_mode = self.env.registry.in_test_mode() or getattr(threading.currentThread(), 'testing', False) + test_mode = self.env.registry.in_test_mode() or getattr(threading.current_thread(), 'testing', False) if self.env.context.get('install_mode', False) or test_mode: return diff --git a/addons/link_tracker/models/mail_render_mixin.py b/addons/link_tracker/models/mail_render_mixin.py index 2c7fa9c5e09..f7ff2d4548b 100644 --- a/addons/link_tracker/models/mail_render_mixin.py +++ b/addons/link_tracker/models/mail_render_mixin.py @@ -4,7 +4,8 @@ import re import markupsafe -from werkzeug import urls, utils +from html import unescape +from werkzeug import urls from odoo import api, models, tools @@ -45,7 +46,7 @@ class MailRenderMixin(models.AbstractModel): continue label = (match[3] or '').strip() - create_vals = dict(link_tracker_vals, url=utils.unescape(long_url), label=utils.unescape(label)) + create_vals = dict(link_tracker_vals, url=unescape(long_url), label=unescape(label)) link = self.env['link.tracker'].search_or_create(create_vals) if link.short_url: # `str` manipulation required to support replacing "&" characters, common in urls @@ -75,7 +76,7 @@ class MailRenderMixin(models.AbstractModel): if blacklist and any(item in parsed.path for item in blacklist): continue - create_vals = dict(link_tracker_vals, url=utils.unescape(original_url)) + create_vals = dict(link_tracker_vals, url=unescape(original_url)) link = self.env['link.tracker'].search_or_create(create_vals) if link.short_url: # Ensures we only replace the same link and not a subpart of a longer one, multiple times if applicable diff --git a/addons/mail/models/mail_mail.py b/addons/mail/models/mail_mail.py index 87defd3147b..9d01695ac35 100644 --- a/addons/mail/models/mail_mail.py +++ b/addons/mail/models/mail_mail.py @@ -197,7 +197,7 @@ class MailMail(models.Model): res = None try: # auto-commit except in testing mode - auto_commit = not getattr(threading.currentThread(), 'testing', False) + auto_commit = not getattr(threading.current_thread(), 'testing', False) res = self.browse(ids).send(auto_commit=auto_commit) except Exception: _logger.exception("Failed processing mail queue") diff --git a/addons/mail/models/mail_thread.py b/addons/mail/models/mail_thread.py index 44298548b4d..cd8a21fdd06 100644 --- a/addons/mail/models/mail_thread.py +++ b/addons/mail/models/mail_thread.py @@ -2480,7 +2480,7 @@ class MailThread(models.AbstractModel): # 2. do not send emails immediately if the registry is not loaded, # to prevent sending email during a simple update of the database # using the command-line. - test_mode = getattr(threading.currentThread(), 'testing', False) + test_mode = getattr(threading.current_thread(), 'testing', False) if force_send and len(emails) < recipients_max and (not self.pool._init or test_mode): # unless asked specifically, send emails after the transaction to # avoid side effects due to emails being sent while the transaction fails diff --git a/addons/mass_mailing/models/mailing.py b/addons/mass_mailing/models/mailing.py index f52a2c75581..5cad182f4a3 100644 --- a/addons/mass_mailing/models/mailing.py +++ b/addons/mass_mailing/models/mailing.py @@ -978,7 +978,7 @@ class MassMailing(models.Model): extra_context = mailing._get_mass_mailing_context() composer = composer.with_context(active_ids=res_ids, **extra_context) # auto-commit except in testing mode - auto_commit = not getattr(threading.currentThread(), 'testing', False) + auto_commit = not getattr(threading.current_thread(), 'testing', False) composer._action_send_mail(auto_commit=auto_commit) mailing.write({ 'state': 'done', diff --git a/addons/partner_autocomplete/models/res_company.py b/addons/partner_autocomplete/models/res_company.py index 86c6e0c9bf7..ecaec41dda5 100644 --- a/addons/partner_autocomplete/models/res_company.py +++ b/addons/partner_autocomplete/models/res_company.py @@ -27,7 +27,7 @@ class ResCompany(models.Model): @api.model_create_multi def create(self, vals_list): res = super().create(vals_list) - if not getattr(threading.currentThread(), 'testing', False): + if not getattr(threading.current_thread(), 'testing', False): res.iap_enrich_auto() return res diff --git a/addons/pos_mercury/models/pos_mercury_transaction.py b/addons/pos_mercury/models/pos_mercury_transaction.py index 7ce0811d714..bb1ca421c0b 100644 --- a/addons/pos_mercury/models/pos_mercury_transaction.py +++ b/addons/pos_mercury/models/pos_mercury_transaction.py @@ -4,7 +4,8 @@ from datetime import date, timedelta import requests -import werkzeug + +from html import unescape from odoo import models, api, service from odoo.tools.translate import _ @@ -69,7 +70,7 @@ class MercuryTransaction(models.Model): try: r = requests.post(url, data=xml_transaction, headers=headers, timeout=65) r.raise_for_status() - response = werkzeug.utils.unescape(r.content.decode()) + response = unescape(r.content.decode()) except Exception: response = "timeout" diff --git a/addons/sale_timesheet/models/product.py b/addons/sale_timesheet/models/product.py index ce0eff95383..3f9d5fc8c91 100644 --- a/addons/sale_timesheet/models/product.py +++ b/addons/sale_timesheet/models/product.py @@ -104,7 +104,7 @@ class ProductTemplate(models.Model): def write(self, vals): # timesheet product can't be archived - test_mode = getattr(threading.currentThread(), 'testing', False) or self.env.registry.in_test_mode() + test_mode = getattr(threading.current_thread(), 'testing', False) or self.env.registry.in_test_mode() if not test_mode and 'active' in vals and not vals['active']: time_product = self.env.ref('sale_timesheet.time_product') if time_product.product_tmpl_id in self: @@ -138,7 +138,7 @@ class ProductProduct(models.Model): def write(self, vals): # timesheet product can't be archived - test_mode = getattr(threading.currentThread(), 'testing', False) or self.env.registry.in_test_mode() + test_mode = getattr(threading.current_thread(), 'testing', False) or self.env.registry.in_test_mode() if not test_mode and 'active' in vals and not vals['active']: time_product = self.env.ref('sale_timesheet.time_product') if time_product in self: diff --git a/addons/sms/models/sms_sms.py b/addons/sms/models/sms_sms.py index 9dd9d0dbacd..ec2b58e0c37 100644 --- a/addons/sms/models/sms_sms.py +++ b/addons/sms/models/sms_sms.py @@ -96,7 +96,7 @@ class SmsSms(models.Model): for batch_ids in self._split_batch(): self.browse(batch_ids)._send(unlink_failed=unlink_failed, unlink_sent=unlink_sent, raise_exception=raise_exception) # auto-commit if asked except in testing mode - if auto_commit is True and not getattr(threading.currentThread(), 'testing', False): + if auto_commit is True and not getattr(threading.current_thread(), 'testing', False): self._cr.commit() def resend_failed(self): @@ -145,7 +145,7 @@ class SmsSms(models.Model): res = None try: # auto-commit except in testing mode - auto_commit = not getattr(threading.currentThread(), 'testing', False) + auto_commit = not getattr(threading.current_thread(), 'testing', False) res = self.browse(ids).send(unlink_failed=False, unlink_sent=True, auto_commit=auto_commit, raise_exception=False) except Exception: _logger.exception("Failed processing SMS queue") diff --git a/addons/stock_sms/models/stock_picking.py b/addons/stock_sms/models/stock_picking.py index 846af19271f..554a160fa56 100644 --- a/addons/stock_sms/models/stock_picking.py +++ b/addons/stock_sms/models/stock_picking.py @@ -23,7 +23,7 @@ class Picking(models.Model): is_delivery = picking.company_id.stock_move_sms_validation \ and picking.picking_type_id.code == 'outgoing' \ and (picking.partner_id.mobile or picking.partner_id.phone) - if is_delivery and not getattr(threading.currentThread(), 'testing', False) \ + if is_delivery and not getattr(threading.current_thread(), 'testing', False) \ and not self.env.registry.in_test_mode() \ and not picking.company_id.has_received_warning_stock_sms \ and picking.company_id.stock_move_sms_validation: @@ -52,7 +52,7 @@ class Picking(models.Model): def _send_confirmation_email(self): super(Picking, self)._send_confirmation_email() - if not self.env.context.get('skip_sms') and not getattr(threading.currentThread(), 'testing', False) and not self.env.registry.in_test_mode(): + if not self.env.context.get('skip_sms') and not getattr(threading.current_thread(), 'testing', False) and not self.env.registry.in_test_mode(): pickings = self.filtered(lambda p: p.company_id.stock_move_sms_validation and p.picking_type_id.code == 'outgoing' and (p.partner_id.mobile or p.partner_id.phone)) for picking in pickings: # Sudo as the user has not always the right to read this sms template. diff --git a/addons/test_crm_full/tests/test_performance.py b/addons/test_crm_full/tests/test_performance.py index 5d23d83864e..85527cd8f6c 100644 --- a/addons/test_crm_full/tests/test_performance.py +++ b/addons/test_crm_full/tests/test_performance.py @@ -42,7 +42,7 @@ class TestCrmPerformance(CrmPerformanceCase): country_be = self.env.ref('base.be') lang_be_id = self.env['res.lang']._lang_get_id('fr_BE') - with freeze_time(self.reference_now), self.assertQueryCount(user_sales_leads=243): # 235, sometimes +6 + with freeze_time(self.reference_now), self.assertQueryCount(user_sales_leads=244): # 235, sometimes +6 self.env.cr._now = self.reference_now # force create_date to check schedulers crm_values = [ {'country_id': country_be.id, diff --git a/addons/website/models/theme_models.py b/addons/website/models/theme_models.py index f03921c3641..21bd5011c20 100644 --- a/addons/website/models/theme_models.py +++ b/addons/website/models/theme_models.py @@ -346,7 +346,7 @@ class IrUiView(models.Model): # During a theme module update, theme views' copies receiving an arch # update should not be considered as `arch_updated`, as this is not a # user made change. - test_mode = getattr(threading.currentThread(), 'testing', False) + test_mode = getattr(threading.current_thread(), 'testing', False) if not (test_mode or self.pool._init): return super().write(vals) no_arch_updated_views = other_views = self.env['ir.ui.view'] diff --git a/odoo/__init__.py b/odoo/__init__.py index 8038761aecc..1b2bcf8fb8b 100644 --- a/odoo/__init__.py +++ b/odoo/__init__.py @@ -100,7 +100,7 @@ def registry(database_name=None): """ if database_name is None: import threading - database_name = threading.currentThread().dbname + database_name = threading.current_thread().dbname return modules.registry.Registry(database_name) #---------------------------------------------------------- diff --git a/odoo/addons/base/models/ir_actions_report.py b/odoo/addons/base/models/ir_actions_report.py index 529a774a3df..d826f332dde 100644 --- a/odoo/addons/base/models/ir_actions_report.py +++ b/odoo/addons/base/models/ir_actions_report.py @@ -6,7 +6,7 @@ from odoo import api, fields, models, tools, SUPERUSER_ID, _ from odoo.exceptions import UserError, AccessError from odoo.tools.safe_eval import safe_eval, time from odoo.tools.misc import find_in_path -from odoo.tools import config, is_html_empty +from odoo.tools import config, is_html_empty, parse_version from odoo.http import request from odoo.osv.expression import NEGATIVE_TERM_OPERATORS, FALSE_DOMAIN @@ -21,7 +21,6 @@ import json from lxml import etree from contextlib import closing -from distutils.version import LooseVersion from reportlab.graphics.barcode import createBarcodeDrawing from PyPDF2 import PdfFileWriter, PdfFileReader, utils from collections import OrderedDict @@ -69,12 +68,12 @@ else: match = re.search(b'([0-9.]+)', out) if match: version = match.group(0).decode('ascii') - if LooseVersion(version) < LooseVersion('0.12.0'): + if parse_version(version) < parse_version('0.12.0'): _logger.info('Upgrade Wkhtmltopdf to (at least) 0.12.0') wkhtmltopdf_state = 'upgrade' else: wkhtmltopdf_state = 'ok' - if LooseVersion(version) >= LooseVersion('0.12.2'): + if parse_version(version) >= parse_version('0.12.2'): wkhtmltopdf_dpi_zoom_ratio = True if config['workers'] == 1: diff --git a/odoo/addons/base/models/ir_mail_server.py b/odoo/addons/base/models/ir_mail_server.py index 5fe2af31701..897c5fb88d5 100644 --- a/odoo/addons/base/models/ir_mail_server.py +++ b/odoo/addons/base/models/ir_mail_server.py @@ -711,7 +711,7 @@ class IrMailServer(models.Model): Can be overridden in tests after mocking the SMTP lib to test in depth the outgoing mail server. """ - return getattr(threading.currentThread(), 'testing', False) or self.env.registry.in_test_mode() + return getattr(threading.current_thread(), 'testing', False) or self.env.registry.in_test_mode() def _neutralize(self): super()._neutralize() diff --git a/odoo/addons/base/models/ir_module.py b/odoo/addons/base/models/ir_module.py index 0e4a5e1cf75..8bdd01e9565 100644 --- a/odoo/addons/base/models/ir_module.py +++ b/odoo/addons/base/models/ir_module.py @@ -578,7 +578,7 @@ class Module(models.Model): } def _button_immediate_function(self, function): - if getattr(threading.currentThread(), 'testing', False): + if getattr(threading.current_thread(), 'testing', False): raise RuntimeError( "Module operations inside tests are not transactional and thus forbidden.\n" "If you really need to perform module operations to test a specific behavior, it " diff --git a/odoo/addons/base/tests/test_mail.py b/odoo/addons/base/tests/test_mail.py index a2defe8668a..5cd017f75ee 100644 --- a/odoo/addons/base/tests/test_mail.py +++ b/odoo/addons/base/tests/test_mail.py @@ -512,6 +512,6 @@ class TestEmailMessage(TransactionCase): msg['References'] = '<345227342212345.1596730777.324691772483620-example-30453-other.reference@test-123.example.com>' smtp = FakeSMTP() - self.patch(threading.currentThread(), 'testing', False) + self.patch(threading.current_thread(), 'testing', False) self.env['ir.mail_server'].send_email(msg, smtp_session=smtp) self.assertTrue(smtp.email_sent) diff --git a/odoo/addons/test_http/tests/test_http.py b/odoo/addons/test_http/tests/test_http.py index d916a6bc3ee..533c05d1cfc 100644 --- a/odoo/addons/test_http/tests/test_http.py +++ b/odoo/addons/test_http/tests/test_http.py @@ -1,5 +1,6 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. +import html import json from unittest.mock import patch from urllib.parse import urlparse @@ -380,7 +381,7 @@ class TestHttpModels(TestHttpBase): milky_way = self.env.ref('test_http.milky_way') res = self.url_open(f'/test_http/{milky_way.id}/9999') # unknown gate self.assertEqual(res.status_code, 400) - self.assertIn("The goa'uld destroyed the gate", res.text) + self.assertIn("The goa'uld destroyed the gate", html.unescape(res.text)) @tagged('post_install', '-at_install') @@ -510,6 +511,7 @@ class TestHttpEnsureDb(TestHttpBase): self.assertEqual(new_session.uid, None) # follow redirection + self.opener.cookies['session_id'] = new_session.sid res = self.multidb_url_open('/test_http/ensure_db') res.raise_for_status() self.assertEqual(res.status_code, 200) diff --git a/odoo/addons/test_lint/tests/test_pylint.py b/odoo/addons/test_lint/tests/test_pylint.py index a5b11305ee6..b41bf6578ae 100644 --- a/odoo/addons/test_lint/tests/test_pylint.py +++ b/odoo/addons/test_lint/tests/test_pylint.py @@ -7,7 +7,6 @@ try: except ImportError: pylint = None import subprocess -from distutils.version import LooseVersion import os from os.path import join import sys @@ -53,10 +52,10 @@ class TestPyLint(TransactionCase): def test_pylint(self): if pylint is None: self._skip_test('please install pylint') - required_pylint_version = LooseVersion('1.6.4') + required_pylint_version = tools.parse_version('1.6.4') if sys.version_info >= (3, 6): - required_pylint_version = LooseVersion('1.7.0') - if LooseVersion(getattr(pylint, '__version__', '0.0.1')) < required_pylint_version: + required_pylint_version = tools.parse_version('1.7.0') + if tools.parse_version(getattr(pylint, '__version__', '0.0.1')) < required_pylint_version: self._skip_test('please upgrade pylint to >= %s' % required_pylint_version) paths = [tools.config['root_path']] diff --git a/odoo/http.py b/odoo/http.py index 12e13cb8873..77132fda7bd 100644 --- a/odoo/http.py +++ b/odoo/http.py @@ -160,6 +160,7 @@ from .tools.geoipresolver import GeoIPResolver from .tools.func import filter_kwargs, lazy_property from .tools.mimetypes import guess_mimetype from .tools._vendor import sessions +from .tools._vendor.useragents import UserAgent _logger = logging.getLogger(__name__) @@ -888,7 +889,7 @@ class Response(werkzeug.wrappers.Response): _logger.warning("%s returns an HTTPException instead of raising it.", fname) raise result - if isinstance(result, werkzeug.wrappers.BaseResponse): + if isinstance(result, werkzeug.wrappers.Response): response = cls.force_type(result) response.set_default() return response @@ -1752,6 +1753,7 @@ class Application: return response(environ, start_response) httprequest = werkzeug.wrappers.Request(environ) + httprequest.user_agent_class = UserAgent # use vendored userAgent since it will be removed in 2.1 httprequest.parameter_storage_class = ( werkzeug.datastructures.ImmutableOrderedMultiDict) request = Request(httprequest) diff --git a/odoo/modules/loading.py b/odoo/modules/loading.py index 5d18e5347fc..3a9da98231e 100644 --- a/odoo/modules/loading.py +++ b/odoo/modules/loading.py @@ -62,7 +62,7 @@ def load_data(cr, idref, mode, kind, package): filename = None try: if kind in ('demo', 'test'): - threading.currentThread().testing = True + threading.current_thread().testing = True for filename in _get_files_of_kind(kind): _logger.info("loading %s/%s", package.name, filename) noupdate = False @@ -71,7 +71,7 @@ def load_data(cr, idref, mode, kind, package): tools.convert_file(cr, package.name, filename, idref, mode, noupdate, kind) finally: if kind in ('demo', 'test'): - threading.currentThread().testing = False + threading.current_thread().testing = False return bool(filename) diff --git a/odoo/netsvc.py b/odoo/netsvc.py index c9b4cd36818..dc54d336d70 100644 --- a/odoo/netsvc.py +++ b/odoo/netsvc.py @@ -128,7 +128,7 @@ def init_logger(): warnings.simplefilter('default', category=DeprecationWarning) # ignore deprecation warnings from invalid escape (there's a ton and it's # pretty likely a super low-value signal) - warnings.filterwarnings('ignore', r'^invalid escape sequence \\.', category=DeprecationWarning) + warnings.filterwarnings('ignore', r'^invalid escape sequence \'?\\.', category=DeprecationWarning) # recordsets are both sequence and set so trigger warning despite no issue warnings.filterwarnings('ignore', r'^Sampling from a set', category=DeprecationWarning, module='odoo') # ignore a bunch of warnings we can't really fix ourselves diff --git a/odoo/service/server.py b/odoo/service/server.py index b7e3fd6f1ba..e93875e4f28 100644 --- a/odoo/service/server.py +++ b/odoo/service/server.py @@ -123,7 +123,7 @@ class RequestHandler(werkzeug.serving.WSGIRequestHandler): self.timeout = 5 # flag the current thread as handling a http request super(RequestHandler, self).setup() - me = threading.currentThread() + me = threading.current_thread() me.name = 'odoo.service.http.request.%s' % (me.ident,) @@ -290,7 +290,7 @@ class FSWatcherInotify(FSWatcherBase): def start(self): self.started = True self.thread = threading.Thread(target=self.run, name="odoo.service.autoreload.watcher") - self.thread.setDaemon(True) + self.thread.daemon = True self.thread.start() def stop(self): @@ -350,7 +350,7 @@ class CommonServer(object): class ThreadedServer(CommonServer): def __init__(self, app): super(ThreadedServer, self).__init__(app) - self.main_thread_id = threading.currentThread().ident + self.main_thread_id = threading.current_thread().ident # Variable keeping track of the number of calls to the signal handler defined # below. This variable is monitored by ``quit_on_signals()``. self.quit_signals_received = 0 @@ -385,7 +385,7 @@ class ThreadedServer(CommonServer): memory = memory_info(psutil.Process(os.getpid())) if config['limit_memory_soft'] and memory > config['limit_memory_soft']: _logger.warning('Server memory limit (%s) reached.', memory) - self.limits_reached_threads.add(threading.currentThread()) + self.limits_reached_threads.add(threading.current_thread()) for thread in threading.enumerate(): if not thread.daemon or getattr(thread, 'type', None) == 'cron': @@ -448,7 +448,7 @@ class ThreadedServer(CommonServer): _logger.debug('cron%d polling for jobs', number) for db_name, registry in registries.d.items(): if registry.ready: - thread = threading.currentThread() + thread = threading.current_thread() thread.start_time = time.time() try: ir_cron._process_jobs(db_name) @@ -472,7 +472,7 @@ class ThreadedServer(CommonServer): def target(): self.cron_thread(i) t = threading.Thread(target=target, name="odoo.service.cron.cron%d" % i) - t.setDaemon(True) + t.daemon = True t.type = 'cron' t.start() _logger.debug("cron%d started!" % i) @@ -483,7 +483,7 @@ class ThreadedServer(CommonServer): def http_spawn(self): t = threading.Thread(target=self.http_thread, name="odoo.service.httpd") - t.setDaemon(True) + t.daemon = True t.start() def start(self, stop=False): @@ -525,11 +525,11 @@ class ThreadedServer(CommonServer): # Manually join() all threads before calling sys.exit() to allow a second signal # to trigger _force_quit() in case some non-daemon threads won't exit cleanly. # threading.Thread.join() should not mask signals (at least in python 2.5). - me = threading.currentThread() + me = threading.current_thread() _logger.debug('current thread: %r', me) for thread in threading.enumerate(): - _logger.debug('process %r (%r)', thread, thread.isDaemon()) - if (thread != me and not thread.isDaemon() and thread.ident != self.main_thread_id and + _logger.debug('process %r (%r)', thread, thread.daemon) + if (thread != me and not thread.daemon and thread.ident != self.main_thread_id and thread not in self.limits_reached_threads): while thread.is_alive() and (time.time() - stop_time) < 1: # We wait for requests to finish, up to 1 second. @@ -1217,7 +1217,7 @@ def _reexec(updated_modules=None): def load_test_file_py(registry, test_file): from odoo.tests.common import OdooSuite - threading.currentThread().testing = True + threading.current_thread().testing = True try: test_path, _ = os.path.splitext(os.path.abspath(test_file)) for mod in [m for m in get_modules() if '%s%s%s' % (os.path.sep, m, os.path.sep) in test_file]: @@ -1233,7 +1233,7 @@ def load_test_file_py(registry, test_file): _logger.error('%s: at least one error occurred in a test', test_file) return finally: - threading.currentThread().testing = False + threading.current_thread().testing = False def preload_registries(dbnames): """ Preload a registries, possibly run a test file.""" diff --git a/odoo/tests/loader.py b/odoo/tests/loader.py index 7c57be1d93e..9e9117c8833 100644 --- a/odoo/tests/loader.py +++ b/odoo/tests/loader.py @@ -72,12 +72,12 @@ def run_suite(suite, module_name=None): # avoid dependency hell from ..modules import module module.current_test = module_name - threading.currentThread().testing = True + threading.current_thread().testing = True results = OdooTestResult() suite(results) - threading.currentThread().testing = False + threading.current_thread().testing = False module.current_test = None return results diff --git a/odoo/tools/_vendor/sessions.py b/odoo/tools/_vendor/sessions.py index e64a8865e21..5bdbc818e99 100644 --- a/odoo/tools/_vendor/sessions.py +++ b/odoo/tools/_vendor/sessions.py @@ -20,14 +20,13 @@ import os import re import tempfile from hashlib import sha1 -from os import path +from os import path, replace as rename from pickle import dump from pickle import HIGHEST_PROTOCOL from pickle import load from time import time from werkzeug.datastructures import CallbackDict -from werkzeug.posixemulation import rename _logger = logging.getLogger(__name__) _sha1_re = re.compile(r"^[a-f0-9]{40}$") diff --git a/odoo/tools/_vendor/useragents.py b/odoo/tools/_vendor/useragents.py new file mode 100644 index 00000000000..7627ccc973b --- /dev/null +++ b/odoo/tools/_vendor/useragents.py @@ -0,0 +1,204 @@ +# -*- coding: utf-8 -*- +""" + werkzeug.useragents + ~~~~~~~~~~~~~~~~~~~ + + This module provides a helper to inspect user agent strings. This module + is far from complete but should work for most of the currently available + browsers. + + + :copyright: 2007 Pallets + :license: BSD-3-Clause + + This package was vendored in odoo in order to prevent errors with werkzeug 2.1 +""" +import re + + +class UserAgentParser(object): + """A simple user agent parser. Used by the `UserAgent`.""" + + platforms = ( + ("cros", "chromeos"), + ("iphone|ios", "iphone"), + ("ipad", "ipad"), + (r"darwin|mac|os\s*x", "macos"), + ("win", "windows"), + (r"android", "android"), + ("netbsd", "netbsd"), + ("openbsd", "openbsd"), + ("freebsd", "freebsd"), + ("dragonfly", "dragonflybsd"), + ("(sun|i86)os", "solaris"), + (r"x11|lin(\b|ux)?", "linux"), + (r"nintendo\s+wii", "wii"), + ("irix", "irix"), + ("hp-?ux", "hpux"), + ("aix", "aix"), + ("sco|unix_sv", "sco"), + ("bsd", "bsd"), + ("amiga", "amiga"), + ("blackberry|playbook", "blackberry"), + ("symbian", "symbian"), + ) + browsers = ( + ("googlebot", "google"), + ("msnbot", "msn"), + ("yahoo", "yahoo"), + ("ask jeeves", "ask"), + (r"aol|america\s+online\s+browser", "aol"), + ("opera", "opera"), + ("edge", "edge"), + ("chrome|crios", "chrome"), + ("seamonkey", "seamonkey"), + ("firefox|firebird|phoenix|iceweasel", "firefox"), + ("galeon", "galeon"), + ("safari|version", "safari"), + ("webkit", "webkit"), + ("camino", "camino"), + ("konqueror", "konqueror"), + ("k-meleon", "kmeleon"), + ("netscape", "netscape"), + (r"msie|microsoft\s+internet\s+explorer|trident/.+? rv:", "msie"), + ("lynx", "lynx"), + ("links", "links"), + ("Baiduspider", "baidu"), + ("bingbot", "bing"), + ("mozilla", "mozilla"), + ) + + _browser_version_re = r"(?:%s)[/\sa-z(]*(\d+[.\da-z]+)?" + _language_re = re.compile( + r"(?:;\s*|\s+)(\b\w{2}\b(?:-\b\w{2}\b)?)\s*;|" + r"(?:\(|\[|;)\s*(\b\w{2}\b(?:-\b\w{2}\b)?)\s*(?:\]|\)|;)" + ) + + def __init__(self): + self.platforms = [(b, re.compile(a, re.I)) for a, b in self.platforms] + self.browsers = [ + (b, re.compile(self._browser_version_re % a, re.I)) + for a, b in self.browsers + ] + + def __call__(self, user_agent): + for platform, regex in self.platforms: # noqa: B007 + match = regex.search(user_agent) + if match is not None: + break + else: + platform = None + for browser, regex in self.browsers: # noqa: B007 + match = regex.search(user_agent) + if match is not None: + version = match.group(1) + break + else: + browser = version = None + match = self._language_re.search(user_agent) + if match is not None: + language = match.group(1) or match.group(2) + else: + language = None + return platform, browser, version, language + + +class UserAgent(object): + """Represents a user agent. Pass it a WSGI environment or a user agent + string and you can inspect some of the details from the user agent + string via the attributes. The following attributes exist: + + .. attribute:: string + + the raw user agent string + + .. attribute:: platform + + the browser platform. The following platforms are currently + recognized: + + - `aix` + - `amiga` + - `android` + - `blackberry` + - `bsd` + - `chromeos` + - `dragonflybsd` + - `freebsd` + - `hpux` + - `ipad` + - `iphone` + - `irix` + - `linux` + - `macos` + - `netbsd` + - `openbsd` + - `sco` + - `solaris` + - `symbian` + - `wii` + - `windows` + + .. attribute:: browser + + the name of the browser. The following browsers are currently + recognized: + + - `aol` * + - `ask` * + - `baidu` * + - `bing` * + - `camino` + - `chrome` + - `edge` + - `firefox` + - `galeon` + - `google` * + - `kmeleon` + - `konqueror` + - `links` + - `lynx` + - `mozilla` + - `msie` + - `msn` + - `netscape` + - `opera` + - `safari` + - `seamonkey` + - `webkit` + - `yahoo` * + + (Browsers marked with a star (``*``) are crawlers.) + + .. attribute:: version + + the version of the browser + + .. attribute:: language + + the language of the browser + """ + + _parser = UserAgentParser() + + def __init__(self, environ_or_string): + if isinstance(environ_or_string, dict): + environ_or_string = environ_or_string.get("HTTP_USER_AGENT", "") + self.string = environ_or_string + self.platform, self.browser, self.version, self.language = self._parser( + environ_or_string + ) + + def to_header(self): + return self.string + + def __str__(self): + return self.string + + def __nonzero__(self): + return bool(self.browser) + + __bool__ = __nonzero__ + + def __repr__(self): + return "<%s %r/%s>" % (self.__class__.__name__, self.browser, self.version) diff --git a/odoo/tools/cache.py b/odoo/tools/cache.py index dd37b0cf60f..662cd0d0653 100644 --- a/odoo/tools/cache.py +++ b/odoo/tools/cache.py @@ -201,7 +201,7 @@ def log_ormcache_stats(sig=None, frame=None): from odoo.modules.registry import Registry import threading - me = threading.currentThread() + me = threading.current_thread() me_dbname = getattr(me, 'dbname', 'n/a') for dbname, reg in sorted(Registry.registries.d.items()): diff --git a/odoo/tools/populate.py b/odoo/tools/populate.py index 44e654fa0ee..7986a4827dc 100644 --- a/odoo/tools/populate.py +++ b/odoo/tools/populate.py @@ -177,5 +177,5 @@ def randdatetime(*, base_date=None, relative_before=None, relative_after=None, s seconds_after = relative_after and ((base_date + relative_after) - base_date).total_seconds() or 0 def get_rand_datetime(random=None, **kwargs): - return base_date + relativedelta(seconds=random.randint(seconds_before, seconds_after)) + return base_date + relativedelta(seconds=random.randint(int(seconds_before), int(seconds_after))) return compute(get_rand_datetime, seed=seed) diff --git a/odoo/tools/safe_eval.py b/odoo/tools/safe_eval.py index 59f3617d359..02c0b31423b 100644 --- a/odoo/tools/safe_eval.py +++ b/odoo/tools/safe_eval.py @@ -86,6 +86,8 @@ _EXPR_OPCODES = _CONST_OPCODES.union(to_opcodes([ # specialised comparisons 'IS_OP', 'CONTAINS_OP', 'DICT_MERGE', 'DICT_UPDATE', + # Basically used in any "generator literal" + 'GEN_START', # added in 3.10 but already removed from 3.11. ])) - _BLACKLIST _SAFE_OPCODES = _EXPR_OPCODES.union(to_opcodes([ diff --git a/odoo/tools/translate.py b/odoo/tools/translate.py index ca11767dc62..fa3f8d5bd62 100644 --- a/odoo/tools/translate.py +++ b/odoo/tools/translate.py @@ -361,7 +361,7 @@ class GettextAlias(object): def _get_db(self): # find current DB based on thread/worker db name (see netsvc) - db_name = getattr(threading.currentThread(), 'dbname', None) + db_name = getattr(threading.current_thread(), 'dbname', None) if db_name: return odoo.sql_db.db_connect(db_name) diff --git a/requirements.txt b/requirements.txt index 46bd76127c7..29b0285c151 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,16 +6,19 @@ ebaysdk==2.1.5 freezegun==0.3.11; python_version < '3.8' freezegun==0.3.15; python_version >= '3.8' gevent==1.5.0 ; python_version == '3.7' -gevent==20.9.0 ; python_version >= '3.8' +gevent==20.9.0 ; python_version > '3.7' and python_version <= '3.9' +gevent==21.8.0 ; python_version > '3.9' # (Jammy) greenlet==0.4.15 ; python_version == '3.7' -greenlet==0.4.17 ; python_version > '3.7' +greenlet==0.4.17 ; python_version > '3.7' and python_version <= '3.9' +greenlet==1.1.2 ; python_version > '3.9' # (Jammy) idna==2.8 Jinja2==2.11.3 # min version = 2.10.1 (Focal - with security backports) libsass==0.18.0 lxml==4.6.5 # min version = 4.5.0 (Focal - with security backports) MarkupSafe==1.1.0 num2words==0.5.6 -ofxparse==0.19 +ofxparse==0.19; python_version <= '3.9' +ofxparse==0.21; python_version > '3.9' # (Jammy) passlib==1.7.3 # min version = 1.7.2 (Focal with security backports) Pillow==9.0.1 # min version = 7.0.0 (Focal with security backports) polib==1.1.0 @@ -37,7 +40,8 @@ reportlab==3.5.59 # version < 3.5.54 are not compatible with Pillow 8.1.2 and 3. requests==2.25.1 # versions < 2.25 aren't compatible w/ urllib3 1.26. Bullseye = 2.25.1. min version = 2.22.0 (Focal) urllib3==1.26.5 # indirect / min version = 1.25.8 (Focal with security backports) vobject==0.9.6.1 -Werkzeug==0.16.1 +Werkzeug==0.16.1 ; python_version <= '3.9' +Werkzeug==2.0.2 ; python_version > '3.9' # (Jammy) xlrd==1.1.0; python_version < '3.8' xlrd==1.2.0; python_version >= '3.8' XlsxWriter==1.1.2