Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[13.0] Deprecate job decorators #274

Merged
merged 10 commits into from
Nov 12, 2020
1 change: 1 addition & 0 deletions .pylintrc-mandatory
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ enable=anomalous-backslash-in-string,
class-camelcase,
dangerous-default-value,
dangerous-view-replace-wo-priority,
development-status-allowed,
duplicate-id-csv,
duplicate-key,
duplicate-xml-fields,
Expand Down
4 changes: 2 additions & 2 deletions base_import_async/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
"website": "https://github.com/OCA/queue",
"category": "Generic Modules",
"depends": ["base_import", "queue_job"],
"data": ["views/base_import_async.xml"],
"data": ["data/queue_job_function_data.xml", "views/base_import_async.xml"],
"qweb": ["static/src/xml/import.xml"],
"installable": True,
"development_status": "Stable",
"development_status": "Production/Stable",
}
21 changes: 21 additions & 0 deletions base_import_async/data/queue_job_function_data.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<odoo noupdate="1">
<record id="job_function_base_import_import_split_file" model="queue.job.function">
<field name="model_id" ref="base_import.model_base_import_import" />
<field name="method">_split_file</field>
<field
name="related_action"
eval='{"func_name": "_related_action_attachment"}'
/>
</record>
<record
id="job_function_base_import_import_import_one_chunk"
model="queue.job.function"
>
<field name="model_id" ref="base_import.model_base_import_import" />
<field name="method">_import_one_chunk</field>
<field
simahawk marked this conversation as resolved.
Show resolved Hide resolved
name="related_action"
eval='{"func_name": "_related_action_attachment"}'
/>
</record>
</odoo>
5 changes: 0 additions & 5 deletions base_import_async/models/base_import_import.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
from odoo.models import fix_import_export_id_paths

from odoo.addons.queue_job.exception import FailedJobError
from odoo.addons.queue_job.job import job, related_action

# options defined in base_import/import.js
OPT_HAS_HEADER = "headers"
Expand Down Expand Up @@ -124,8 +123,6 @@ def _extract_chunks(model_obj, fields, data, chunk_size):
if row_from < len(data):
yield row_from, len(data) - 1

@job
@related_action("_related_action_attachment")
def _split_file(
self,
model_name,
Expand Down Expand Up @@ -172,8 +169,6 @@ def _split_file(
self._link_attachment_to_job(delayed_job, attachment)
priority += 1

@job
@related_action("_related_action_attachment")
def _import_one_chunk(self, model_name, attachment, options):
model_obj = self.env[model_name]
fields, data = self._read_csv_attachment(attachment, options)
Expand Down
3 changes: 2 additions & 1 deletion queue_job/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

{
"name": "Job Queue",
"version": "13.0.1.7.0",
"version": "13.0.2.0.0",
"author": "Camptocamp,ACSONE SA/NV,Odoo Community Association (OCA)",
"website": "https://github.com/OCA/queue/queue_job",
"license": "LGPL-3",
Expand All @@ -15,6 +15,7 @@
"security/ir.model.access.csv",
"views/queue_job_views.xml",
"data/queue_data.xml",
"data/queue_job_function_data.xml",
],
"installable": True,
"development_status": "Mature",
Expand Down
6 changes: 6 additions & 0 deletions queue_job/data/queue_job_function_data.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<odoo noupdate="1">
<record id="job_function_queue_job__test_job" model="queue.job.function">
<field name="model_id" ref="queue_job.model_queue_job" />
<field name="method">_test_job</field>
</record>
</odoo>
96 changes: 80 additions & 16 deletions queue_job/job.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2013-2016 Camptocamp
# Copyright 2013-2020 Camptocamp
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html)

import functools
Expand Down Expand Up @@ -42,9 +42,6 @@ class DelayableRecordset(object):
delayable = DelayableRecordset(recordset, priority=20)
delayable.method(args, kwargs)

``method`` must be a method of the recordset's Model, decorated with
:func:`~odoo.addons.queue_job.job.job`.

The method call will be processed asynchronously in the job queue, with
the passed arguments.

Expand Down Expand Up @@ -78,12 +75,6 @@ def __getattr__(self, name):
)
)
recordset_method = getattr(self.recordset, name)
if not getattr(recordset_method, "delayable", None):
raise AttributeError(
"method %s on %s is not allowed to be delayed, "
"it should be decorated with odoo.addons.queue_job.job.job"
% (name, self.recordset)
)

