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

Pass non-slack arguments when using handlers #559

Closed
2 of 4 tasks
tom0010 opened this issue Dec 31, 2021 · 6 comments
Closed
2 of 4 tasks

Pass non-slack arguments when using handlers #559

tom0010 opened this issue Dec 31, 2021 · 6 comments
Labels
area:sync question Further information is requested

Comments

@tom0010
Copy link

tom0010 commented Dec 31, 2021

Looking at #542
I'd like to do something similar, I'd like to pass extra args into my function.
bolt already gives you body, ack and logger as some examples, I'd like to also pass my own ones.
However, as this is done automatically, I can't pass any args as python then complains about the positional args missing.
Here is some more context: https://community.slack.com/archives/CHL4CLRCZ/p1640959405021200
One of the args I'd like to pass is actually an sqlalchemy object. Because after my slack action (button) is clicked, I want to double check my DB to make sure the user has been authed and there is an entry in the DB for said user. Now I have this working already when using a big file, but I'm trying to move away from having huge files, and have it more modular by importing different files using handlers as per @seratch's example (https://community.slack.com/archives/CHL4CLRCZ/p1634250648022400?thread_ts=1633856012.019600&cid=CHL4CLRCZ):

# handlers.py
class HiCommand:
    def handle(self, ack, body):
        ack()

    def add_to(app: App):
        app.command("/hi")(self.handle)

# main.py
from handlers import HiCommand

app = App()
HiCommand().add_to(app)

app.start()

Category (place an x in each of the [ ])

  • slack_bolt.App and/or its core components
  • slack_bolt.async_app.AsyncApp and/or its core components
  • Adapters in slack_bolt.adapter
  • Others

Requirements

Please read the Contributing guidelines and Code of Conduct before creating this issue or pull request. By submitting, you are agreeing to those rules.

@seratch seratch added area:sync question Further information is requested labels Jan 1, 2022
@seratch
Copy link
Member

seratch commented Jan 1, 2022

Hi @tom0010, thanks for asking the question!

As you've already noticed, there is no way to add custom arguments to listener functions. The recommended way is to add your additional values to the context object. A common way is to use a global middleware to resolve the custom values before listener execution.

https://slack.dev/bolt-python/concepts#global-middleware

@app.middleware
def set_db_object(context, body, next):
    context["db"] = acquire_database_session(body)
    next()

Hope this helps.

@tom0010
Copy link
Author

tom0010 commented Jan 3, 2022

Hey @seratch thanks for the info and the quick response!
How would I do that using handlers like below with flask on top?

app.py:

from slack_bolt.adapter.socket_mode import SocketModeHandler
from slack_bolt.app import App
from handlers.message import Message

from flask import Flask

app = Flask(__name__)

BOT_TOKEN = os.environ["SLACK_TOKEN"]
APP_TOKEN = os.environ["SLACK_APP_TOKEN"]
SIGNING_SECRET = os.environ["SLACK_SIGNING_SECRET"]

bolt = App(token=BOT_TOKEN, signing_secret=SIGNING_SECRET)
Message().add_to(bolt)
SocketModeHandler(bolt, APP_TOKEN).connect()

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=10325, threaded=True)

handlers/message.py:

from handlers.messages import assign


class Message():
    def add_to(self, app):
        app.message(re.compile(r"!assign to <@(\S+)>"))(assign.assign_ticket)

handlers/messages/assign.py

def assign_ticket(message, say, logger):
    logger.info(f"{message['text']} by {message['user']}")
    user = message["text"][24:-1:]
    text = "my message"
    return say(f"{user} you have been assigned {text}")

Tree:

.
├── handlers
│   ├── __init__.py
│   ├── message.py
│   ├── messages
│   │   ├── __init__.py
│   │   ├── assign.py
└── app.py

@seratch
Copy link
Member

seratch commented Jan 3, 2022

@tom0010 You can register global middleware in a similar way:

bolt = App(token=BOT_TOKEN, signing_secret=SIGNING_SECRET)

class Middleware():
    def _set_db_object(self, context, body, next):
        context["db"] = acquire_database_session(body)
        next()

    def add_to(self, app):
        app.middleware(self._set_db_object)

Middleware().add_to(bolt)
Message().add_to(bolt)

Regarding the original question here, everything is clear now. Would you mind closing this issue?

@tom0010
Copy link
Author

tom0010 commented Jan 4, 2022

@seratch thanks, even though this works, it's not really something I was looking for. I was hoping that I could pass the db into the class which would be a new file. It doesn't seem possible? It would mean I would have to have that Middleware class inside the app.py? Ideally I would like to avoid that.
As I said, your solution above does work and it does minimize the overall code by having a handlers folder with the code inside, but I was trying to do something like this:

app.py:

bolt = App(token=BOT_TOKEN, signing_secret=SIGNING_SECRET)

something = "1234345345"

Middleware().add_to(bolt, something)
Message().add_to(bolt)

handlers/middleware.py:

class Middleware():
    def _set_db_object(self, context, body, next, something):
        context["db"] = something
        next()

    def add_to(self, app, something):
        app.middleware(self._set_db_object(something))

This gives me this error:

Traceback (most recent call last):
  File "/Users/tom/project/./app.py", line 35, in <module>
    Middleware().add_to(bolt, something)
  File "/Users/tom/project/handlers/middleware.py", line 8, in add_to
    app.middleware(self._set_db_object(something))
TypeError: _set_db_object() missing 3 required positional arguments: 'body', 'next', and 'something'

But this turns out that it is pretty much the same as before when I tried to pass arguments in my previous post.
Also, I know you're keen to close this, but I'm still trying to figure out if this is possible.
Thanks again.

@seratch
Copy link
Member

seratch commented Jan 4, 2022

@tom0010 As you saw, you cannot add the something to the global middleware. How about doing like this?

class Middleware():
    def _set_db_object(self, context, body, next):
        context["db"] = self.something
        next()

    def add_to(self, app, something):
        self.something = something
        app.middleware(self._set_db_object)

Middleware().add_to(bolt, init_something())

I would recommend using constructor argument for something though:

class Middleware():
    def __init__(self, something):
        self.something = something

    def _set_db_object(self, context, body, next):
        context["db"] = self.something
        next()

    def add_to(self, app):
        app.middleware(self._set_db_object)

Middleware(init_something()).add_to(bolt)

Either is fine.

@tom0010
Copy link
Author

tom0010 commented Jan 4, 2022

Oh yes, using self works fine, not sure why I didn't think of that!
Thanks a lot - will close this now :)

@tom0010 tom0010 closed this as completed Jan 4, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area:sync question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants