[Django] #37206: Drop tasks' is_module_level_function in favor of the established deconstructable paradigm

2 views
Skip to first unread message

Django

unread,
Jul 6, 2026, 2:16:10 PM (22 hours ago) Jul 6
to django-...@googlegroups.com
#37206: Drop tasks' is_module_level_function in favor of the established
deconstructable paradigm
-------------------------------------+-------------------------------------
Reporter: Johannes Maron | Type:
| Cleanup/optimization
Status: new | Component: Tasks
Version: 6.0 | Severity: Normal
Keywords: | Triage Stage:
| Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
The task framework introduces a new `is_module_level_function` inspection
function instead of relying on the established deconstructible paradigm.

Django's task functions (`Task.func`) need to be serializable. However,
Django has an established paradigm for how to do this since the
introduction of migrations: deconstructible classes that need to either
implement `deconstruct` or use the `@deconstructalbe` decorator.

Yes, it will also add support for callables, but more importantly, we
don't introduce too many serialization paradigms internally and provide
integrators a familiar interface.
--
Ticket URL: <https://code.djangoproject.com/ticket/37206>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

Django

unread,
2:58 AM (9 hours ago) 2:58 AM
to django-...@googlegroups.com
#37206: Drop tasks' is_module_level_function in favor of the established
deconstructable paradigm
-------------------------------------+-------------------------------------
Reporter: Johannes Maron | Owner: (none)
Type: | Status: new
Cleanup/optimization |
Component: Tasks | Version: 6.0
Severity: Normal | Resolution:
Keywords: | Triage Stage:
| Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Sarah Boyce):

* cc: Jake Howard (added)

Comment:

Sorry Johannes, can you clarify what changes you are expecting here in
terms of what would be supported?
I think this is saying you would like support to be added for something
like

{{{#!python

from django.utils.deconstruct import deconstructible


@deconstructible
class NoopCallable:
def __init__(self, value):
self.value = value

def __call__(self, *args, **kwargs):
pass


my_task = task(foo=5, bar=600)(NoopCallable(value=123))
}}}

And that the check in `BaseTaskBackend.validate_task` might become:


{{{#!python

if not is_module_level_function(task.func) and not hasattr(task.func,
"deconstruct"):
raise InvalidTask(
"Task function must be a module-level function or deconstructible
callable."
)

}}}

Is that right? Can you add an example to illustrate in case I have
misunderstood?
--
Ticket URL: <https://code.djangoproject.com/ticket/37206#comment:1>

Django

unread,
4:43 AM (7 hours ago) 4:43 AM
to django-...@googlegroups.com
#37206: Drop tasks' is_module_level_function in favor of the established
deconstructable paradigm
-------------------------------------+-------------------------------------
Reporter: Johannes Maron | Owner: (none)
Type: | Status: new
Cleanup/optimization |
Component: Tasks | Version: 6.0
Severity: Normal | Resolution:
Keywords: | Triage Stage:
| Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Comment (by Johannes Maron):

Yes, that looks about right. Just like you'd write a validator in the ORM,
right 🤷‍♂️

I stumbled upon this when implementing retry callbacks in
[https://github.com/codingjoe/threadmill/blob/f9749f870179f754b2b17ebc96fe847d41c40b49/threadmill/backends/base.py#L186-L193
Threadmill]. There using callable objects is really handy. BUT I don't
think there is a real use case for tasks. But I feel like serialization
should be handled as consistently as possible across Django. Which is why
I didn't sell it as a feature; I don't believe there is one. This is just
my OCD.
--
Ticket URL: <https://code.djangoproject.com/ticket/37206#comment:2>

Django

unread,
4:56 AM (7 hours ago) 4:56 AM
to django-...@googlegroups.com
#37206: Drop tasks' is_module_level_function in favor of the established
deconstructable paradigm
-------------------------------------+-------------------------------------
Reporter: Johannes Maron | Owner: (none)
Type: | Status: new
Cleanup/optimization |
Component: Tasks | Version: 6.0
Severity: Normal | Resolution:
Keywords: | Triage Stage:
| Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Comment (by Jake Howard):

What is being serialized here is different. For a task, it's a function,
where as for `deconstructible` it's a class. You can't use
`deconstructible` on a function, and I don't think there's really value in
defining tasks as callable classes (given no other aspect of a class is
usable for a task).

I could perhaps see some value in making `Task` and `TaskResult`
`deconstructible`, since those are classes with some kind of
serialization, however that was recently handled in #36919 separately.
--
Ticket URL: <https://code.djangoproject.com/ticket/37206#comment:3>

Django

unread,
8:33 AM (3 hours ago) 8:33 AM
to django-...@googlegroups.com
#37206: Drop tasks' is_module_level_function in favor of the established
deconstructable paradigm
-------------------------------------+-------------------------------------
Reporter: Johannes Maron | Owner: (none)
Type: | Status: closed
Cleanup/optimization |
Component: Tasks | Version: 6.0
Severity: Normal | Resolution:
| needsnewfeatureprocess
Keywords: | Triage Stage:
| Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Johannes Maron):

* resolution: => needsnewfeatureprocess
* status: new => closed

Comment:

Hm… yes, on second thought. I would probably add complexity to the
implementation.
I will check if we can reuse the `is_module_level_function` somewhere else
in the codebase and close this.

That being said, I have thought about it some during a walk; someone could
use callable tasks to dynamically build parametrized tasks. If you can
think of it someone will probably try to do it. However, that would
definitely shift this to a new feature.
--
Ticket URL: <https://code.djangoproject.com/ticket/37206#comment:4>
Reply all
Reply to author
Forward
0 new messages