def delay(*args, **kwargs):
return Job.enqueue(
Expand Down Expand Up @@ -453,6 +444,12 @@ def __init__(
self.job_model = self.env["queue.job"]
self.job_model_name = "queue.job"

self.job_config = self.env["queue.job.function"].job_config(
self.env["queue.job.function"].job_function_name(
self.model_name, self.method_name
)
)

self.state = PENDING

self.retry = 0
Expand Down Expand Up @@ -554,9 +551,14 @@ def store(self):
if self.identity_key:
vals["identity_key"] = self.identity_key

job_model = self.env["queue.job"]
# The sentinel is used to prevent edition sensitive fields (such as
# method_name) from RPC methods.
edit_sentinel = job_model.EDIT_SENTINEL

db_record = self.db_record()
if db_record:
db_record.write(vals)
db_record.with_context(_job_edit_sentinel=edit_sentinel).write(vals)
else:
date_created = self.date_created
# The following values must never be modified after the
Expand All @@ -578,7 +580,7 @@ def store(self):
if self.channel:
vals.update({"channel": self.channel})

self.env[self.job_model_name].sudo().create(vals)
job_model.with_context(_job_edit_sentinel=edit_sentinel).sudo().create(vals)

def db_record(self):
return self.db_record_from_uuid(self.env, self.uuid)
Expand Down Expand Up @@ -672,7 +674,10 @@ def __repr__(self):
return "<Job %s, priority:%d>" % (self.uuid, self.priority)

def _get_retry_seconds(self, seconds=None):
retry_pattern = self.func.retry_pattern
retry_pattern = self.job_config.retry_pattern
if not retry_pattern:
# TODO deprecated by :job-no-decorator:
retry_pattern = getattr(self.func, "retry_pattern", None)
if not seconds and retry_pattern:
# ordered from higher to lower count of retries
patt = sorted(retry_pattern.items(), key=lambda t: t[0])
Expand Down Expand Up @@ -701,20 +706,29 @@ def postpone(self, result=None, seconds=None):

def related_action(self):
record = self.db_record()
if hasattr(self.func, "related_action"):
if not self.job_config.related_action_enable:
return None

funcname = self.job_config.related_action_func_name
if not funcname and hasattr(self.func, "related_action"):
# TODO deprecated by :job-no-decorator:
funcname = self.func.related_action
# decorator is set but empty: disable the default one
if not funcname:
return None
else:

if not funcname:
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"
)
action = getattr(record, funcname)
action_kwargs = getattr(self.func, "kwargs", {})
action_kwargs = self.job_config.related_action_kwargs
if not action_kwargs:
# TODO deprecated by :job-no-decorator:
action_kwargs = getattr(self.func, "kwargs", {})
return action(**action_kwargs)


Expand All @@ -724,9 +738,13 @@ def _is_model_method(func):
)


# TODO deprecated by :job-no-decorator:
def job(func=None, default_channel="root", retry_pattern=None):
"""Decorator for job methods.

Deprecated. Use ``queue.job.function`` XML records (details in
``readme/USAGE.rst``).

It enables the possibility to use a Model's method as a job function.

Optional argument:
Expand Down Expand Up @@ -810,6 +828,25 @@ def retryable_example():
job, default_channel=default_channel, retry_pattern=retry_pattern
)

xml_fields = [
' <field name="model_id" ref="[insert model xmlid]" />\n'
' <field name="method">_test_job</field>\n'
]
if default_channel:
xml_fields.append(' <field name="channel_id" ref="[insert channel xmlid]"/>')
if retry_pattern:
xml_fields.append(' <field name="retry_pattern">{retry_pattern}</field>')

xml_record = (
'<record id="job_function_[insert model]_{method}"'
' model="queue.job.function">\n' + "\n".join(xml_fields) + "\n</record>"
).format(**{"method": func.__name__, "retry_pattern": retry_pattern})
_logger.warning(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@guewen IMHO this is too much as it will break all runbot builds in repos that use queues. See https://runbot.odoo-community.org/runbot/build/3450323 for example.

Is it wanted ? Or maybe this can go into INFO.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right, I didn't realize runbot would not agree. We should move it to info, and move the XML snippet to debug because it is too verbose at the moment. I'll try to open a PR tomorrow.

"@job is deprecated and no longer needed, if you need custom options, "
"use an XML record:\n%s",
xml_record,
)

def delay_from_model(*args, **kwargs):
raise AttributeError(
"method.delay() can no longer be used, the general form is "
Expand All @@ -832,9 +869,13 @@ def delay_from_model(*args, **kwargs):
return func


# TODO deprecated by :job-no-decorator:
def related_action(action=None, **kwargs):
"""Attach a *Related Action* to a job (decorator)

