Skip to content

Commit d8d039e

Browse files
author
Joachim Jablon
authored
Merge pull request procrastinate-org#428 from procrastinate-org/blueprints-2
Second pass on blueprints
2 parents e094777 + 3955023 commit d8d039e

28 files changed

+556
-446
lines changed

docs/conf.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@
4747
else:
4848
extensions.append("sphinxcontrib.spelling")
4949

50+
set_type_checking_flag = True
51+
5052
# Add any paths that contain templates here, relative to this directory.
5153
templates_path = ["_templates"]
5254

docs/howto/blueprints.rst

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,15 @@ Procrastinate provides Blueprints as a way to factor a large number of tasks
55
into smaller self contained collections.
66

77
You may want to create a collection for simple organsational reasons within the
8-
same project. Or you may want to maintain a collection of tasks in a seperate
9-
package which is maintained independently of you Procrastinate server and
10-
workers. eg::
8+
same project. Or you may want to maintain a collection of tasks in a seperate
9+
package which is maintained independently of your Procrastinate codebase::
1110

1211
...
1312

1413
from my_external_package import tasks_blueprint
1514
...
1615

17-
app.register_blueprint(tasks_blueprint)
16+
app.add_tasks_from(tasks_blueprint, namespace="my_external_package")
1817

1918

2019
Blueprints are easy to use, and task creation follows the pattern and API as
@@ -36,4 +35,4 @@ In your projcet register the blueprint with the `App` after you have created it:
3635

3736
app = App(connector=AiopgConnector())
3837

39-
app.register_blueprint(my_blueprint)
38+
app.add_tasks_from(my_blueprint, namespace="unique_name")

docs/howto/delete_finished_jobs.rst

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ From the CLI
3434

3535
.. code-block:: console
3636
37-
$ procrastinate defer procrastinate.builtin_tasks.remove_old_jobs max_hours=72
37+
$ procrastinate defer builtin:procrastinate.builtin_tasks.remove_old_jobs max_hours=72
3838
3939
For more information about this task's parameter,
4040
see :py:func:`~procrastinate.builtin_tasks.remove_old_jobs`
@@ -53,18 +53,26 @@ See also the `periodic launch <cron>` section for related information.
5353
In Python code
5454
^^^^^^^^^^^^^^
5555

56+
Import the following module:
57+
58+
.. code-block:: python
59+
60+
from procrastinate import builtin_tasks
61+
62+
Then:
63+
5664
.. code-block:: python
5765
58-
app.builtin_tasks["remove_old_jobs"].defer(max_hours=72)
66+
builtin_tasks.remove_old_jobs.defer(max_hours=72)
5967
60-
You can access the builtin task through `App.builtin_tasks`.
68+
You can access the builtin task through ``procrastinate.builtin_tasks``.
6169
The parameters are the same than when accessing the task through the CLI.
6270

6371
For example, to use a queueing lock:
6472

