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

feat: implement basic hook logic #4

Merged
merged 26 commits into from
Dec 13, 2023
Merged

feat: implement basic hook logic #4

merged 26 commits into from
Dec 13, 2023

Conversation

WilliamBergamin
Copy link
Contributor

Summary

This PR aims to introduce an implementation of the Slack CLI interface that enables the Slack CLI to interact with Bolt Python projects

Testing

Follow the instructions in the README.md
When it asks to pip install slack-cli-hooks execute the following instead pip install -i https://test.pypi.org/simple/ slack-cli-hooks==0.0.0.dev1

Special notes

  • I debated implementing a more object oriented solution, opted for this since it felt simpler with less boiler plate, let me know what you think
  • Looking for feedback on code style and cleanliness
  • Did I miss anything?

Requirements

  • I've read and understood the Contributing Guidelines and have done my best effort to follow them.
  • I've read and agree to the Code of Conduct.
  • I've run ./scripts/install_and_run_tests.sh after making the changes.

@WilliamBergamin WilliamBergamin added the enhancement New feature or request label Dec 5, 2023
@WilliamBergamin WilliamBergamin self-assigned this Dec 5, 2023
Copy link
Member

@seratch seratch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work! Left a few comments

@@ -11,7 +11,7 @@ jobs:
timeout-minutes: 2
strategy:
matrix:
python-version: ["3.12"]
python-version: ["3.11"]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can add a comment mentioning the reason behind this

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I originally thought that flake8 did not support python 3.12 but flake8 6.1.0 has fixed this, I therefor update the format dependencies of the project

README.md Outdated Show resolved Hide resolved
SLACK_APP_TOKEN = "SLACK_APP_TOKEN"


class EnvVarHandler:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to better understand the benefit of having this class. This class does destructive changes to real os.environ. In this case, is the state represented by self._is_set necessary? If the main benefit of this is to have the clean method, these methods could be simple static functions instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question, The goal of EnvVarHandler is to allow users to set their own environment variable values and not have them over-written and deleted by this hooks implementation

If a user sets the environment variable SLACK_APP_TOKEN then the app should use it and its value should still be present after the program terminates

self._is_set is necessary since it dictates wether the environment variable was set by the user prior to the execution of the program

Let me know if this should be renamed or if there is a better implementation

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, sorry I misread the code and now I understand the intention of it.

Allowing devs to go with their own env variables should be fine but we can make the underlying behavior more visible, especially when the CLI associated tokens are unused. When a dev maintains a few Slack apps, they can unintentionally have env variables like SLACK_BOT_TOKEN in a terminal shell session. How about adding logging outputs like your SLACK_BOT_TOKEN env variable is used over the CLI's tokens etc.?

Also, I'd suggest renaming the class and methods to be even clearer:

# Let's add "os" to avoid confusion with `slack env`
os_env_vars = ManagedOSEnvVars()
try:
    os_env_vars.set_if_absent(from=SLACK_CLI_XOXB, to=SLACK_BOT_TOKEN)
    os_env_vars.set_if_absent(from=SLACK_CLI_XAPP, to=SLACK_APP_TOKEN)
finally:
    os_env_vars.clear()

You can adjust the details but I hope this psuedo code illustrates my idea well.

from flask import Flask
from flask_sockets import Sockets

socket_mode_envelopes = json.dumps(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't examined which patterns are actualy used but if many of the test data are unused, you can simplifty the code as much as possible.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point 💯 I've cleaned up this file to the minimal requirements

"error": "invalid_auth",
}

