Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
2a5ccb8
[19.0] queue_job: migrate + tests
Oct 1, 2025
ce67593
[pre-commit] update excluded addons (queue_job/test_queue_job now ins…
Oct 1, 2025
b283cd8
[pre-commit] apply hook updates + ruff fixes\n\n- Commit .pre-commit-…
Oct 1, 2025
df50b83
[lint] ruff fixes (E402, UP031, E501)
Oct 1, 2025
b7129d7
[pre-commit] ruff-format applied
Oct 1, 2025
ac812d7
[lint] satisfy pylint-odoo mandatory checks\n\n- Use http.request.env…
Oct 1, 2025
a7d4364
[pre-commit] ruff and format auto-fixes
Oct 1, 2025
4da9fae
[lint] tests: use odoo.tools.groupby instead of itertools.groupby
Oct 1, 2025
f494bcc
[pre-commit] reordered imports (ruff)
Oct 1, 2025
3b733f1
[lint] lazy translations + test groupby fix
Oct 1, 2025
ee67632
[fix] channel: enforce root deletion guard in ondelete hook
Oct 1, 2025
d827d58
style(queue): convert safe string formatting to f-strings and extract…
Oct 4, 2025
9aeebee
tests(doctest): reset optionflags to 0; remove no-op unlink override …
Oct 4, 2025
9ac6b25
style(queue): use PEP 604 unions in isinstance checks (Python 3.10+) …
Oct 4, 2025
99cc44f
style(queue): extract i18n raise messages into variables for consistency
Oct 4, 2025
211e467
autovacuum: restore original channel iteration form; keep linter sile…
Oct 4, 2025
620413a
tests(common): restore search([]) for readability and silence linter …
Oct 4, 2025
82d62a1
chore(queue): remove unnecessary migration comments in security and c…
Oct 4, 2025
81a1488
search: use dynamic datetime macros for date_created filters (now -1d…
Oct 4, 2025
6d330f2
security: set 'Job Queue' privilege sequence to 50 (under Settings › …
Oct 4, 2025
f685d12
pre-commit: all hooks pass locally
Oct 4, 2025
d27f83d
chore(queue): remove Odoo 19 migration comments
Oct 11, 2025
8dd4af0
tests(common): improve _format_job_call readability by using intermed…
Oct 11, 2025
231705d
jobrunner: read [queue_job] from odoo.conf when server_environment mi…
Oct 11, 2025
9ea78c5
models(queue_job): drop index_exists guards around create_index
Oct 11, 2025
038c334
models(base), tests(test_queue_job): remove _patch_method; patch in _…
Oct 11, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,10 @@ exclude: |
(?x)
# NOT INSTALLABLE ADDONS
^base_import_async/|
^queue_job/|
^queue_job_batch/|
^queue_job_cron/|
^queue_job_cron_jobrunner/|
^queue_job_subscribe/|
^test_queue_job/|
^test_queue_job_batch/|
# END NOT INSTALLABLE ADDONS
# Files and folders generated by bots, to avoid loops
Expand Down
10 changes: 5 additions & 5 deletions queue_job/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ Job Queue
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
:alt: License: LGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fqueue-lightgray.png?logo=github
:target: https://github.com/OCA/queue/tree/18.0/queue_job
:target: https://github.com/OCA/queue/tree/19.0/queue_job
:alt: OCA/queue
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/queue-18-0/queue-18-0-queue_job
:target: https://translation.odoo-community.org/projects/queue-19-0/queue-19-0-queue_job
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
:target: https://runboat.odoo-community.org/builds?repo=OCA/queue&target_branch=18.0
:target: https://runboat.odoo-community.org/builds?repo=OCA/queue&target_branch=19.0
:alt: Try me on Runboat

|badge1| |badge2| |badge3| |badge4| |badge5|
Expand Down Expand Up @@ -661,7 +661,7 @@ Bug Tracker
Bugs are tracked on `GitHub Issues <https://github.com/OCA/queue/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
`feedback <https://github.com/OCA/queue/issues/new?body=module:%20queue_job%0Aversion:%2018.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
`feedback <https://github.com/OCA/queue/issues/new?body=module:%20queue_job%0Aversion:%2019.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.

Do not contact contributors directly about support or help with technical issues.

Expand Down Expand Up @@ -720,6 +720,6 @@ Current `maintainer <https://odoo-community.org/page/maintainer-role>`__:

|maintainer-guewen|

This module is part of the `OCA/queue <https://github.com/OCA/queue/tree/18.0/queue_job>`_ project on GitHub.
This module is part of the `OCA/queue <https://github.com/OCA/queue/tree/19.0/queue_job>`_ project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
6 changes: 3 additions & 3 deletions queue_job/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

{
"name": "Job Queue",
"version": "18.0.2.0.2",
"version": "19.0.1.0.0",
"author": "Camptocamp,ACSONE SA/NV,Odoo Community Association (OCA)",
"website": "https://github.com/OCA/queue",
"license": "LGPL-3",
Expand All @@ -24,10 +24,10 @@
],
"assets": {
"web.assets_backend": [
"/queue_job/static/src/views/**/*",
"queue_job/static/src/views/**/*",
],
},
"installable": False,
"installable": True,
"development_status": "Mature",
"maintainers": ["guewen"],
"post_init_hook": "post_init_hook",
Expand Down
6 changes: 3 additions & 3 deletions queue_job/controllers/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from psycopg2 import OperationalError, errorcodes
from werkzeug.exceptions import BadRequest, Forbidden

from odoo import SUPERUSER_ID, _, api, http
from odoo import SUPERUSER_ID, api, http
from odoo.modules.registry import Registry
from odoo.service.model import PG_CONCURRENCY_ERRORS_TO_RETRY

Expand Down Expand Up @@ -179,7 +179,7 @@ def create_test_job(
failure_rate=0,
):
if not http.request.env.user.has_group("base.group_erp_manager"):
raise Forbidden(_("Access Denied"))
raise Forbidden(http.request.env._("Access Denied"))

if failure_rate is not None:
try:
Expand Down Expand Up @@ -280,7 +280,7 @@ def _create_graph_test_jobs(
priority=priority,
max_retries=max_retries,
channel=channel,
description="%s #%d" % (description, current_count),
description=f"{description} #{current_count}",
)._test_job(failure_rate=failure_rate)
)

Expand Down
3 changes: 2 additions & 1 deletion queue_job/delay.py
Original file line number Diff line number Diff line change
Expand Up @@ -496,7 +496,8 @@ def __del__(self):
def _set_from_dict(self, properties):
for key, value in properties.items():
if key not in self._properties:
raise ValueError(f"No property {key}")
msg = f"No property {key}"
raise ValueError(msg)
setattr(self, key, value)

def set(self, *args, **kwargs):
Expand Down
6 changes: 4 additions & 2 deletions queue_job/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

from odoo import fields, models
from odoo.tools.func import lazy
from odoo.tools.misc import SENTINEL


class JobSerialized(fields.Json):
Expand Down Expand Up @@ -38,13 +39,14 @@ class JobSerialized(fields.Json):
),
}

