-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: implement basic hook logic (#4)
* Add logic and tests --------- Co-authored-by: Kazuhiro Sera <[email protected]> Co-authored-by: Fil Maj <[email protected]>
- Loading branch information
1 parent
ab6dcec
commit 8e64ddb
Showing
42 changed files
with
1,100 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,154 @@ | ||
# python-slack-hooks | ||
Helper library implementing the contract between the Slack CLI and Bolt for Python | ||
<h1 align="center">Python Slack Hooks</h1> | ||
|
||
A helper library implementing the contract between the | ||
[Slack CLI][slack-cli-docs] and | ||
[Bolt for Python](https://slack.dev/bolt-python/) | ||
|
||
## Environment requirements | ||
|
||
Before getting started, make sure you have a development workspace where you | ||
have permissions to install apps. **Please note that leveraging all features in | ||
this project require that the workspace be part of | ||
[a Slack paid plan](https://slack.com/pricing).** | ||
|
||
### Install the Slack CLI | ||
|
||
Install the Slack CLI. Step-by-step instructions can be found in this | ||
[Quickstart Guide][slack-cli-docs]. | ||
|
||
### Environment Setup | ||
|
||
Create a project folder and a | ||
[virtual environment](https://docs.python.org/3/library/venv.html#module-venv) | ||
within it | ||
|
||
```zsh | ||
# Python 3.6+ required | ||
mkdir myproject | ||
cd myproject | ||
python3 -m venv .venv | ||
``` | ||
|
||
Activate the environment | ||
|
||
```zsh | ||
source .venv/bin/activate | ||
``` | ||
|
||
### Pypi | ||
|
||
Install this package using pip. | ||
|
||
```zsh | ||
pip install -U slack-cli-hooks | ||
``` | ||
|
||
### Clone | ||
|
||
Clone this project using git. | ||
|
||
```zsh | ||
git clone https://github.com/slackapi/python-slack-hooks.git | ||
``` | ||
|
||
Follow the | ||
[Develop Locally](https://github.com/slackapi/python-slack-hooks/blob/main/.github/maintainers_guide.md#develop-locally) | ||
steps in the maintainers guide to build and use this package. | ||
|
||
## Simple project | ||
|
||
In the same directory where we installed `slack-cli-hooks` | ||
|
||
1. Define basic information and metadata about our app via an | ||
[App Manifest](https://api.slack.com/reference/manifests) (`manifest.json`). | ||
2. Create a `slack.json` file that defines the interface between the | ||
[Slack CLI][slack-cli-docs] and [Bolt for Python][bolt-python-docs]. | ||
3. Use an `app.py` file to define the entrypoint for a | ||
[Bolt for Python][bolt-python-docs] project. | ||
|
||
### Application Configuration | ||
|
||
Define your [Application Manifest](https://api.slack.com/reference/manifests) in | ||
a `manifest.json` file. | ||
|
||
```json | ||
{ | ||
"display_information": { | ||
"name": "simple-app" | ||
}, | ||
"outgoing_domains": [], | ||
"settings": { | ||
"org_deploy_enabled": true, | ||
"socket_mode_enabled": true, | ||
}, | ||
"features": { | ||
"bot_user": { | ||
"display_name": "simple-app" | ||
} | ||
}, | ||
"oauth_config": { | ||
"scopes": { | ||
"bot": ["chat:write"] | ||
} | ||
} | ||
} | ||
``` | ||
|
||
### CLI/Bolt Interface Configuration | ||
|
||
Define the Slack CLI configuration in a file named `slack.json`. | ||
|
||
```json | ||
{ | ||
"hooks": { | ||
"get-hooks": "python3 -m slack_cli_hooks.hooks.get_hooks" | ||
} | ||
} | ||
``` | ||
|
||
### Source code | ||
|
||
Create a [Bolt for Python][bolt-python-docs] app in a file named `app.py`. | ||
Alternatively you can use an existing app instead. | ||
|
||
```python | ||
from slack_bolt import App | ||
from slack_bolt.adapter.socket_mode import SocketModeHandler | ||
|
||
app = App() | ||
|
||
# Add functionality here | ||
|
||
if __name__ == "__main__": | ||
SocketModeHandler(app).start() | ||
``` | ||
|
||
## Running the app | ||
|
||
You should now be able to harness the power of the Slack CLI and Bolt. | ||
|
||
Run the app this way: | ||
|
||
```zsh | ||
slack run | ||
``` | ||
|
||
## Getting Help | ||
|
||
If you get stuck we're here to help. Ensure your issue is related to this | ||
project and not to [Bolt for Python][bolt-python-docs]. The following are the | ||
best ways to get assistance working through your issue: | ||
|
||
- [Issue Tracker](https://github.com/slackapi/python-slack-hooks/issues) for | ||
questions, bug reports, feature requests, and general discussion. **Try | ||
searching for an existing issue before creating a new one.** | ||
- Email our developer support team: `[email protected]` | ||
|
||
## Contributing | ||
|
||
Contributions are more then welcome. Please look at the | ||
[contributing guidelines](https://github.com/slackapi/python-slack-hooks/blob/main/.github/CONTRIBUTING.md) | ||
for more info! | ||
|
||
[slack-cli-docs]: https://api.slack.com/automation/cli | ||
[bolt-python-docs]: https://slack.dev/bolt-python/concepts |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,5 @@ | ||
black==22.8.0; python_version=="3.6" | ||
black; python_version>"3.6" # Until we drop Python 3.6 support, we have to stay with this version | ||
flake8>=5.0.4; python_version=="3.6" | ||
flake8==6.0.0; python_version>"3.6" | ||
flake8>=5.0.4, <7; | ||
pytype; (python_version<"3.11" or python_version>"3.11") | ||
pytype==2023.11.29; python_version=="3.11" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,6 @@ | ||
# pip install -r requirements/testing.txt | ||
pytest>=6.2.5,<7 | ||
pytest-cov>=3,<4 | ||
Flask>=2.0.3,<4 | ||
gevent>=22.10.2,<24 | ||
gevent-websocket>=0.10.1,<1 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
""" | ||
A Slack CLI hooks implementation in Python to build Bolt Slack apps leveraging the full power of the [Slack CLI](https://api.slack.com/automation/cli/install). Look at our [code examples](https://github.com/slackapi/python-slack-hooks/tree/main/examples) to learn how to build apps using the SLack CLI and Bolt. | ||
* Slack CLI: https://api.slack.com/automation/cli/install | ||
* Bolt Website: https://slack.dev/bolt-python/ | ||
* GitHub repository: https://github.com/slackapi/python-slack-hooks | ||
""" # noqa: E501 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
class CliError(Exception): | ||
"""General class for cli error""" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
"""Slack CLI contract implementation for Bolt. | ||
""" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
#!/usr/bin/env python | ||
import json | ||
from slack_cli_hooks.protocol import Protocol, MessageBoundaryProtocol, DefaultProtocol, build_protocol | ||
|
||
PROTOCOL: Protocol | ||
EXEC = "python3" | ||
|
||
|
||
hooks_payload = { | ||
"hooks": { | ||
"get-manifest": f"{EXEC} -m slack_cli_hooks.hooks.get_manifest", | ||
"start": f"{EXEC} -X dev -m slack_cli_hooks.hooks.start", | ||
}, | ||
"config": { | ||
"watch": {"filter-regex": "(^manifest\\.json$)", "paths": ["."]}, | ||
"protocol-version": [MessageBoundaryProtocol.name, DefaultProtocol.name], | ||
"sdk-managed-connection-enabled": True, | ||
}, | ||
} | ||
|
||
if __name__ == "__main__": | ||
PROTOCOL = build_protocol() | ||
PROTOCOL.respond(json.dumps(hooks_payload)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
#!/usr/bin/env python | ||
import os | ||
import re | ||
from typing import List | ||
|
||
from slack_cli_hooks.error import CliError | ||
from slack_cli_hooks.protocol import Protocol, build_protocol | ||
|
||
PROTOCOL: Protocol | ||
|
||
EXCLUDED_DIRECTORIES = [ | ||
"lib", | ||
"bin", | ||
"include", | ||
"node_modules", | ||
"packages", | ||
"logs", | ||
"build", | ||
"coverage", | ||
"target", | ||
"tmp", | ||
"test", | ||
"tests", | ||
] | ||
|
||
DIRECTORY_IGNORE_REGEX = re.compile(r"(^\.|^\_|^{}$)".format("$|^".join(EXCLUDED_DIRECTORIES)), re.IGNORECASE) | ||
|
||
|
||
def filter_directories(directories: List[str]) -> List[str]: | ||
return [directory for directory in directories if not DIRECTORY_IGNORE_REGEX.match(directory)] | ||
|
||
|
||
def find_file_path(path: str, file_name: str) -> str: | ||
for root, dirs, files in os.walk(path, topdown=True, followlinks=False): | ||
dirs[:] = filter_directories(dirs) | ||
if file_name in files: | ||
return os.path.join(root, file_name) | ||
raise CliError(f"Could not find a {file_name} file") | ||
|
||
|
||
def get_manifest(working_directory: str) -> str: | ||
file_path = find_file_path(working_directory, "manifest.json") | ||
|
||
with open(file_path, "r") as manifest: | ||
return manifest.read() | ||
|
||
|
||
if __name__ == "__main__": | ||
PROTOCOL = build_protocol() | ||
PROTOCOL.respond(get_manifest(os.getcwd())) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
#!/usr/bin/env python | ||
import os | ||
import runpy | ||
import sys | ||
|
||
from slack_cli_hooks.error import CliError | ||
from slack_cli_hooks.hooks.utils import ManagedOSEnvVars | ||
from slack_cli_hooks.protocol import Protocol, build_protocol | ||
|
||
PROTOCOL: Protocol | ||
|
||
DEFAULT_MAIN_FILE = "app.py" | ||
|
||
SLACK_CLI_XOXB = "SLACK_CLI_XOXB" | ||
SLACK_CLI_XAPP = "SLACK_CLI_XAPP" | ||
SLACK_BOT_TOKEN = "SLACK_BOT_TOKEN" | ||
SLACK_APP_TOKEN = "SLACK_APP_TOKEN" | ||
|
||
|
||
def validate_env() -> None: | ||
if not os.environ.get(SLACK_CLI_XOXB): | ||
raise CliError(f"Missing local run bot token ({SLACK_CLI_XOXB}).") | ||
if not os.environ.get(SLACK_CLI_XAPP): | ||
raise CliError(f"Missing local run app token ({SLACK_CLI_XAPP}).") | ||
|
||
|
||
def get_main_file() -> str: | ||
custom_file = os.environ.get("SLACK_APP_PATH") | ||
if custom_file: | ||
return custom_file | ||
return DEFAULT_MAIN_FILE | ||
|
||
|
||
def get_main_path(working_directory: str) -> str: | ||
main_file = get_main_file() | ||
main_raw_path = os.path.join(working_directory, main_file) | ||
return os.path.abspath(main_raw_path) | ||
|
||
|
||
def start(working_directory: str) -> None: | ||
validate_env() | ||
|
||
entrypoint_path = get_main_path(working_directory) | ||
|
||
if not os.path.exists(entrypoint_path): | ||
raise CliError(f"Could not find {get_main_file()} file") | ||
|
||
parent_package = os.path.dirname(entrypoint_path) | ||
os_env_vars = ManagedOSEnvVars(PROTOCOL) | ||
|
||
try: | ||
os_env_vars.set_if_absent(SLACK_BOT_TOKEN, os.environ[SLACK_CLI_XOXB]) | ||
os_env_vars.set_if_absent(SLACK_APP_TOKEN, os.environ[SLACK_CLI_XAPP]) | ||
sys.path.insert(0, parent_package) # Add parent package to sys path | ||
|
||
runpy.run_path(entrypoint_path, run_name="__main__") | ||
finally: | ||
sys.path.remove(parent_package) | ||
os_env_vars.clear() | ||
|
||
|
||
if __name__ == "__main__": | ||
PROTOCOL = build_protocol() | ||
start(os.getcwd()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
from .managed_os_env_vars import ManagedOSEnvVars | ||
|
||
__all__ = [ | ||
"ManagedOSEnvVars", | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import os | ||
from typing import List | ||
from slack_cli_hooks.protocol import Protocol | ||
|
||
|
||
class ManagedOSEnvVars: | ||
def __init__(self, protocol: Protocol) -> None: | ||
self._protocol = protocol | ||
self._os_env_vars: List[str] = [] | ||
|
||
def set_if_absent(self, os_env_var: str, value: str) -> None: | ||
if os_env_var in os.environ: | ||
self._protocol.info( | ||
f"{os_env_var} environment variable detected in session, using it over the provided one!" | ||
) | ||
return | ||
self._os_env_vars.append(os_env_var) | ||
os.environ[os_env_var] = value | ||
|
||
def clear(self) -> None: | ||
for os_env_var in self._os_env_vars: | ||
os.environ.pop(os_env_var, None) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import argparse | ||
import sys | ||
from typing import List | ||
from .default_protocol import DefaultProtocol | ||
from .message_boundary_protocol import MessageBoundaryProtocol | ||
from .protocol import Protocol | ||
|
||
__all__ = [ | ||
"DefaultProtocol", | ||
"MessageBoundaryProtocol", | ||
"Protocol", | ||
] | ||
|
||
|
||
def build_protocol(argv: List[str] = sys.argv[1:]) -> Protocol: | ||
parser = argparse.ArgumentParser() | ||
parser.add_argument("--protocol", type=str, required=False) | ||
parser.add_argument("--boundary", type=str, required=False) | ||
|
||
args, unknown = parser.parse_known_args(args=argv) | ||
|
||
if args.protocol == MessageBoundaryProtocol.name: | ||
return MessageBoundaryProtocol(boundary=args.boundary) | ||
return DefaultProtocol() |
Oops, something went wrong.