6573
.. code-block:: python
6674
67-
deferrer = app.builtin_tasks["remove_old_jobs"].configure(queueing_lock="remove_old_jobs")
75+
deferrer = builtin_tasks.remove_old_jobs.configure(queueing_lock="remove_old_jobs")
6876
deferrer.defer(max_hours=72)
6977
7078
The call to ``defer`` will raise an `AlreadyEnqueued` exception if there already is
@@ -79,7 +87,7 @@ tasks" functionality. This is how you can make ``remove_old_jobs`` periodic:
7987
@app.periodic(cron="0 4 * * *")
8088
@app.task(queueing_lock="remove_old_jobs", pass_context=True)
8189
async def remove_old_jobs(context, timestamp):
82-
return await app.builtin_tasks["remove_old_jobs"](
90+
return await builtin_tasks.remove_old_jobs(
8391
context=context,
8492
max_hours=72,
8593
remove_error=True,

docs/reference.rst

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ App
66

77
.. autoclass:: procrastinate.App
88
:members: open, open_async, task, run_worker, run_worker_async, configure_task,
9-
from_path, with_connector
9+
from_path, add_tasks_from, add_task_alias, with_connector
1010

1111
Connectors
1212
----------
@@ -29,11 +29,19 @@ When tasks are created with argument ``pass_context``, they are provided a
2929
.. autoclass:: procrastinate.JobContext
3030
:members: app, worker_name, worker_queues, job, task
3131

32+
Blueprints
33+
----------
34+
35+
.. autoclass:: procrastinate.blueprints.Blueprint
36+
:members: task, add_tasks_from, add_task_alias
37+
38+
3239
Builtin tasks
3340
-------------
3441

3542
Procrastinate has builtin tasks that are all available from the CLI.
3643
For all tasks, the context argument will be passed automatically.
44+
The name of the tasks will be: ``builtin:procrastinate.builtin.<task_name>``
3745

3846
.. automodule:: procrastinate.builtin_tasks
3947
:members:
@@ -86,10 +94,3 @@ SQLAlchemy
8694
----------
8795

8896
.. autoclass:: procrastinate.contrib.sqlalchemy.SQLAlchemyPsycopg2Connector
89-
90-
91-
Blueprints
92-
----------
93-
94-
.. autoclass:: procrastinate.blueprints.Blueprint
95-
:members: task

poetry.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

procrastinate/app.py

Lines changed: 14 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,18 @@
11
import functools
22
import logging
3-
import sys
4-
from typing import (
5-
TYPE_CHECKING,
6-
Any,
7-
Callable,
8-
Dict,
9-
Iterable,
10-
List,
11-
Optional,
12-
Set,
13-
Union,
14-
)
3+
from typing import TYPE_CHECKING, Any, Dict, Iterable, Optional, Union
154

5+
from procrastinate import blueprints
166
from procrastinate import connector as connector_module
17-
from procrastinate import exceptions, jobs, manager, protocols
18-
from procrastinate import retry as retry_module
19-
from procrastinate import schema, utils
7+
from procrastinate import exceptions, jobs, manager, schema, utils
208

219
if TYPE_CHECKING:
22-
from procrastinate import blueprints, tasks, worker
10+
from procrastinate import worker
2311

2412
logger = logging.getLogger(__name__)
2513

2614

27-
class App(protocols.TaskCreator):
15+
class App(blueprints.Blueprint):
2816
"""
2917
The App is the main entry point for procrastinate integration.
3018
@@ -38,9 +26,6 @@ class App(protocols.TaskCreator):
3826
tasks : ``Dict[str, tasks.Task]``
3927
The mapping of all tasks known by the app. Only procrastinate is expected to
4028
make changes to this mapping.
41-
builtin_tasks : ``Dict[str, tasks.Task]``
42-
The mapping of builtin tasks. Use it to programmatically access builtin tasks,
43-
to defer them.
4429
job_manager : `manager.JobManager`
4530
The `JobManager` linked to the application
4631
"""
@@ -96,12 +81,9 @@ def __init__(
9681
"""
9782
from procrastinate import periodic
9883

99-
self._check_stack()
84+
super().__init__()
10085

10186
self.connector = connector
102-
self.tasks: Dict[str, "tasks.Task"] = {}
103-
self.builtin_tasks: Dict[str, "tasks.Task"] = {}
104-
self.queues: Set[str] = set()
10587
self.import_paths = import_paths or []
10688
self.worker_defaults = worker_defaults or {}
10789
periodic_defaults = periodic_defaults or {}
@@ -113,25 +95,6 @@ def __init__(
11395

11496
self._register_builtin_tasks()
11597

116-
def _check_stack(self):
117-
# Emit a warning if the app is defined in the __main__ module
118-
try:
119-
if utils.caller_module_name() == "__main__":
120-
logger.warning(
121-
"The Procrastinate app is instantiated in the main Python module "
122-
f"({sys.argv[0]}). "
123-
"See https://procrastinate.readthedocs.io/en/stable/discussions.html#top-level-app .",
124-
extra={"action": "app_defined_in___main__"},
125-
exc_info=True,
126-
)
127-
except exceptions.CallerModuleUnknown:
128-
logger.warning(
129-
"Unable to determine where the app was defined. "
130-
"See https://procrastinate.readthedocs.io/en/stable/discussions.html#top-level-app .",
131-
extra={"action": "app_location_unknown"},
132-
exc_info=True,
133-
)
134-
13598
def with_connector(self, connector: connector_module.BaseConnector) -> "App":
13699
"""
137100
Create another app instance sychronized with this one, with a different
@@ -153,94 +116,6 @@ def with_connector(self, connector: connector_module.BaseConnector) -> "App":
153116
app.periodic_deferrer = self.periodic_deferrer
154117
return app
155118

156-
def task(
157-
self,
158-
_func: Optional[Callable] = None,
159-
*,
160-
name: Optional[str] = None,
161-
aliases: Optional[List[str]] = None,
162-
retry: retry_module.RetryValue = False,
163-
pass_context: bool = False,
164-
queue: str = jobs.DEFAULT_QUEUE,
165-
lock: Optional[str] = None,
166-
queueing_lock: Optional[str] = None,
167-
) -> Any:
168-
"""
169-
Declare a function as a task. This method is meant to be used as a decorator::
170-
171-
@app.task(...)
172-
def my_task(args):
173-
...
174-
175-
or::
176-
177-
@app.task
178-
def my_task(args):
179-
...
180-
181-
The second form will use the default value for all parameters.
182-
183-
Parameters
184-
----------
185-
_func :
186-
The decorated function
187-
queue :
188-
The name of the queue in which jobs from this task will be launched, if
189-
the queue is not overridden at launch.
190-
Default is ``"default"``.
191-
When a worker is launched, it can listen to specific queues, or to all
192-
queues.
193-
lock :
194-
Default value for the ``lock`` (see `Task.defer`).
195-
queueing_lock:
196-
Default value for the ``queueing_lock`` (see `Task.defer`).
197-
name :
198-
Name of the task, by default the full dotted path to the decorated function.
199-
if the function is nested or dynamically defined, it is important to give
200-
it a unique name, and to make sure the module that defines this function
201-
is listed in the ``import_paths`` of the `procrastinate.App`.
202-
aliases:
203-
Additional names for the task.
204-
The main use case is to gracefully rename tasks by moving the old
205-
name to aliases (these tasks can have been scheduled in a distant
206-
future, so the aliases can remain for a long time).
207-
retry :
208-
Details how to auto-retry the task if it fails. Can be:
209-
210-
- A ``boolean``: will either not retry or retry indefinitely
211-
- An ``int``: the number of retries before it gives up
212-
- A `procrastinate.RetryStrategy` instance for complex cases
213-
214-
Default is no retry.
215-
pass_context :
216-
Passes the task execution context in the task as first
217-
"""
218-
# Because of https://github.com/python/mypy/issues/3157, this function
219-
# is quite impossible to type consistently, so, we're just using "Any"
220-
221-
def _wrap(func: Callable[..., "tasks.Task"]):
222-
from procrastinate import tasks
223-
224-
task = tasks.Task(
225-
func,
226-
app=self,
227-
queue=queue,
228-
lock=lock,
229-
queueing_lock=queueing_lock,
230-
name=name,
231-
aliases=aliases,
232-
retry=retry,
233-
pass_context=pass_context,
234-
)
235-
self._register(task)
236-
237-
return functools.update_wrapper(task, func, updated=())
238-
239-
if _func is None: # Called as @app.task(...)
240-
return _wrap
241-
242-
return _wrap(_func) # Called as @app.task
243-
244119
def periodic(self, *, cron: str):
245120
"""
246121
Task decorator, marks task as being scheduled for periodic deferring (see
@@ -253,25 +128,17 @@ def periodic(self, *, cron: str):
253128
"""
254129
return self.periodic_deferrer.periodic_decorator(cron=cron)
255130

256-
def register_blueprint(self, blueprint: "blueprints.Blueprint") -> None:
257-
blueprint.register(self)
258-
259-
def _register(self, task: "tasks.Task") -> None:
260-
self.tasks[task.name] = task
261-
for alias in task.aliases:
262-
self.tasks[alias] = task
263-
queue = task.queue
264-
if queue not in self.queues:
265-
logger.debug(
266-
f"Registering queue {queue}",
267-
extra={"action": "register_queue", "queue": queue},
268-
)
269-
self.queues.add(queue)
270-
271131
def _register_builtin_tasks(self) -> None:
272132
from procrastinate import builtin_tasks
273133

274-
builtin_tasks.register_builtin_tasks(self)
134+
self.add_tasks_from(builtin_tasks.builtin, namespace="builtin")
135+
136+
# New tasks will be "builtin:procrastinate.builtin_tasks.remove_old_jobs"
137+
# but for compatibility, we can keep the old name around.
138+
self.add_task_alias(
139+
task=builtin_tasks.remove_old_jobs,
140+
alias="procrastinate.builtin_tasks.remove_old_jobs",
141+
)
275142

276143
def configure_task(
277144
self, name: str, *, allow_unknown: bool = True, **kwargs: Any

0 commit comments

Comments
 (0)