def __init__(self, string=fields.SENTINEL, base_type=fields.SENTINEL, **kwargs):
def __init__(self, string=SENTINEL, base_type=SENTINEL, **kwargs):
super().__init__(string=string, _base_type=base_type, **kwargs)

def _setup_attrs(self, model, name): # pylint: disable=missing-return
super()._setup_attrs(model, name)
if self._base_type not in self._default_json_mapping:
raise ValueError(f"{self._base_type} is not a supported base type")
msg = f"{self._base_type} is not a supported base type"
raise ValueError(msg)

def _base_type_default_json(self, env):
default_json = self._default_json_mapping.get(self._base_type)
Expand Down
12 changes: 6 additions & 6 deletions queue_job/job.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,8 @@ def load(cls, env, job_uuid):
"""
stored = cls.db_records_from_uuids(env, [job_uuid])
if not stored:
raise NoSuchJobError(f"Job {job_uuid} does no longer exist in the storage.")
msg = f"Job {job_uuid} does no longer exist in the storage."
raise NoSuchJobError(msg)
return cls._load_from_db_record(stored)

@classmethod
Expand Down Expand Up @@ -505,7 +506,7 @@ def perform(self):
# traceback and message:
# http://blog.ianbicking.org/2007/09/12/re-raising-exceptions/
new_exc = FailedJobError(
"Max. retries (%d) reached: %s" % (self.max_retries, value or type_)
f"Max. retries ({self.max_retries}) reached: {value or type_}"
)
raise new_exc from err
raise
Expand Down Expand Up @@ -813,7 +814,7 @@ def set_failed(self, **kw):
setattr(self, k, v)

def __repr__(self):
return "<Job %s, priority:%d>" % (self.uuid, self.priority)
return f"<Job {self.uuid}, priority:{self.priority}>"

def _get_retry_seconds(self, seconds=None):
retry_pattern = self.job_config.retry_pattern
Expand All @@ -828,7 +829,7 @@ def _get_retry_seconds(self, seconds=None):
break
elif not seconds:
seconds = RETRY_INTERVAL
if isinstance(seconds, (list | tuple)):
if isinstance(seconds, list | tuple):
seconds = randint(seconds[0], seconds[1])
return seconds

Expand Down Expand Up @@ -856,8 +857,7 @@ def related_action(self):
funcname = record._default_related_action
if not isinstance(funcname, str):
raise ValueError(
"related_action must be the name of the "
"method on queue.job as string"
"related_action must be the name of the method on queue.job as string"
)
action = getattr(record, funcname)
action_kwargs = self.job_config.related_action_kwargs
Expand Down
14 changes: 12 additions & 2 deletions queue_job/jobrunner/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,32 @@
import logging
from threading import Thread
import time
from configparser import ConfigParser

from odoo.service import server
from odoo.tools import config

try:
# Preferred source when available: structured [queue_job] section provided
# by OCA's server_environment addon.
from odoo.addons.server_environment import serv_config

if serv_config.has_section("queue_job"):
queue_job_config = serv_config["queue_job"]
else:
queue_job_config = {}
except ImportError:
queue_job_config = config.misc.get("queue_job", {})
# No server_environment: try to read a [queue_job] section from odoo.conf
queue_job_config = {}
cfg_path = config.get("config")
if cfg_path:
cp = ConfigParser(interpolation=None)
cp.read(cfg_path)
if cp.has_section("queue_job"):
queue_job_config = dict(cp["queue_job"])


from .runner import QueueJobRunner, _channels
from .runner import QueueJobRunner, _channels # noqa: E402

_logger = logging.getLogger(__name__)

Expand Down
15 changes: 6 additions & 9 deletions queue_job/jobrunner/channels.py
Original file line number Diff line number Diff line change
Expand Up @@ -455,12 +455,9 @@ def get_subchannel_by_name(self, subchannel_name):

def __str__(self):
capacity = "∞" if self.capacity is None else str(self.capacity)
return "%s(C:%s,Q:%d,R:%d,F:%d)" % (
self.fullname,
capacity,
len(self._queue),
len(self._running),
len(self._failed),
return (
f"{self.fullname}(C:{capacity},Q:{len(self._queue)},"
f"R:{len(self._running)},F:{len(self._failed)})"
)

def remove(self, job):
Expand Down Expand Up @@ -894,8 +891,7 @@ def parse_simple_config(cls, config_string):
)
if k in config:
raise ValueError(
f"Invalid channel config {config_string}: "
f"duplicate key {k}"
f"Invalid channel config {config_string}: duplicate key {k}"
)
config[k] = v
else:
Expand Down Expand Up @@ -996,7 +992,8 @@ def get_channel_by_name(
if channel_name in self._channels_by_name:
return self._channels_by_name[channel_name]
if not autocreate and not parent_fallback:
raise ChannelNotFound(f"Channel {channel_name} not found")
msg = f"Channel {channel_name} not found"
raise ChannelNotFound(msg)
parent = self._root_channel
if parent_fallback:
# Look for first direct parent w/ config.
Expand Down
27 changes: 15 additions & 12 deletions queue_job/models/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,9 +193,17 @@ def foo_job_options(self, arg1):
}
def _register_hook(self):
self._patch_method(
# patch the method at registry time
patched = self._patch_job_auto_delay(
"foo", context_key="auto_delay_foo"
)
setattr(
type(self),
"foo",
self._patch_job_auto_delay("foo", context_key="auto_delay_foo")
functools.update_wrapper(
patched,
getattr(type(self), "foo"),
),
)
return super()._register_hook()
Expand Down Expand Up @@ -224,8 +232,9 @@ def auto_delay_wrapper(self, *args, **kwargs):
delayed = self.with_delay(**job_options)
return getattr(delayed, method_name)(*args, **kwargs)

origin = getattr(self, method_name)
return functools.update_wrapper(auto_delay_wrapper, origin)
origin_func = getattr(type(self), method_name)
auto_delay_wrapper.origin = origin_func
return functools.update_wrapper(auto_delay_wrapper, origin_func)

@api.model
def _job_store_values(self, job):
Expand Down Expand Up @@ -260,11 +269,5 @@ def _job_prepare_context_before_enqueue(self):
if key in self._job_prepare_context_before_enqueue_keys()
}

@classmethod
def _patch_method(cls, name, method):
origin = getattr(cls, name)
method.origin = origin
# propagate decorators from origin to method, and apply api decorator
wrapped = api.propagate(origin, method)
wrapped.origin = origin
setattr(cls, name, wrapped)
# Note: no local _patch_method helper; if needed, patch methods
# directly in _register_hook as shown above.
Loading