Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom endpoints for AsyncSocketModeHandler #622

Closed
ImRohan01 opened this issue Mar 22, 2022 · 8 comments
Closed

Custom endpoints for AsyncSocketModeHandler #622

ImRohan01 opened this issue Mar 22, 2022 · 8 comments
Labels
area:adapter area:async area:examples issues related to example or sample code area:sync docs Improvements or additions to documentation
Milestone

Comments

@ImRohan01
Copy link

I want to deploy a slack app within a container in K8s with AsyncApp client and AsyncSocketModeHandler as shown in https://github.com/slackapi/bolt-python/blob/main/examples/socket_mode_async.py. I wanted to understand how we could create custom endpoints like one for health check.

@seratch seratch added docs Improvements or additions to documentation area:async area:adapter area:sync area:examples issues related to example or sample code labels Mar 23, 2022
@seratch seratch added this to the 1.13.1 milestone Mar 23, 2022
seratch added a commit to seratch/bolt-python that referenced this issue Mar 23, 2022
@seratch
Copy link
Member

seratch commented Mar 23, 2022

Hi @ImRohan01 thanks for asking the question!

If you are fine to use the AIOHTTP's web server feature, checking the example code in the PR https://github.com/slackapi/bolt-python/pull/623/files should be helpful for you. AsyncApp depends on AIOHTTP's client feature. If you go with its server feature as my example does, you don't need to install any extra dependency for it.

I hope this was helpful to you! Is everything clear now?

@ImRohan01
Copy link
Author

Hi @seratch thanks for the quick response. I understand the approach and have been able to follow it for my use case. I am using FastAPI in place of aiohttp. Things seem to be working fine except for while shutting down, I get this error:

Failed to check the current session (s_id) or reconnect to the server (error: CancelledError, message: )
ERROR:    Traceback (most recent call last):
  File "path_to_proj/src/.venv/lib/python3.7/site-packages/starlette/routing.py", line 624, in lifespan
    await receive()
  File "path_to_proj/src/.venv/lib/python3.7/site-packages/uvicorn/lifespan/on.py", line 135, in receive
    return await self.receive_queue.get()
  File "/usr/lib64/python3.7/asyncio/queues.py", line 159, in get
    await getter
concurrent.futures._base.CancelledError

unhandled exception during asyncio.run() shutdown
task: <Task finished coro=<SocketModeClient.receive_messages() done, defined at path_to_proj/src/.venv/lib/python3.7/site-packages/slack_sdk/socket_mode/aiohttp/__init__.py:231> exception=UnboundLocalError("local variable 'type' referenced before assignment")>
Traceback (most recent call last):
  File "path_to_proj/src/.venv/lib/python3.7/site-packages/slack_sdk/socket_mode/aiohttp/__init__.py", line 254, in receive_messages
    message: WSMessage = await session.receive()
  File "path_to_proj/src/.venv/lib64/python3.7/site-packages/aiohttp/client_ws.py", line 229, in receive
    msg = await self._reader.read()
  File "path_to_proj/src/.venv/lib64/python3.7/site-packages/aiohttp/streams.py", line 657, in read
    return await super().read()
  File "path_to_proj/src/.venv/lib64/python3.7/site-packages/aiohttp/streams.py", line 616, in read
    await self._waiter
concurrent.futures._base.CancelledError

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "path_to_proj/src/.venv/lib/python3.7/site-packages/slack_sdk/socket_mode/aiohttp/__init__.py", line 334, in receive_messages
    f"Failed to receive or enqueue a message: {type(e).__name__}, {e} ({session_id})"
UnboundLocalError: local variable 'type' referenced before assignment
unhandled exception during asyncio.run() shutdown
task: <Task finished coro=<SocketModeClient.monitor_current_session() done, defined at path_to_proj/src/.venv/lib/python3.7/site-packages/slack_sdk/socket_mode/aiohttp/__init__.py:144> exception=CancelledError()>
concurrent.futures._base.CancelledError
Unclosed client session
client_session: <aiohttp.client.ClientSession object at 0x7f2f47f3b410>
Exception ignored in: <function ClientResponse.__del__ at 0x7f2f7924a5f0>
Traceback (most recent call last):
  File "path_to_proj/src/.venv/lib64/python3.7/site-packages/aiohttp/client_reqrep.py", line 810, in __del__
    self._connection.release()
  File "path_to_proj/src/.venv/lib64/python3.7/site-packages/aiohttp/connector.py", line 179, in release
    self._key, self._protocol, should_close=self._protocol.should_close
  File "path_to_proj/src/.venv/lib64/python3.7/site-packages/aiohttp/connector.py", line 665, in _release
    protocol.close()
  File "path_to_proj/src/.venv/lib64/python3.7/site-packages/aiohttp/client_proto.py", line 63, in close
    transport.close()
  File "uvloop/sslproto.pyx", line 51, in uvloop.loop._SSLProtocolTransport.close
  File "uvloop/sslproto.pyx", line 572, in uvloop.loop.SSLProtocol._start_shutdown
  File "uvloop/loop.pyx", line 1313, in uvloop.loop.Loop.call_later
  File "uvloop/loop.pyx", line 703, in uvloop.loop.Loop._check_closed
