Skip to content

Feature/task scheduling decorators #84

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file.
9 changes: 9 additions & 0 deletions nest/core/apscheduler/apscheduler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from pytz import utc
from apscheduler.schedulers.background import BackgroundScheduler

"""
An instance of the BackgroundScheduler class from the APScheduler library.
"""

scheduler = BackgroundScheduler()
scheduler.configure(timezone=utc)
100 changes: 100 additions & 0 deletions nest/core/apscheduler/enums/cron_expression.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
from enum import Enum
from apscheduler.triggers.cron import CronTrigger

class CronExpression(Enum):
"""
Enum that contains cron expressions.
A cron expression is a string representing a set of times, using 6 space-separated fields.
Fields:
- second (0-59)
- minute (0-59)
- hour (0-23)
- day of month (1-31)
- month (1-12)
- day of week (0-6) (Sunday to Saturday)
- year (optional)
- timezone (optional)
"""
EVERY_SECOND = CronTrigger(second="*")
EVERY_5_SECONDS = CronTrigger(second="*/5")
EVERY_10_SECONDS = CronTrigger(second="*/10")
EVERY_30_SECONDS = CronTrigger(second="*/30")
EVERY_MINUTE = CronTrigger(minute="*/1")
EVERY_5_MINUTES = CronTrigger(minute="*/5")
EVERY_10_MINUTES = CronTrigger(minute="*/10")
EVERY_30_MINUTES = CronTrigger(minute="*/30")
EVERY_HOUR = CronTrigger(minute=0, hour="0-23/1")
EVERY_2_HOURS = CronTrigger(minute=0, hour="0-23/2")
EVERY_3_HOURS = CronTrigger(minute=0, hour="0-23/3")
EVERY_4_HOURS = CronTrigger(minute=0, hour="0-23/4")
EVERY_5_HOURS = CronTrigger(minute=0, hour="0-23/5")
EVERY_6_HOURS = CronTrigger(minute=0, hour="0-23/6")
EVERY_7_HOURS = CronTrigger(minute=0, hour="0-23/7")
EVERY_8_HOURS = CronTrigger(minute=0, hour="0-23/8")
EVERY_9_HOURS = CronTrigger(minute=0, hour="0-23/9")
EVERY_10_HOURS = CronTrigger(minute=0, hour="0-23/10")
EVERY_11_HOURS = CronTrigger(minute=0, hour="0-23/11")
EVERY_12_HOURS = CronTrigger(minute=0, hour="0-23/12")
EVERY_DAY_AT_1AM = CronTrigger(minute=0, hour=1)
EVERY_DAY_AT_2AM = CronTrigger(minute=0, hour=2)
EVERY_DAY_AT_3AM = CronTrigger(minute=0, hour=3)
EVERY_DAY_AT_4AM = CronTrigger(minute=0, hour=4)
EVERY_DAY_AT_5AM = CronTrigger(minute=0, hour=5)
EVERY_DAY_AT_6AM = CronTrigger(minute=0, hour=6)
EVERY_DAY_AT_7AM = CronTrigger(minute=0, hour=7)
EVERY_DAY_AT_8AM = CronTrigger(minute=0, hour=8)
EVERY_DAY_AT_9AM = CronTrigger(minute=0, hour=9)
EVERY_DAY_AT_10AM = CronTrigger(minute=0, hour=10)
EVERY_DAY_AT_11AM = CronTrigger(minute=0, hour=11)
EVERY_DAY_AT_NOON = CronTrigger(minute=0, hour=12)
EVERY_DAY_AT_1PM = CronTrigger(minute=0, hour=13)
EVERY_DAY_AT_2PM = CronTrigger(minute=0, hour=14)
EVERY_DAY_AT_3PM = CronTrigger(minute=0, hour=15)
EVERY_DAY_AT_4PM = CronTrigger(minute=0, hour=16)
EVERY_DAY_AT_5PM = CronTrigger(minute=0, hour=17)
EVERY_DAY_AT_6PM = CronTrigger(minute=0, hour=18)
EVERY_DAY_AT_7PM = CronTrigger(minute=0, hour=19)
EVERY_DAY_AT_8PM = CronTrigger(minute=0, hour=20)
EVERY_DAY_AT_9PM = CronTrigger(minute=0, hour=21)
EVERY_DAY_AT_10PM = CronTrigger(minute=0, hour=22)
EVERY_DAY_AT_11PM = CronTrigger(minute=0, hour=23)
EVERY_DAY_AT_MIDNIGHT = CronTrigger(minute=0, hour=0)
EVERY_WEEK = CronTrigger(minute=0, hour=0, day_of_week=0)
EVERY_WEEKDAY = CronTrigger(minute=0, hour=0, day_of_week="1-5")
EVERY_WEEKEND = CronTrigger(minute=0, hour=0, day_of_week="6,0")
EVERY_1ST_DAY_OF_MONTH_AT_MIDNIGHT = CronTrigger(minute=0, hour=0, day=1)
EVERY_1ST_DAY_OF_MONTH_AT_NOON = CronTrigger(minute=0, hour=12, day=1)
EVERY_2ND_HOUR = CronTrigger(minute=0, hour="*/2")
EVERY_2ND_HOUR_FROM_1AM_THROUGH_11PM = CronTrigger(minute=0, hour="1-23/2")
EVERY_2ND_MONTH = CronTrigger(minute=0, hour=0, day=1, month="*/2")
EVERY_QUARTER = CronTrigger(minute=0, hour=0, day=1, month="*/3")
EVERY_6_MONTHS = CronTrigger(minute=0, hour=0, day=1, month="*/6")
EVERY_YEAR = CronTrigger(minute=0, hour=0, day=1, month=1)
Comment on lines +18 to +72
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CronExpression enum contains many repetitive entries that could be generated programmatically. This would make the code more concise and easier to maintain.


