Added pydocstyle (D) rules to Ruff and fixed all violations.
CI / Formatting (push) Successful in 12s
CI / Linting (push) Successful in 13s
CI / Tests (Python 3.12) (push) Successful in 26s
CI / Tests (Python 3.13) (push) Successful in 25s
CI / Tests (Python 3.14) (push) Successful in 25s
CI / Type Checking (push) Successful in 26s
CI / Formatting (push) Successful in 12s
CI / Linting (push) Successful in 13s
CI / Tests (Python 3.12) (push) Successful in 26s
CI / Tests (Python 3.13) (push) Successful in 25s
CI / Tests (Python 3.14) (push) Successful in 25s
CI / Type Checking (push) Successful in 26s
This commit is contained in:
@@ -56,8 +56,7 @@ __all__ = [
|
||||
|
||||
@on_setup
|
||||
async def setup(ctx: ModuleContext) -> None:
|
||||
"""
|
||||
Initialize the timers module.
|
||||
"""Initialize the timers module.
|
||||
|
||||
Creates the database schema, initializes chat counters for enabled timers,
|
||||
and starts the background scheduler.
|
||||
@@ -97,8 +96,7 @@ async def setup(ctx: ModuleContext) -> None:
|
||||
|
||||
@on_teardown
|
||||
async def teardown(ctx: ModuleContext) -> None:
|
||||
"""
|
||||
Clean up the timers module.
|
||||
"""Clean up the timers module.
|
||||
|
||||
Stops the background scheduler task.
|
||||
|
||||
|
||||
@@ -32,8 +32,7 @@ from .scheduler import get_scheduler
|
||||
|
||||
@on_event(EventType.CHAT, priority=Priority.LOWEST)
|
||||
async def count_chat_message(ctx: EventContext[ChatEvent]) -> None:
|
||||
"""
|
||||
Count a chat message for all tracked timers.
|
||||
"""Count a chat message for all tracked timers.
|
||||
|
||||
Runs at lowest priority so all other CHAT handlers (moderation, etc.)
|
||||
execute first. Skips bot messages and hidden messages.
|
||||
@@ -60,8 +59,7 @@ async def count_chat_message(ctx: EventContext[ChatEvent]) -> None:
|
||||
|
||||
@on_event(EventType.VISIBILITY_UPDATE, priority=Priority.LOWEST)
|
||||
async def handle_visibility_update(ctx: EventContext[VisibilityUpdateEvent]) -> None:
|
||||
"""
|
||||
Remove hidden messages from chat counts.
|
||||
"""Remove hidden messages from chat counts.
|
||||
|
||||
Only handles the hide case. Un-hiding does not re-add messages because
|
||||
we cannot distinguish previously counted user messages from bot messages
|
||||
|
||||
@@ -33,8 +33,7 @@ _NAME_PATTERN = re.compile(r"^[a-zA-Z_][a-zA-Z0-9_]{0,31}$")
|
||||
|
||||
|
||||
async def _resolve_timer(ctx: CommandContext, identifier: str) -> aiosqlite.Row | None:
|
||||
"""
|
||||
Resolve a timer by numeric ID or name.
|
||||
"""Resolve a timer by numeric ID or name.
|
||||
|
||||
Tries parsing as an integer first, then falls back to a name lookup.
|
||||
|
||||
@@ -66,8 +65,7 @@ async def _resolve_timer(ctx: CommandContext, identifier: str) -> aiosqlite.Row
|
||||
|
||||
|
||||
def _timer_display(row: aiosqlite.Row) -> str:
|
||||
"""
|
||||
Format a timer's display identifier.
|
||||
"""Format a timer's display identifier.
|
||||
|
||||
:param row: Timer database row.
|
||||
:return: Display string like "timer_name (#3)" or "timer #3".
|
||||
@@ -79,8 +77,7 @@ def _timer_display(row: aiosqlite.Row) -> str:
|
||||
|
||||
@on_command("addtimer", requires_moderator=True)
|
||||
async def addtimer(ctx: CommandContext) -> None:
|
||||
"""
|
||||
Create a new empty timer with an optional name.
|
||||
"""Create a new empty timer with an optional name.
|
||||
|
||||
Usage: !addtimer [name]
|
||||
|
||||
@@ -123,8 +120,7 @@ async def addtimer(ctx: CommandContext) -> None:
|
||||
|
||||
@on_command("settimermessage", requires_moderator=True)
|
||||
async def settimermessage(ctx: CommandContext) -> None:
|
||||
"""
|
||||
Set the message text for a timer.
|
||||
"""Set the message text for a timer.
|
||||
|
||||
Usage: !settimermessage <id|name> <message>
|
||||
|
||||
@@ -164,8 +160,7 @@ async def settimermessage(ctx: CommandContext) -> None:
|
||||
|
||||
@on_command("settimerinterval", requires_moderator=True)
|
||||
async def settimerinterval(ctx: CommandContext) -> None:
|
||||
"""
|
||||
Set the interval for a timer.
|
||||
"""Set the interval for a timer.
|
||||
|
||||
Accepts simple durations (15m, 1h30m) or cron expressions (*/15 * * * *).
|
||||
|
||||
@@ -212,8 +207,7 @@ async def settimerinterval(ctx: CommandContext) -> None:
|
||||
|
||||
@on_command("settimerlines", requires_moderator=True)
|
||||
async def settimerlines(ctx: CommandContext) -> None:
|
||||
"""
|
||||
Set the minimum chat lines between timer firings.
|
||||
"""Set the minimum chat lines between timer firings.
|
||||
|
||||
Usage: !settimerlines <id|name> <count>
|
||||
|
||||
@@ -260,8 +254,7 @@ async def settimerlines(ctx: CommandContext) -> None:
|
||||
|
||||
@on_command("enabletimer", requires_moderator=True)
|
||||
async def enabletimer(ctx: CommandContext) -> None:
|
||||
"""
|
||||
Enable a timer.
|
||||
"""Enable a timer.
|
||||
|
||||
Won't enable a timer that has no message set.
|
||||
|
||||
@@ -310,8 +303,7 @@ async def enabletimer(ctx: CommandContext) -> None:
|
||||
|
||||
@on_command("disabletimer", requires_moderator=True)
|
||||
async def disabletimer(ctx: CommandContext) -> None:
|
||||
"""
|
||||
Disable a timer.
|
||||
"""Disable a timer.
|
||||
|
||||
Usage: !disabletimer <id|name>
|
||||
|
||||
@@ -351,8 +343,7 @@ async def disabletimer(ctx: CommandContext) -> None:
|
||||
|
||||
@on_command("deletetimer", requires_moderator=True)
|
||||
async def deletetimer(ctx: CommandContext) -> None:
|
||||
"""
|
||||
Permanently delete a timer.
|
||||
"""Permanently delete a timer.
|
||||
|
||||
Usage: !deletetimer <id|name>
|
||||
|
||||
@@ -382,8 +373,7 @@ async def deletetimer(ctx: CommandContext) -> None:
|
||||
|
||||
@on_command("listtimers", requires_moderator=True, cooldown=15)
|
||||
async def listtimers(ctx: CommandContext) -> None:
|
||||
"""
|
||||
Send the URL to the timer list web page.
|
||||
"""Send the URL to the timer list web page.
|
||||
|
||||
Usage: !listtimers
|
||||
|
||||
|
||||
@@ -31,8 +31,7 @@ _jinja_env = jinja2.Environment(
|
||||
|
||||
@on_route("/list", methods=["GET"])
|
||||
async def timer_list_page(ctx: RouteContext) -> web.Response:
|
||||
"""
|
||||
Serve an HTML page listing all timers in a table.
|
||||
"""Serve an HTML page listing all timers in a table.
|
||||
|
||||
Columns: #, Name, Message, Interval, Min Lines, Status, Last Fired.
|
||||
Accessible at /owlbot/timers/list.
|
||||
|
||||
@@ -51,8 +51,7 @@ _MIN_CRON_MINUTES = 1
|
||||
|
||||
|
||||
def parse_interval(value: str) -> tuple[IntervalType, str]:
|
||||
"""
|
||||
Parse and validate an interval string.
|
||||
"""Parse and validate an interval string.
|
||||
|
||||
Simple durations (no spaces) are parsed with a regex. Cron expressions
|
||||
(contain spaces) are validated with cronsim. Returns the detected type
|
||||
@@ -72,8 +71,7 @@ def parse_interval(value: str) -> tuple[IntervalType, str]:
|
||||
|
||||
|
||||
def _parse_simple(value: str) -> tuple[IntervalType, str]:
|
||||
"""
|
||||
Parse and validate a simple duration string.
|
||||
"""Parse and validate a simple duration string.
|
||||
|
||||
:param value: Duration string like "30s", "5m", "1h30m".
|
||||
:return: Tuple of (IntervalType.SIMPLE, value).
|
||||
@@ -99,8 +97,7 @@ def _parse_simple(value: str) -> tuple[IntervalType, str]:
|
||||
|
||||
|
||||
def _parse_cron(value: str) -> tuple[IntervalType, str]:
|
||||
"""
|
||||
Validate a cron expression.
|
||||
"""Validate a cron expression.
|
||||
|
||||
:param value: Cron expression string (5 fields).
|
||||
:return: Tuple of (IntervalType.CRON, value).
|
||||
@@ -125,8 +122,7 @@ def _parse_cron(value: str) -> tuple[IntervalType, str]:
|
||||
|
||||
|
||||
def _duration_to_seconds(value: str) -> int:
|
||||
"""
|
||||
Convert a validated simple duration string to total seconds.
|
||||
"""Convert a validated simple duration string to total seconds.
|
||||
|
||||
:param value: A previously validated duration string.
|
||||
:return: Total seconds.
|
||||
@@ -141,8 +137,7 @@ def _duration_to_seconds(value: str) -> int:
|
||||
|
||||
|
||||
def _next_fire_time(row: aiosqlite.Row, now: datetime) -> datetime | None:
|
||||
"""
|
||||
Compute when a timer will next be time-due.
|
||||
"""Compute when a timer will next be time-due.
|
||||
|
||||
:param row: Database row with interval_type, interval_value, last_fired_at.
|
||||
:param now: Current UTC time.
|
||||
@@ -167,8 +162,7 @@ def _next_fire_time(row: aiosqlite.Row, now: datetime) -> datetime | None:
|
||||
|
||||
|
||||
def _is_timer_due(row: aiosqlite.Row, now: datetime) -> bool:
|
||||
"""
|
||||
Check whether a timer should fire based on its interval and last fire time.
|
||||
"""Check whether a timer should fire based on its interval and last fire time.
|
||||
|
||||
:param row: Database row with interval_type, interval_value, last_fired_at.
|
||||
:param now: Current UTC time.
|
||||
@@ -187,6 +181,7 @@ class TimerScheduler:
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize an empty scheduler with no running task."""
|
||||
self._task: asyncio.Task[None] | None = None
|
||||
self._counted_ids: dict[int, set[str]] = {}
|
||||
self._wake_event: asyncio.Event | None = None
|
||||
@@ -197,8 +192,7 @@ class TimerScheduler:
|
||||
return self._counted_ids
|
||||
|
||||
def init_counted_ids(self, timer_ids: list[int]) -> None:
|
||||
"""
|
||||
Initialize empty counter sets for the given timer IDs.
|
||||
"""Initialize empty counter sets for the given timer IDs.
|
||||
|
||||
Called during module setup to prepare tracking for enabled timers.
|
||||
|
||||
@@ -207,8 +201,7 @@ class TimerScheduler:
|
||||
self._counted_ids = {tid: set() for tid in timer_ids}
|
||||
|
||||
def start(self, ctx: ModuleContext) -> None:
|
||||
"""
|
||||
Start the background scheduler task.
|
||||
"""Start the background scheduler task.
|
||||
|
||||
:param ctx: The module context.
|
||||
"""
|
||||
@@ -219,8 +212,7 @@ class TimerScheduler:
|
||||
ctx.logger.debug("Timer scheduler started.")
|
||||
|
||||
async def stop(self, ctx: ModuleContext) -> None:
|
||||
"""
|
||||
Cancel the background scheduler task and wait for it to exit.
|
||||
"""Cancel the background scheduler task and wait for it to exit.
|
||||
|
||||
:param ctx: The module context.
|
||||
"""
|
||||
@@ -233,8 +225,7 @@ class TimerScheduler:
|
||||
ctx.logger.info("Timer scheduler stopped.")
|
||||
|
||||
def reschedule(self) -> None:
|
||||
"""
|
||||
Wake the scheduler so it recalculates the next fire time.
|
||||
"""Wake the scheduler so it recalculates the next fire time.
|
||||
|
||||
Called by timer management commands when timer state changes.
|
||||
"""
|
||||
@@ -242,8 +233,7 @@ class TimerScheduler:
|
||||
self._wake_event.set()
|
||||
|
||||
async def _scheduler_loop(self, ctx: ModuleContext, wake: asyncio.Event) -> None:
|
||||
"""
|
||||
Background loop that sleeps until the next timer is due and fires it.
|
||||
"""Background loop that sleeps until the next timer is due and fires it.
|
||||
|
||||
Uses an asyncio.Event to allow early wake-ups when timer state changes.
|
||||
|
||||
@@ -283,8 +273,7 @@ class TimerScheduler:
|
||||
ctx.logger.exception("Scheduler tick failed.")
|
||||
|
||||
async def _tick(self, ctx: ModuleContext) -> None:
|
||||
"""
|
||||
Single scheduler tick: query enabled timers and fire any that are due.
|
||||
"""Single scheduler tick: query enabled timers and fire any that are due.
|
||||
|
||||
:param ctx: The module context.
|
||||
"""
|
||||
@@ -342,8 +331,7 @@ class TimerScheduler:
|
||||
|
||||
|
||||
async def _compute_next_delay(ctx: ModuleContext) -> tuple[float | None, str | None]:
|
||||
"""
|
||||
Query enabled timers and return seconds until the soonest one is time-due.
|
||||
"""Query enabled timers and return seconds until the soonest one is time-due.
|
||||
|
||||
:param ctx: The module context.
|
||||
:return: Tuple of (seconds until next timer, display name of that timer),
|
||||
@@ -394,8 +382,7 @@ _scheduler: TimerScheduler | None = None
|
||||
|
||||
|
||||
def get_scheduler() -> TimerScheduler:
|
||||
"""
|
||||
Return the active scheduler instance.
|
||||
"""Return the active scheduler instance.
|
||||
|
||||
:return: The active TimerScheduler.
|
||||
:raises RuntimeError: If the scheduler has not been initialized.
|
||||
@@ -406,8 +393,7 @@ def get_scheduler() -> TimerScheduler:
|
||||
|
||||
|
||||
def set_scheduler(scheduler: TimerScheduler) -> None:
|
||||
"""
|
||||
Set the active scheduler instance.
|
||||
"""Set the active scheduler instance.
|
||||
|
||||
:param scheduler: The TimerScheduler to install.
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user