Skip to content

Commit fba264c

Browse files
authored
Merge pull request #10 from Ambro17/add-support-for-args-injection
Add support for args injection in view functions
2 parents a7c36f4 + 98b1aa7 commit fba264c

19 files changed

+316
-57
lines changed

.flake8

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
[flake8]
2+
ignore=E302
23
max-line-length=119
4+
max-complexity=10

.pre-commit-config.yaml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
repos:
2+
- repo: https://github.com/pre-commit/pre-commit-hooks
3+
rev: v3.0.0
4+
hooks:
5+
- id: trailing-whitespace
6+
- id: end-of-file-fixer
7+
- id: check-added-large-files
8+
- id: check-merge-conflict
9+
- id: debug-statements
10+
- id: end-of-file-fixer
11+
- id: requirements-txt-fixer
12+
13+
- repo: https://gitlab.com/pycqa/flake8
14+
rev: 3.8.1
15+
hooks:
16+
- id: flake8
17+
language_version: python3
18+
args: ['src/slackify', 'tests']
19+
stages:
20+
- push

README.md

Lines changed: 32 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Slackify
22
![Build & Test](https://github.com/Ambro17/flask-slack/workflows/Build%20&%20Test/badge.svg)
33
[![codecov](https://codecov.io/gh/Ambro17/flask-slack/branch/master/graph/badge.svg)](https://codecov.io/gh/Ambro17/flask-slack)
4+
[![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white)](https://github.com/pre-commit/pre-commit)
45

56
`Slackify` is a light framework designed to accelerate your development of slack apps by letting you focus on **what you want** instead of fighting with *how to do it*
67

@@ -78,6 +79,21 @@ Yes! See [examples/actions.py](examples/actions.py) for a quickstart.
7879
### And slack events?
7980
As you may have guessed, they are also supported. See [examples/events.py](examples/events.py) for an example.
8081

82+
## Dependency Injection
83+
As you add more and more commands you will find yourself repeating yourself while parsing slack request on every function
84+
85+
The lib offers a solution to this with dependency injection.
86+
```python
87+
@slackify.command
88+
def hello(command, command_args, response_url):
89+
return reply_text(f"You called `{command} {command_args}`. Use {response_url} for delayed responses")
90+
```
91+
Your view function will now get the request command, arguments and response_url for free! Pretty cool, right?
92+
93+
If you are a user of pytest, this idea is similar to pytest fixtures
94+
95+
See [examples/injection.py](examples/injection.py) for the full example
96+
8197

8298
## Full example
8399
Here you have a more complete example showcasing all functionality. It includes:
@@ -143,20 +159,18 @@ def hello():
143159

144160

145161
@slackify.action("yes")
146-
def yes():
162+
def yes(payload):
147163
"""If a user clicks yes on the message above, execute this callback"""
148-
action = json.loads(request.form["payload"])
149164
text_blok = text_block('Super! I do too :thumbsup:')
150-
respond(action['response_url'], {'blocks': [text_blok]})
165+
respond(payload['response_url'], {'blocks': [text_blok]})
151166
return OK
152167

153168

154169
@slackify.action("no")
155-
def no():
170+
def no(payload):
156171
"""If a user clicks no on the hello message, execute this callback"""
157-
action = json.loads(request.form["payload"])
158172
text_blok = text_block('Boo! You are so boring :thumbsdown:')
159-
respond(action['response_url'], {'blocks': [text_blok]})
173+
respond(payload['response_url'], {'blocks': [text_blok]})
160174
return OK
161175

162176

@@ -230,12 +244,11 @@ def register():
230244

231245

232246
@slackify.view("registration_form")
233-
def register_callback():
247+
def register_callback(payload):
234248
"""Handle registration form submission."""
235-
action = json.loads(request.form["payload"])
236-
response = action['view']['state']['values']
249+
response = payload['view']['state']['values']
237250
text_blok = text_block(f':heavy_check_mark: You are now registered.\nForm payload:\n```{response}```')
238-
send_message(cli, [text_blok], action['user']['id'])
251+
send_message(cli, [text_blok], payload['user']['id'])
239252
return ACK
240253

241254

@@ -245,9 +258,8 @@ def send_message(cli, blocks, user_id):
245258

246259

247260
@slackify.shortcut('dice_roll')
248-
def dice_roll():
261+
def dice_roll(payload):
249262
"""Roll a virtual dice to give a pseudo-random number"""
250-
payload = json.loads(request.form['payload'])
251263
dice_value = random.randint(1, 6)
252264
msg = f'🎲 {dice_value}'
253265
send_message(cli, blocks=[text_block(msg)], user_id=payload['user']['id'])
@@ -273,12 +285,11 @@ def say_hi(payload):
273285
```
274286

275287
## Usage as a Blueprint
276-
If you already have a Flask app, you can attach
288+
If you already have a Flask app, you can attach
277289
flask functionality _slackifying_ your blueprint
278290
```python
279291
# slack_blueprint.py
280-
from flask import Blueprint
281-
from slackify import Slackify, reply_text
292+
from slackify import Slackify, reply_text, Blueprint
282293

283294
bp = Blueprint('slackify_bp', __name__, url_prefix='/slack')
284295
slackify = Slackify(app=bp)
@@ -299,6 +310,7 @@ def create_app():
299310
return app
300311

301312
```
313+
> Note: You must import Blueprint from slackify instead of flask to get it working
302314
303315
## API Reference
304316
```python
@@ -333,7 +345,7 @@ or
333345
@slackify.default
334346

335347
# Handle unexpected errors that occur inside handlers.
336-
# By default returns status 500 and a generic message.
348+
# By default returns status 500 and a generic message.
337349
# The exception will be passed as a positional argument to the decorated function
338350
@slackify.error
339351
```
@@ -349,11 +361,11 @@ The lib exposes a main class called `Slackify` that can either receive a Flask i
349361
as `app` argument or creates one on the fly.
350362
It then binds two routes. One for commands, shortcuts, actions and another one for slack events.
351363

352-
The first route is `/` by default, it inspects the incoming requests and looks for any declared handler that is interested in handling this request to redirect it.
364+
The first route is `/` by default, it inspects the incoming requests and looks for any declared handler that is interested in handling this request to redirect it.
353365

354-
If it finds a handler, it redirects the request to that function by overriding its `Request.url_rule.endpoint`
366+
If it finds a handler, it injects any dependency the view may require as a view argument, and then call the view function, passing the return value as the request response.
355367

356-
If there's no match, it ignores the request and it follows the
368+
If there's no match, it ignores the request and it follows the
357369
normal request lifecycle.
358370

359371
If there's an error, an overridable function through `@slackify.error` is executed to show a friendly message.
@@ -363,9 +375,6 @@ The second route the lib adds is the events route at `/slack/events`.
363375
When it receives a post request, it emits an event through `pyee.ExecutorEventEmitter` with the event type and quickly responds with the response acknowledgment slack requires to avoid showing an error to the user. This allows asynchronous execution of the function, while still responding quickly to slack.
364376
In other words, when you decorate a function with `app.event('event_type')` what you are really doing is setting up a listener for the `event_type` that will receive the event payload. No magic.
365377

366-
If after reading this you have an idea of how we can extend or improve this lib in any way, i would be really grateful to receive an issue or pull request!
378+
If after reading this you have an idea of how we can extend or improve this lib in any way, it would be great to receive an issue or pull request!
367379
I feel there's still a void on slack bots with python that java and javascript have covered with [bolt's](https://github.com/slackapi/bolt) beautiful API.
368380
Below you can find the current roadmap of features i would like to include.
369-
370-
## Roadmap
371-
1. Inject payload argument to slack event handlers to avoid code repetition on loading request data.

app.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,4 @@
1515
"value": ""
1616
}
1717
}
18-
}
18+
}

examples/as_blueprint.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
# slack_blueprint.py
2-
from flask import Blueprint
3-
from slackify import Slackify, reply_text
2+
from slackify import Slackify, Blueprint, reply_text
43

54
bp = Blueprint('slackify_bp', __name__, url_prefix='/slack')
65
slackify = Slackify(app=bp)
@@ -12,7 +11,7 @@ def hello():
1211

1312

1413
# app.py
15-
from flask import Flask
14+
from flask import Flask # noqa: Ignore E402, as it is an example
1615
# from slack_blueprint import bp
1716

1817
def create_app():

examples/commands.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,4 @@ def hello():
1515

1616
@slackify.command(name='bye')
1717
def goodbye():
18-
return reply_text(f'Goodbye')
18+
return reply_text('Goodbye')

examples/full.py

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import json
21
import os
32
import random
43

@@ -51,16 +50,14 @@ def hello():
5150

5251

5352
@slackify.action("yes")
54-
def yes():
55-
action = json.loads(request.form["payload"])
53+
def yes(action):
5654
text_blok = text_block('Super! I do too :thumbsup:')
5755
respond(action['response_url'], {'blocks': [text_blok]})
5856
return OK
5957

6058

6159
@slackify.action("no")
62-
def no():
63-
action = json.loads(request.form["payload"])
60+
def no(action):
6461
text_blok = text_block('Boo! You are so boring :thumbsdown:')
6562
respond(action['response_url'], {'blocks': [text_blok]})
6663
return OK
@@ -135,11 +132,10 @@ def register():
135132

136133

137134
@slackify.view("registration_form")
138-
def register_callback():
139-
action = json.loads(request.form["payload"])
140-
response = action['view']['state']['values']
135+
def register_callback(payload):
136+
response = payload['view']['state']['values']
141137
text_blok = text_block(f':heavy_check_mark: You are now registered.\nForm payload:\n```{response}```')
142-
send_message(cli, [text_blok], action['user']['id'])
138+
send_message(cli, [text_blok], payload['user']['id'])
143139
return ACK
144140

145141

@@ -149,8 +145,7 @@ def send_message(cli, blocks, user_id):
149145

150146

151147
@slackify.shortcut('dice_roll')
152-
def dice_roll():
153-
payload = json.loads(request.form['payload'])
148+
def dice_roll(payload):
154149
dice_value = random.randint(1, 6)
155150
msg = f'🎲 {dice_value}'
156151
send_message(cli, blocks=[text_block(msg)], user_id=payload['user']['id'])

examples/injection.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from flask import Flask
2+
from slackify import Slackify, reply_text, Slack, ACK
3+
4+
app = Flask(__name__)
5+
slackify = Slackify(app=app)
6+
cli = Slack('xoxb-SECRET')
7+
8+
@slackify.command
9+
def hello(command, command_args, response_url):
10+
return reply_text(f"You called `{command} {command_args}`. POST to {response_url} for delayed responses (>3sec)")
11+
12+
13+
@slackify.shortcut('greet_me')
14+
def goodbye(payload):
15+
cli.chat_postMessage(channel='#general', text=f'Knock Knock\n`{payload}`')
16+
return ACK

examples/shortcuts.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,5 @@ def tell_joke():
1414
payload = json.loads(request.form['payload'])
1515
user = payload['user']
1616
name = user.get('username', user.get('id'))
17-
cli.chat_postMessage('#general', text=f'Knock Knock `{name}`')
17+
cli.chat_postMessage(channel='#general', text=f'Knock Knock `{name}`')
1818
return '', 200

examples/views.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
from slackify import Slackify, request, text_block, Slack, ACK
33
import json
44

5+
from slackify.tasks import async_task
6+
57

68
# Important! Before running set FLASK_APP=examples.async_task:app
79
app = Flask(__name__)
@@ -97,5 +99,6 @@ def register_callback():
9799
return ACK
98100

99101

102+
@async_task
100103
def send_message(cli, blocks, user_id):
101104
return cli.chat_postMessage(channel=user_id, user_id=user_id, blocks=blocks)

0 commit comments

Comments
 (0)