Applied idiomatic Python improvements and micro-optimizations across registries and API layer.
CI / Formatting (push) Successful in 5s
CI / Linting (push) Successful in 5s
CI / Tests (Python 3.12) (push) Successful in 20s
CI / Tests (Python 3.13) (push) Successful in 19s
CI / Tests (Python 3.14) (push) Successful in 16s
CI / Type Checking (push) Successful in 9s
CI / Spelling (push) Successful in 5s

This commit is contained in:
2026-03-26 12:46:50 -04:00
parent 3cf93da28d
commit 0ddffe5e09
18 changed files with 192 additions and 172 deletions
+4 -4
View File
@@ -68,11 +68,11 @@ class CommandInfo:
requires_authenticated: bool = False
requires_moderator: bool = False
cooldown: int | float = 0
all_triggers: frozenset[str] = field(init=False, repr=False)
@property
def all_triggers(self) -> frozenset[str]:
"""All names that trigger this command (name + aliases)."""
return frozenset({self.name}) | self.aliases
def __post_init__(self) -> None:
"""Compute derived fields."""
self.all_triggers = frozenset({self.name}) | self.aliases
def on_command(
+17 -15
View File
@@ -321,7 +321,7 @@ class Config:
def load(self) -> None:
"""Load config from YAML, applying defaults for missing sections."""
logger.debug(f"Loading configuration from: {self.config_path.absolute()}")
logger.debug("Loading configuration from: %s", self.config_path.absolute())
if self.config_path.exists():
try:
with self.config_path.open() as f:
@@ -360,30 +360,32 @@ class Config:
logger.info("Generated webhook secret and saved to config file.")
logger.debug("Webhook Secret: [set]")
logger.debug(f"Server Bind Address: {self.host}:{self.port}")
logger.debug(f"Owncast Server URL: {self.owncast_url}")
logger.debug("Server Bind Address: %s:%s", self.host, self.port)
logger.debug("Owncast Server URL: %s", self.owncast_url)
logger.debug(
"Owncast Access Token: "
f"{'[set]' if self.owncast_access_token else '[unset]'}"
"Owncast Access Token: %s",
"[set]" if self.owncast_access_token else "[unset]",
)
logger.debug(f"Command Prefix: {self.command_prefix!r}")
logger.debug(f"Handler Timeout: {self.handler_timeout}s")
logger.debug(f"Storage Directory: {self.storage_dir}")
logger.debug(f"Modules Directory: {self.modules_dir}")
logger.debug(f"Log Directory: {self.log_dir or '[unset]'}")
logger.debug(f"Public Base URL: {self.public_base_url}")
logger.debug("Command Prefix: %r", self.command_prefix)
logger.debug("Handler Timeout: %ss", self.handler_timeout)
logger.debug("Storage Directory: %s", self.storage_dir)
logger.debug("Modules Directory: %s", self.modules_dir)
logger.debug("Log Directory: %s", self.log_dir or "[unset]")
logger.debug("Public Base URL: %s", self.public_base_url)
logger.debug(
f"Admin API Enabled: {'[set]' if self.admin_enabled else '[unset]'}"
"Admin API Enabled: %s",
"[set]" if self.admin_enabled else "[unset]",
)
if self.admin_enabled:
logger.debug(f"Admin API Username: {self.admin_username}")
logger.debug("Admin API Username: %s", self.admin_username)
logger.debug(
f"Admin API Password: {'[set]' if self.admin_password else '[unset]'}"
"Admin API Password: %s",
"[set]" if self.admin_password else "[unset]",
)
def save(self) -> None:
"""Write current configuration to the YAML file."""
logger.debug(f"Saving configuration to: {self.config_path.absolute()}")
logger.debug("Saving configuration to: %s", self.config_path.absolute())
try:
with self.config_path.open("w") as f:
yaml.safe_dump(
+26 -29
View File
@@ -23,6 +23,8 @@ from datetime import datetime
from enum import StrEnum
from typing import Any
_NANO_RE = re.compile(r"(\.\d{6})\d+")
logger = logging.getLogger("owlbot.events")
@@ -64,7 +66,7 @@ def _parse_timestamp(ts: str | None) -> datetime | None:
# Go's time.Time emits nanosecond-precision fractional seconds (9 digits),
# but Python's datetime only supports microseconds (6 digits). Truncate
# any excess fractional digits to avoid a ValueError from fromisoformat().
ts = re.sub(r"(\.\d{6})\d+", r"\1", ts)
ts = _NANO_RE.sub(r"\1", ts)
try:
return datetime.fromisoformat(ts)
@@ -86,7 +88,7 @@ class User:
name_changed_at: datetime | None
is_bot: bool
is_authenticated: bool
scopes: list[str]
scopes: frozenset[str]
@classmethod
def from_dict(cls, data: dict[str, Any]) -> User:
@@ -104,7 +106,7 @@ class User:
name_changed_at=_parse_timestamp(data.get("nameChangedAt")),
is_bot=data.get("isBot", False),
is_authenticated=data.get("authenticated", False),
scopes=data.get("scopes", []),
scopes=frozenset(data.get("scopes", ())),
)
@property
@@ -423,29 +425,24 @@ def log_event(event_type: EventType, event: Event) -> None:
:param event_type: The type of event being logged.
:param event: The parsed event instance.
"""
if isinstance(event, ChatEvent):
logger.info(
f"[{event_type}] {event.user.display_name} "
f"({event.message_id}): {event.body}"
)
elif isinstance(event, UserJoinedEvent):
logger.info(f"[{event_type}] {event.user.display_name} joined.")
elif isinstance(event, UserPartedEvent):
logger.info(f"[{event_type}] {event.user.display_name} parted.")
elif isinstance(event, NameChangedEvent):
logger.info(
f"[{event_type}] {event.user.display_name} "
f"changed name to {event.new_name}."
)
elif isinstance(event, StreamStartedEvent):
logger.info(f'[{event_type}] Stream started: "{event.stream_title}"')
elif isinstance(event, StreamStoppedEvent):
logger.info(f"[{event_type}] Stream ended.")
elif isinstance(event, StreamTitleUpdatedEvent):
logger.info(f'[{event_type}] Title changed to "{event.stream_title}"')
elif isinstance(event, VisibilityUpdateEvent):
action = "shown" if event.is_visible else "hidden"
ids = ", ".join(event.message_ids)
logger.info(
f"[{event_type}] {len(event.message_ids)} message(s) {action}: {ids}"
)
match event:
case ChatEvent(user=user, message_id=mid, body=body):
logger.info(f"[{event_type}] {user.display_name} ({mid}): {body}")
case UserJoinedEvent(user=user):
logger.info(f"[{event_type}] {user.display_name} joined.")
case UserPartedEvent(user=user):
logger.info(f"[{event_type}] {user.display_name} parted.")
case NameChangedEvent(user=user, new_name=new_name):
logger.info(
f"[{event_type}] {user.display_name} changed name to {new_name}."
)
case StreamStartedEvent(stream_title=title):
logger.info(f'[{event_type}] Stream started: "{title}"')
case StreamStoppedEvent():
logger.info(f"[{event_type}] Stream ended.")
case StreamTitleUpdatedEvent(stream_title=title):
logger.info(f'[{event_type}] Title changed to "{title}"')
case VisibilityUpdateEvent(is_visible=visible, message_ids=ids):
action = "shown" if visible else "hidden"
id_list = ", ".join(ids)
logger.info(f"[{event_type}] {len(ids)} message(s) {action}: {id_list}")
+5 -5
View File
@@ -84,7 +84,7 @@ class OwncastClient:
"Authorization": f"Bearer {access_token}"
}
self._auth: aiohttp.BasicAuth | None = None
self._logger.debug(f"Owncast API client initialized for: {self._base_url}")
self._logger.debug("Owncast API client initialized for: %s", self._base_url)
@property
def base_url(self) -> str:
@@ -207,7 +207,7 @@ class OwncastClient:
:raises OwncastError: If the request fails.
"""
url = f"{self._base_url}{endpoint}"
self._logger.debug(f"POST {endpoint}")
self._logger.debug("POST %s", endpoint)
try:
async with self._http.session.post(
url,
@@ -223,7 +223,7 @@ class OwncastClient:
f"Error {response.status} on POST {endpoint}: {message}"
)
raise OwncastError(response.status, message)
self._logger.debug(f"POST {endpoint} -> {response.status}")
self._logger.debug("POST %s -> %d", endpoint, response.status)
if response.content_type == "application/json":
try:
result = await response.json()
@@ -266,7 +266,7 @@ class OwncastClient:
:raises OwncastError: If the request fails.
"""
url = f"{self._base_url}{endpoint}"
self._logger.debug(f"GET {endpoint}")
self._logger.debug("GET %s", endpoint)
try:
async with self._http.session.get(
url,
@@ -282,7 +282,7 @@ class OwncastClient:
f"Error {response.status} on GET {endpoint}: {message}"
)
raise OwncastError(response.status, message)
self._logger.debug(f"GET {endpoint} -> {response.status}")
self._logger.debug("GET %s -> %d", endpoint, response.status)
try:
result = await response.json()
except (ValueError, ContentTypeError):
+16 -6
View File
@@ -141,7 +141,9 @@ class ModuleStorage:
:raises StorageError: If execution fails.
"""
async with self._connection() as conn:
self._logger.debug(f"Execute: {sql[:80]}{'...' if len(sql) > 80 else ''}")
self._logger.debug(
"Execute: %s%s", sql[:80], "..." if len(sql) > 80 else ""
)
try:
return await conn.execute(sql, parameters)
except aiosqlite.Error as e:
@@ -164,8 +166,10 @@ class ModuleStorage:
"""
async with self._connection() as conn:
self._logger.debug(
f"Execute many ({len(parameters)} rows): "
f"{sql[:80]}{'...' if len(sql) > 80 else ''}"
"Execute many (%d rows): %s%s",
len(parameters),
sql[:80],
"..." if len(sql) > 80 else "",
)
try:
return await conn.executemany(sql, parameters)
@@ -187,7 +191,9 @@ class ModuleStorage:
:raises StorageError: If execution fails.
"""
async with self._connection() as conn:
self._logger.debug(f"Fetch one: {sql[:80]}{'...' if len(sql) > 80 else ''}")
self._logger.debug(
"Fetch one: %s%s", sql[:80], "..." if len(sql) > 80 else ""
)
try:
cursor = await conn.execute(sql, parameters)
return await cursor.fetchone()
@@ -209,7 +215,9 @@ class ModuleStorage:
:raises StorageError: If execution fails.
"""
async with self._connection() as conn:
self._logger.debug(f"Fetch all: {sql[:80]}{'...' if len(sql) > 80 else ''}")
self._logger.debug(
"Fetch all: %s%s", sql[:80], "..." if len(sql) > 80 else ""
)
try:
cursor = await conn.execute(sql, parameters)
return list(await cursor.fetchall())
@@ -243,7 +251,9 @@ class ModuleStorage:
await conn.execute("PRAGMA foreign_keys = ON")
self._all_connections.append(conn)
self._logger.debug(
f"Pool connection created ({len(self._all_connections)}/{self._pool_size})."
"Pool connection created (%d/%d).",
len(self._all_connections),
self._pool_size,
)
return conn