Skip to content

Commit

Permalink
0.1.4:
Browse files Browse the repository at this point in the history
- Added option to pause and resume the scheduler
- Jobs don't have to be in the future any more
- Sorted imports with isort
  • Loading branch information
spacemanspiff2007 committed Jun 1, 2021
1 parent 33d6dcb commit 849fe8f
Show file tree
Hide file tree
Showing 32 changed files with 174 additions and 70 deletions.
10 changes: 10 additions & 0 deletions .isort.cfg
@@ -0,0 +1,10 @@
[settings]
line_length = 120

src_paths=src, tests

ensure_newline_before_comments = True
force_alphabetical_sort_within_sections = True

balanced_wrapping = True
multi_line_output = 5
6 changes: 6 additions & 0 deletions .pre-commit-config.yaml
Expand Up @@ -6,6 +6,12 @@ repos:
- id: end-of-file-fixer
- id: trailing-whitespace

- repo: https://github.com/pycqa/isort
rev: 5.8.0
hooks:
- id: isort
name: isort (python)

- repo: https://gitlab.com/PyCQA/flake8
rev: '3.9.1'
hooks:
Expand Down
5 changes: 5 additions & 0 deletions readme.md
Expand Up @@ -19,6 +19,11 @@ Easy Async Scheduler (or EAScheduler) is a asyncio scheduler with a nice and eas

## Changelog

#### 0.1.4 (01.06.2021)
- Added option to pause and resume the scheduler
- Jobs don't have to be in the future any more
- Sorted imports with isort

#### 0.1.3 (06.05.2021)
- Fixed a bug where a negative offset/jitter could result in multiple executions

Expand Down
1 change: 1 addition & 0 deletions src/eascheduler/__init__.py
@@ -1,3 +1,4 @@
# isort: skip_file
from eascheduler.__version__ import __version__

from eascheduler.const import SKIP_EXECUTION
Expand Down
2 changes: 1 addition & 1 deletion src/eascheduler/__version__.py
@@ -1 +1 @@
__version__ = '0.1.3'
__version__ = '0.1.4'
2 changes: 1 addition & 1 deletion src/eascheduler/errors/__init__.py
@@ -1 +1 @@
from .errors import FirstRunInThePastError, JobAlreadyCanceledException, UnknownWeekdayError, BoundaryFunctionError
from .errors import BoundaryFunctionError, FirstRunInThePastError, JobAlreadyCanceledException, UnknownWeekdayError
1 change: 0 additions & 1 deletion src/eascheduler/errors/handler.py
@@ -1,7 +1,6 @@
import logging
from typing import Any, Callable


HANDLER: Callable[[Exception], Any] = lambda x: logging.getLogger('EAScheduler').error(x)


Expand Down
2 changes: 1 addition & 1 deletion src/eascheduler/executors/__init__.py
@@ -1 +1 @@
from .executor import ExecutorBase, SyncExecutor, AsyncExecutor, AsyncThreadSafeExecutor
from .executor import AsyncExecutor, AsyncThreadSafeExecutor, ExecutorBase, SyncExecutor
4 changes: 2 additions & 2 deletions src/eascheduler/jobs/__init__.py
@@ -1,5 +1,5 @@
from .job_countdown import CountdownJob
from .job_day_of_week import DayOfWeekJob
from .job_one_time import OneTimeJob
from .job_reoccuring import ReoccurringJob
from .job_day_of_week import DayOfWeekJob
from .job_sun import SunriseJob, SunsetJob, DuskJob, DawnJob
from .job_sun import DawnJob, DuskJob, SunriseJob, SunsetJob
13 changes: 7 additions & 6 deletions src/eascheduler/jobs/job_base.py
@@ -1,12 +1,13 @@
from datetime import datetime, timedelta
from datetime import datetime
from datetime import time as dt_time
from datetime import timedelta
from typing import Optional, TYPE_CHECKING, Union

from pendulum import from_timestamp, DateTime, instance
from pendulum import DateTime, from_timestamp, instance
from pendulum import now as get_now

from eascheduler.const import FAR_FUTURE, local_tz
from eascheduler.errors import JobAlreadyCanceledException, FirstRunInThePastError
from eascheduler.errors import FirstRunInThePastError, JobAlreadyCanceledException
from eascheduler.executors import ExecutorBase

