diff --git a/queue_job/__manifest__.py b/queue_job/__manifest__.py index 79594a2d9f..856bb3fd3d 100644 --- a/queue_job/__manifest__.py +++ b/queue_job/__manifest__.py @@ -3,7 +3,7 @@ { "name": "Job Queue", - "version": "14.0.1.3.1", + "version": "15.0.1.0.0", "author": "Camptocamp,ACSONE SA/NV,Odoo Community Association (OCA)", "website": "https://github.com/OCA/queue", "license": "LGPL-3", @@ -22,7 +22,7 @@ "data/queue_data.xml", "data/queue_job_function_data.xml", ], - "installable": False, + "installable": True, "development_status": "Mature", "maintainers": ["guewen"], "post_init_hook": "post_init_hook", diff --git a/queue_job/controllers/main.py b/queue_job/controllers/main.py index 8078285ff3..a68897e7f4 100644 --- a/queue_job/controllers/main.py +++ b/queue_job/controllers/main.py @@ -9,8 +9,7 @@ from psycopg2 import OperationalError from werkzeug.exceptions import Forbidden -import odoo -from odoo import _, http, tools +from odoo import SUPERUSER_ID, _, api, http, registry, tools from odoo.service.model import PG_CONCURRENCY_ERRORS_TO_RETRY from ..exception import FailedJobError, NothingToDoJob, RetryableJobError @@ -39,17 +38,15 @@ def _try_perform_job(self, env, job): @http.route("/queue_job/runjob", type="http", auth="none", save_session=False) def runjob(self, db, job_uuid, **kw): http.request.session.db = db - env = http.request.env(user=odoo.SUPERUSER_ID) + env = http.request.env(user=SUPERUSER_ID) def retry_postpone(job, message, seconds=None): job.env.clear() - with odoo.api.Environment.manage(): - with odoo.registry(job.env.cr.dbname).cursor() as new_cr: - job.env = job.env(cr=new_cr) - job.postpone(result=message, seconds=seconds) - job.set_pending(reset_retry=False) - job.store() - new_cr.commit() + with registry(job.env.cr.dbname).cursor() as new_cr: + job.env = api.Environment(new_cr, SUPERUSER_ID, {}) + job.postpone(result=message, seconds=seconds) + job.set_pending(reset_retry=False) + job.store() # ensure the job to run is in the correct state and lock the record env.cr.execute( @@ -101,12 +98,10 @@ def retry_postpone(job, message, seconds=None): traceback.print_exc(file=buff) _logger.error(buff.getvalue()) job.env.clear() - with odoo.api.Environment.manage(): - with odoo.registry(job.env.cr.dbname).cursor() as new_cr: - job.env = job.env(cr=new_cr) - job.set_failed(exc_info=buff.getvalue()) - job.store() - new_cr.commit() + with registry(job.env.cr.dbname).cursor() as new_cr: + job.env = api.Environment(new_cr, SUPERUSER_ID, {}) + job.set_failed(exc_info=buff.getvalue()) + job.store() raise return "" diff --git a/queue_job/fields.py b/queue_job/fields.py index 50183993d8..9075900665 100644 --- a/queue_job/fields.py +++ b/queue_job/fields.py @@ -40,7 +40,7 @@ class JobSerialized(fields.Field): def __init__(self, string=fields.Default, base_type=fields.Default, **kwargs): super().__init__(string=string, _base_type=base_type, **kwargs) - def _setup_attrs(self, model, name): + def _setup_attrs(self, model, name): # pylint: disable=W8110 super()._setup_attrs(model, name) if self._base_type not in self._default_json_mapping: raise ValueError("%s is not a supported base type" % (self._base_type)) diff --git a/queue_job/job.py b/queue_job/job.py index 51136052db..4ee90390e6 100644 --- a/queue_job/job.py +++ b/queue_job/job.py @@ -618,7 +618,7 @@ def description(self): @property def uuid(self): - """Job ID, this is an UUID """ + """Job ID, this is an UUID""" if self._uuid is None: self._uuid = str(uuid.uuid4()) return self._uuid diff --git a/queue_job/jobrunner/__init__.py b/queue_job/jobrunner/__init__.py index 7ad479ff33..c42fa04e45 100644 --- a/queue_job/jobrunner/__init__.py +++ b/queue_job/jobrunner/__init__.py @@ -48,7 +48,7 @@ def stop(self): class WorkerJobRunner(server.Worker): - """ Jobrunner workers """ + """Jobrunner workers""" def __init__(self, multi): super().__init__(multi) @@ -58,7 +58,7 @@ def __init__(self, multi): def sleep(self): pass - def signal_handler(self, sig, frame): + def signal_handler(self, sig, frame): # pylint: disable=W8110 _logger.debug("WorkerJobRunner (%s) received signal %s", self.pid, sig) super().signal_handler(sig, frame) self.runner.stop() diff --git a/queue_job/jobrunner/channels.py b/queue_job/jobrunner/channels.py index 1f42a10cc9..536fec5f68 100644 --- a/queue_job/jobrunner/channels.py +++ b/queue_job/jobrunner/channels.py @@ -504,7 +504,7 @@ def set_running(self, job): _logger.debug("job %s marked running in channel %s", job.uuid, self) def set_failed(self, job): - """Mark the job as failed. """ + """Mark the job as failed.""" if job not in self._failed: self._queue.remove(job) self._running.remove(job) @@ -873,11 +873,11 @@ def parse_simple_config(cls, config_string): capacity = config_items[1] try: config["capacity"] = int(capacity) - except Exception: + except Exception as ex: raise ValueError( "Invalid channel config %s: " "invalid capacity %s" % (config_string, capacity) - ) + ) from ex for config_item in config_items[2:]: kv = split_strip(config_item, "=") if len(kv) == 1: diff --git a/queue_job/models/queue_job_channel.py b/queue_job/models/queue_job_channel.py index 374e7417f7..920b021261 100644 --- a/queue_job/models/queue_job_channel.py +++ b/queue_job/models/queue_job_channel.py @@ -11,7 +11,7 @@ class QueueJobChannel(models.Model): name = fields.Char() complete_name = fields.Char( - compute="_compute_complete_name", store=True, readonly=True + compute="_compute_complete_name", store=True, readonly=True, recursive=True ) parent_id = fields.Many2one( comodel_name="queue.job.channel", string="Parent Channel", ondelete="restrict" diff --git a/queue_job/models/queue_job_function.py b/queue_job/models/queue_job_function.py index a80f4920d3..3d984f65fd 100644 --- a/queue_job/models/queue_job_function.py +++ b/queue_job/models/queue_job_function.py @@ -6,7 +6,7 @@ import re from collections import namedtuple -from odoo import _, api, exceptions, fields, models, tools +from odoo import SUPERUSER_ID, _, api, exceptions, fields, models, tools from ..fields import JobSerialized @@ -93,7 +93,11 @@ def _inverse_name(self): raise exceptions.UserError(_("Invalid job function: {}").format(self.name)) model_name = groups[1] method = groups[2] - model = self.env["ir.model"].search([("model", "=", model_name)], limit=1) + model = ( + self.env["ir.model"] + .with_user(SUPERUSER_ID) + .search([("model", "=", model_name)], limit=1) + ) if not model: raise exceptions.UserError(_("Model {} not found").format(model_name)) self.model_id = model.id @@ -112,8 +116,10 @@ def _inverse_edit_retry_pattern(self): self.retry_pattern = ast.literal_eval(edited) else: self.retry_pattern = {} - except (ValueError, TypeError, SyntaxError): - raise exceptions.UserError(self._retry_pattern_format_error_message()) + except (ValueError, TypeError, SyntaxError) as ex: + raise exceptions.UserError( + self._retry_pattern_format_error_message() + ) from ex @api.depends("related_action") def _compute_edit_related_action(self): @@ -127,8 +133,10 @@ def _inverse_edit_related_action(self): self.related_action = ast.literal_eval(edited) else: self.related_action = {} - except (ValueError, TypeError, SyntaxError): - raise exceptions.UserError(self._related_action_format_error_message()) + except (ValueError, TypeError, SyntaxError) as ex: + raise exceptions.UserError( + self._related_action_format_error_message() + ) from ex @staticmethod def job_function_name(model_name, method_name): @@ -193,10 +201,10 @@ def _check_retry_pattern(self): for value in all_values: try: int(value) - except ValueError: + except ValueError as ex: raise exceptions.UserError( record._retry_pattern_format_error_message() - ) + ) from ex def _related_action_format_error_message(self): return _( diff --git a/queue_job/tests/common.py b/queue_job/tests/common.py index e1e877b0a2..dcb948e764 100644 --- a/queue_job/tests/common.py +++ b/queue_job/tests/common.py @@ -108,7 +108,9 @@ class OdooDocTestCase(doctest.DocTestCase): - output a more meaningful test name than default "DocTestCase.runTest" """ - def __init__(self, doctest, optionflags=0, setUp=None, tearDown=None, checker=None): + def __init__( + self, doctest, optionflags=0, setUp=None, tearDown=None, checker=None, seq=0 + ): super().__init__( doctest._dt_test, optionflags=optionflags, @@ -116,6 +118,7 @@ def __init__(self, doctest, optionflags=0, setUp=None, tearDown=None, checker=No tearDown=tearDown, checker=checker, ) + self.test_sequence = seq def setUp(self): """Log an extra statement which test is started.""" @@ -139,8 +142,8 @@ def load_tests(loader, tests, ignore): doctest.DocTestCase.doClassCleanups = lambda: None doctest.DocTestCase.tearDown_exceptions = [] - for test in doctest.DocTestSuite(module): - odoo_test = OdooDocTestCase(test) + for idx, test in enumerate(doctest.DocTestSuite(module)): + odoo_test = OdooDocTestCase(test, seq=idx) odoo_test.test_tags = {"standard", "at_install", "queue_job", "doctest"} tests.addTest(odoo_test) diff --git a/queue_job/tests/test_model_job_channel.py b/queue_job/tests/test_model_job_channel.py index 3fdc7b4c74..bf04ec17aa 100644 --- a/queue_job/tests/test_model_job_channel.py +++ b/queue_job/tests/test_model_job_channel.py @@ -37,11 +37,20 @@ def test_channel_complete_name_uniq(self): self.assertEqual(channel.complete_name, "root.sub") self.Channel.create({"name": "sub", "parent_id": self.root_channel.id}) - with self.assertRaises(IntegrityError): - # Flush process all the pending recomputations (or at least the - # given field and flush the pending updates to the database. - # It is normally called on commit. + + # Flush process all the pending recomputations (or at least the + # given field and flush the pending updates to the database. + # It is normally called on commit. + + # The context manager 'with self.assertRaises(IntegrityError)' purposefully + # not uses here due to its 'flush()' method inside it and exception raises + # before the line 'self.env["base"].flush()'. So, we are expecting an IntegrityError. + try: self.env["base"].flush() + except IntegrityError as ex: + self.assertIn("queue_job_channel_name_uniq", ex.pgerror) + else: + self.assertEqual(True, False) def test_channel_name_get(self): channel = self.Channel.create( diff --git a/queue_job/tests/test_model_job_function.py b/queue_job/tests/test_model_job_function.py index c9bdea56e8..965e26d8f2 100644 --- a/queue_job/tests/test_model_job_function.py +++ b/queue_job/tests/test_model_job_function.py @@ -5,7 +5,7 @@ from odoo.tests import common -class TestJobFunction(common.SavepointCase): +class TestJobFunction(common.TransactionCase): def test_function_name_compute(self): function = self.env["queue.job.function"].create( {"model_id": self.env.ref("base.model_res_users").id, "method": "read"} diff --git a/queue_job/tests/test_queue_job_protected_write.py b/queue_job/tests/test_queue_job_protected_write.py index cf8380bcec..018b3f23f4 100644 --- a/queue_job/tests/test_queue_job_protected_write.py +++ b/queue_job/tests/test_queue_job_protected_write.py @@ -5,7 +5,7 @@ from odoo.tests import common -class TestJobWriteProtected(common.SavepointCase): +class TestJobWriteProtected(common.TransactionCase): def test_create_error(self): with self.assertRaises(exceptions.AccessError): self.env["queue.job"].create( diff --git a/test_queue_job/__manifest__.py b/test_queue_job/__manifest__.py index 8f5b31c0fe..41c52cf217 100644 --- a/test_queue_job/__manifest__.py +++ b/test_queue_job/__manifest__.py @@ -14,5 +14,5 @@ "data/queue_job_function_data.xml", "security/ir.model.access.csv", ], - "installable": False, + "installable": True, }