Skip to content

Commit

Permalink
Merge pull request #222 from AKVorrat/scheduler-logic-state
Browse files Browse the repository at this point in the history
General scheduling logic
  • Loading branch information
iameru authored Nov 28, 2023
2 parents b91be81 + fd50744 commit 519f5f5
Show file tree
Hide file tree
Showing 8 changed files with 159 additions and 3 deletions.
47 changes: 47 additions & 0 deletions doc/scheduler.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Scheduler

The scheduler handles background tasks of the application. It is logically
seperated by "modules" with each module holding tasks.

## Module

Currently implemented `modules` are:

* `calls`

## Task Arguments

Example:
```yml
interval: 20.2 # in seconds, Optional as Tasks have their own default values
wait_first: false # true(default) or false, Optional
```
### interval
Time in seconds after which the task is executed again.
### wait_first
Optional. Defaults to `false`. If set to `true` the task's first execution is
after `seconds`, not immediately after startup.

## Calls Module

The `calls` module allows for scheduled, call relevant functions to be executed
regularly.

Currently supports the tasks:

* `build_queue`
* `handle_queue`

### build_queue

This task builds a queue of Users who have given the information that they
wanted to be called at the current time.

### handle_queue

This task checks the queue created by `build_queue` and makes a single phone
call to the first user in the queue.
23 changes: 23 additions & 0 deletions server/dearmep/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,28 @@ class RecommenderConfig(BaseModel):
n_clear_feedback_threshold: int = Field(ge=0, default=8)


class SchedulerTaskConfig(BaseModel):
interval: float
wait_first: bool = True


class SchedulerCallBuildQueueConfig(SchedulerTaskConfig):
interval: float = 30.2


class SchedulerCallHandleQueueConfig(SchedulerTaskConfig):
interval: float = 33.3


class SchedulerCallConfig(BaseModel):
build_queue: Optional[SchedulerCallBuildQueueConfig]
handle_queue: Optional[SchedulerCallHandleQueueConfig]


class SchedulerConfig(BaseModel):
calls: Optional[SchedulerCallConfig]


class Config(BaseModel):
"""The main application configuration supplied via the config file."""
api: APIConfig
Expand All @@ -336,6 +358,7 @@ class Config(BaseModel):
l10n: L10nConfig
telephony: TelephonyConfig
recommender: RecommenderConfig
scheduler: Optional[SchedulerConfig]

_instance: ClassVar[Optional["Config"]] = None
_patch: ClassVar[Optional[Dict]] = None
Expand Down
18 changes: 17 additions & 1 deletion server/dearmep/example-config.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Options ending in `_duration` or `_timeout` (or simply named `duration` or
# Options ending in `_duration`, `_interval` or `_timeout` (or simply named `duration`, `interval` or
# `timeout`) should be specified in seconds, unless noted otherwise.

# Options ending in `_limit` (or simply named `limit`) should contain a string
Expand Down Expand Up @@ -408,6 +408,22 @@ recommender:
# https://www.wolframalpha.com/input?i=plot+1%2F%281%28abs%28x%2F%28N*8%29%29*3%29%5E4+%2B1%29+for+-40%3C%3Dx%3C%3D40%2C+N%3D10
n_clear_feedback_threshold: 8

# The Scheduler is responsible for scheduling background tasks of the
# application, for example to initiate calls or cleaning up of database.
# Check the `doc/scheduler.md` file for more information.
scheduler:
calls:
# This builds a queue of calls to be made automatically from the saved
# schedules of Users.
build_queue:
# time in seconds after which the task is executed again
interval: 30.2 # this is the default value for this task
# wait_first: false means that the task is executed immediately after
# startup.
wait_first: false
# This initiates the next call in the queue.
handle_queue:
interval: 33.3 # this is the default value for this task

# Localization options.
l10n:
Expand Down
11 changes: 10 additions & 1 deletion server/dearmep/main.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import logging
from typing import Optional

from contextlib import asynccontextmanager
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.routing import APIRoute
from starlette_exporter import PrometheusMiddleware, handle_metrics
from starlette_exporter.optional_metrics import request_body_size, \
response_body_size

from . import __version__, markdown_files, static_files
from . import __version__, markdown_files, schedules, static_files
from .api import v1 as api_v1
from .phone import elks
from .config import APP_NAME, Config
Expand Down Expand Up @@ -50,9 +51,17 @@ def create_app(config_dict: Optional[dict] = None) -> FastAPI:
else:
config = Config.load_dict(config_dict)

@asynccontextmanager
async def lifespan(app: FastAPI):
for task in schedules.get_background_tasks(config):
_logger.info(f"Loading background Task: {task.__name__}")
await task()
yield

app = FastAPI(
title=APP_NAME,
version=__version__,
lifespan=lifespan,
)
setup_cors(app, config)

Expand Down
38 changes: 38 additions & 0 deletions server/dearmep/schedules/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from typing import Callable, List, Tuple

from fastapi_utils.tasks import repeat_every

from .calls import build_queue, handle_queue
from ..config import Config, SchedulerTaskConfig

SchedulerTask = Callable[[], None]


def get_background_tasks(config: Config):
"""
Returns a list of configured background tasks to be run at startup.
"""
tasks: List[Tuple[SchedulerTaskConfig, SchedulerTask]] = []

if not config.scheduler:
return []

if config.scheduler.calls:
# We add our tasks to the list of tasks to be run at startup if we find
# their config.
if (build_queue_cfg := config.scheduler.calls.build_queue):
tasks.append((build_queue_cfg, build_queue))

if (handle_queue_cfg := config.scheduler.calls.handle_queue):
tasks.append((handle_queue_cfg, handle_queue))

return [
repeat_every(
seconds=cfg.interval,
wait_first=cfg.wait_first,
)(func) for cfg, func in tasks]


__all__ = [
"get_background_tasks",
]
6 changes: 6 additions & 0 deletions server/dearmep/schedules/calls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
def build_queue() -> None:
pass


def handle_queue() -> None:
pass
18 changes: 17 additions & 1 deletion server/poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions server/pyproject.toml
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ canonicaljson = "^2.0.0"
phonenumbers = "^8.13.22"
pytz = "^2023.3.post1"
alembic = "^1.12.1"
fastapi-utils = "^0.2.1"

[tool.poetry.group.dev.dependencies]
flake8 = "^4.0.1"
Expand Down

0 comments on commit 519f5f5

Please sign in to comment.