oauth_v2_access_response = json.dumps(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't examined which patterns are actualy used but if many of the test data are unused, you can simplifty the code as much as possible.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point 💯 I've cleaned up this file to the minimal requirements

Copy link
Contributor

@filmaj filmaj left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nicely done! Lots of progress here.

A majority of my comments are from a "README-driven development" perspective; as an outsider to the details of this project, I used the README to frame how I think about the project and that helps shape the project as well.

The test server is badass!

One question: I see we have "scenario tests" and "slack cli hooks" tests. I think the latter are unit tests. Is that right? And the former leverage the test server so are more integration style tests?

Another question re: testing: can we get code coverage reporting based on unit test execution?

README.md Outdated Show resolved Hide resolved
README.md Outdated Show resolved Hide resolved
README.md Outdated Show resolved Hide resolved
README.md Show resolved Hide resolved
README.md Outdated Show resolved Hide resolved
"start": f"{EXEC} -m slack_cli_hooks.hooks.start",
},
"config": {
"watcher": {"filter-regex": "^manifest\\.(json)$", "paths": ["."]},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably want to watch for .py changes too, right? Since Bolt will manage the websocket connection, it will operate as a single long-running process (as opposed to how the deno SDK operates, where every event starts a new deno process). Therefore, the Bolt process needs to be restarted every time app source code is changed to reflect the change.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch 💯 I tried implementing this but can't seem to get it to work, I've brought it up to the wider team

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Brining it up with the wider team reviled that the Slack CLI will not restart the start hook

I've decided to leave the "filter-regex": "^manifest\\.(json)$" since support for autoreload will be added in follow up PRs

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Totally fair! Thanks for following up on it.

slack_cli_hooks/hooks/get_hooks.py Show resolved Hide resolved
slack_cli_hooks/protocol/__init__.py Show resolved Hide resolved
from .protocol import Protocol


class DefaultProtocol(Protocol):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Damn, this is clean! 💯

logging.error(msg, *args, **kwargs)

def respond(self, data: str):
print(self.boundary + data + self.boundary)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👨‍🍳 💋

tests/mock_socket_mode_server.py Show resolved Hide resolved
tests/mock_web_api_server.py Show resolved Hide resolved
@@ -11,7 +11,7 @@ jobs:
timeout-minutes: 2
strategy:
matrix:
python-version: ["3.12"]
python-version: ["3.11"]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I originally thought that flake8 did not support python 3.12 but flake8 6.1.0 has fixed this, I therefor update the format dependencies of the project

SLACK_APP_TOKEN = "SLACK_APP_TOKEN"


class EnvVarHandler:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question, The goal of EnvVarHandler is to allow users to set their own environment variable values and not have them over-written and deleted by this hooks implementation

If a user sets the environment variable SLACK_APP_TOKEN then the app should use it and its value should still be present after the program terminates

self._is_set is necessary since it dictates wether the environment variable was set by the user prior to the execution of the program

Let me know if this should be renamed or if there is a better implementation

from flask import Flask
from flask_sockets import Sockets

socket_mode_envelopes = json.dumps(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point 💯 I've cleaned up this file to the minimal requirements

"error": "invalid_auth",
}

oauth_v2_access_response = json.dumps(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point 💯 I've cleaned up this file to the minimal requirements

README.md Outdated Show resolved Hide resolved
README.md Outdated Show resolved Hide resolved
README.md Outdated Show resolved Hide resolved
Copy link
Contributor

@filmaj filmaj left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great! Thanks for addressing my comments.


app = App()
## Simple project
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice! Eventually I imagine this entire section gets replaced with a sample, i.e. slack create -t slack-samples/bolt-starter-project or whatever.

README.md Outdated
Define the Slack CLI configuration in a file named `slack.json`.

```json
{
"hooks": {
"get-hooks": "python3 -m slack_cli_hooks.hooks.get_hooks"
"get-hooks": "python3 -X dev -m slack_cli_hooks.hooks.get_hooks"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oooo things I learned today: the -X option

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-X dev: enable Python Development Mode, introducing additional runtime checks that are too expensive to be enabled by default.
https://docs.python.org/3.12/using/cmdline.html#cmdoption-X

How much does the dev mode cost in terms of runtime performance? Since what the get-hooks does is fairly simple, it should be ignorable. That being said, we may should avoid recommending "dev" mode for production-deployed apps regardless of the cost. If we can enable this only for slack run (or it works only when running slack run), we don't need to worry about it at all though.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, it's a good concern / callout @seratch , as the get-hooks hook gets invoked on every single CLI command.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call 💯 Adding it only to the start hook seems worth while, we can also remove it with minimal effect (but it might be harder to add later on)

README.md Outdated
Define the Slack CLI configuration in a file named `slack.json`.

```json
{
"hooks": {
"get-hooks": "python3 -m slack_cli_hooks.hooks.get_hooks"
"get-hooks": "python3 -X dev -m slack_cli_hooks.hooks.get_hooks"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-X dev: enable Python Development Mode, introducing additional runtime checks that are too expensive to be enabled by default.
https://docs.python.org/3.12/using/cmdline.html#cmdoption-X

How much does the dev mode cost in terms of runtime performance? Since what the get-hooks does is fairly simple, it should be ignorable. That being said, we may should avoid recommending "dev" mode for production-deployed apps regardless of the cost. If we can enable this only for slack run (or it works only when running slack run), we don't need to worry about it at all though.

README.md Outdated
"settings": {
"org_deploy_enabled": true,
"socket_mode_enabled": true,
"token_rotation_enabled": false
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this property required? If not, we can remove this line to make the example as simple as possible. I believe the default value for this is false.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, this line is not required 💯

SLACK_APP_TOKEN = "SLACK_APP_TOKEN"


class EnvVarHandler:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, sorry I misread the code and now I understand the intention of it.

Allowing devs to go with their own env variables should be fine but we can make the underlying behavior more visible, especially when the CLI associated tokens are unused. When a dev maintains a few Slack apps, they can unintentionally have env variables like SLACK_BOT_TOKEN in a terminal shell session. How about adding logging outputs like your SLACK_BOT_TOKEN env variable is used over the CLI's tokens etc.?

Also, I'd suggest renaming the class and methods to be even clearer:

# Let's add "os" to avoid confusion with `slack env`
os_env_vars = ManagedOSEnvVars()
try:
    os_env_vars.set_if_absent(from=SLACK_CLI_XOXB, to=SLACK_BOT_TOKEN)
    os_env_vars.set_if_absent(from=SLACK_CLI_XAPP, to=SLACK_APP_TOKEN)
finally:
    os_env_vars.clear()

You can adjust the details but I hope this psuedo code illustrates my idea well.

slack_cli_hooks/protocol/__init__.py Outdated Show resolved Hide resolved
Copy link
Member

@seratch seratch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left two minor comments but this looks great to me now!


def set_if_absent(self, os_env_var: str, value: str) -> None:
if os_env_var in os.environ:
self._protocol.warning(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is not a problem, it'd be worth considering to go with info level instead. That being said, if only warn or above works in this scenario, warn level is fine too.

@WilliamBergamin WilliamBergamin merged commit 8e64ddb into main Dec 13, 2023
11 checks passed
@WilliamBergamin WilliamBergamin deleted the hooks-logic branch December 13, 2023 15:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants