From 66e00d6fc96916e2fd3ec4537556325f407b26f9 Mon Sep 17 00:00:00 2001 From: Dan Bader Date: Mon, 1 Feb 2021 10:29:09 -0800 Subject: [PATCH 01/11] Update GitHub button in docs --- docs/conf.py | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/conf.py b/docs/conf.py index 25dd50da..a3dfedef 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -139,6 +139,7 @@ 'github_repo': 'schedule', 'github_banner': True, 'github_button': True, + 'github_type': 'star', 'show_related': False } From 4c20df5a6889cb8d5e4012cf31eb75932eb8f0c6 Mon Sep 17 00:00:00 2001 From: Sijmen Date: Mon, 1 Feb 2021 19:52:51 +0100 Subject: [PATCH 02/11] Fix typo in examples.rst (#426) --- docs/examples.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/examples.rst b/docs/examples.rst index 09a23fe0..dd57676a 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -75,7 +75,7 @@ To remove a job from the scheduler, use the ``schedule.cancel_job(job)`` method print('Hello world') job = schedule.every().day.at('22:30').do(some_task) - shcedule.cancel_job(job) + schedule.cancel_job(job) Run a job once @@ -238,4 +238,4 @@ Jobs are re-scheduled after finishing, just like they would if they were execute # Add the delay_seconds argument to run the jobs with a number # of seconds delay in between. - schedule.run_all(delay_seconds=10) \ No newline at end of file + schedule.run_all(delay_seconds=10) From 45ca5766451d1972ebfc6bf8392137814bfa38e5 Mon Sep 17 00:00:00 2001 From: Ramon Hagenaars Date: Tue, 2 Feb 2021 22:06:21 +0100 Subject: [PATCH 03/11] Added a decorator (#148) * Added 'repeat' decorator and written a test. Increased the version by 0.0.1. * Removed the type of the given job for python 2.* compatibility. * Added some newlines to comply with FLAKE8 * Added import for 'repeat' in the code example * Added decorator docs and the ability to pass arguments to the job through the decorator * Fix FLAKE8 * Added rhagenaars to authors * Apply suggestions from code review --- AUTHORS.rst | 3 ++- docs/examples.rst | 29 ++++++++++++++++++++++++++++- schedule/__init__.py | 15 +++++++++++++++ test_schedule.py | 40 +++++++++++++++++++++++++++++++++++++++- 4 files changed, 84 insertions(+), 3 deletions(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index 0c0c628f..352eb423 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -25,4 +25,5 @@ Thanks to all the wonderful folks who have contributed to schedule over the year - chankeypathak - vubon - gaguirregabiria -- Skenvy \ No newline at end of file +- rhagenaars +- Skenvy diff --git a/docs/examples.rst b/docs/examples.rst index dd57676a..c18e7411 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -43,10 +43,30 @@ Run a job every x minute schedule.every().minute.at(":17").do(job) while True: - # run_pending schedule.run_pending() time.sleep(1) +Use a decorator to schedule a job +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Use the ``@repeat`` to schedule a function. +Pass it an interval using the same syntax as above while omitting the ``.do()``. + +.. code-block:: python + + from schedule import every, repeat, run_pending + import time + + @repeat(every(10).minutes) + def job(): + print("I am a scheduled job") + + while True: + run_pending() + time.sleep(1) + +The ``@repeat`` decorator does not work on non-static class methods. + Pass arguments to a job ~~~~~~~~~~~~~~~~~~~~~~~ @@ -62,6 +82,13 @@ Pass arguments to a job schedule.every(2).seconds.do(greet, name='Alice') schedule.every(4).seconds.do(greet, name='Bob') + from schedule import every, repeat + + @repeat(every().second, "World") + @repeat(every().day, "Mars") + def hello(planet): + print("Hello", planet) + Cancel a job ~~~~~~~~~~~~ diff --git a/schedule/__init__.py b/schedule/__init__.py index adbc5650..99dbef9f 100644 --- a/schedule/__init__.py +++ b/schedule/__init__.py @@ -645,3 +645,18 @@ def idle_seconds(): :data:`default scheduler instance `. """ return default_scheduler.idle_seconds + + +def repeat(job, *args, **kwargs): + """ + Decorator to schedule a new periodic job. + + Any additional arguments are passed on to the decorated function + when the job runs. + + :param job: a :class:`Jobs ` + """ + def _schedule_decorator(decorated_function): + job.do(decorated_function, *args, **kwargs) + return decorated_function + return _schedule_decorator diff --git a/test_schedule.py b/test_schedule.py index 099336a3..559efbdd 100644 --- a/test_schedule.py +++ b/test_schedule.py @@ -9,7 +9,8 @@ # pylint: disable-msg=R0201,C0111,E0102,R0904,R0901 import schedule -from schedule import every, ScheduleError, ScheduleValueError, IntervalError +from schedule import every, repeat, \ + ScheduleError, ScheduleValueError, IntervalError def make_mock_job(name=None): @@ -357,6 +358,43 @@ def test_run_all(self): schedule.run_all() assert mock_job.call_count == 3 + def test_run_all_with_decorator(self): + mock_job = make_mock_job() + + @repeat(every().minute) + def job1(): + mock_job() + + @repeat(every().hour) + def job2(): + mock_job() + + @repeat(every().day.at('11:00')) + def job3(): + mock_job() + schedule.run_all() + assert mock_job.call_count == 3 + + def test_run_all_with_decorator_args(self): + mock_job = make_mock_job() + + @repeat(every().minute, 1, 2, 'three', foo=23, bar={}) + def job(*args, **kwargs): + mock_job(*args, **kwargs) + + schedule.run_all() + mock_job.assert_called_once_with(1, 2, 'three', foo=23, bar={}) + + def test_run_all_with_decorator_defaultargs(self): + mock_job = make_mock_job() + + @repeat(every().minute) + def job(nothing=None): + mock_job(nothing) + + schedule.run_all() + mock_job.assert_called_once_with(None) + def test_job_func_args_are_passed_on(self): mock_job = make_mock_job() every().second.do(mock_job, 1, 2, 'three', foo=23, bar={}) From a1c13e196c8a08505944c3f0ccc8d322815f197c Mon Sep 17 00:00:00 2001 From: "Avery Fischer (biggerfisch)" Date: Wed, 3 Feb 2021 21:53:52 +0100 Subject: [PATCH 04/11] Fix str of job when there is no __name__ (#430) Partial functions for example do not have a __name__, so they need the same treatment in __str__ as in __repr__ in order to show a name-like thing. --- schedule/__init__.py | 7 ++++++- test_schedule.py | 11 ++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/schedule/__init__.py b/schedule/__init__.py index 99dbef9f..b69bec56 100644 --- a/schedule/__init__.py +++ b/schedule/__init__.py @@ -223,6 +223,11 @@ def __lt__(self, other): return self.next_run < other.next_run def __str__(self): + if hasattr(self.job_func, '__name__'): + job_func_name = self.job_func.__name__ + else: + job_func_name = repr(self.job_func) + return ( "Job(interval={}, " "unit={}, " @@ -231,7 +236,7 @@ def __str__(self): "kwargs={})" ).format(self.interval, self.unit, - self.job_func.__name__, + job_func_name, self.job_func.args, self.job_func.keywords) diff --git a/test_schedule.py b/test_schedule.py index 559efbdd..fa71b3cf 100644 --- a/test_schedule.py +++ b/test_schedule.py @@ -430,7 +430,7 @@ def test_to_string_lambda_job_func(self): assert len(str(every().minute.do(lambda: 1))) > 1 assert len(str(every().day.at('10:30').do(lambda: 1))) > 1 - def test_to_string_functools_partial_job_func(self): + def test_repr_functools_partial_job_func(self): def job_fun(arg): pass job_fun = functools.partial(job_fun, 'foo') @@ -439,6 +439,15 @@ def job_fun(arg): assert 'bar=True' in job_repr assert 'somekey=23' in job_repr + def test_to_string_functools_partial_job_func(self): + def job_fun(arg): + pass + job_fun = functools.partial(job_fun, 'foo') + job_str = str(every().minute.do(job_fun, bar=True, somekey=23)) + assert 'functools.partial' in job_str + assert 'bar=True' in job_str + assert 'somekey=23' in job_str + def test_run_pending(self): """Check that run_pending() runs pending jobs. We do this by overriding datetime.datetime with mock objects From 6aa67b1e09ddd88a999bc02f640eb7c25fe471b2 Mon Sep 17 00:00:00 2001 From: Sijmen Date: Fri, 5 Feb 2021 19:42:58 +0100 Subject: [PATCH 05/11] Installation instructions cleanup + added conda (#421) * Added conda to installation instructions * Update installation.rst --- docs/installation.rst | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index a15d60e9..9daa66d9 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -23,8 +23,8 @@ Installation instructions Problems? Check out :doc:`faq`. -Option 1: PIP -************* +PIP (preferred) +*************** The recommended way to install this package is to use pip. Use the following command to install it: @@ -36,8 +36,8 @@ Schedule is now installed. Check out the :doc:`examples ` or go to the :doc:`the documentation overview `. -Option 2: Linux package manager -******************************* +Using another package manager +***************************** Schedule is available through some linux package managers. These packages are not maintained by the maintainers of this project. It cannot be guarantee that these packages are up-to-date (and will stay up-to-date) with the latest released version. @@ -46,9 +46,7 @@ If you don't mind having an old version, you can use it. Ubuntu ------- -**OUTDATED!** - -At the time of writing, the packages for 20.04LTS and below use version 0.3.2 (2015). +**OUTDATED!** At the time of writing, the packages for 20.04LTS and below use version 0.3.2 (2015). .. code-block:: bash @@ -59,10 +57,7 @@ See `package page Debian ------ -**OUTDATED!** - -At the time of writing, the packages for buster and below use version 0.3.2 (2015). - +**OUTDATED!** At the time of writing, the packages for buster and below use version 0.3.2 (2015). .. code-block:: bash @@ -81,8 +76,15 @@ For yay users, run: $ yay -S python-schedule +Conda (Anaconda) +---------------- + +Schedule is `published `__ in conda (the Anaconda package manager). + +For installation instructions, visit `the conda-forge Schedule repo `__. +The release of Schedule on conda is maintained by the `conda-forge project `__. -Option 3: Install manually +Install manually ************************** If you don't have access to a package manager or need more control, you can manually copy the library into your project. This is easy as the schedule library consists of a single sourcefile MIT licenced. From a3c3f80502ff130fe22ecbe81abf66f52e225edc Mon Sep 17 00:00:00 2001 From: Martin Thoma Date: Mon, 1 Feb 2021 20:12:42 +0100 Subject: [PATCH 06/11] Add type annotations Closes #392 --- AUTHORS.rst | 2 +- schedule/__init__.py | 116 +++++++++++++++++++++++-------------------- setup.cfg | 3 ++ 3 files changed, 65 insertions(+), 56 deletions(-) create mode 100644 setup.cfg diff --git a/AUTHORS.rst b/AUTHORS.rst index c99ece41..a67af15b 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -28,4 +28,4 @@ Thanks to all the wonderful folks who have contributed to schedule over the year - rhagenaars - Skenvy - zcking -- Martin Thoma +- Martin Thoma \ No newline at end of file diff --git a/schedule/__init__.py b/schedule/__init__.py index 7c45d433..f27dfd38 100644 --- a/schedule/__init__.py +++ b/schedule/__init__.py @@ -44,6 +44,7 @@ import random import re import time +from typing import Set, List, Optional, Callable, Union logger = logging.getLogger("schedule") @@ -51,27 +52,17 @@ class ScheduleError(Exception): """Base schedule exception""" - pass - class ScheduleValueError(ScheduleError): """Base schedule value error""" - pass - class IntervalError(ScheduleValueError): """An improper interval was used""" - pass - class CancelJob(object): - """ - Can be returned from a job to unschedule itself. - """ - - pass + """Can be returned from a job to unschedule itself.""" class Scheduler(object): @@ -81,10 +72,10 @@ class Scheduler(object): handle their execution. """ - def __init__(self): - self.jobs = [] + def __init__(self) -> None: + self.jobs: List[Job] = [] - def run_pending(self): + def run_pending(self) -> None: """ Run all jobs that are scheduled to run. @@ -98,7 +89,7 @@ def run_pending(self): for job in sorted(runnable_jobs): self._run_job(job) - def run_all(self, delay_seconds=0): + def run_all(self, delay_seconds: int = 0) -> None: """ Run all jobs regardless if they are scheduled to run or not. @@ -117,7 +108,7 @@ def run_all(self, delay_seconds=0): self._run_job(job) time.sleep(delay_seconds) - def get_jobs(self, tag=None): + def get_jobs(self, tag: Optional[Hashable] = None) -> List["Job"]: """ Gets scheduled jobs marked with the given tag, or all jobs if tag is omitted. @@ -130,7 +121,7 @@ def get_jobs(self, tag=None): else: return [job for job in self.jobs if tag in job.tags] - def clear(self, tag=None): + def clear(self, tag: Optional[Hashable] = None) -> None: """ Deletes scheduled jobs marked with the given tag, or all jobs if tag is omitted. @@ -139,13 +130,13 @@ def clear(self, tag=None): jobs to delete """ if tag is None: - logger.debug('Deleting *all* jobs') + logger.debug("Deleting *all* jobs") del self.jobs[:] else: logger.debug('Deleting all jobs tagged "%s"', tag) self.jobs[:] = (job for job in self.jobs if tag not in job.tags) - def cancel_job(self, job): + def cancel_job(self, job: "Job") -> None: """ Delete a scheduled job. @@ -157,7 +148,7 @@ def cancel_job(self, job): except ValueError: logger.debug('Cancelling not-scheduled job "%s"', str(job)) - def every(self, interval=1): + def every(self, interval: int = 1) -> "Job": """ Schedule a new periodic job. @@ -167,13 +158,13 @@ def every(self, interval=1): job = Job(interval, self) return job - def _run_job(self, job): + def _run_job(self, job: "Job") -> None: ret = job.run() if isinstance(ret, CancelJob) or ret is CancelJob: self.cancel_job(job) @property - def next_run(self): + def next_run(self) -> Optional[datetime.datetime]: """ Datetime when the next job should run. @@ -185,7 +176,7 @@ def next_run(self): return min(self.jobs).next_run @property - def idle_seconds(self): + def idle_seconds(self) -> Optional[float]: """ :return: Number of seconds until :meth:`next_run ` @@ -214,35 +205,46 @@ class Job(object): method, which also defines its `interval`. """ - def __init__(self, interval, scheduler=None): - self.interval = interval # pause interval * unit between runs - self.latest = None # upper limit to the interval - self.job_func = None # the job job_func to run - self.unit = None # time units, e.g. 'minutes', 'hours', ... - self.at_time = None # optional time at which this job runs - self.last_run = None # datetime of the last run - self.next_run = None # datetime of the next run - self.period = None # timedelta between runs, only valid for - self.start_day = None # Specific day of the week to start on - self.tags = set() # unique set of tags for the job - self.scheduler = scheduler # scheduler to register with - - def __lt__(self, other): + def __init__(self, interval, scheduler: Optional[Scheduler] = None): + self.interval: int = interval # pause interval * unit between runs + self.latest: Optional[int] = None # upper limit to the interval + self.job_func: Optional[Callable] = None # the job job_func to run + + # time units, e.g. 'minutes', 'hours', ... + self.unit: Optional[str] = None + + # optional time at which this job runs + self.at_time: Optional[datetime.time] = None + + # datetime of the last run + self.last_run: Optional[datetime.datetime] = None + + # datetime of the next run + self.next_run: Optional[datetime.datetime] = None + + # timedelta between runs, only valid for + self.period: Optional[datetime.timedelta] = None + + # Specific day of the week to start on + self.start_day: Optional[str] = None + + self.tags: Set[Hashable] = set() # unique set of tags for the job + self.scheduler: Optional[Scheduler] = scheduler # scheduler to register with + + def __lt__(self, other) -> bool: """ PeriodicJobs are sortable based on the scheduled time they run next. """ return self.next_run < other.next_run - def __str__(self): + def __str__(self) -> str: if hasattr(self.job_func, "__name__"): - job_func_name = self.job_func.__name__ + job_func_name = self.job_func.__name__ # type: ignore else: job_func_name = repr(self.job_func) - return ( - "Job(interval={}, unit={}, do={}, args={}, kwargs={})" - ).format( + return ("Job(interval={}, unit={}, do={}, args={}, kwargs={})").format( self.interval, self.unit, job_func_name, @@ -397,7 +399,7 @@ def sunday(self): self.start_day = "sunday" return self.weeks - def tag(self, *tags): + def tag(self, *tags: Hashable): """ Tags the job with one or more unique indentifiers. @@ -444,6 +446,9 @@ def at(self, time_str): if not re.match(r"^:[0-5]\d$", time_str): raise ScheduleValueError(("Invalid time format for a minutely job")) time_values = time_str.split(":") + hour: Union[str, int] + mintue: Union[str, int] + second: Union[str, int] if len(time_values) == 3: hour, minute, second = time_values elif len(time_values) == 2 and self.unit == "minutes": @@ -470,7 +475,7 @@ def at(self, time_str): self.at_time = datetime.time(hour, minute, second) return self - def to(self, latest): + def to(self, latest: int): """ Schedule the job to run at an irregular (randomized) interval. @@ -503,10 +508,11 @@ def do(self, job_func, *args, **kwargs): return self @property - def should_run(self): + def should_run(self) -> bool: """ :return: ``True`` if the job should be run now. """ + assert self.next_run is not None, "must run _schedule_next_run before" return datetime.datetime.now() >= self.next_run def run(self): @@ -521,7 +527,7 @@ def run(self): self._schedule_next_run() return ret - def _schedule_next_run(self): + def _schedule_next_run(self) -> None: """ Compute the instant when this job should run next. """ @@ -566,7 +572,7 @@ def _schedule_next_run(self): kwargs["hour"] = self.at_time.hour if self.unit in ["days", "hours"] or self.start_day is not None: kwargs["minute"] = self.at_time.minute - self.next_run = self.next_run.replace(**kwargs) + self.next_run = self.next_run.replace(**kwargs) # type: ignore # Make sure we run at the specified time *today* (or *this hour*) # as well. This accounts for when a job takes so long it finished # in the next period. @@ -604,56 +610,56 @@ def _schedule_next_run(self): jobs = default_scheduler.jobs # todo: should this be a copy, e.g. jobs()? -def every(interval=1): +def every(interval: int = 1) -> Job: """Calls :meth:`every ` on the :data:`default scheduler instance `. """ return default_scheduler.every(interval) -def run_pending(): +def run_pending() -> None: """Calls :meth:`run_pending ` on the :data:`default scheduler instance `. """ default_scheduler.run_pending() -def run_all(delay_seconds=0): +def run_all(delay_seconds: int = 0) -> None: """Calls :meth:`run_all ` on the :data:`default scheduler instance `. """ default_scheduler.run_all(delay_seconds=delay_seconds) -def get_jobs(tag=None): +def get_jobs(tag: Optional[Hashable] = None) -> List[Job]: """Calls :meth:`get_jobs ` on the :data:`default scheduler instance `. """ return default_scheduler.get_jobs(tag) -def clear(tag=None): +def clear(tag: Optional[Hashable] = None) -> None: """Calls :meth:`clear ` on the :data:`default scheduler instance `. """ default_scheduler.clear(tag) -def cancel_job(job): +def cancel_job(job: Job) -> None: """Calls :meth:`cancel_job ` on the :data:`default scheduler instance `. """ default_scheduler.cancel_job(job) -def next_run(): +def next_run() -> Optional[datetime.datetime]: """Calls :meth:`next_run ` on the :data:`default scheduler instance `. """ return default_scheduler.next_run -def idle_seconds(): +def idle_seconds() -> Optional[float]: """Calls :meth:`idle_seconds ` on the :data:`default scheduler instance `. """ diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 00000000..fc1dec8d --- /dev/null +++ b/setup.cfg @@ -0,0 +1,3 @@ +[mypy] +files=schedule +ignore_missing_imports = True From 068677c5c8943ff8706b3d128ed97dedeca782fb Mon Sep 17 00:00:00 2001 From: sijmenhuizenga Date: Wed, 10 Feb 2021 23:06:34 +0100 Subject: [PATCH 07/11] Added type checking to github actions --- requirements-dev.txt | 3 ++- tox.ini | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index a3b529d2..063560b4 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -5,4 +5,5 @@ pytest pytest-cov pytest-flake8 Sphinx -black==20.8b1 \ No newline at end of file +black==20.8b1 +mypy \ No newline at end of file diff --git a/tox.ini b/tox.ini index 2bcaf157..dccb25ff 100644 --- a/tox.ini +++ b/tox.ini @@ -15,6 +15,7 @@ deps = -rrequirements-dev.txt commands = py.test test_schedule.py --flake8 schedule -v --cov schedule --cov-report term-missing python setup.py check --strict --metadata --restructuredtext + python -m mypy -p schedule [testenv:docs] changedir = docs From 9fe46f3b1cf7df17e916f54b2d14da61f0a7fb14 Mon Sep 17 00:00:00 2001 From: sijmenhuizenga Date: Wed, 10 Feb 2021 23:26:05 +0100 Subject: [PATCH 08/11] set job_func type to functools.partial, make job scheduler no longer optional --- schedule/__init__.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/schedule/__init__.py b/schedule/__init__.py index f27dfd38..06a8b573 100644 --- a/schedule/__init__.py +++ b/schedule/__init__.py @@ -205,10 +205,10 @@ class Job(object): method, which also defines its `interval`. """ - def __init__(self, interval, scheduler: Optional[Scheduler] = None): + def __init__(self, interval, scheduler: Scheduler): self.interval: int = interval # pause interval * unit between runs self.latest: Optional[int] = None # upper limit to the interval - self.job_func: Optional[Callable] = None # the job job_func to run + self.job_func: Optional[functools.partial] = None # the job job_func to run # time units, e.g. 'minutes', 'hours', ... self.unit: Optional[str] = None @@ -229,7 +229,7 @@ def __init__(self, interval, scheduler: Optional[Scheduler] = None): self.start_day: Optional[str] = None self.tags: Set[Hashable] = set() # unique set of tags for the job - self.scheduler: Optional[Scheduler] = scheduler # scheduler to register with + self.scheduler: Scheduler = scheduler # scheduler to register with def __lt__(self, other) -> bool: """ @@ -248,8 +248,8 @@ def __str__(self) -> str: self.interval, self.unit, job_func_name, - self.job_func.args, - self.job_func.keywords, + "()" if self.job_func is None else self.job_func.args, + "{}" if self.job_func is None else self.job_func.keywords, ) def __repr__(self): @@ -490,7 +490,7 @@ def to(self, latest: int): self.latest = latest return self - def do(self, job_func, *args, **kwargs): + def do(self, job_func: Callable, *args, **kwargs): """ Specifies the job_func that should be called every time the job runs. From f5985911805a3fff561dae62412b9e33b56ac949 Mon Sep 17 00:00:00 2001 From: sijmenhuizenga Date: Wed, 10 Feb 2021 23:38:07 +0100 Subject: [PATCH 09/11] Make job accept None schedule again --- schedule/__init__.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/schedule/__init__.py b/schedule/__init__.py index 06a8b573..1da4bae8 100644 --- a/schedule/__init__.py +++ b/schedule/__init__.py @@ -205,7 +205,7 @@ class Job(object): method, which also defines its `interval`. """ - def __init__(self, interval, scheduler: Scheduler): + def __init__(self, interval, scheduler: Scheduler = None): self.interval: int = interval # pause interval * unit between runs self.latest: Optional[int] = None # upper limit to the interval self.job_func: Optional[functools.partial] = None # the job job_func to run @@ -229,7 +229,7 @@ def __init__(self, interval, scheduler: Scheduler): self.start_day: Optional[str] = None self.tags: Set[Hashable] = set() # unique set of tags for the job - self.scheduler: Scheduler = scheduler # scheduler to register with + self.scheduler: Optional[Scheduler] = scheduler # scheduler to register with def __lt__(self, other) -> bool: """ @@ -504,6 +504,8 @@ def do(self, job_func: Callable, *args, **kwargs): self.job_func = functools.partial(job_func, *args, **kwargs) functools.update_wrapper(self.job_func, job_func) self._schedule_next_run() + if self.scheduler is None: + raise ScheduleError("Unable to a add job to schedule. Job is not associated with a scheduler.") self.scheduler.jobs.append(self) return self From 3445c08734a46a3539a59f2dd682b0c6c378a7fc Mon Sep 17 00:00:00 2001 From: sijmenhuizenga Date: Wed, 10 Feb 2021 23:40:35 +0100 Subject: [PATCH 10/11] Fix formatting --- schedule/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/schedule/__init__.py b/schedule/__init__.py index 1da4bae8..5652542e 100644 --- a/schedule/__init__.py +++ b/schedule/__init__.py @@ -505,7 +505,9 @@ def do(self, job_func: Callable, *args, **kwargs): functools.update_wrapper(self.job_func, job_func) self._schedule_next_run() if self.scheduler is None: - raise ScheduleError("Unable to a add job to schedule. Job is not associated with a scheduler.") + raise ScheduleError( + "Unable to a add job to schedule. Job is not associated with a scheduler." + ) self.scheduler.jobs.append(self) return self From 6695bc781c0ee41581e3292988cbcfdd0ed0c461 Mon Sep 17 00:00:00 2001 From: sijmenhuizenga Date: Wed, 10 Feb 2021 23:44:54 +0100 Subject: [PATCH 11/11] Fix formatting --- schedule/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/schedule/__init__.py b/schedule/__init__.py index 5652542e..751aef89 100644 --- a/schedule/__init__.py +++ b/schedule/__init__.py @@ -506,7 +506,8 @@ def do(self, job_func: Callable, *args, **kwargs): self._schedule_next_run() if self.scheduler is None: raise ScheduleError( - "Unable to a add job to schedule. Job is not associated with a scheduler." + "Unable to a add job to schedule. " + "Job is not associated with a scheduler." ) self.scheduler.jobs.append(self) return self