Finding type: Conciseness

EVERY_30_MINUTES_BETWEEN_9AM_AND_5PM = CronTrigger(minute="*/30", hour="9-17")
EVERY_30_MINUTES_BETWEEN_9AM_AND_6PM = CronTrigger(minute="*/30", hour="9-18")
EVERY_30_MINUTES_BETWEEN_10AM_AND_7PM = CronTrigger(minute="*/30", hour="10-19")
MONDAY_TO_FRIDAY_AT_1AM = CronTrigger(minute=0, hour=1, day_of_week="1-5")
MONDAY_TO_FRIDAY_AT_2AM = CronTrigger(minute=0, hour=2, day_of_week="1-5")
MONDAY_TO_FRIDAY_AT_3AM = CronTrigger(minute=0, hour=3, day_of_week="1-5")
MONDAY_TO_FRIDAY_AT_4AM = CronTrigger(minute=0, hour=4, day_of_week="1-5")
MONDAY_TO_FRIDAY_AT_5AM = CronTrigger(minute=0, hour=5, day_of_week="1-5")
MONDAY_TO_FRIDAY_AT_6AM = CronTrigger(minute=0, hour=6, day_of_week="1-5")
MONDAY_TO_FRIDAY_AT_7AM = CronTrigger(minute=0, hour=7, day_of_week="1-5")
MONDAY_TO_FRIDAY_AT_8AM = CronTrigger(minute=0, hour=8, day_of_week="1-5")
MONDAY_TO_FRIDAY_AT_9AM = CronTrigger(minute=0, hour=9, day_of_week="1-5")
MONDAY_TO_FRIDAY_AT_09_30AM = CronTrigger(minute=30, hour=9, day_of_week="1-5")
MONDAY_TO_FRIDAY_AT_10AM = CronTrigger(minute=0, hour=10, day_of_week="1-5")
MONDAY_TO_FRIDAY_AT_11AM = CronTrigger(minute=0, hour=11, day_of_week="1-5")
MONDAY_TO_FRIDAY_AT_11_30AM = CronTrigger(minute=30, hour=11, day_of_week="1-5")
MONDAY_TO_FRIDAY_AT_12PM = CronTrigger(minute=0, hour=12, day_of_week="1-5")
MONDAY_TO_FRIDAY_AT_1PM = CronTrigger(minute=0, hour=13, day_of_week="1-5")
MONDAY_TO_FRIDAY_AT_2PM = CronTrigger(minute=0, hour=14, day_of_week="1-5")
MONDAY_TO_FRIDAY_AT_3PM = CronTrigger(minute=0, hour=15, day_of_week="1-5")
MONDAY_TO_FRIDAY_AT_4PM = CronTrigger(minute=0, hour=16, day_of_week="1-5")
MONDAY_TO_FRIDAY_AT_5PM = CronTrigger(minute=0, hour=17, day_of_week="1-5")
MONDAY_TO_FRIDAY_AT_6PM = CronTrigger(minute=0, hour=18, day_of_week="1-5")
MONDAY_TO_FRIDAY_AT_7PM = CronTrigger(minute=0, hour=19, day_of_week="1-5")
MONDAY_TO_FRIDAY_AT_8PM = CronTrigger(minute=0, hour=20, day_of_week="1-5")
MONDAY_TO_FRIDAY_AT_9PM = CronTrigger(minute=0, hour=21, day_of_week="1-5")
MONDAY_TO_FRIDAY_AT_10PM = CronTrigger(minute=0, hour=22, day_of_week="1-5")
MONDAY_TO_FRIDAY_AT_11PM = CronTrigger(minute=0, hour=23, day_of_week="1-5")
10 changes: 10 additions & 0 deletions nest/core/apscheduler/enums/scheduler_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from enum import Enum

