Skip to content

Commit 393259c

Browse files
committed
Add docstrings on the new delayable classes
1 parent aa4e42f commit 393259c

4 files changed

Lines changed: 171 additions & 24 deletions

File tree

queue_job/delay.py

Lines changed: 154 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,49 @@
1515

1616

1717
def group(*delayables):
18+
"""Return a group of delayable to form a graph
19+
20+
A group means that jobs can be executed concurrently.
21+
A job or a group of jobs depending on a group can be executed only after
22+
all the jobs of the group are done.
23+
24+
Shortcut to :class:`~odoo.addons.queue_job.delay.DelayableGroup`.
25+
26+
Example::
27+
28+
g1 = group(delayable1, delayable2)
29+
g2 = group(delayable3, delayable4)
30+
g1.done(g2)
31+
g1.delay()
32+
"""
1833
return DelayableGroup(*delayables)
1934

2035

2136
def chain(*delayables):
37+
"""Return a chain of delayable to form a graph
38+
39+
A chain means that jobs must be executed sequentially.
40+
A job or a group of jobs depending on a group can be executed only after
41+
the last job of the chain is done.
42+
43+
Shortcut to :class:`~odoo.addons.queue_job.delay.DelayableChain`.
44+
45+
Example::
46+
47+
chain1 = chain(delayable1, delayable2, delayable3)
48+
chain2 = chain(delayable4, delayable5, delayable6)
49+
chain1.done(chain2)
50+
chain1.delay()
51+
"""
2252
return DelayableChain(*delayables)
2353

2454

2555
class Graph:
56+
"""Acyclic directed graph holding vertices of any hashable type
57+
58+
This graph is not specifically designed to hold :class:`~Delayable`
59+
instances, although ultimately it is used for this purpose.
60+
"""
2661
__slots__ = ('_graph')
2762

2863
def __init__(self, graph=None):
@@ -32,16 +67,26 @@ def __init__(self, graph=None):
3267
self._graph = {}
3368

3469
def add_vertex(self, vertex):
70+
"""Add a vertex
71+
72+
Has no effect if called several times with the same vertex
73+
"""
3574
self._graph.setdefault(vertex, set())
3675

3776
def add_edge(self, parent, child):
77+
"""Add an edge between a parent and a child vertex
78+
79+
Has no effect if called several times with the same pair of vertices
80+
"""
3881
self.add_vertex(child)
3982
self._graph.setdefault(parent, set()).add(child)
4083

4184
def vertices(self):
85+
"""Return the vertices (nodes) of the graph"""
4286
return set(self._graph)
4387

