diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..4276404 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,30 @@ +version: 2.1 + +jobs: + test: + docker: + - image: cimg/python:3.12 + steps: + - checkout + - run: + name: Install uv + command: | + curl -sSL https://install.uv.tools | bash + - run: + name: Install dependencies + command: uv init # Installs dependencies from pyproject.toml + - run: + name: Run pytest tests + command: pytest tests + - run: + name: Run ruff for linting + command: ruff check . + - run: + name: Run mypy for type checking + command: mypy . + +workflows: + version: 2 + test_and_static_analysis: + jobs: + - test diff --git a/.gitignore b/.gitignore index 82f9275..f06313f 100644 --- a/.gitignore +++ b/.gitignore @@ -154,6 +154,9 @@ dmypy.json # Cython debug symbols cython_debug/ +# Mac metadata file +.DS_Store + # PyCharm # JetBrains specific template is maintained in a separate JetBrains.gitignore that can # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..e4fba21 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.12 diff --git a/README.md b/README.md index 73b708f..9afa4b7 100644 --- a/README.md +++ b/README.md @@ -1 +1,116 @@ -# mAIgic-nyu \ No newline at end of file +# mAIgic + +## Project Description +**mAIgic** is an AI-powered assistant designed to enhance productivity and organization by tracking messages and important information across multiple platforms. It identifies and reminds users about follow-ups, ensuring timely responses and efficient information management. With intelligent search capabilities, mAIgic enables users to easily locate conversations and details tied to specific individuals or tasks. + +The project supports popular platforms such as Slack, Trello, and Gmail, providing seamless follow-up tracking, reminders, and contextual search across these communication channels. This integration leverages AI to automate the reminder process, making it easier to stay on top of essential follow-ups and tasks. + +## Features +- Unit testing with `pytest` +- Static code analysis with `ruff` (linter) +- Type checking with `mypy` +- Continuous Integration (CI) pipeline with CircleCI, running both tests and static analysis automatically on every push. + +--- + +## Setup Instructions + +### 1. Clone the Repository +```bash +git clone https://github.com/your-username/mAIgic.git +cd mAIgic +``` + +### 2. Install Dependencies + ```bash + pip install -r requirements.txt + ``` + +Alternatively, if using the `uv` package manager: +```bash +uv sync +``` + +The above command will create a virtual environment `.venv` and install all the dependencies from the `uv.lock` file. Activate the environment by executing +```bash +source .venv/bin/activate +``` + +--- + +## Running Tests + +### 1. Pytest +You can run unit tests using `pytest` by running the following command: +```bash +pytest tests +``` + +**Example Tests in `tests/test_samples.py`:** +- Basic arithmetic (addition, subtraction, multiplication, division) +- String operations (case-insensitive checks) +- List and dictionary operations +- Exception handling tests (checking for specific exceptions) + +### 2. Ruff (Linter) +Use `ruff` to run static analysis (linting) on your code: +```bash +ruff check . +``` + +### 3. Mypy (Type Checking) +To perform static type checking using `mypy`, run: +```bash +mypy . +``` + +--- + +## CircleCI Configuration + +The project is integrated with CircleCI for continuous integration. Every push to the repository automatically triggers the following steps: +1. **Install dependencies**: Installs `pytest`, `ruff`, and `mypy`. +2. **Run tests**: Executes all tests in the `tests/` folder using `pytest`. +3. **Run static analysis**: Runs `ruff` for linting and `mypy` for type checking. + + + +### View CircleCI Status: +The latest CircleCI build can be viewed [here](https://app.circleci.com/pipelines/circleci/L7kpZ5X2tZyEgUBhR4SB2j/NxWta8V9bEwRzTNu9Vzc3c/9/workflows/be567b6f-0c9c-41c8-91f7-e4789784b41f). + +--- + +## Troubleshooting + +### Common Errors: +1. **Missing Dependencies**: If any dependency-related issues arise, ensure all dependencies are installed by running: + ```bash + pip install -r requirements.txt + ``` + or + ```bash + uv init + ``` + + +## License +This project is licensed under the MIT License. + + +### Explanation of the Sections: + +1. **Project Description**: Provides an overview of what the project does. +2. **Features**: Highlights the major tools and features used. +3. **Setup Instructions**: Guides users on how to clone the repo, install dependencies, and initialize the project. +4. **Running Tests**: Explains how to run unit tests, static analysis, and type checks. +5. **CircleCI Configuration**: Outlines how CircleCI automates the process of testing and analysis, including the YAML configuration. +6. **Troubleshooting**: Offers solutions for common errors like string comparison and missing dependencies. +7. **License**: A placeholder for your project's license type. + +## Teammates +- Siddharth Singh - sms10221@nyu.edu +- Adittya Mittal - am14079@nyu.edu +- Anushka Tawte - at5849@nyu.edu +- Rafael de Leon - rdl404@nyu.edu +- Alex Ying - aty2009@nyu.edu +- Mridul Mittal - mm13171@nyu.edu diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 0000000..abe3150 --- /dev/null +++ b/mypy.ini @@ -0,0 +1,3 @@ +[mypy] +python_version = 3.12 +ignore_missing_imports = True diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..dbf384c --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,17 @@ +[project] +name = "mAIgic" +version = "0.1.0" +description = "An AI-powered assistant designed to enhance productivity and organization by tracking messages and important information across multiple platforms" +readme = "README.md" +requires-python = ">=3.12" +dependencies = [ + "pytest", + "ruff", + "mypy", + "slack-bolt>=1.21.2", + "python-dotenv>=1.0.1", +] + +[tool.ruff.lint] +select = ["ALL"] +extend-ignore = ["S101", "D211", "D213"] diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..006b0cd Binary files /dev/null and b/requirements.txt differ diff --git a/src/hello.py b/src/hello.py new file mode 100644 index 0000000..3e239d3 --- /dev/null +++ b/src/hello.py @@ -0,0 +1,21 @@ +import os +from maigic_nyu.api import SlackApp +from dotenv import load_dotenv + + +# Example usage +def message_handler(message, say): + say(f"Hey there <@{message['user']}>! I am responding to: {message['text']}") + + +def main() -> None: + load_dotenv() + app = SlackApp( + message_handler=message_handler, + bot_token=os.environ.get("SLACK_BOT_TOKEN"), + app_token=os.environ["SLACK_APP_TOKEN"] + ) + app.run() + +if __name__ == "__main__": + main() diff --git a/src/maigic_nyu/__init__.py b/src/maigic_nyu/__init__.py new file mode 100644 index 0000000..a54270b --- /dev/null +++ b/src/maigic_nyu/__init__.py @@ -0,0 +1 @@ +"""Make maigic_nyu directory a referenceable package.""" diff --git a/src/maigic_nyu/_temp_math.py b/src/maigic_nyu/_temp_math.py new file mode 100644 index 0000000..897589f --- /dev/null +++ b/src/maigic_nyu/_temp_math.py @@ -0,0 +1,16 @@ +"""Placeholder API file to test imports.""" + +def add(num1:int, num2:int) -> int: + return num1 + num2 + +def subtract(num1:int, num2:int) -> int: + return num1 - num2 + +def multiply(num1:int, num2:int) -> int: + return num1 * num2 + +def divide(num1:int, num2:int) -> float: + return num1 / num2 + +def modulo(num1:int, num2:int) -> int: + return num1 % num2 diff --git a/src/maigic_nyu/api.py b/src/maigic_nyu/api.py new file mode 100644 index 0000000..2905bf2 --- /dev/null +++ b/src/maigic_nyu/api.py @@ -0,0 +1,9 @@ +from ._temp_math import ( + add as add, + divide as divide, + modulo as modulo, + multiply as multiply, + subtract as subtract, +) + +from .slackbot.slackbot import SlackApp as SlackApp diff --git a/src/maigic_nyu/slackbot/__init__.py b/src/maigic_nyu/slackbot/__init__.py new file mode 100644 index 0000000..10b939e --- /dev/null +++ b/src/maigic_nyu/slackbot/__init__.py @@ -0,0 +1,3 @@ +"""Make slack directory a referenceable package.""" + +# from slackbot import SlackApp as SlackApp \ No newline at end of file diff --git a/src/maigic_nyu/slackbot/slackbot.py b/src/maigic_nyu/slackbot/slackbot.py new file mode 100644 index 0000000..b7fede1 --- /dev/null +++ b/src/maigic_nyu/slackbot/slackbot.py @@ -0,0 +1,23 @@ +import os +from slack_bolt import App +from slack_bolt.adapter.socket_mode import SocketModeHandler +from slack_bolt.error import BoltError + +class SlackApp: + def __init__(self, message_handler, bot_token, app_token): + # Attempts to initializes app with bot token and socket mode handler + try: + self.app = App(token=os.environ.get(bot_token)) + @self.app.message() + def wrapped_handler(message, say): + self.handler(message, say) + + self.handler = message_handler + self.socket = SocketModeHandler(self.app, app_token) + except BoltError as e: + # Generally, this error arises if the "SLACK_BOT_TOKEN" is invalid + print("Error initializing Slack App:", e) + + def run(self): + self.socket.start() + \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..6fe7109 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1 @@ +"""Make tests directory as a referenceable package.""" diff --git a/tests/test_api.py b/tests/test_api.py new file mode 100644 index 0000000..9f2682e --- /dev/null +++ b/tests/test_api.py @@ -0,0 +1,22 @@ +from src.maigic_nyu.api import ( + add as add, + divide as divide, + modulo as modulo, + multiply as multiply, + subtract as subtract, +) + +def test_api_add() -> None: + assert add(7, 2) == 9 + +def test_api_subtract() -> None: + assert subtract(7, 2) == 5 + +def test_api_multiply() -> None: + assert multiply(7, 2) == 14 + +def test_api_divide() -> None: + assert divide(7, 2) == 3.5 + +def test_api_modulo() -> None: + assert modulo(7, 2) == 1 \ No newline at end of file diff --git a/tests/test_environment.py b/tests/test_environment.py new file mode 100644 index 0000000..2b70705 --- /dev/null +++ b/tests/test_environment.py @@ -0,0 +1,19 @@ +import os +from dotenv import load_dotenv +import pytest + +# Load environment variables from .env file +load_dotenv() + +# List of required environment variables +REQUIRED_ENV_VARS = ["SLACK_APP_TOKEN", "SLACK_BOT_TOKEN"] + +def test_env_file_exists(): + """Test that the .env file exists.""" + assert os.path.exists(".env"), "The .env file is missing." + +@pytest.mark.parametrize("env_var", REQUIRED_ENV_VARS) +def test_env_var_exist(env_var): + value = os.getenv(env_var) + assert value, f'Missing definition of environment variable \'{env_var}\'' + \ No newline at end of file diff --git a/tests/test_samples.py b/tests/test_samples.py new file mode 100644 index 0000000..bdf4ff2 --- /dev/null +++ b/tests/test_samples.py @@ -0,0 +1,31 @@ +import pytest + + +def test_addition() -> None: + assert 2 + 2 == 4 + +def test_subtraction() -> None: + assert 5 - 3 == 2 + +def test_multiplication() -> None: + assert 3 * 3 == 9 + +def test_division() -> None: + assert 8 / 2 == 4 + +def test_string_equality() -> None: + assert "hello".upper() == "HELLO" + +def test_list_append() -> None: + lst = [1, 2, 3] + lst.append(4) + assert lst == [1, 2, 3, 4] + +def test_dictionary_key() -> None: + d = {"name": "Alice", "age": 30} + assert "name" in d + assert d["name"] == "Alice" + +def test_raise_error() -> None: + with pytest.raises(ZeroDivisionError): + 1 / 0 diff --git a/tests/test_slack.py b/tests/test_slack.py new file mode 100644 index 0000000..edabe69 --- /dev/null +++ b/tests/test_slack.py @@ -0,0 +1,56 @@ +import os +from unittest.mock import MagicMock, patch +import pytest +from slack_bolt import App +from slack_bolt.adapter.socket_mode import SocketModeHandler +from slack_bolt.error import BoltError +from src.maigic_nyu.api import SlackApp # Replace 'your_module_name' with the actual file name +from dotenv import load_dotenv + +def test_slack_app_initialization(): + load_dotenv() + """Test if the SlackApp initializes without errors.""" + + # Mock handler + mock_handler = MagicMock() + + try: + slack_app = SlackApp(message_handler=mock_handler, bot_token=os.environ.get("SLACK_BOT_TOKEN"), app_token=os.environ.get("SLACK_APP_TOKEN")) + assert isinstance(slack_app.app, App) + assert isinstance(slack_app.socket, SocketModeHandler) + except BoltError: + pytest.fail("SlackApp initialization failed!") + +@patch("slack_bolt.App.message") +def test_message_handler_registration(mock_message_decorator): + """Test if the message handler is registered properly.""" + load_dotenv() + mock_handler = MagicMock() + + # Initialize SlackApp + slack_app = SlackApp(message_handler=mock_handler, bot_token=os.environ.get("SLACK_BOT_TOKEN"), app_token=os.environ.get("SLACK_APP_TOKEN")) + + # Ensure the decorator is called + mock_message_decorator.assert_called_once() + +def test_message_handler_execution(): + """Test if the custom message handler is executed when a message event occurs.""" + load_dotenv() + + def mock_handler(message, say): + say(f"Handled message: {message['text']}") + + # Initialize SlackApp + slack_app = SlackApp(message_handler=mock_handler, bot_token=os.environ.get("SLACK_BOT_TOKEN"), app_token=os.environ.get("SLACK_APP_TOKEN")) + + # Mock the say function + mock_say = MagicMock() + + # Sample message + test_message = {"user": "U123", "text": "Hello, Slack!"} + + # Simulate the message handler + slack_app.handler(test_message, mock_say) + + # Assert that the say function was called with the correct message + mock_say.assert_called_once_with("Handled message: Hello, Slack!") diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..16aa23d --- /dev/null +++ b/uv.lock @@ -0,0 +1,170 @@ +version = 1 +requires-python = ">=3.12" + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, +] + +[[package]] +name = "iniconfig" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, +] + +[[package]] +name = "maigic" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "mypy" }, + { name = "pytest" }, + { name = "python-dotenv" }, + { name = "ruff" }, + { name = "slack-bolt" }, +] + +[package.metadata] +requires-dist = [ + { name = "mypy" }, + { name = "pytest" }, + { name = "python-dotenv", specifier = ">=1.0.1" }, + { name = "ruff" }, + { name = "slack-bolt", specifier = ">=1.21.2" }, +] + +[[package]] +name = "mypy" +version = "1.13.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mypy-extensions" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e8/21/7e9e523537991d145ab8a0a2fd98548d67646dc2aaaf6091c31ad883e7c1/mypy-1.13.0.tar.gz", hash = "sha256:0291a61b6fbf3e6673e3405cfcc0e7650bebc7939659fdca2702958038bd835e", size = 3152532 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/31/c526a7bd2e5c710ae47717c7a5f53f616db6d9097caf48ad650581e81748/mypy-1.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5c7051a3461ae84dfb5dd15eff5094640c61c5f22257c8b766794e6dd85e72d5", size = 11077900 }, + { url = "https://files.pythonhosted.org/packages/83/67/b7419c6b503679d10bd26fc67529bc6a1f7a5f220bbb9f292dc10d33352f/mypy-1.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39bb21c69a5d6342f4ce526e4584bc5c197fd20a60d14a8624d8743fffb9472e", size = 10074818 }, + { url = "https://files.pythonhosted.org/packages/ba/07/37d67048786ae84e6612575e173d713c9a05d0ae495dde1e68d972207d98/mypy-1.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:164f28cb9d6367439031f4c81e84d3ccaa1e19232d9d05d37cb0bd880d3f93c2", size = 12589275 }, + { url = "https://files.pythonhosted.org/packages/1f/17/b1018c6bb3e9f1ce3956722b3bf91bff86c1cefccca71cec05eae49d6d41/mypy-1.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a4c1bfcdbce96ff5d96fc9b08e3831acb30dc44ab02671eca5953eadad07d6d0", size = 13037783 }, + { url = "https://files.pythonhosted.org/packages/cb/32/cd540755579e54a88099aee0287086d996f5a24281a673f78a0e14dba150/mypy-1.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0affb3a79a256b4183ba09811e3577c5163ed06685e4d4b46429a271ba174d2", size = 9726197 }, + { url = "https://files.pythonhosted.org/packages/11/bb/ab4cfdc562cad80418f077d8be9b4491ee4fb257440da951b85cbb0a639e/mypy-1.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a7b44178c9760ce1a43f544e595d35ed61ac2c3de306599fa59b38a6048e1aa7", size = 11069721 }, + { url = "https://files.pythonhosted.org/packages/59/3b/a393b1607cb749ea2c621def5ba8c58308ff05e30d9dbdc7c15028bca111/mypy-1.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d5092efb8516d08440e36626f0153b5006d4088c1d663d88bf79625af3d1d62", size = 10063996 }, + { url = "https://files.pythonhosted.org/packages/d1/1f/6b76be289a5a521bb1caedc1f08e76ff17ab59061007f201a8a18cc514d1/mypy-1.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2904956dac40ced10931ac967ae63c5089bd498542194b436eb097a9f77bc8", size = 12584043 }, + { url = "https://files.pythonhosted.org/packages/a6/83/5a85c9a5976c6f96e3a5a7591aa28b4a6ca3a07e9e5ba0cec090c8b596d6/mypy-1.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7bfd8836970d33c2105562650656b6846149374dc8ed77d98424b40b09340ba7", size = 13036996 }, + { url = "https://files.pythonhosted.org/packages/b4/59/c39a6f752f1f893fccbcf1bdd2aca67c79c842402b5283563d006a67cf76/mypy-1.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:9f73dba9ec77acb86457a8fc04b5239822df0c14a082564737833d2963677dbc", size = 9737709 }, + { url = "https://files.pythonhosted.org/packages/3b/86/72ce7f57431d87a7ff17d442f521146a6585019eb8f4f31b7c02801f78ad/mypy-1.13.0-py3-none-any.whl", hash = "sha256:9c250883f9fd81d212e0952c92dbfcc96fc237f4b7c92f56ac81fd48460b3e5a", size = 2647043 }, +] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/98/a4/1ab47638b92648243faf97a5aeb6ea83059cc3624972ab6b8d2316078d3f/mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782", size = 4433 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695 }, +] + +[[package]] +name = "packaging" +version = "24.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, +] + +[[package]] +name = "pytest" +version = "8.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8b/6c/62bbd536103af674e227c41a8f3dcd022d591f6eed5facb5a0f31ee33bbc/pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181", size = 1442487 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/77/7440a06a8ead44c7757a64362dd22df5760f9b12dc5f11b6188cd2fc27a0/pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2", size = 342341 }, +] + +[[package]] +name = "python-dotenv" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bc/57/e84d88dfe0aec03b7a2d4327012c1627ab5f03652216c63d49846d7a6c58/python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca", size = 39115 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a", size = 19863 }, +] + +[[package]] +name = "ruff" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/21/5c6e05e0fd3fbb41be4fb92edbc9a04de70baf60adb61435ce0c6b8c3d55/ruff-0.7.1.tar.gz", hash = "sha256:9d8a41d4aa2dad1575adb98a82870cf5db5f76b2938cf2206c22c940034a36f4", size = 3181670 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/65/45/8a20a9920175c9c4892b2420f80ff3cf14949cf3067118e212f9acd9c908/ruff-0.7.1-py3-none-linux_armv6l.whl", hash = "sha256:cb1bc5ed9403daa7da05475d615739cc0212e861b7306f314379d958592aaa89", size = 10389268 }, + { url = "https://files.pythonhosted.org/packages/1b/d3/2f8382db2cf4f9488e938602e33e36287f9d26cb283aa31f11c31297ce79/ruff-0.7.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:27c1c52a8d199a257ff1e5582d078eab7145129aa02721815ca8fa4f9612dc35", size = 10188348 }, + { url = "https://files.pythonhosted.org/packages/a2/31/7d14e2a88da351200f844b7be889a0845d9e797162cf76b136d21b832a23/ruff-0.7.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:588a34e1ef2ea55b4ddfec26bbe76bc866e92523d8c6cdec5e8aceefeff02d99", size = 9841448 }, + { url = "https://files.pythonhosted.org/packages/db/99/738cafdc768eceeca0bd26c6f03e213aa91203d2278e1d95b1c31c4ece41/ruff-0.7.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94fc32f9cdf72dc75c451e5f072758b118ab8100727168a3df58502b43a599ca", size = 10674864 }, + { url = "https://files.pythonhosted.org/packages/fe/12/bcf2836b50eab53c65008383e7d55201e490d75167c474f14a16e1af47d2/ruff-0.7.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:985818742b833bffa543a84d1cc11b5e6871de1b4e0ac3060a59a2bae3969250", size = 10192105 }, + { url = "https://files.pythonhosted.org/packages/2b/71/261d5d668bf98b6c44e89bfb5dfa4cb8cb6c8b490a201a3d8030e136ea4f/ruff-0.7.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32f1e8a192e261366c702c5fb2ece9f68d26625f198a25c408861c16dc2dea9c", size = 11194144 }, + { url = "https://files.pythonhosted.org/packages/90/1f/0926d18a3b566fa6e7b3b36093088e4ffef6b6ba4ea85a462d9a93f7e35c/ruff-0.7.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:699085bf05819588551b11751eff33e9ca58b1b86a6843e1b082a7de40da1565", size = 11917066 }, + { url = "https://files.pythonhosted.org/packages/cd/a8/9fac41f128b6a44ab4409c1493430b4ee4b11521e8aeeca19bfe1ce851f9/ruff-0.7.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:344cc2b0814047dc8c3a8ff2cd1f3d808bb23c6658db830d25147339d9bf9ea7", size = 11458821 }, + { url = "https://files.pythonhosted.org/packages/25/cd/59644168f086ab13fe4e02943b9489a0aa710171f66b178e179df5383554/ruff-0.7.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4316bbf69d5a859cc937890c7ac7a6551252b6a01b1d2c97e8fc96e45a7c8b4a", size = 12700379 }, + { url = "https://files.pythonhosted.org/packages/fb/30/3bac63619eb97174661829c07fc46b2055a053dee72da29d7c304c1cd2c0/ruff-0.7.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79d3af9dca4c56043e738a4d6dd1e9444b6d6c10598ac52d146e331eb155a8ad", size = 11019813 }, + { url = "https://files.pythonhosted.org/packages/4b/af/f567b885b5cb3bcdbcca3458ebf210cc8c9c7a9f61c332d3c2a050c3b21e/ruff-0.7.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c5c121b46abde94a505175524e51891f829414e093cd8326d6e741ecfc0a9112", size = 10662146 }, + { url = "https://files.pythonhosted.org/packages/bc/ad/eb930d3ad117a9f2f7261969c21559ebd82bb13b6e8001c7caed0d44be5f/ruff-0.7.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8422104078324ea250886954e48f1373a8fe7de59283d747c3a7eca050b4e378", size = 10256911 }, + { url = "https://files.pythonhosted.org/packages/20/d5/af292ce70a016fcec792105ca67f768b403dd480a11888bc1f418fed0dd5/ruff-0.7.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:56aad830af8a9db644e80098fe4984a948e2b6fc2e73891538f43bbe478461b8", size = 10767488 }, + { url = "https://files.pythonhosted.org/packages/24/85/cc04a3bd027f433bebd2a097e63b3167653c079f7f13d8f9a1178e693412/ruff-0.7.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:658304f02f68d3a83c998ad8bf91f9b4f53e93e5412b8f2388359d55869727fd", size = 11093368 }, + { url = "https://files.pythonhosted.org/packages/0b/fb/c39cbf32d1f3e318674b8622f989417231794926b573f76dd4d0ca49f0f1/ruff-0.7.1-py3-none-win32.whl", hash = "sha256:b517a2011333eb7ce2d402652ecaa0ac1a30c114fbbd55c6b8ee466a7f600ee9", size = 8594180 }, + { url = "https://files.pythonhosted.org/packages/5a/71/ec8cdea34ecb90c830ca60d54ac7b509a7b5eab50fae27e001d4470fe813/ruff-0.7.1-py3-none-win_amd64.whl", hash = "sha256:f38c41fcde1728736b4eb2b18850f6d1e3eedd9678c914dede554a70d5241307", size = 9419751 }, + { url = "https://files.pythonhosted.org/packages/79/7b/884553415e9f0a9bf358ed52fb68b934e67ef6c5a62397ace924a1afdf9a/ruff-0.7.1-py3-none-win_arm64.whl", hash = "sha256:19aa200ec824c0f36d0c9114c8ec0087082021732979a359d6f3c390a6ff2a37", size = 8717402 }, +] + +[[package]] +name = "slack-bolt" +version = "1.21.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "slack-sdk" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/1d/b4209957ee6d102e7d74b7e909e4d426d8ea53dcb85e9d9b77991ef161bd/slack_bolt-1.21.2.tar.gz", hash = "sha256:05ac2d454adfddfc629fb63c7a3723bd1432a24373119368bc81f2f52b029cbf", size = 129963 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/b8/40a673356f7ea185aa02ed1aa5f8730aaba791031361fcba1afc977f27b7/slack_bolt-1.21.2-py2.py3-none-any.whl", hash = "sha256:6860fc8693ca543b653c5d49a09b8b542f5fb7a02638342a7ddd18d8bc6f3ba0", size = 229474 }, +] + +[[package]] +name = "slack-sdk" +version = "3.33.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e6/80/1deaf0d20bdfd04613f67f0b8603c374dbde29199433bf070f29dc8f44aa/slack_sdk-3.33.3.tar.gz", hash = "sha256:4cc44c9ffe4bb28a01fbe3264c2f466c783b893a4eca62026ab845ec7c176ff1", size = 232956 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/bc/9af41edcc7ebc825f5bc700a9d8a865d5372dd5564f6047d672b4f35d620/slack_sdk-3.33.3-py2.py3-none-any.whl", hash = "sha256:0515fb93cd03b18de61f876a8304c4c3cef4dd3c2a3bad62d7394d2eb5a3c8e6", size = 291952 }, +] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, +]