RuntimeError: Event loop is closed
Task was destroyed but it is pending!
task: <Task pending coro=<SocketModeClient.monitor_current_session() done, defined at path_to_proj/src/.venv/lib/python3.7/site-packages/slack_sdk/socket_mode/aiohttp/__init__.py:144> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x7f2f45cba110>()]>>
Task was destroyed but it is pending!
task: <Task pending coro=<SocketModeClient.receive_messages() done, defined at path_to_proj/src/.venv/lib/python3.7/site-packages/slack_sdk/socket_mode/aiohttp/__init__.py:231> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x7f2f45cba790>()]>>

I am not sure if this is related to slack bolt or fastapi or uvicorn as such. But any help would be appreciated.

Thanks!

@seratch
Copy link
Member

seratch commented Mar 23, 2022

@ImRohan01 It seems that FastAPI also has startup callbacks. Can you try adding an async function to start a Socket Mode client in the same way? https://fastapi.tiangolo.com/ru/advanced/events/

@ImRohan01
Copy link
Author

@seratch This is my code for reference, I had been using the startup event all this time.

import os
from typing import Optional
from slack_bolt.async_app import AsyncApp
from slack_sdk.socket_mode.aiohttp import SocketModeClient
from slack_bolt.adapter.socket_mode.async_handler import AsyncSocketModeHandler
from fastapi import FastAPI

SLACK_APP_TOKEN = os.getenv("SLACK_APP_TOKEN")
SLACK_BOT_TOKEN = os.getenv("SLACK_BOT_TOKEN")

app = AsyncApp(token=SLACK_BOT_TOKEN)
socket_mode_client: Optional[SocketModeClient] = None

@app.event({
    "type": "message"
})
async def receive_message(event, say):
    await say("Hiya")

async def main():
    handler = AsyncSocketModeHandler(app, SLACK_APP_TOKEN)
    await handler.connect_async()
    global socket_mode_client
    socket_mode_client = handler.client

web_app = FastAPI()

@web_app.get("/healthcheck")
async def healthcheck():
    if socket_mode_client is not None and await socket_mode_client.is_connected():
        return "OK"
    return "BAD"

@web_app.on_event('startup')
async def start_slack_socket_conn():
    await main()

I run this using poetry run uvicorn main:web_app --host 0.0.0.0 --port 8080

@seratch
Copy link
Member

seratch commented Mar 23, 2022

@ImRohan01 Can you try this example out?

import os
from slack_bolt.async_app import AsyncApp
from slack_bolt.adapter.socket_mode.async_handler import AsyncSocketModeHandler
from fastapi import FastAPI
import logging

logging.basicConfig(level=logging.DEBUG)

app = AsyncApp(token=os.getenv("SLACK_BOT_TOKEN"))
handler = AsyncSocketModeHandler(app, os.getenv("SLACK_APP_TOKEN"))

@app.event({"type": "message"})
async def receive_message(event, say):
    await say("Hiya")

web_app = FastAPI()

@web_app.get("/healthcheck")
async def healthcheck():
    if handler.client is not None and await handler.client.is_connected():
        return "OK"
    return "BAD"

@web_app.on_event('startup')
async def start_slack_socket_conn():
    await handler.connect_async()

@web_app.on_event('shutdown')
async def start_slack_socket_conn():
    await handler.close_async()

@ImRohan01
Copy link
Author

@seratch the above solved the issue. Thanks for the help!

@tracy-ash
Copy link

It seems that on_event is not longer supported, and lifespan is to now be used.
@seratch how would you modify your example to use lifespan ?

@seratch
Copy link
Member

seratch commented Feb 13, 2024

@tracy-ash Nothing is surprising. Just moving the event handlers into a single lifespan function works for you:

import os
from slack_bolt.async_app import AsyncApp
from slack_bolt.adapter.socket_mode.async_handler import AsyncSocketModeHandler

import logging
logging.basicConfig(level=logging.DEBUG)

app = AsyncApp(token=os.getenv("SLACK_BOT_TOKEN"))
handler = AsyncSocketModeHandler(app, os.getenv("SLACK_APP_TOKEN"))

@app.event({"type": "message"})
async def receive_message(event, say):
    await say("Hiya")


from fastapi import FastAPI
from contextlib import asynccontextmanager

@asynccontextmanager
async def lifespan(app: FastAPI):
    await handler.connect_async()
    yield
    await handler.close_async()

web_app = FastAPI(lifespan=lifespan)

@web_app.get("/healthcheck")
async def healthcheck():
    if handler.client is not None and await handler.client.is_connected():
        return "OK"
    return "BAD"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area:adapter area:async area:examples issues related to example or sample code area:sync docs Improvements or additions to documentation
Projects
None yet
Development

No branches or pull requests

3 participants