Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Pin the exact patch release and Debian suite. A new tag invalidates the CI
# build cache, so the apt-get upgrade below runs again and picks up security fixes.
FROM python:3.14.9-slim-trixie AS base
FROM python:3.14.10-slim-trixie AS base

RUN apt-get update && apt-get upgrade -y && apt-get install -y --no-install-recommends ca-certificates wget && rm -rf /var/lib/apt/lists/*

Expand Down
4 changes: 1 addition & 3 deletions docs/CLAUDE_CODE_REVIEW.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,7 @@ params["Service"] = [service_records[0]["id"]] # IndexError if no records found
```python
res_json = await self.get(url, params=params)
records = res_json["records"] # KeyError if API returns error response
self.record_id_to_name[table_name] = {
record["id"]: record["fields"]["Name"] for record in records
}
self.record_id_to_name[table_name] = {record["id"]: record["fields"]["Name"] for record in records}
```

**Problem:** Airtable API error responses don't include `records` key. Also assumes all records have a `Name` field.
Expand Down
4 changes: 1 addition & 3 deletions docs/UPGRADE_ANALYSIS.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,7 @@ if not asyncio.iscoroutinefunction(handler):
#### asyncio.get_event_loop() Behavior
```python
# sirbot/bot.py current code:
self["http_session"] = aiohttp.ClientSession(
loop=kwargs.get("loop") or asyncio.get_event_loop()
)
self["http_session"] = aiohttp.ClientSession(loop=kwargs.get("loop") or asyncio.get_event_loop())
```
**Impact**: `asyncio.get_event_loop()` deprecation warnings in 3.10+, different behavior in 3.12.
**Fix**: Remove explicit loop passing; aiohttp 3.9+ handles this automatically.
Expand Down
79 changes: 34 additions & 45 deletions docs/UPGRADE_PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -241,14 +241,12 @@ class TestAsyncHandlers:
for handler in handlers:
# Get the wrapped function if decorated
func = getattr(handler, "__wrapped__", handler)
assert asyncio.iscoroutinefunction(func), \
f"{handler.__name__} must be async"
assert asyncio.iscoroutinefunction(func), f"{handler.__name__} must be async"

def test_event_handlers_are_async(self):
from pybot.endpoints.slack.events import team_join

assert asyncio.iscoroutinefunction(team_join), \
"team_join must be async"
assert asyncio.iscoroutinefunction(team_join), "team_join must be async"

def test_action_handlers_are_async(self):
from pybot.endpoints.slack.actions.general_actions import (
Expand All @@ -259,20 +257,21 @@ class TestAsyncHandlers:

handlers = [claimed, delete_message, reset_claim]
for handler in handlers:
assert asyncio.iscoroutinefunction(handler), \
f"{handler.__name__} must be async"
assert asyncio.iscoroutinefunction(handler), f"{handler.__name__} must be async"


class TestPluginLoading:
"""Verify plugins can be instantiated."""

def test_airtable_plugin_instantiates(self):
from pybot.plugins.airtable.plugin import AirtablePlugin

plugin = AirtablePlugin()
assert plugin.__name__ == "airtable"

def test_api_plugin_instantiates(self):
from pybot.plugins.api.plugin import APIPlugin

plugin = APIPlugin()
assert plugin.__name__ == "api"
```
Expand Down Expand Up @@ -343,9 +342,7 @@ class TestLunchCommandResponse:

mock_location = {
"name": "Test Restaurant",
"location": {
"display_address": ["123 Main St", "Los Angeles, CA 90210"]
}
"location": {"display_address": ["123 Main St", "Los Angeles, CA 90210"]},
}

response = cmd._build_response_text(mock_location)
Expand Down Expand Up @@ -379,29 +376,29 @@ import pytest
class TestRollParsing:
"""Test dice roll input parsing."""

@pytest.mark.parametrize("input_text,expected_valid", [
("1d6", True),
("2d10", True),
("10d20", True),
("1D6", True), # Case insensitive
("0d6", False), # Zero dice
("11d6", False), # Too many dice
("1d0", False), # Zero sides
("1d21", False), # Too many sides
("abc", False), # Not a dice format
("1d", False), # Incomplete
("d6", False), # Missing count
])
@pytest.mark.parametrize(
"input_text,expected_valid",
[
("1d6", True),
("2d10", True),
("10d20", True),
("1D6", True), # Case insensitive
("0d6", False), # Zero dice
("11d6", False), # Too many dice
("1d0", False), # Zero sides
("1d21", False), # Too many sides
("abc", False), # Not a dice format
("1d", False), # Incomplete
("d6", False), # Missing count
],
)
def test_roll_input_validation(self, input_text, expected_valid):
"""Test various roll command inputs."""
try:
text = input_text.lower()
numdice, typedice = [int(num) for num in text.split("d")]
valid = (
0 < numdice <= 10 and
0 < typedice <= 20
)
except (ValueError, IndexError):
valid = 0 < numdice <= 10 and 0 < typedice <= 20
except ValueError, IndexError:
valid = False

assert valid == expected_valid
Expand Down Expand Up @@ -629,6 +626,7 @@ class SlackAPI(_SlackAPI):
self._session = session
super().__init__(**kwargs)


# This should work as-is, but check for any loop= usage
```

Expand Down Expand Up @@ -875,9 +873,7 @@ class SlackPlugin:
handler = _ensure_async(handler)

if admin and not self.admins:
LOG.warning(
"Slack admin IDs are not set. Admin-limited endpoints will not work."
)
LOG.warning("Slack admin IDs are not set. Admin-limited endpoints will not work.")

configuration = {"mention": mention, "admin": admin, "wait": wait}
self.routers["message"].register(
Expand Down Expand Up @@ -906,9 +902,7 @@ class SlackPlugin:
"""Register handler for a block_actions type action."""
handler = _ensure_async(handler)
configuration = {"wait": wait}
self.routers["action"].register_block_action(
block_id, (handler, configuration), action_id
)
self.routers["action"].register_block_action(block_id, (handler, configuration), action_id)

def on_dialog_submission(
self,
Expand All @@ -919,14 +913,10 @@ class SlackPlugin:
"""Register handler for a dialog_submission type action."""
handler = _ensure_async(handler)
configuration = {"wait": wait}
self.routers["action"].register_dialog_submission(
callback_id, (handler, configuration)
)
self.routers["action"].register_dialog_submission(callback_id, (handler, configuration))

async def find_bot_id(self, app: Any) -> None:
rep = await self.api.query(
url=methods.USERS_INFO, data={"user": self.bot_user_id}
)
rep = await self.api.query(url=methods.USERS_INFO, data={"user": self.bot_user_id})
self.bot_id = rep["user"]["profile"]["bot_id"]
LOG.warning(
'`SLACK_BOT_ID` not set. For a faster start time set it to: "%s"',
Expand All @@ -938,6 +928,7 @@ class SlackPlugin:

```python
"""Slack plugin for sirbot."""

from pybot._vendor.sirbot.plugins.slack.plugin import SlackPlugin

__all__ = ["SlackPlugin"]
Expand All @@ -950,6 +941,7 @@ __all__ = ["SlackPlugin"]
Vendored sirbot library for Python 3.12+
Original: https://github.com/pyslackers/sir-bot-a-lot-2
"""

from pybot._vendor.sirbot.bot import SirBot

__all__ = ["SirBot"]
Expand Down Expand Up @@ -1083,6 +1075,7 @@ async def bot() -> SirBot:
b = SirBot()
# Manually create session for testing (normally done on startup)
import aiohttp

b["http_session"] = aiohttp.ClientSession()

slack = SlackPlugin(
Expand Down Expand Up @@ -1299,12 +1292,8 @@ class APIPlugin:
def load(self, sirbot):
self.session = sirbot.http_session

sirbot.router.add_route(
"GET", "/pybot/api/v1/slack/{resource}", endpoints.slack_api
)
sirbot.router.add_route(
"POST", "/pybot/api/v1/slack/{resource}", endpoints.slack_api
)
sirbot.router.add_route("GET", "/pybot/api/v1/slack/{resource}", endpoints.slack_api)
sirbot.router.add_route("POST", "/pybot/api/v1/slack/{resource}", endpoints.slack_api)

def on_get(self, request, handler, **kwargs):
if not asyncio.iscoroutinefunction(handler):
Expand Down
Loading
Loading