0

First of all, I checked this question in order to make sure that I have no duplicates.

I have a singleton class that has 2 internal methods that I want to trigger via a public method. Each method should be run in a background job scheduled by a BackgroundScheduler. Each method has a different interval.

Here is the code:

import inspect
import threading
import structlog
from apscheduler.schedulers.background import BackgroundScheduler
from typing import Optional, Iterator, Callable

SCAN_A_INTERVAL: int = 30
SCAN_B_INTERVAL: int = 10
scheduler = BackgroundScheduler()


class Scanner:
    __instance: Optional["Scanner"] = None

    def __init__(self):
        self.__logger = structlog.get_logger()

    def __new__(cls, *args, **kwargs):
        if cls.__instance is None:
            cls.__instance = super(Scanner, cls).__new__(cls)

        return cls.__instance
    
    @scheduler.scheduled_job("interval", seconds=SCAN_A_INTERVAL)
    def _scan_a(self):
        print("I'm in a")

    @scheduler.scheduled_job("interval", seconds=SCAN_B_INTERVAL)
    def _scan_b(self):
        print("I'm in b")

    def scan_all(self):
        scan_methods: Iterator[Callable] = (
            tup[1] for tup in inspect.getmembers(self, inspect.ismethod)
            if tup[0].startswith("_scan_")
        )

        for method in scan_methods:
            threading.Thread(target=method, daemon=True).start()  # We want the method to starts manually the first time

        scheduler.start()


if __name__ == '__main__':
    Scanner().scan_all()
    #  Flask application running so the main thread doesn't die

When running this I get the following error:

File "full_path/scanner/scanner.py", line 44, in <module>
    class Scanner:
  File "full_path/scanner/scanner.py", line 64, in Scanner
    def _scan_a(self):
  File "full_path/virtualenvs/repo/lib/python3.8/site-packages/apscheduler/schedulers/base.py", line 470, in inner
    self.add_job(func, trigger, args, kwargs, id, name, misfire_grace_time, coalesce,
  File "full_path/virtualenvs/repo/lib/python3.8/site-packages/apscheduler/schedulers/base.py", line 438, in add_job
    job = Job(self, **job_kwargs)
  File "full_path/virtualenvs/repo/lib/python3.8/site-packages/apscheduler/job.py", line 49, in __init__
    self._modify(id=id or uuid4().hex, **kwargs)
  File "full_path/virtualenvs/repo/lib/python3.8/site-packages/apscheduler/job.py", line 180, in _modify
    check_callable_args(func, args, kwargs)
  File "full_path/virtualenvs/repo/lib/python3.8/site-packages/apscheduler/util.py", line 399, in check_callable_args
    raise ValueError('The following arguments have not been supplied: %s' %
ValueError: The following arguments have not been supplied: self

What I tried: When adding the jobs manually in a loop (

for method in scan_methods:
   scheduler.add_job(method, "interval", seconds=<accordingly>)

), The code runs perfectly. I want to use the decorated function and not add the job manually.

Thank you!

Yoni Melki
  • 205
  • 3
  • 16
  • 1
    Do you need a reference to `self` or `cls` within the scan methods? if not, then maybe you could add the @staticmethod decorator, so they the `self` parameter isn't needed. It's also not clear if the decorators here are executing and scheduling the jobs as the interpreter initially reads the script (i.e. without an instance existing) or if it waits until after your Scanner().scan_all() call. – nigh_anxiety Aug 21 '22 at 18:34
  • I need a reference to self in the scan methods. According to `apscheduler` docs regarding `scheduled_job`: "A decorator version of add_job(), except that `replace_existing` is always `True`". Therefore, I assume that the behavior of the decorator is the same as `add_job`. The jobs are being scheduled once we start the scheduler in `scan_all` @nigh_anxiety – Yoni Melki Aug 22 '22 at 05:40
  • You're decorating an unbound method. Where in your opinion is the `self` argument supposed to come from when there aren't any instances around at that point? – Alex Grönholm Aug 22 '22 at 07:56
  • @AlexGrönholm So what is the workaround you suggest doing? – Yoni Melki Aug 22 '22 at 09:31
  • 2
    Don't try to use the decorator on instance methods. Use `scheduler.add_job(scanner._scan_a, ...)` when you have an instance available. – Alex Grönholm Aug 23 '22 at 10:18

0 Answers0