aboutsummaryrefslogtreecommitdiff
path: root/webmentions_ssg/tasks
diff options
context:
space:
mode:
Diffstat (limited to 'webmentions_ssg/tasks')
-rw-r--r--webmentions_ssg/tasks/extension.py102
-rw-r--r--webmentions_ssg/tasks/scanner.py1
2 files changed, 83 insertions, 20 deletions
diff --git a/webmentions_ssg/tasks/extension.py b/webmentions_ssg/tasks/extension.py
index 95f190f..25f8e47 100644
--- a/webmentions_ssg/tasks/extension.py
+++ b/webmentions_ssg/tasks/extension.py
@@ -2,22 +2,50 @@
#
# SPDX-License-Identifier: BSD-3-Clause
+from collections.abc import Callable
from functools import wraps
-from typing import Any, Callable
+from typing import Any, ParamSpec, TypeVar
from urllib.parse import urlsplit, urlunsplit
from flask import Flask
+from huey import Huey as BaseHuey
+from huey.api import TaskWrapper
+
+P = ParamSpec("P")
+R = TypeVar("R")
class Huey:
- def __init__(self, app: Flask | None = None):
+ """
+ Provide Flask integration for a Huey instance.
+
+ The extension initializes a Huey backend from the Flask configuration and
+ wraps tasks so that they execute within an application context.
+ """
+
+ def __init__(self, app: Flask | None = None) -> None:
+ """
+ Initialize the Huey extension.
+
+ :param app: Flask application to initialize immediately, if provided.
+ """
self.app: Flask | None = None
- self._huey = None
+ self._huey: BaseHuey | None = None
if app is not None:
self.init_app(app)
- def init_app(self, app: Flask):
+ def init_app(self, app: Flask) -> None:
+ """
+ Initialize Huey for a Flask application.
+
+ The backend and its storage options are derived from ``HUEY_URL`` and the
+ resulting extension is registered with the application.
+
+ :param app: Flask application to initialize.
+ :raises RuntimeError: If the configured Huey backend URL is invalid or uses
+ an unsupported scheme.
+ """
config: dict[str, Any] = {
"name": app.import_name,
"results": True,
@@ -36,7 +64,13 @@ class Huey:
app.extensions["huey"] = self
@property
- def huey(self):
+ def huey(self) -> BaseHuey:
+ """
+ Return the initialized Huey instance.
+
+ :return: Configured Huey backend instance.
+ :raises RuntimeError: If the extension has not been initialized.
+ """
if self._huey is None:
raise RuntimeError(
"Huey has not been initialized. "
@@ -44,10 +78,20 @@ class Huey:
)
return self._huey
- def task(self, *task_args: Any, **task_kwargs: Any):
- def decorator(func: Callable):
+ def task(
+ self, *task_args: Any, **task_kwargs: Any
+ ) -> Callable[[Callable[P, object]], TaskWrapper]:
+ """
+ Create a Huey task that runs within the Flask application context.
+
+ :param task_args: Positional arguments forwarded to Huey's task decorator.
+ :param task_kwargs: Keyword arguments forwarded to Huey's task decorator.
+ :return: Decorator that registers the wrapped function as a Huey task.
+ """
+
+ def decorator(func: Callable[P, R]) -> TaskWrapper:
@wraps(func)
- def wrapper(*args: Any, **kwargs: Any):
+ def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
if self.app is None:
raise RuntimeError("Flask app is not available.")
@@ -58,10 +102,23 @@ class Huey:
return decorator
- def periodic_task(self, *task_args: Any, **task_kwargs: Any):
- def decorator(func: Callable):
+ def periodic_task(
+ self, *task_args: Any, **task_kwargs: Any
+ ) -> Callable[[Callable[P, object]], TaskWrapper]:
+ """
+ Create a periodic Huey task that runs within the Flask application context.
+
+ :param task_args: Positional arguments forwarded to Huey's periodic task
+ decorator.
+ :param task_kwargs: Keyword arguments forwarded to Huey's periodic task
+ decorator.
+ :return: Decorator that registers the wrapped function as a periodic Huey
+ task.
+ """
+
+ def decorator(func: Callable[P, R]) -> TaskWrapper:
@wraps(func)
- def wrapper(*args: Any, **kwargs: Any):
+ def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
if self.app is None:
raise RuntimeError("Flask app is not available.")
@@ -72,23 +129,30 @@ class Huey:
return decorator
- def __getattr__(self, name: str):
+ def __getattr__(self, name: str) -> Any:
"""
- Forward unknown attributes to the real Huey instance.
+ Forward an unknown attribute to the underlying Huey instance.
- This lets you still use things like:
- huey.enqueue(...)
- huey.scheduled()
- huey.pending()
+ :param name: Name of the attribute to retrieve.
+ :return: Attribute from the initialized Huey instance.
+ :raises RuntimeError: If the extension has not been initialized.
"""
return getattr(self.huey, name)
@staticmethod
- def backend_from_url(url: str) -> tuple[Any, dict[str, str]]:
+ def backend_from_url(url: str) -> tuple[type[BaseHuey], dict[str, Any]]:
+ """
+ Determine the Huey backend and storage options from a URL.
+
+ :param url: Huey backend URL.
+ :return: Huey backend class and keyword arguments for its storage backend.
+ :raises RuntimeError: If the URL is malformed for the selected backend or
+ uses an unsupported scheme.
+ """
parsed = urlsplit(url)
scheme = parsed.scheme.lower()
- if scheme.startswith("redis") or scheme.startswith("rediss"):
+ if scheme.startswith(("redis", "rediss")):
fixed_url = urlunsplit(parsed._replace(scheme=scheme.split("+", 1)[0]))
if scheme.endswith("priority+expire"):
diff --git a/webmentions_ssg/tasks/scanner.py b/webmentions_ssg/tasks/scanner.py
index 0cf038a..17898ba 100644
--- a/webmentions_ssg/tasks/scanner.py
+++ b/webmentions_ssg/tasks/scanner.py
@@ -488,7 +488,6 @@ def scan_sources() -> None:
if webmention.sent_revision is None:
webmention.processed_revision = source.revision
- # Persist the desired state before queueing any work.
db.session.commit()
pending_count = 0