Bienvenue sur IndexError.

Ici vous pouvez poser des questions sur Python et le Framework Django.

Mais aussi sur les technos front comme React, Angular, Typescript et Javascript en général.

Consultez la FAQ pour améliorer vos chances d'avoir des réponses à vos questions.

un décorator qui retourne plus d'arguments que necessaire

+4 votes

pour faire suite à cette question/solution j'ai une erreur...

voilà où j'en suis arrivé, dans mon tasks.py j'ai :

from django_th.tasks_generic import tasks

@tasks('default')
def read_data():
    """
    The purpose of this tasks is to put data in cache
    because of the read_data function of each
    service
    """
    trigger = TriggerService.objects.filter(status=True).select_related(
    'consumer__name', 'provider__name')

    for service in trigger:
        reading.delay(service)

dans tasks_generic.py j'ai mis

def task_decorator_factory(strategy):

    def tasks(function):
        print("strat {}".format(strategy))
        # drop the existing parameter in the @tasks() decorator 
        # when it's for Celery because it does not need them
        if strategy.__name__ == 'shared_task':
            function = strategy()
            print(function)
        else:
            function = strategy(function)
            print(function)
        return function
    return tasks


try:
    from django_rq import job

    tasks = task_decorator_factory(job)

except ImportError:
    try:
    from celery import shared_task

    tasks = task_decorator_factory(shared_task)

    except ImportError:
    raise ImportError('Celery or Django-rq must be installed before you can use this')

et à l'execution de ma tache read_data j'ai une exception liée à RQ :

strat <function job at 0x7fa62a3e5158>
<rq.decorators.job object at 0x7fa628a67e80>
23:04:41 TypeError: read_data() takes 1 positional argument but 4 were given
Traceback (most recent call last):
  File "/home/foxmask/DjangoVirtualEnv/django-trigger-happy/lib/python3.4/site-packages/rq/worker.py", line 568, in perform_job
    rv = job.perform()
  File "/home/foxmask/DjangoVirtualEnv/django-trigger-happy/lib/python3.4/site-packages/rq/job.py", line 495, in perform
    self._result = self.func(*self.args, **self.kwargs)
  File "/home/foxmask/DjangoVirtualEnv/django-trigger-happy/django-th/django_th/tasks.py", line 156, in reading
    service.date_triggered)
TypeError: read_data() takes 1 positional argument but 4 were given
Traceback (most recent call last):
  File "/home/foxmask/DjangoVirtualEnv/django-trigger-happy/lib/python3.4/site-packages/rq/worker.py", line 568, in perform_job
    rv = job.perform()
  File "/home/foxmask/DjangoVirtualEnv/django-trigger-happy/lib/python3.4/site-packages/rq/job.py", line 495, in perform
    self._result = self.func(*self.args, **self.kwargs)
  File "/home/foxmask/DjangoVirtualEnv/django-trigger-happy/django-th/django_th/tasks.py", line 156, in reading
    service.date_triggered)
TypeError: read_data() takes 1 positional argument but 4 were given
23:04:41 Invoking exception handler <bound method Worker.move_to_failed_queue of <rq.worker.Worker object at 0x7fa629bf0cc0>>
23:04:41 Moving job to 'failed' queue
23:04:41 Sent heartbeat to prevent worker timeout. Next one should arrive within 420 seconds.
23:04:41 
23:04:41 *** Listening on default...
23:04:41 Sent heartbeat to prevent worker timeout. Next one should arrive within 420 seconds.

Pour autant si dans tasks_generic.py j'inverse les 2 imports pour mettre celery en premier, quand je l'execute ; je n'ai aucune erreur, parce que je shoot les paramètres passés au décorator :

strat <function shared_task at 0x7f1e1233cea0>
<function shared_task.<locals>.create_shared_task.<locals>.__inner at 0x7f1e109961e0>

une idée ?

demandé 24-Fev-2016 par foxmask (2,880 points)

1 Réponse

+3 votes
 
Meilleure réponse

The problem here is that your strategy should NOT be directly the celery or django_rq function, but a wrapper dedicated each solution. Indeed, celery or django_rq functions don't have the same API : they don't take the same parameter, and they don't pass the same parameter to read_data().

So you need to write celery_task_wrapper and django_rq_job_wrapper, 2 functions:

  • using the stragegy;
  • accepting *args, **kwargs;
  • wrapping the decorated function (read_data) so that they normalize the parameters passed to it;

Yes, creating a normalized API from 2 different tools is a lot of work :)

répondu 24-Fev-2016 par Sam (4,984 points)
sélectionné 29-Fev-2016 par foxmask

I didnt know we switched of language :)

I'll try all of that. That makes me a node to my brain :)

Thnaks

Ah merde, désolé, je travaille tout la journée en anglais en ce moment et j'ai pas fais gaffe. Quel con.

j'ai reparcouru ton tuto pour pouvoir pondre un décorateur passant des parm à lui meme et à la fonction decorée.

Seulement si on file à la factory des parms qui sont ceux de la methode décorée, comment on fait pour passer "la strategy" au wrapper concernée ?

pour faire clair pour le lecteur ici

