Skip to content

Commit cffd16c

Browse files
authored
Optional env var for slash command + upgrade Starlette (#6)
1 parent 980f182 commit cffd16c

File tree

11 files changed

+40
-71
lines changed

11 files changed

+40
-71
lines changed

.github/workflows/test.yml

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -31,36 +31,6 @@ jobs:
3131
run: |
3232
pylint tests multi_reaction_add multi_reaction_add/oauth/installation_store/google_cloud_storage multi_reaction_add/oauth/state_store/google_cloud_storage
3333
34-
pydocstyle:
35-
runs-on: ubuntu-latest
36-
steps:
37-
- uses: actions/checkout@v3
38-
- name: Set up Python 3.10
39-
uses: actions/setup-python@v4
40-
with:
41-
python-version: "3.10"
42-
- name: Install dependencies
43-
run: |
44-
python -m pip install --upgrade pip
45-
pip install pydocstyle
46-
- name: Pydocsyle
47-
run: pydocstyle multi_reaction_add
48-
49-
darglint:
50-
runs-on: ubuntu-latest
51-
steps:
52-
- uses: actions/checkout@v3
53-
- name: Set up Python 3.10
54-
uses: actions/setup-python@v4
55-
with:
56-
python-version: "3.10"
57-
- name: Install dependencies
58-
run: |
59-
python -m pip install --upgrade pip
60-
pip install darglint
61-
- name: Darglint
62-
run: darglint multi_reaction_add
63-
6434
coverage:
6535
runs-on: ubuntu-latest
6636
steps:

.pydocstylerc

Lines changed: 0 additions & 2 deletions
This file was deleted.

.pylintrc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
# https://www.codeac.io/documentation/pylint-configuration.html
2+
[MAIN]
3+
load-plugins=pylint.extensions.docparams,pylint.extensions.docstyle
24

35
[DESIGN]
46
max-args=8
@@ -11,3 +13,11 @@ min-similarity-lines=6
1113

1214
[VARIABLES]
1315
redefining-builtins-modules=asyncio
16+
17+
# https://pylint.readthedocs.io/en/v2.17.4/user_guide/checkers/extensions.html#parameter-documentation-checker
18+
[tool.pylint.parameter_documentation]
19+
accept-no-param-doc = false
20+
accept-no-raise-doc = false
21+
accept-no-return-doc = false
22+
accept-no-yields-doc = false
23+
default-docstring-type = google

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
FROM python:3.10-alpine3.17 as builder
44

5-
RUN apk add gcc==12.2.1_git20220924-r4 musl-dev==1.2.3-r4
5+
RUN apk add gcc==12.2.1_git20220924-r4 musl-dev==1.2.3-r5
66

77
COPY requirements.txt .
88

README.md

Lines changed: 9 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -259,24 +259,25 @@ After all information has been set and the application is deployed, users are no
259259

260260
# Environment variables
261261

262-
Mandatory environment variables for the App Engine Service are taken from the app's **Basic Information** page (see [Slack Application](#slack-application) section):
262+
Mandatory environment variables for the App Engine Service / Docker image are taken from the app's **Basic Information** page (see [Slack Application](#slack-application) section):
263263
- SLACK_CLIENT_ID: the **Client ID**
264264
- SLACK_CLIENT_SECRET: the **Client Secret**
265265
- SLACK_SIGNING_SECRET: the **Signing Secret**
266266

267267
<img src="docs/img/app-credentials.png" alt="app-credentials" width="500"/>
268268

269-
Along with [Google Cloud Storage](https://cloud.google.com/storage/) variables:
270269
- GOOGLE_APPLICATION_CREDENTIALS: path to a json file with credentials for an account with permissions to GCS buckets (e.g. sa-multireact-key.json). Not required if the bot is running inside Google Cloud.
271270
- SLACK_INSTALLATION_GOOGLE_BUCKET_NAME: name of a bucket used to store Slack app install data per user (e.g. slack-multireact-installation)
272271
- SLACK_STATE_GOOGLE_BUCKET_NAME: bucket name for storing temporary OAuth state (e.g. slack-multireact-oauthstate)
273272
- USER_DATA_BUCKET_NAME: bucket for user emoji data (e.g. slack-multireact-userdata)
274273

275-
Optional variables can be set for the underlying [Uvicorn ASGI](https://www.uvicorn.org/settings/), like:
276-
- UVICORN_PORT: specify a server socket to bind. Defaults to `3000`.
277-
- UVICORN_WORKERS: the number of worker processes. Defaults to `1`.
278-
- UVICORN_LOG_LEVEL: log verosity. defaults to `info`.
279-
- UVICORN_HOST: network interfaces to bind the server. Default to `0.0.0.0`.
274+
Optional environment variables:
275+
- SLACK_SLASH_COMMAND: specifies what is the slash command the app should listen for. Useful for multiple deployment in the same Slack workspace. Defaults to `/multireact`
276+
- [Uvicorn ASGI](https://www.uvicorn.org/settings/) variables, like:
277+
- UVICORN_PORT: specify a server socket to bind. Defaults to `3000`.
278+
- UVICORN_WORKERS: the number of worker processes. Defaults to `1`.
279+
- UVICORN_LOG_LEVEL: log verosity. defaults to `info`.
280+
- UVICORN_HOST: network interfaces to bind the server. Default to `0.0.0.0`.
280281

281282
# Development
282283
Make sure you have at least Python version **3.10**, [ngrok](https://ngrok.com/download) and [Google Cloud SDK](https://cloud.google.com/sdk/docs/install), then run:
@@ -335,25 +336,13 @@ Use the following `.vscode/launch.json` file to setup a debug configuration for
335336
Then press `F5` to start debugging.
336337

337338
## Linting
338-
Use [pylint](http://pylint.pycqa.org/en/latest/tutorial.html) to run static code analysis. **Code rate should always be 10.00/10**.
339+
Use [pylint](http://pylint.pycqa.org/en/latest/tutorial.html) to run static code analysis and check if the code has well formatted docstrings according to [Google style](https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html). **Code rate should always be 10.00/10**.
339340
```bash
340341
pip install pylint
341342

342343
pylint tests multi_reaction_add multi_reaction_add/oauth/installation_store/google_cloud_storage multi_reaction_add/oauth/state_store/google_cloud_storage
343344
```
344345

345-
Then use [pydocstyle](http://www.pydocstyle.org/en/stable/usage.html) and [darglint](https://github.com/terrencepreilly/darglint) to check if the code has well formatted docstrings according to [Google style](https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html). **No errors or warnings should be reported.**
346-
```bash
347-
pip install pydocstyle darglint
348-
349-
pydocstyle multi_reaction_add
350-
darglint multi_reaction_add
351-
```
352-
353-
_ℹ Note: Darglint errors can be "cryptic" and you should check the [documentation](https://pythonrepo.com/repo/terrencepreilly-darglint-python-linters-style-checkers#error-codes) for the error code explanations._
354-
355-
_ℹ Note: Darglint takes a couple of minutes to finish._
356-
357346
## Testing and code coverage
358347

359348
The [tests](tests) folder contains unit tests for the app logic. You can run the tests from commandline with:

multi_reaction_add/handlers.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
storage_client = Client() # initialize the Google Storage client
4343
bucket = storage_client.bucket(os.environ["USER_DATA_BUCKET_NAME"])
4444
slack_client_id = os.environ["SLACK_CLIENT_ID"]
45+
slash_command = os.environ.get("SLACK_SLASH_COMMAND", "/multireact")
4546
# initialize the app with the OAuth configuration
4647
app = AsyncApp(
4748
signing_secret=os.environ["SLACK_SIGNING_SECRET"],
@@ -69,7 +70,7 @@
6970

7071

7172
# https://api.slack.com/interactivity/slash-commands, https://slack.dev/bolt-python/concepts#commands
72-
@app.command("/multireact")
73+
@app.command(slash_command)
7374
async def save_or_display_reactions(
7475
ack: AsyncAck,
7576
client: AsyncWebClient,
@@ -121,20 +122,20 @@ async def save_or_display_reactions(
121122
reactions = " ".join(reactions)
122123
blob.upload_from_string(reactions)
123124
await respond("Great! Your new reactions are saved :sunglasses: "
124-
"Type `/multireact` to see them at any time.")
125+
f"Type `{slash_command}` to see them at any time.")
125126
logger.info("User %s saved %s", user_id, reactions)
126127

127128
else: # otherwise, report to user any reactions they have
128129
if blob.exists(): # display any reactions the user has saved
129130
reactions = blob.download_as_text(encoding="utf-8")
130131
reactions = " ".join([f":{r}:" for r in reactions.split(" ")])
131132
await respond(f"Your current reactions are: {reactions}. "
132-
"Type `/multireact <new list of emojis>` to change them.")
133+
f"Type `{slash_command} <new list of emojis>` to change them.")
133134
logger.info("User %s loaded %s", user_id, reactions)
134135

135136
else: # or say that user doesn't have any
136137
await respond("You do not have any reactions set :anguished:\n"
137-
"Type `/multireact <list of emojis>` to set one.")
138+
f"Type `{slash_command} <list of emojis>` to set one.")
138139
logger.info("User %s has no reactions", user_id)
139140

140141

@@ -220,7 +221,7 @@ async def add_reactions(
220221
"text": {
221222
"type": "mrkdwn",
222223
"text": ("You do not have any reactions set :anguished:\n"
223-
"Type `/multireact <list of emojis>` in the chat to set one.")
224+
f"Type `{slash_command} <list of emojis>` in the chat to set one.")
224225
}
225226
}
226227
]
@@ -266,9 +267,9 @@ async def update_home_tab(
266267
user_id = event["user"]
267268
# https://cloud.google.com/appengine/docs/standard/python/how-requests-are-routed#domain_name_is_included_in_the_request_data
268269
if "host" in request.headers and len(request.headers["host"]) > 0:
269-
view = build_home_tab_view(app_url=f"https://{request.headers['host'][0]}")
270+
view = build_home_tab_view(slash_command, app_url=f"https://{request.headers['host'][0]}")
270271
else:
271-
view = build_home_tab_view()
272+
view = build_home_tab_view(slash_command)
272273

273274
await client.views_publish(
274275
user_id=user_id, # Use the user ID associated with the event

multi_reaction_add/internals.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -180,10 +180,11 @@ def user_data_key(slack_client_id: str, enterprise_id: Optional[str], team_id: s
180180
f"{slack_client_id}/none-{team_id}/{user_id}")
181181

182182

183-
def build_home_tab_view(app_url: str = None) -> dict:
183+
def build_home_tab_view(slash_command: str, app_url: str = None) -> dict:
184184
"""Builds a Slack Block Kit view for the App Home Tab.
185185
186186
Args:
187+
slash_command (str): Slack slash command the app responds to.
187188
app_url (str): Application URL to add additional pictures in the view. Defaults to None.
188189
189190
Returns:
@@ -204,7 +205,7 @@ def build_home_tab_view(app_url: str = None) -> dict:
204205
"type": "section",
205206
"text": {
206207
"type": "mrkdwn",
207-
"text": "Type `/multireact <list of emojis>` in any chat to set a list of emojis for later usage."
208+
"text": f"Type `{slash_command} <list of emojis>` in any chat to set a list of emojis for later usage."
208209
}
209210
}
210211
])
@@ -226,7 +227,7 @@ def build_home_tab_view(app_url: str = None) -> dict:
226227
"type": "section",
227228
"text": {
228229
"type": "mrkdwn",
229-
"text": "You can view what you saved any moment by typing `/multireact` in any chat."
230+
"text": f"You can view what you saved any moment by typing `{slash_command}` in any chat."
230231
}
231232
})
232233
if app_url:

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ slack-bolt==1.16.2
22
google-cloud-storage==2.7.0
33
uvicorn==0.20.0
44
aiohttp==3.8.3
5-
starlette==0.25.0
5+
starlette==0.28.0
66
python-json-logger==2.0.4
77

88
# pin the sdk dependency of bolt

tests/test_handlers.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
install, oauth_redirect
2929

3030

31-
# pylint: disable=too-many-instance-attributes,attribute-defined-outside-init
31+
# pylint: disable=too-many-instance-attributes,attribute-defined-outside-init,missing-param-doc,missing-return-doc,missing-return-type-doc
3232
class TestHandlers(unittest.IsolatedAsyncioTestCase):
3333
"""Test all methods from handlers.py"""
3434

@@ -272,7 +272,7 @@ async def test_update_home_tab(self, build_home_tab_view: Mock):
272272
event={"user": "uid"},
273273
logger=self.logger,
274274
request=request)
275-
build_home_tab_view.assert_called_once_with(app_url="https://host1")
275+
build_home_tab_view.assert_called_once_with("/multireact", app_url="https://host1")
276276
self.client.views_publish.assert_awaited_once_with(user_id="uid", view="view")
277277

278278
build_home_tab_view.reset_mock()
@@ -284,7 +284,7 @@ async def test_update_home_tab(self, build_home_tab_view: Mock):
284284
event={"user": "uid"},
285285
logger=self.logger,
286286
request=request)
287-
self.assertEqual(build_home_tab_view.call_args, call())
287+
build_home_tab_view.assert_called_once_with("/multireact")
288288
self.client.views_publish.assert_awaited_once_with(user_id="uid", view="view")
289289

290290
@patch.dict("os.environ", {"SLACK_CLIENT_ID": "", "SLACK_CLIENT_SECRET": ""})

tests/test_installation_store.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
from multi_reaction_add.oauth.installation_store.google_cloud_storage import GoogleCloudStorageInstallationStore
1616

17-
# pylint: disable=too-many-instance-attributes,attribute-defined-outside-init
17+
# pylint: disable=too-many-instance-attributes,attribute-defined-outside-init,missing-param-doc
1818
class TestGoogleInstallationStore(unittest.IsolatedAsyncioTestCase):
1919
"""Tests for GoogleCloudStorageInstallationStore"""
2020

0 commit comments

Comments
 (0)