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
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:
@@ -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
@@ -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
@@ -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}")
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user