def createur_de_decorateur_avec_arguments(decorator_arg1, decorator_arg2):

    print("Je créé des décorateur et j'accepte des arguments:", decorator_arg1, decorator_arg2)

    def mon_decorateur(func):
    print("Je suis un décorateur, vous me passez des arguments:", decorator_arg1, decorator_arg2)

    # Ne pas mélanger les arguments du décorateurs et de la fonction !
    def wrapped(function_arg1, function_arg2) :
        print("Je suis le wrapper autour de la fonction décorée.\n"
          "Je peux accéder à toutes les variables\n"
          "\t- du décorateur: {0} {1}\n"
          "\t- de l'appel de la fonction: {2} {3}\n"
          "Et je les passe ensuite à la fonction décorée"
          .format(decorator_arg1, decorator_arg2,
              function_arg1, function_arg2))
        return func(function_arg1, function_arg2)

    return wrapped

    return mon_decorateur

@createur_de_decorateur_avec_arguments("Leonard", "Sheldon")
def fonction_decoree_avec_arguments(function_arg1, function_arg2):
    print("Je suis une fonctions décorée, je ne me soucie que de mes arguments: {0}"
       " {1}".format(function_arg1, function_arg2))

si je fais ensuite :

try:
    from django_rq import job

    tasks = createur_de_decorateur_avec_arguments(job)

except ImportError:
    try:
    from celery import shared_task

    tasks = createur_de_decorateur_avec_arguments(shared_task)

    except ImportError:
            raise ImportError('Celery or Django-rq must be installed before you can use this')

C'est evidement que la "strategy" va prendre la place de l'argument 1 et va peter une exception parceque pas d'argument 2

Du coup comment s'y prend-on pour mixer ces 2 pattern ?

j'y suis presque . ca passe pour l'un des deux

C'est au final plus tordu que prévu ...

Voici ce que j'ai pu faire

def decorator_factory(decorator_arg1):

    print("Je créé des décorateur et j'accepte des arguments:", decorator_arg1)

    def my_decorator(func=None):
    print("Je suis un décorateur, vous me passez des arguments:", decorator_arg1)

    # Ne pas mélanger les arguments du décorateurs et de la fonction !
    def wrapped(*args, **kwargs):
        print("nom du decorateur ", decorator_arg1.__name__)

        def rq_wrapped(*args, **kwargs):
        print("RQ ", decorator_arg1, ' argument ', args[0], " func ", func)
        return decorator_arg1(args[0], func)

        def celery_wrapped(*args, **kwargs):
        print("CELERY ", decorator_arg1, ' argument ', args[0], " func ", func)
        return decorator_arg1

        print("Je suis le wrapper autour de la fonction décorée.\n"
          "Je peux accéder à toutes les variables\n"
          "\t- du décorateur: {0}\n"
          "\t- de l'appel de la fonction: {1}\n"
          "Et je les passe ensuite à la fonction décorée"
          .format(decorator_arg1, args))

        if decorator_arg1.__name__ == 'job':
        return rq_wrapped(*args, **kwargs)
        elif decorator_arg1.__name__ == 'shared_task':
        return celery_wrapped(*args, **kwargs)

        #return func(*args, **kwargs)

    return wrapped

    return my_decorator


    #from django_rq import job
    #decorator_factory = decorator_factory(job)

from celery import shared_task
decorator_factory = decorator_factory(shared_task)

@decorator_factory('default')
def read_data(arg1="tata"):
    print("Je suis la fonction décorée avec mon argument {0}".format(arg1))

pour RQ ca marche au poil :

Je créé des décorateur et j'accepte des arguments: <function job at 0x7fc7501dc2f0>
Je suis un décorateur, vous me passez des arguments: <function job at 0x7fc7501dc2f0>
nom du decorateur  job
Je suis le wrapper autour de la fonction décorée.
Je peux accéder à toutes les variables
    - du décorateur: <function job at 0x7fc7501dc2f0>
    - de l'appel de la fonction: (<function read_data at 0x7fc74fa03048>,)
Et je les passe ensuite à la fonction décorée
RQ  <function job at 0x7fc7501dc2f0>  argument  <function read_data at 0x7fc74fa03048>  func  default

Mais pour celery ca se corse ...

dans la doc il est indiqué qu'on fait d'une fonction une "task" celery en collant le décorateur @shared_task et que dans la foulée pour la lancer de façon asynchrone on fait ma_tache.delay()

seulement .delay() n'est pas une méthode du décorateur à l'inverse de RQ. et Donc résultat des courses :

  File "/home/foxmask/DjangoVirtualEnv/django-trigger-happy/django-th/django_th/management/commands/fire_read_data.py", line 22, in handle
    read_data.delay()
AttributeError: 'function' object has no attribute 'delay'

J'ai du coup l'impression d'avoir passé tout ce temps pour pas pouvoir pondre ce satané decorateur générique

Bon cela dit... ca marche sur un pied ...

Quand je cascade 2 méthodes décorées, la 2nd n'est pas appelée

@decorator_factory('default')
def reading():
    print("pouette")


@decorator_factory('default')
def read_data(arg1="tata"):
    print("Je suis la fonction décorée avec mon argument {0}".format(arg1))

    reading.delay()

donne

./manage.py fire_read_data

Je suis la fonction décorée avec mon argument tata

j'ai perdu pouette

Comme j'ai pas ton code au complet et qu'il a du changé 20 fois, là c'est difficile de t'aider :) Reposter le code complet. Un petit edit dans la question ?

Laisse tomber. Je vous pas d'issue vu que celery ajouté le décorateur différemment on accroche pas la méthode delay().

Merci pour ton aide ;)

Le delay est triggered quand tu lances la tache. Tout ce que tu as à faire, c'est que ton wrapper, quand il est exécuté, lance la tache également.

...