Deprecated. Use ``queue.job.function`` XML records (details in
``readme/USAGE.rst``).

A *Related Action* will appear as a button on the Odoo view.
The button will execute the action, usually it will open the
form view of the record related to the job.
Expand Down Expand Up @@ -894,6 +935,29 @@ def export_product(self):
"""

def decorate(func):
related_action_dict = {
"func_name": action,
}
if kwargs:
related_action_dict["kwargs"] = kwargs

xml_fields = (
' <field name="model_id" ref="[insert model xmlid]" />\n'
' <field name="method">_test_job</field>\n'
' <field name="related_action">{related_action}</field>'
)

xml_record = (
'<record id="job_function_[insert model]_{method}"'
' model="queue.job.function">\n' + xml_fields + "\n</record>"
).format(**{"method": func.__name__, "related_action": action})
_logger.warning(
"@related_action is deprecated and no longer needed,"
" add these options in a 'queue.job.function'"
" XML record:\n%s",
xml_record,
)

func.related_action = action
func.kwargs = kwargs
return func
Expand Down
23 changes: 23 additions & 0 deletions queue_job/migrations/13.0.2.0.0/post-migration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html)

import logging

from odoo import SUPERUSER_ID, api, exceptions

_logger = logging.getLogger(__name__)


def migrate(cr, version):
with api.Environment.manage():
env = api.Environment(cr, SUPERUSER_ID, {})
for job_func in env["queue.job.function"].search([]):
try:
# trigger inverse field to set model_id and method
job_func.name = job_func.name
except exceptions.UserError:
# ignore invalid entries not to block migration
_logger.error(
"could not migrate job function '%s' (id: %s), invalid name",
job_func.name,
job_func.id,
)
20 changes: 15 additions & 5 deletions queue_job/models/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class Base(models.AbstractModel):

_inherit = "base"

# TODO deprecated by :job-no-decorator:
def _register_hook(self):
"""Register marked jobs"""
super(Base, self)._register_hook()
Expand All @@ -45,15 +46,22 @@ def with_delay(
):
""" Return a ``DelayableRecordset``

The returned instance allow to enqueue any method of the recordset's
Model which is decorated by :func:`~odoo.addons.queue_job.job.job`.
The returned instance allows to enqueue any method of the recordset's
Model.

Usage::

self.env['res.users'].with_delay().write({'name': 'test'})

In the line above, in so far ``write`` is allowed to be delayed with
``@job``, the write will be executed in an asynchronous job.
``with_delay()`` accepts job properties which specify how the job will
be executed.

Usage with job properties::

delayable = env['a.model'].with_delay(priority=30, eta=60*60*5)
delayable.export_one_thing(the_thing_to_export)
# => the job will be executed with a low priority and not before a
# delay of 5 hours from now

:param priority: Priority of the job, 0 being the higher priority.
Default is 10.
Expand All @@ -69,7 +77,9 @@ def with_delay(
defined on the function
:param identity_key: key uniquely identifying the job, if specified
and a job with the same key has not yet been run,
the new job will not be added.
the new job will not be added. It is either a
string, either a function that takes the job as
argument (see :py:func:`..job.identity_exact`).
:return: instance of a DelayableRecordset
:rtype: :class:`odoo.addons.queue_job.job.DelayableRecordset`

Expand Down
Loading