if TYPE_CHECKING:
Expand Down Expand Up @@ -73,7 +74,7 @@ def get_first_timestamp(base_time: Union[None, int, float, timedelta, dt_time, d

if base_time is None:
# If we don't specify a datetime we start it now
new_base = now.add(microseconds=1000)
new_base = now
elif isinstance(base_time, timedelta):
# if it is a timedelta add it to now to easily specify points in the future
new_base = now + base_time
Expand All @@ -90,7 +91,7 @@ def get_first_timestamp(base_time: Union[None, int, float, timedelta, dt_time, d
new_base = instance(base_time, tz=local_tz).astimezone(local_tz)

assert isinstance(new_base, DateTime), type(new_base)
if new_base <= now:
raise FirstRunInThePastError(f'First run must be in the future! Now: {now}, run: {new_base}')
if new_base < now:
raise FirstRunInThePastError(f'First run can not be in the past! Now: {now}, run: {new_base}')

return new_base.timestamp()
14 changes: 7 additions & 7 deletions src/eascheduler/jobs/job_base_datetime.py
@@ -1,21 +1,21 @@
from __future__ import annotations

from datetime import datetime, timedelta
from datetime import datetime
from datetime import time as dt_time
from datetime import timedelta
from random import uniform
from typing import Callable, Optional, Tuple, Union

from pendulum import UTC, from_timestamp, instance, DateTime
from pendulum import DateTime, from_timestamp, instance
from pendulum import now as get_now
from pendulum import UTC

from eascheduler.const import SKIP_EXECUTION
from eascheduler.const import local_tz, FAR_FUTURE
from eascheduler.errors import JobAlreadyCanceledException, BoundaryFunctionError
from eascheduler.const import FAR_FUTURE, local_tz, SKIP_EXECUTION
from eascheduler.errors import BoundaryFunctionError, JobAlreadyCanceledException
from eascheduler.executors.executor import ExecutorBase
from eascheduler.jobs.job_base import ScheduledJobBase, get_first_timestamp
from eascheduler.jobs.job_base import get_first_timestamp, ScheduledJobBase
from eascheduler.schedulers import AsyncScheduler


try:
from typing import Literal
except ImportError:
Expand Down
3 changes: 2 additions & 1 deletion src/eascheduler/jobs/job_countdown.py
@@ -1,7 +1,8 @@
from __future__ import annotations

from datetime import datetime, timedelta
from datetime import datetime
from datetime import time as dt_time
from datetime import timedelta
from typing import Union

from pendulum import UTC
Expand Down
4 changes: 2 additions & 2 deletions src/eascheduler/jobs/job_day_of_week.py
Expand Up @@ -2,13 +2,13 @@

from datetime import date, datetime
from datetime import time as dt_time
from typing import Union, Set, Iterable, Dict
from typing import Dict, Iterable, Set, Union

from pendulum import DateTime, from_timestamp
from pendulum import now as get_now

from eascheduler.const import local_tz
from eascheduler.errors import UnknownWeekdayError, JobAlreadyCanceledException
from eascheduler.errors import JobAlreadyCanceledException, UnknownWeekdayError
from eascheduler.executors.executor import ExecutorBase
from eascheduler.jobs.job_base_datetime import DateTimeJobBase
from eascheduler.schedulers import AsyncScheduler
Expand Down
6 changes: 4 additions & 2 deletions src/eascheduler/jobs/job_one_time.py
@@ -1,7 +1,9 @@
from datetime import timedelta, time as dt_time, datetime
from datetime import datetime
from datetime import time as dt_time
from datetime import timedelta
from typing import Union

from eascheduler.jobs.job_base import ScheduledJobBase, get_first_timestamp
from eascheduler.jobs.job_base import get_first_timestamp, ScheduledJobBase


class OneTimeJob(ScheduledJobBase):
Expand Down
2 changes: 1 addition & 1 deletion src/eascheduler/jobs/job_reoccuring.py
Expand Up @@ -4,8 +4,8 @@
from typing import Union

from pendulum import DateTime
from pendulum import UTC
from pendulum import now as get_now
from pendulum import UTC

from eascheduler.const import FAR_FUTURE
from eascheduler.errors import JobAlreadyCanceledException
Expand Down
7 changes: 5 additions & 2 deletions src/eascheduler/jobs/job_sun.py
@@ -1,12 +1,15 @@
from __future__ import annotations

from datetime import timedelta, datetime, time as dt_time
from datetime import datetime
from datetime import time as dt_time
from datetime import timedelta
from typing import Optional, Union

from astral import Observer, sun # type: ignore
from pendulum import DateTime, UTC, from_timestamp
from pendulum import DateTime, from_timestamp
from pendulum import instance as pd_instance
from pendulum import now as get_now
from pendulum import UTC

from eascheduler.executors import ExecutorBase
from eascheduler.jobs.job_base_datetime import DateTimeJobBase
Expand Down
7 changes: 4 additions & 3 deletions src/eascheduler/scheduler_view.py
@@ -1,11 +1,12 @@
from datetime import datetime as dt_datetime
from datetime import time as dt_time
from datetime import timedelta as dt_timedelta
from typing import Union, Type, Iterable
from typing import Iterable, Type, Union

from eascheduler.executors import ExecutorBase
from eascheduler.jobs import OneTimeJob, CountdownJob, ReoccurringJob, DayOfWeekJob, \
SunriseJob, SunsetJob, DuskJob, DawnJob
from eascheduler.jobs import (
CountdownJob, DawnJob, DayOfWeekJob, DuskJob, OneTimeJob, ReoccurringJob, SunriseJob, SunsetJob
)
from eascheduler.schedulers import AsyncScheduler


Expand Down
32 changes: 24 additions & 8 deletions src/eascheduler/schedulers/scheduler_async.py
@@ -1,15 +1,15 @@
import asyncio
from asyncio import Future, CancelledError, create_task
from asyncio import CancelledError, create_task, Future
from bisect import insort
from collections import deque
from typing import Deque, Optional, Set

from pendulum import UTC
from pendulum import now as get_now
from pendulum import UTC

from eascheduler.const import FAR_FUTURE
from eascheduler.jobs.job_base import ScheduledJobBase
from eascheduler.errors.handler import process_exception
from eascheduler.jobs.job_base import ScheduledJobBase


class AsyncScheduler:
Expand All @@ -18,6 +18,22 @@ def __init__(self):
self.job_objs: Set[ScheduledJobBase] = set()

self.worker: Optional[Future] = None
self.worker_paused: bool = False

def pause(self):
assert not self.worker_paused

self.worker_paused = True
if self.worker is not None:
self.worker.cancel()
self.worker = None

def resume(self):
assert self.worker_paused

if self.jobs:
self.worker = create_task(self._run_next())
self.worker_paused = False

def add_job(self, job: ScheduledJobBase):
# cancel task if the new job is earlier than the next one or if it is the next one
Expand All @@ -33,8 +49,8 @@ def add_job(self, job: ScheduledJobBase):
self.jobs.remove(job)
insort(self.jobs, job)

if self.worker is None:
self.worker = create_task(self.__run_next())
if self.worker is None and not self.worker_paused:
self.worker = create_task(self._run_next())

def remove_job(self, job: ScheduledJobBase):
if self.jobs and job is self.jobs[0]:
Expand All @@ -46,8 +62,8 @@ def remove_job(self, job: ScheduledJobBase):
self.jobs.popleft()
self.job_objs.remove(job)

if self.jobs:
self.worker = create_task(self.__run_next())
if self.jobs and not self.worker_paused:
self.worker = create_task(self._run_next())
else:
self.jobs.remove(job)
self.job_objs.remove(job)
Expand All @@ -62,7 +78,7 @@ def cancel_all(self):
self.job_objs.remove(job)
job._parent = None

async def __run_next(self):
async def _run_next(self):
try:
while self.jobs:
job = self.jobs[0]
Expand Down
22 changes: 21 additions & 1 deletion src/eascheduler/schedulers/scheduler_asyncthread.py
@@ -1,7 +1,7 @@
import logging
from asyncio import AbstractEventLoop, run_coroutine_threadsafe
from threading import _MainThread # type: ignore
from threading import current_thread
from threading import _MainThread # type: ignore

from eascheduler.jobs.job_base import ScheduledJobBase
from eascheduler.schedulers import AsyncScheduler
Expand All @@ -12,6 +12,26 @@
class ThreadSafeAsyncScheduler(AsyncScheduler):
LOOP: AbstractEventLoop

async def __pause(self):
super().pause()

def pause(self):
if not isinstance(current_thread(), _MainThread):
run_coroutine_threadsafe(self.__pause(), self.LOOP).result()
else:
super().pause()
return None

async def __resume(self):
super().pause()

def resume(self):
if not isinstance(current_thread(), _MainThread):
run_coroutine_threadsafe(self.__resume(), self.LOOP).result()
else:
super().resume()
return None

async def __add_job(self, job: ScheduledJobBase):
super().add_job(job)

Expand Down
5 changes: 3 additions & 2 deletions tests/conftest.py
@@ -1,8 +1,9 @@
import pendulum
import pytest
from asyncio import CancelledError
from traceback import format_exc

import pendulum
import pytest

from eascheduler.errors import handler
from eascheduler.jobs import job_sun
from eascheduler.schedulers import AsyncScheduler
Expand Down
4 changes: 3 additions & 1 deletion tests/helper.py
Expand Up @@ -6,8 +6,10 @@
from mock import AsyncMock

from datetime import datetime as dt_datetime
from pendulum import datetime, instance, from_timestamp, UTC, DateTime

from pendulum import datetime, DateTime, from_timestamp, instance
from pendulum import set_test_now as __set_test_now
from pendulum import UTC

from eascheduler.const import local_tz
from eascheduler.executors import AsyncExecutor
Expand Down
5 changes: 3 additions & 2 deletions tests/jobs/test_all_jobs.py
Expand Up @@ -3,8 +3,9 @@
import pytest

from eascheduler.errors import JobAlreadyCanceledException
from eascheduler.jobs import ReoccurringJob, DayOfWeekJob, CountdownJob, OneTimeJob, \
SunsetJob, SunriseJob, DuskJob, DawnJob
from eascheduler.jobs import (
CountdownJob, DawnJob, DayOfWeekJob, DuskJob, OneTimeJob, ReoccurringJob, SunriseJob, SunsetJob
)


@pytest.mark.parametrize(
Expand Down
2 changes: 1 addition & 1 deletion tests/jobs/test_job_countdown.py
Expand Up @@ -3,9 +3,9 @@

import pytest

from eascheduler.executors import AsyncExecutor
from eascheduler.jobs import CountdownJob
from eascheduler.schedulers import AsyncScheduler
from eascheduler.executors import AsyncExecutor


@pytest.mark.asyncio
Expand Down

0 comments on commit 849fe8f

Please sign in to comment.