4488
def edges(self):
89+
"""Return the edges (links) of the graph"""
4590
links = []
4691
for vertex, neighbours in self._graph.items():
4792
for neighbour in neighbours:
@@ -58,7 +103,6 @@ def paths(self, vertex):
58103
[[1, 2, 3], [1, 2, 4], [1, 3]]
59104
>>> sorted(self.paths(3))
60105
[[3, 1, 2, 4]]
61-
62106
"""
63107
path = [vertex] # path traversed so far
64108
seen = {vertex} # set of vertices in path
@@ -100,6 +144,10 @@ def topological_sort(self):
100144
queue.append(node)
101145

102146
def root_vertices(self):
147+
"""Returns the root vertices
148+
149+
meaning they do not depend on any other job.
150+
"""
103151
dependency_vertices = set()
104152
for dependencies in self._graph.values():
105153
dependency_vertices.update(dependencies)
@@ -117,9 +165,21 @@ def __repr__(self):
117165

118166

119167
class DelayableGraph(Graph):
120-
"""Directed Graph for Delayable dependencies"""
168+
"""Directed Graph for :class:`~Delayable` dependencies
169+
170+
It connects together the :class:`~Delayable`, :class:`~DelayableGroup` and
171+
:class:`~DelayableChain` graphs, and creates then enqueued the jobs.
172+
"""
121173

122174
def _merge_graph(self, graph):
175+
"""Merge a graph in the current graph
176+
177+
It takes each vertex, which can be :class:`~Delayable`,
178+
:class:`~DelayableChain` or :class:`~DelayableGroup`, and updates the
179+
current graph with the edges between Delayable objects (connecting
180+
heads and tails of the groups and chains), so that at the end, the
181+
graph contains only Delayable objects and their links.
182+
"""
123183
for vertex, neighbours in graph._graph.items():
124184
tails = vertex._tail()
125185
for tail in tails:
@@ -128,6 +188,11 @@ def _merge_graph(self, graph):
128188
self._graph.setdefault(tail, set()).update(heads)
129189

130190
def _connect_graphs(self):
191+
"""Visit the vertices' graphs and connect them, return the whole graph
192+
193+
Build a new graph, walk the vertices and their related vertices, merge
194+
their graph in the new one, until we have visited all the vertices
195+
"""
131196
graph = DelayableGraph()
132197
graph._merge_graph(self)
133198

@@ -149,6 +214,11 @@ def _connect_graphs(self):
149214
return graph
150215

151216
def _has_to_execute_directly(self, vertices):
217+
"""Used for tests to run tests directly instead of storing them
218+
219+
In tests, prefer to use
220+
:func:`odoo.addons.queue_job.tests.common.mock_jobs`.
221+
"""
152222
if os.getenv('TEST_QUEUE_JOB_NO_DELAY'):
153223
_logger.warn(
154224
'`TEST_QUEUE_JOB_NO_DELAY` env var found. NO JOB scheduled.'
@@ -166,6 +236,7 @@ def _has_to_execute_directly(self, vertices):
166236

167237
@staticmethod
168238
def _ensure_same_graph_uuid(jobs):
239+
"""Set the same graph uuid on all jobs of the same graph"""
169240
jobs_count = len(jobs)
170241
if jobs_count == 0:
171242
raise ValueError("Expecting jobs")
@@ -189,6 +260,7 @@ def _ensure_same_graph_uuid(jobs):
189260
job.graph_uuid = graph_uuid
190261

191262
def delay(self):
263+
"""Build the whole graph, creates jobs and delay them"""
192264
graph = self._connect_graphs()
193265

194266
vertices = graph.vertices()
@@ -240,6 +312,23 @@ def _execute_graph_direct(self, graph):
240312

241313

242314
class DelayableChain:
315+
"""Chain of delayables to form a graph
316+
317+
Delayables can be other :class:`~Delayable`, :class:`~DelayableChain` or
318+
:class:`~DelayableGroup` objects.
319+
320+
A chain means that jobs must be executed sequentially.
321+
A job or a group of jobs depending on a group can be executed only after
322+
the last job of the chain is done.
323+
324+
Chains can be connected to other Delayable, DelayableChain or
325+
DelayableGroup objects by using :meth:`~done`.
326+
327+
A Chain is enqueued by calling :meth:`~delay`, which delays the whole
328+
graph.
329+
Important: :meth:`~delay` must be called on the top-level
330+
delayable/chain/group object of the graph.
331+
"""
243332
__slots__ = ('_graph', '__head', '__tail')
244333

245334
def __init__(self, *delayables):
@@ -263,15 +352,38 @@ def __repr__(self):
263352
return 'DelayableChain({})'.format(self._graph)
264353

265354
def done(self, *delayables):
355+
"""Connects the current chain to other delayables/chains/groups
356+
357+
The delayables/chains/groups passed in the parameters will be executed
358+
when the current Chain is done.
359+
"""
266360
for delayable in delayables:
267361
self._graph.add_edge(self.__tail, delayable)
268362
return self
269363

270364
def delay(self):
365+
"""Delay the whole graph"""
271366
self._graph.delay()
272367

273368

274369
class DelayableGroup:
370+
"""Group of delayables to form a graph
371+
372+
Delayables can be other :class:`~Delayable`, :class:`~DelayableChain` or
373+
:class:`~DelayableGroup` objects.
374+
375+
A group means that jobs must be executed sequentially.
376+
A job or a group of jobs depending on a group can be executed only after
377+
the all the jobs of the group are done.
378+
379+
Groups can be connected to other Delayable, DelayableChain or
380+
DelayableGroup objects by using :meth:`~done`.
381+
382+
A group is enqueued by calling :meth:`~delay`, which delays the whole
383+
graph.
384+
Important: :meth:`~delay` must be called on the top-level
385+
delayable/chain/group object of the graph.
386+
"""
275387
__slots__ = ('_graph', '_delayables')
276388

277389
def __init__(self, *delayables):
@@ -294,16 +406,46 @@ def __repr__(self):
294406
return 'DelayableGroup({})'.format(self._graph)
295407

296408
def done(self, *delayables):
409+
"""Connects the current group to other delayables/chains/groups
410+
411+
The delayables/chains/groups passed in the parameters will be executed
412+
when the current Group is done.
413+
"""
297414
for parent in self._delayables:
298415
for child in delayables:
299416
self._graph.add_edge(parent, child)
300417
return self
301418

302419
def delay(self):
420+
"""Delay the whole graph"""
303421
self._graph.delay()
304422

305423

306424
class Delayable:
425+
"""Unit of a graph, one Delayable will lead to an enqueued job
426+
427+
Delayables can have dependencies on each others, as well as dependencies on
428+
:class:`~DelayableGroup` or :class:`~DelayableChain` objects.
429+
430+
This class will generally not be used directly, it is used internally
431+
by :meth:`~odoo.addons.queue_job.models.base.Base.delayable`. Look
432+
in the base model for more details.
433+
434+
Delayables can be connected to other Delayable, DelayableChain or
435+
DelayableGroup objects by using :meth:`~done`.
436+
437+
Properties of the future job can be set using the :meth:`~set` method,
438+
which always return ``self``::
439+
440+
delayable.set(priority=15).set({"max_retries": 5, "eta": 15}).delay()
441+
442+
It can be used for example to set properties dynamically.
443+
444+
A Delayable is enqueued by calling :meth:`delay()`, which delays the whole
445+
graph.
446+
Important: :meth:`delay()` must be called on the top-level
447+
delayable/chain/group object of the graph.
448+
"""
307449
_properties = (
308450
'priority', 'eta', 'max_retries', 'description',
309451
'channel', 'identity_key'
@@ -359,18 +501,28 @@ def _set_from_dict(self, properties):
359501
setattr(self, key, value)
360502

361503
def set(self, *args, **kwargs):
504+
"""Set job properties and return self
505+
506+
The values can be either a dictionary and/or keywork args
507+
"""
362508
if args:
363509
# args must be a dict
364510
self._set_from_dict(*args)
365511
self._set_from_dict(kwargs)
366512
return self
367513

368514
def done(self, *delayables):
515+
"""Connects the current Delayable to other delayables/chains/groups
516+
517+
The delayables/chains/groups passed in the parameters will be executed
518+
when the current Delayable is done.
519+
"""
369520
for child in delayables:
370521
self._graph.add_edge(self, child)
371522
return self
372523

373524
def delay(self):
525+
"""Delay the whole graph"""
374526
self._graph.delay()
375527

376528
def _build_job(self):
@@ -419,9 +571,6 @@ class DelayableRecordset(object):
419571
delayable = DelayableRecordset(recordset, priority=20)
420572
delayable.method(args, kwargs)
421573
422-
``method`` must be a method of the recordset's Model, decorated with
423-
:func:`~odoo.addons.queue_job.job.job`.
424-
425574
The method call will be processed asynchronously in the job queue, with
426575
the passed arguments.
427576

queue_job/models/base.py

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -79,24 +79,22 @@ def delayable(self, priority=None, eta=None,
7979
8080
Usage::
8181
82-
delayable = self.env['res.users'].browse(10).delayable(priority=20)
83-
delayable.do_work({'name': 'test'}).delay()
82+
delayable = self.env["res.users"].browse(10).delayable(priority=20)
83+
delayable.do_work(name="test"}).delay()
8484
85-
In the line above, ``do_work`` is allowed to be delayed because the
86-
method definition of the fictive method ``do_work`` is decorated by
87-
``@job``. The ``do_work`` method will to be executed directly. It will
88-
be executed in an asynchronous job.
85+
In this example, the ``do_work`` method will not be executed directly.
86+
It will be executed in an asynchronous job.
8987
9088
Method calls on a Delayable generally return themselves, so calls can
9189
be chained together::
9290
93-
delayable.set(priority=15).do_work({'name': 'test'}).delay()
91+
delayable.set(priority=15).do_work(name="test"}).delay()
9492
9593
The order of the calls that build the job is not relevant, beside
9694
the call to ``delay()`` that must happen at the very end. This is
97-
equivalent to the one before::
95+
equivalent to the example above::
9896
99-
delayable.do_work({'name': 'test'}).set(priority=15).delay()
97+
delayable.do_work(name="test"}).set(priority=15).delay()
10098
10199
Very importantly, ``delay()`` must be called on the top-most parent
102100
of a chain of jobs, so if you have this::
@@ -135,16 +133,6 @@ def delayable(self, priority=None, eta=None,
135133
the new job will not be added.
136134
:return: instance of a Delayable
137135
:rtype: :class:`odoo.addons.queue_job.job.Delayable`
138-
139-
Note for developers: if you want to run tests or simply disable
140-
jobs queueing for debugging purposes, you can:
141-
142-
a. set the env var `TEST_QUEUE_JOB_NO_DELAY=1`
143-
b. pass a ctx key `test_queue_job_no_delay=1`
144-
145-
In tests you'll have to mute the logger like:
146-
147-
@mute_logger('odoo.addons.queue_job.models.base')
148136
"""
149137
return Delayable(self, priority=priority,
150138
eta=eta,

queue_job/readme/USAGE.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,10 @@ Tip: you can do this at test case level like this
373373
Then all your tests execute the job methods synchronously without delaying any
374374
jobs.
375375
376+
In tests you'll have to mute the logger like:
377+
378+
@mute_logger('odoo.addons.queue_job.models.base')
379+
376380
.. NOTE:: in graphs of jobs, the ``test_queue_job_no_delay`` context key must be in at
377381
least one job's env of the graph for the whole graph to be executed synchronously
378382

0 commit comments

Comments
 (0)