class SchedulerTypes(Enum):
"""
An enumeration of scheduler types.
"""
CRON = 'cron',
DATE = 'date'
INTERVAL = 'interval',
Comment on lines +7 to +9
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are trailing commas after 'cron' and 'interval' in the SchedulerTypes enum, which is inconsistent with the 'date' entry.


Finding type: Naming and Typos


73 changes: 73 additions & 0 deletions nest/core/decorators/scheduler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
from typing import Optional, Callable, Type

from nest.core.apscheduler import scheduler


from nest.core.apscheduler.enums.cron_expression import CronExpression
from nest.core.apscheduler.enums.scheduler_type import SchedulerType

def Cron(expression: CronExpression = CronExpression.EVERY_MINUTE) -> Callable:
"""
Decorator that schedules a function to run at a specific time.

Args:
expression (CronExpression): A cron expression.

Returns:
function: The decorated function.
"""
def decorated(func: Callable) -> Callable:
def wrapper(*args, **kwargs):
"""
Wrapper function that schedules the function to run at a specific time.
"""
try:
if not isinstance(expression, CronExpression):
raise ValueError("Invalid cron expression.")
scheduler.add_job(
func,
trigger = expression.value,
id = func.__name__,
)
except Exception as e:
raise ValueError(f"Invalid cron expression: {e}")


return wrapper

return decorated
Comment on lines +9 to +38
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Cron decorator function contains nested try-except blocks and conditionals that can be simplified. The type checking can be moved to a separate function for better readability.


Finding type: Conciseness


def Interval(seconds: Optional[int] = 10, minutes: Optional[int] = None, hours: Optional[int] = None, days: Optional[int] = None) -> Callable:
"""
Decorator that schedules a function to run at a specific interval.

Args:
seconds (int): The number of seconds between each run.
minutes (int): The number of minutes between each run.
hours (int): The number of hours between each run.
days (int): The number of days between each run.

Returns:
function: The decorated function.
"""
def decorated(func: Callable) -> Callable:
def wrapper(*args, **kwargs):
"""
Wrapper function that schedules the function to run at a specific interval.
"""
try:
scheduler.add_job(
func,
trigger = SchedulerType.INTERVAL.value,
seconds = seconds,
minutes = minutes,
hours = hours,
days = days,
id = func.__name__
)
except Exception as e:
raise ValueError(f"Invalid interval: {e}")

return wrapper

return decorated
4 changes: 4 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ dependencies = [
"pydantic<2.0.0",
"sqlalchemy == 2.0.19",
"alembic == 1.7.5",
"APScheduler >= "3.10.4",
"pytz >= "2024.2",
"six >= "1.16.0",
Comment on lines +36 to +38
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are typos in the version specifications for APScheduler, pytz, and tzlocal. The quotation marks are incorrectly placed.


Finding type: Naming and Typos

"tzlocal >= "5.2",
]

[tool.setuptools.dynamic]
Expand Down
6 changes: 5 additions & 1 deletion requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,8 @@ click==8.1.6
PyYAML==6.0.1
injector==0.21.0
pydantic<2.0.0
astor>=0.8.1
astor>=0.8.1
APScheduler==3.10.4
pytz==2024.2
six==1.16.0
tzlocal==5.2
4 changes: 4 additions & 0 deletions requirements-release.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ beanie==1.20.0
PyYAML==6.0.1
injector==0.21.0
astor==0.8.1
APScheduler==3.10.4
pytz==2024.2
six==1.16.0
tzlocal==5.2

# package release
setuptools
Expand Down
4 changes: 4 additions & 0 deletions requirements-tests.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,7 @@ pydantic<2.0.0
python-dotenv>=1.0.0
uvicorn>=0.23.1
astor>=0.8.1
APScheduler==3.10.4
pytz==2024.2
six==1.16.0
tzlocal==5.2