From 6812c6280594ca29d80a1c2d79442f50f3361461 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Mon, 15 Jul 2024 21:03:24 +0200 Subject: [PATCH 001/168] Initial commit --- .github/workflows/lint.yaml | 35 +++++++ .gitignore | 31 ++++++ .pre-commit-config.yaml | 18 ++++ LICENSE.txt | 7 ++ README.md | 184 ++++++++++++++++++++++++++++++++++++ pyproject.toml | 44 +++++++++ requirements-dev.txt | 6 ++ samples/Pipfile | 15 +++ samples/pyproject.toml | 19 ++++ 9 files changed, 359 insertions(+) create mode 100644 .github/workflows/lint.yaml create mode 100644 .gitignore create mode 100644 .pre-commit-config.yaml create mode 100644 LICENSE.txt create mode 100644 README.md create mode 100644 pyproject.toml create mode 100644 requirements-dev.txt create mode 100644 samples/Pipfile create mode 100644 samples/pyproject.toml diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml new file mode 100644 index 0000000..7f67e80 --- /dev/null +++ b/.github/workflows/lint.yaml @@ -0,0 +1,35 @@ +# GitHub Action workflow enforcing our code style. + +name: Lint + +# Trigger the workflow on both push (to the main repository, on the main branch) +# and pull requests (against the main repository, but from any repo, from any branch). +on: + push: + branches: + - main + pull_request: + +# Brand new concurrency setting! This ensures that not more than one run can be triggered for the same commit. +# It is useful for pull requests coming from the main repository since both triggers will match. +concurrency: lint-${{ github.sha }} + +jobs: + lint: + runs-on: ubuntu-latest + + env: + # The Python version your project uses. Feel free to change this if required. + PYTHON_VERSION: "3.12" + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Python ${{ env.PYTHON_VERSION }} + uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Run pre-commit hooks + uses: pre-commit/action@v3.0.1 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..233eb87 --- /dev/null +++ b/.gitignore @@ -0,0 +1,31 @@ +# Files generated by the interpreter +__pycache__/ +*.py[cod] + +# Environment specific +.venv +venv +.env +env + +# Unittest reports +.coverage* + +# Logs +*.log + +# PyEnv version selector +.python-version + +# Built objects +*.so +dist/ +build/ + +# IDEs +# PyCharm +.idea/ +# VSCode +.vscode/ +# MacOS +.DS_Store diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..4bccb6f --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,18 @@ +# Pre-commit configuration. +# See https://github.com/python-discord/code-jam-template/tree/main#pre-commit-run-linting-before-committing + +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.6.0 + hooks: + - id: check-toml + - id: check-yaml + - id: end-of-file-fixer + - id: trailing-whitespace + args: [--markdown-linebreak-ext=md] + + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.5.0 + hooks: + - id: ruff + - id: ruff-format diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..5a04926 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,7 @@ +Copyright 2021 Python Discord + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..d50f7b7 --- /dev/null +++ b/README.md @@ -0,0 +1,184 @@ +# Python Discord Code Jam Repository Template + +## A primer + +Hello code jam participants! We've put together this repository template for you to use in [our code jams](https://pythondiscord.com/events/) or even other Python events! + +This document contains the following information: + +1. [What does this template contain?](#what-does-this-template-contain) +2. [How do I use this template?](#how-do-i-use-this-template) +3. [How do I adapt this template to my project?](#how-do-i-adapt-this-template-to-my-project) + +> [!TIP] +> You can also look at [our style guide](https://pythondiscord.com/events/code-jams/code-style-guide/) to get more information about what we consider a maintainable code style. + +## What does this template contain? + +Here is a quick rundown of what each file in this repository contains: + +- [`LICENSE.txt`](LICENSE.txt): [The MIT License](https://opensource.org/licenses/MIT), an OSS approved license which grants rights to everyone to use and modify your project, and limits your liability. We highly recommend you to read the license. +- [`.gitignore`](.gitignore): A list of files and directories that will be ignored by Git. Most of them are auto-generated or contain data that you wouldn't want to share publicly. +- [`requirements-dev.txt`](requirements-dev.txt): Every PyPI package used for the project's development, to ensure a common development environment. More on that [below](#using-the-default-pip-setup). +- [`pyproject.toml`](pyproject.toml): Configuration and metadata for the project, as well as the linting tool Ruff. If you're interested, you can read more about `pyproject.toml` in the [Python Packaging documentation](https://packaging.python.org/en/latest/guides/writing-pyproject-toml/). +- [`.pre-commit-config.yaml`](.pre-commit-config.yaml): The configuration of the [pre-commit](https://pre-commit.com/) tool. +- [`.github/workflows/lint.yaml`](.github/workflows/lint.yaml): A [GitHub Actions](https://github.com/features/actions) workflow, a set of actions run by GitHub on their server after each push, to ensure the style requirements are met. + +Each of these files have comments for you to understand easily, and modify to fit your needs. + +### Ruff: general style rules + +Our first tool is Ruff. It will check your codebase and warn you about any non-conforming lines. +It is run with the command `ruff check` in the project root. + +Here is a sample output: + +```shell +$ ruff check +app.py:1:5: N802 Function name `helloWorld` should be lowercase +app.py:1:5: ANN201 Missing return type annotation for public function `helloWorld` +app.py:2:5: D400 First line should end with a period +app.py:2:5: D403 First word of the first line should be capitalized: `docstring` -> `Docstring` +app.py:3:15: W292 No newline at end of file +Found 5 errors. +``` + +Each line corresponds to an error. The first part is the file path, then the line number, and the column index. +Then comes the error code, a unique identifier of the error, and then a human-readable message. + +If, for any reason, you do not wish to comply with this specific error on a specific line, you can add `# noqa: CODE` at the end of the line. +For example: + +```python +def helloWorld(): # noqa: N802 + ... + +``` + +This will ignore the function naming issue and pass linting. + +> [!WARNING] +> We do not recommend ignoring errors unless you have a good reason to do so. + +### Ruff: formatting + +Ruff also comes with a formatter, which can be run with the command `ruff format`. +It follows the same code style enforced by [Black](https://black.readthedocs.io/en/stable/index.html), so there's no need to pick between them. + +### Pre-commit: run linting before committing + +The second tool doesn't check your code, but rather makes sure that you actually *do* check it. + +It makes use of a feature called [Git hooks](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks) which allow you to run a piece of code before running `git commit`. +The good thing about it is that it will cancel your commit if the lint doesn't pass. You won't have to wait for GitHub Actions to report issues and have a second fix commit. + +It is *installed* by running `pre-commit install` and can be run manually by calling only `pre-commit`. + +[Lint before you push!](https://soundcloud.com/lemonsaurusrex/lint-before-you-push) + +#### List of hooks + +- `check-toml`: Lints and corrects your TOML files. +- `check-yaml`: Lints and corrects your YAML files. +- `end-of-file-fixer`: Makes sure you always have an empty line at the end of your file. +- `trailing-whitespace`: Removes whitespaces at the end of each line. +- `ruff`: Runs the Ruff linter. +- `ruff-format`: Runs the Ruff formatter. + +## How do I use this template? + +### Creating your team repository + +One person in the team, preferably the leader, will have to create the repository and add other members as collaborators. + +1. In the top right corner of your screen, where **Clone** usually is, you have a **Use this template** button to click. + ![use-this-template-button](https://docs.github.com/assets/images/help/repository/use-this-template-button.png) +2. Give the repository a name and a description. + ![create-repository-name](https://docs.github.com/assets/images/help/repository/create-repository-name.png) +3. Click **Create repository from template**. +4. Click **Settings** in your newly created repository. + ![repo-actions-settings](https://docs.github.com/assets/images/help/repository/repo-actions-settings.png) +5. In the "Access" section of the sidebar, click **Collaborators**. + ![collaborators-settings](https://github.com/python-discord/code-jam-template/assets/63936253/c150110e-d1b5-4e4d-93e0-0a2cf1de352b) +6. Click **Add people**. +7. Insert the names of each of your teammates, and invite them. Once they have accepted the invitation in their email, they will have write access to the repository. + +You are now ready to go! Sit down, relax, and wait for the kickstart! + +> [!IMPORTANT] +> Don't forget to swap "Python Discord" in the [`LICENSE.txt`](LICENSE.txt) file for the name of each of your team members or the name of your team *after* the start of the code jam. + +### Using the default pip setup + +Our default setup includes a bare requirements file to be used with a [virtual environment](https://docs.python.org/3/library/venv.html). +We recommend this if you have never used any other dependency manager, although if you have, feel free to switch to it. More on that [below](#how-do-i-adapt-this-template-to-my-project). + +#### Creating the environment + +Create a virtual environment in the folder `.venv`. + +```shell +python -m venv .venv +``` + +#### Entering the environment + +It will change based on your operating system and shell. + +```shell +# Linux, Bash +$ source .venv/bin/activate +# Linux, Fish +$ source .venv/bin/activate.fish +# Linux, Csh +$ source .venv/bin/activate.csh +# Linux, PowerShell Core +$ .venv/bin/Activate.ps1 +# Windows, cmd.exe +> .venv\Scripts\activate.bat +# Windows, PowerShell +> .venv\Scripts\Activate.ps1 +``` + +#### Installing the dependencies + +Once the environment is created and activated, use this command to install the development dependencies. + +```shell +pip install -r requirements-dev.txt +``` + +#### Exiting the environment + +Interestingly enough, it is the same for every platform. + +```shell +deactivate +``` + +Once the environment is activated, all the commands listed previously should work. + +> [!IMPORTANT] +> We highly recommend that you run `pre-commit install` as soon as possible. + +## How do I adapt this template to my project? + +If you wish to use Pipenv or Poetry, you will have to move the dependencies in [`requirements-dev.txt`](requirements-dev.txt) to the development dependencies of your tool. + +We've included a porting of [`requirements-dev.txt`](requirements-dev.txt) to both [Poetry](samples/pyproject.toml) and [Pipenv](samples/Pipfile) in the [`samples` folder](samples). +If you use the Poetry setup, make sure to change the project name, description, and authors at the top of the file. +Also note that the Poetry [`pyproject.toml`](samples/pyproject.toml) file does not include the Ruff configuration, so if you simply replace the file then the Ruff configuration will be lost. + +When installing new dependencies, don't forget to [pin](https://pip.pypa.io/en/stable/topics/repeatable-installs/#pinning-the-package-versions) them by adding a version tag at the end. +For example, if I wish to install [Click](https://click.palletsprojects.com/en/8.1.x/), a quick look at [PyPI](https://pypi.org/project/click/) tells me that `8.1.7` is the latest version. +I will then add `click~=8.1`, without the last number, to my requirements file or dependency manager. + +> [!IMPORTANT] +> A code jam project is left unmaintained after the end of the event. If the dependencies aren't pinned, the project will break after any major change in an API. + +## Final words + +> [!IMPORTANT] +> Don't forget to replace this README with an actual description of your project! Images are also welcome! + +We hope this template will be helpful. Good luck in the jam! diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..0880be9 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,44 @@ +[tool.ruff] +# Increase the line length. This breaks PEP8 but it is way easier to work with. +# The original reason for this limit was a standard vim terminal is only 79 characters, +# but this doesn't really apply anymore. +line-length = 119 +# Target Python 3.12. If you decide to use a different version of Python +# you will need to update this value. +target-version = "py312" +# Automatically fix auto-fixable issues. +fix = true +# The directory containing the source code. If you choose a different project layout +# you will need to update this value. +src = ["src"] + +[tool.ruff.lint] +# Enable all linting rules. +select = ["ALL"] +# Ignore some of the most obnoxious linting errors. +ignore = [ + # Missing docstrings. + "D100", + "D104", + "D105", + "D106", + "D107", + # Docstring whitespace. + "D203", + "D213", + # Docstring punctuation. + "D415", + # Docstring quotes. + "D301", + # Builtins. + "A", + # Print statements. + "T20", + # TODOs. + "TD002", + "TD003", + "FIX", + # Annotations. + "ANN101", + "ANN102", +] diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..d529f2e --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,6 @@ +# This file contains all the development requirements for our linting toolchain. +# Don't forget to pin your dependencies! +# This list will have to be migrated if you wish to use another dependency manager. + +ruff~=0.5.0 +pre-commit~=3.7.1 diff --git a/samples/Pipfile b/samples/Pipfile new file mode 100644 index 0000000..27673c0 --- /dev/null +++ b/samples/Pipfile @@ -0,0 +1,15 @@ +# Sample Pipfile. + +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] + +[dev-packages] +ruff = "~=0.5.0" +pre-commit = "~=3.7.1" + +[requires] +python_version = "3.12" diff --git a/samples/pyproject.toml b/samples/pyproject.toml new file mode 100644 index 0000000..835045d --- /dev/null +++ b/samples/pyproject.toml @@ -0,0 +1,19 @@ +# Sample poetry configuration. + +[tool.poetry] +name = "Name" +version = "0.1.0" +description = "Description" +authors = ["Author 1 "] +license = "MIT" + +[tool.poetry.dependencies] +python = "3.12.*" + +[tool.poetry.dev-dependencies] +ruff = "~0.5.0" +pre-commit = "~3.7.1" + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" From 60e03972b7f381d985e81e128f69af3b90726d07 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Mon, 15 Jul 2024 20:08:45 +0000 Subject: [PATCH 002/168] chore: initial commit --- .devcontainer/Dockerfile | 1 + .devcontainer/devcontainer.json | 60 + .devcontainer/postcreate.sh | 7 + .devcontainer/updatecontent.sh | 4 + .github/workflows/build-devcontainer.yaml | 32 + .github/workflows/ci.yaml | 38 + .github/workflows/lint.yaml | 35 - .github/workflows/publish.yaml | 49 + .gitignore | 181 ++- .markdownlint.json | 12 + .markdownlintignore | 1 + .pre-commit-config.yaml | 32 +- LICENSE.txt => LICENSE | 2 +- README.md | 184 --- courageous_comets/__init__.py | 0 docs/README.md | 17 + .../development-environment.md | 83 ++ docs/contributor-guide/documentation.md | 79 ++ docs/contributor-guide/index.md | 11 + docs/contributor-guide/testing.md | 99 ++ docs/contributor-guide/version-control.md | 223 ++++ docs/stylesheets/table.css | 11 + docs/stylesheets/tabs.css | 56 + examples/test__example.py | 76 ++ mkdocs.yaml | 71 + poetry.lock | 1173 +++++++++++++++++ pyproject.toml | 99 +- requirements-dev.txt | 6 - samples/Pipfile | 15 - samples/pyproject.toml | 19 - 30 files changed, 2359 insertions(+), 317 deletions(-) create mode 100644 .devcontainer/Dockerfile create mode 100644 .devcontainer/devcontainer.json create mode 100644 .devcontainer/postcreate.sh create mode 100644 .devcontainer/updatecontent.sh create mode 100644 .github/workflows/build-devcontainer.yaml create mode 100644 .github/workflows/ci.yaml delete mode 100644 .github/workflows/lint.yaml create mode 100644 .github/workflows/publish.yaml create mode 100644 .markdownlint.json create mode 100644 .markdownlintignore rename LICENSE.txt => LICENSE (96%) delete mode 100644 README.md create mode 100644 courageous_comets/__init__.py create mode 100644 docs/README.md create mode 100644 docs/contributor-guide/development-environment.md create mode 100644 docs/contributor-guide/documentation.md create mode 100644 docs/contributor-guide/index.md create mode 100644 docs/contributor-guide/testing.md create mode 100644 docs/contributor-guide/version-control.md create mode 100644 docs/stylesheets/table.css create mode 100644 docs/stylesheets/tabs.css create mode 100644 examples/test__example.py create mode 100644 mkdocs.yaml create mode 100644 poetry.lock delete mode 100644 requirements-dev.txt delete mode 100644 samples/Pipfile delete mode 100644 samples/pyproject.toml diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..d5d6a65 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1 @@ +FROM mcr.microsoft.com/vscode/devcontainers/base:debian diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..897ab93 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,60 @@ +{ + "build": { + "cacheFrom": "ghcr.io/thijsfranck/courageous-comets-devcontainer", + "dockerfile": "Dockerfile" + }, + "containerEnv": { + "POETRY_VIRTUALENVS_CREATE": "false" + }, + "customizations": { + "vscode": { + "extensions": [ + "charliermarsh.ruff", + "DavidAnson.vscode-markdownlint", + "KnisterPeter.vscode-commitizen", + "tamasfe.even-better-toml", + "-ms-python.autopep8" + ], + "settings": { + "[python]": { + "editor.defaultFormatter": "charliermarsh.ruff" + }, + "editor.formatOnSave": true, + "editor.formatOnSaveMode": "file", + "files.exclude": { + "**/.pytest_cache": true, + "**/.ruff_cache": true, + "**/__pycache__": true + }, + "files.insertFinalNewline": true, + "python.analysis.typeCheckingMode": "basic", + "python.testing.pytestArgs": [ + "." + ], + "python.testing.pytestEnabled": true, + "python.testing.unittestEnabled": false + } + } + }, + "features": { + "ghcr.io/devcontainers-contrib/features/act:1": {}, + "ghcr.io/devcontainers-contrib/features/poetry:2": {}, + "ghcr.io/devcontainers/features/docker-outside-of-docker:1": {}, + "ghcr.io/devcontainers/features/python:1": { + "installTools": false, + "version": "3.12" + } + }, + "forwardPorts": [ + 8000 + ], + "name": "Courageous Comets \u2604\ufe0f", + "portsAttributes": { + "8000": { + "label": "MkDocs", + "onAutoForward": "notify" + } + }, + "postCreateCommand": "bash .devcontainer/postcreate.sh", + "updateContentCommand": "bash .devcontainer/updatecontent.sh" +} diff --git a/.devcontainer/postcreate.sh b/.devcontainer/postcreate.sh new file mode 100644 index 0000000..2628186 --- /dev/null +++ b/.devcontainer/postcreate.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +# Mark the git repository as safe +git config --global --add safe.directory $PWD + +# Install pre-commit hooks +poetry run pre-commit install diff --git a/.devcontainer/updatecontent.sh b/.devcontainer/updatecontent.sh new file mode 100644 index 0000000..2ef1414 --- /dev/null +++ b/.devcontainer/updatecontent.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +# Install or update dependencies +poetry install diff --git a/.github/workflows/build-devcontainer.yaml b/.github/workflows/build-devcontainer.yaml new file mode 100644 index 0000000..f671f7f --- /dev/null +++ b/.github/workflows/build-devcontainer.yaml @@ -0,0 +1,32 @@ +name: Build Devcontainer + +on: + push: + branches: + - main + paths: + - .devcontainer/**/* + - .github/workflows/build-devcontainer.yaml + +permissions: write-all + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Log in to the Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Pre-build devcontainer image + uses: devcontainers/ci@v0.3 + with: + imageName: ghcr.io/thijsfranck/courageous-comets-devcontainer + cacheFrom: ghcr.io/thijsfranck/courageous-comets-devcontainer + push: always diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..9872e4d --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,38 @@ +name: CI + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + ci: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Python, Poetry and dependencies + uses: packetcoders/action-setup-cache-python-poetry@main + with: + python-version: 3.12 + poetry-version: 1.8.3 + + - name: Run pre-commit hooks + uses: pre-commit/action@v3.0.1 + + - name: Set up Pyright + run: echo "$(poetry env info --path)/bin" >> $GITHUB_PATH + + - name: Run type checking + uses: jakebailey/pyright-action@v2 + with: + version: 1.1.370 + python-version: 3.12 + + - name: Run tests + run: poetry run pytest diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml deleted file mode 100644 index 7f67e80..0000000 --- a/.github/workflows/lint.yaml +++ /dev/null @@ -1,35 +0,0 @@ -# GitHub Action workflow enforcing our code style. - -name: Lint - -# Trigger the workflow on both push (to the main repository, on the main branch) -# and pull requests (against the main repository, but from any repo, from any branch). -on: - push: - branches: - - main - pull_request: - -# Brand new concurrency setting! This ensures that not more than one run can be triggered for the same commit. -# It is useful for pull requests coming from the main repository since both triggers will match. -concurrency: lint-${{ github.sha }} - -jobs: - lint: - runs-on: ubuntu-latest - - env: - # The Python version your project uses. Feel free to change this if required. - PYTHON_VERSION: "3.12" - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Set up Python ${{ env.PYTHON_VERSION }} - uses: actions/setup-python@v5 - with: - python-version: ${{ env.PYTHON_VERSION }} - - - name: Run pre-commit hooks - uses: pre-commit/action@v3.0.1 diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml new file mode 100644 index 0000000..1cf0033 --- /dev/null +++ b/.github/workflows/publish.yaml @@ -0,0 +1,49 @@ +name: Publish + +on: + push: + tags: + - v*.*.* + +permissions: write-all + +jobs: + publish: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Log in to the Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup Python, Poetry and Dependencies + uses: packetcoders/action-setup-cache-python-poetry@main + with: + python-version: 3.12 + poetry-version: 1.8.3 + +# - name: Build the Application +# run: poetry build -f wheel +# shell: bash +# +# - name: Build and Publish the Docker Image +# uses: docker/build-push-action@v6 +# with: +# context: . +# push: true +# tags: ghcr.io/thijsfranck/courageous-comets:${{ github.ref_name }},ghcr.io/thijsfranck/courageous-comets:latest + + - name: Set up Git user + uses: fregante/setup-git-user@v2.0.1 + + - name: Publish documentation + run: poetry run mike deploy -b public-docs --push --update-aliases ${{ github.ref_name }} latest + shell: bash diff --git a/.gitignore b/.gitignore index 233eb87..75f0fc0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,31 +1,174 @@ -# Files generated by the interpreter +### Python ### +# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] +*$py.class -# Environment specific -.venv -venv -.env -env +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt -# Unittest reports -.coverage* +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ -# Logs +# Translations +*.mo +*.pot + +# Django stuff: *.log +local_settings.py +db.sqlite3 +db.sqlite3-journal -# PyEnv version selector -.python-version +# Flask stuff: +instance/ +.webassets-cache -# Built objects -*.so -dist/ -build/ +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ -# IDEs # PyCharm -.idea/ +# 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 +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +### Python Patch ### +# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration +poetry.toml + +# ruff +.ruff_cache/ + +# LSP config files +pyrightconfig.json + # VSCode .vscode/ -# MacOS -.DS_Store diff --git a/.markdownlint.json b/.markdownlint.json new file mode 100644 index 0000000..227692d --- /dev/null +++ b/.markdownlint.json @@ -0,0 +1,12 @@ +{ + "MD013": { + "code_blocks": false, + "line_length": 110, + "tables": false + }, + "MD024": { + "siblings_only": true + }, + "MD046": false, + "default": true +} diff --git a/.markdownlintignore b/.markdownlintignore new file mode 100644 index 0000000..a9bb6dc --- /dev/null +++ b/.markdownlintignore @@ -0,0 +1 @@ +docs/CHANGELOG.md diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4bccb6f..8ffb973 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,18 +1,44 @@ -# Pre-commit configuration. -# See https://github.com/python-discord/code-jam-template/tree/main#pre-commit-run-linting-before-committing +default_install_hook_types: [commit-msg, pre-commit] repos: + - repo: https://github.com/commitizen-tools/commitizen + rev: v3.27.0 + hooks: + - id: commitizen + name: Check commit message format + stages: [commit-msg] + - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.6.0 hooks: + - id: check-merge-conflict + name: Check for merge conflicts + - id: check-json + name: Check for JSON syntax errors - id: check-toml + name: Check for TOML syntax errors - id: check-yaml + name: Check for YAML syntax errors - id: end-of-file-fixer + name: Ensure files end with a newline + - id: pretty-format-json + name: Format JSON files + args: [--autofix] - id: trailing-whitespace + name: Trim trailing whitespace args: [--markdown-linebreak-ext=md] - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.5.0 + rev: v0.5.2 hooks: - id: ruff + name: Check for Python linting errors + args: [ --fix ] - id: ruff-format + name: Run Python formatter + + - repo: https://github.com/igorshubovych/markdownlint-cli + rev: v0.41.0 + hooks: + - id: markdownlint + name: Check for Markdown linting errors diff --git a/LICENSE.txt b/LICENSE similarity index 96% rename from LICENSE.txt rename to LICENSE index 5a04926..d08daf7 100644 --- a/LICENSE.txt +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright 2021 Python Discord +Copyright 2024 Courageous Comets Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/README.md b/README.md deleted file mode 100644 index d50f7b7..0000000 --- a/README.md +++ /dev/null @@ -1,184 +0,0 @@ -# Python Discord Code Jam Repository Template - -## A primer - -Hello code jam participants! We've put together this repository template for you to use in [our code jams](https://pythondiscord.com/events/) or even other Python events! - -This document contains the following information: - -1. [What does this template contain?](#what-does-this-template-contain) -2. [How do I use this template?](#how-do-i-use-this-template) -3. [How do I adapt this template to my project?](#how-do-i-adapt-this-template-to-my-project) - -> [!TIP] -> You can also look at [our style guide](https://pythondiscord.com/events/code-jams/code-style-guide/) to get more information about what we consider a maintainable code style. - -## What does this template contain? - -Here is a quick rundown of what each file in this repository contains: - -- [`LICENSE.txt`](LICENSE.txt): [The MIT License](https://opensource.org/licenses/MIT), an OSS approved license which grants rights to everyone to use and modify your project, and limits your liability. We highly recommend you to read the license. -- [`.gitignore`](.gitignore): A list of files and directories that will be ignored by Git. Most of them are auto-generated or contain data that you wouldn't want to share publicly. -- [`requirements-dev.txt`](requirements-dev.txt): Every PyPI package used for the project's development, to ensure a common development environment. More on that [below](#using-the-default-pip-setup). -- [`pyproject.toml`](pyproject.toml): Configuration and metadata for the project, as well as the linting tool Ruff. If you're interested, you can read more about `pyproject.toml` in the [Python Packaging documentation](https://packaging.python.org/en/latest/guides/writing-pyproject-toml/). -- [`.pre-commit-config.yaml`](.pre-commit-config.yaml): The configuration of the [pre-commit](https://pre-commit.com/) tool. -- [`.github/workflows/lint.yaml`](.github/workflows/lint.yaml): A [GitHub Actions](https://github.com/features/actions) workflow, a set of actions run by GitHub on their server after each push, to ensure the style requirements are met. - -Each of these files have comments for you to understand easily, and modify to fit your needs. - -### Ruff: general style rules - -Our first tool is Ruff. It will check your codebase and warn you about any non-conforming lines. -It is run with the command `ruff check` in the project root. - -Here is a sample output: - -```shell -$ ruff check -app.py:1:5: N802 Function name `helloWorld` should be lowercase -app.py:1:5: ANN201 Missing return type annotation for public function `helloWorld` -app.py:2:5: D400 First line should end with a period -app.py:2:5: D403 First word of the first line should be capitalized: `docstring` -> `Docstring` -app.py:3:15: W292 No newline at end of file -Found 5 errors. -``` - -Each line corresponds to an error. The first part is the file path, then the line number, and the column index. -Then comes the error code, a unique identifier of the error, and then a human-readable message. - -If, for any reason, you do not wish to comply with this specific error on a specific line, you can add `# noqa: CODE` at the end of the line. -For example: - -```python -def helloWorld(): # noqa: N802 - ... - -``` - -This will ignore the function naming issue and pass linting. - -> [!WARNING] -> We do not recommend ignoring errors unless you have a good reason to do so. - -### Ruff: formatting - -Ruff also comes with a formatter, which can be run with the command `ruff format`. -It follows the same code style enforced by [Black](https://black.readthedocs.io/en/stable/index.html), so there's no need to pick between them. - -### Pre-commit: run linting before committing - -The second tool doesn't check your code, but rather makes sure that you actually *do* check it. - -It makes use of a feature called [Git hooks](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks) which allow you to run a piece of code before running `git commit`. -The good thing about it is that it will cancel your commit if the lint doesn't pass. You won't have to wait for GitHub Actions to report issues and have a second fix commit. - -It is *installed* by running `pre-commit install` and can be run manually by calling only `pre-commit`. - -[Lint before you push!](https://soundcloud.com/lemonsaurusrex/lint-before-you-push) - -#### List of hooks - -- `check-toml`: Lints and corrects your TOML files. -- `check-yaml`: Lints and corrects your YAML files. -- `end-of-file-fixer`: Makes sure you always have an empty line at the end of your file. -- `trailing-whitespace`: Removes whitespaces at the end of each line. -- `ruff`: Runs the Ruff linter. -- `ruff-format`: Runs the Ruff formatter. - -## How do I use this template? - -### Creating your team repository - -One person in the team, preferably the leader, will have to create the repository and add other members as collaborators. - -1. In the top right corner of your screen, where **Clone** usually is, you have a **Use this template** button to click. - ![use-this-template-button](https://docs.github.com/assets/images/help/repository/use-this-template-button.png) -2. Give the repository a name and a description. - ![create-repository-name](https://docs.github.com/assets/images/help/repository/create-repository-name.png) -3. Click **Create repository from template**. -4. Click **Settings** in your newly created repository. - ![repo-actions-settings](https://docs.github.com/assets/images/help/repository/repo-actions-settings.png) -5. In the "Access" section of the sidebar, click **Collaborators**. - ![collaborators-settings](https://github.com/python-discord/code-jam-template/assets/63936253/c150110e-d1b5-4e4d-93e0-0a2cf1de352b) -6. Click **Add people**. -7. Insert the names of each of your teammates, and invite them. Once they have accepted the invitation in their email, they will have write access to the repository. - -You are now ready to go! Sit down, relax, and wait for the kickstart! - -> [!IMPORTANT] -> Don't forget to swap "Python Discord" in the [`LICENSE.txt`](LICENSE.txt) file for the name of each of your team members or the name of your team *after* the start of the code jam. - -### Using the default pip setup - -Our default setup includes a bare requirements file to be used with a [virtual environment](https://docs.python.org/3/library/venv.html). -We recommend this if you have never used any other dependency manager, although if you have, feel free to switch to it. More on that [below](#how-do-i-adapt-this-template-to-my-project). - -#### Creating the environment - -Create a virtual environment in the folder `.venv`. - -```shell -python -m venv .venv -``` - -#### Entering the environment - -It will change based on your operating system and shell. - -```shell -# Linux, Bash -$ source .venv/bin/activate -# Linux, Fish -$ source .venv/bin/activate.fish -# Linux, Csh -$ source .venv/bin/activate.csh -# Linux, PowerShell Core -$ .venv/bin/Activate.ps1 -# Windows, cmd.exe -> .venv\Scripts\activate.bat -# Windows, PowerShell -> .venv\Scripts\Activate.ps1 -``` - -#### Installing the dependencies - -Once the environment is created and activated, use this command to install the development dependencies. - -```shell -pip install -r requirements-dev.txt -``` - -#### Exiting the environment - -Interestingly enough, it is the same for every platform. - -```shell -deactivate -``` - -Once the environment is activated, all the commands listed previously should work. - -> [!IMPORTANT] -> We highly recommend that you run `pre-commit install` as soon as possible. - -## How do I adapt this template to my project? - -If you wish to use Pipenv or Poetry, you will have to move the dependencies in [`requirements-dev.txt`](requirements-dev.txt) to the development dependencies of your tool. - -We've included a porting of [`requirements-dev.txt`](requirements-dev.txt) to both [Poetry](samples/pyproject.toml) and [Pipenv](samples/Pipfile) in the [`samples` folder](samples). -If you use the Poetry setup, make sure to change the project name, description, and authors at the top of the file. -Also note that the Poetry [`pyproject.toml`](samples/pyproject.toml) file does not include the Ruff configuration, so if you simply replace the file then the Ruff configuration will be lost. - -When installing new dependencies, don't forget to [pin](https://pip.pypa.io/en/stable/topics/repeatable-installs/#pinning-the-package-versions) them by adding a version tag at the end. -For example, if I wish to install [Click](https://click.palletsprojects.com/en/8.1.x/), a quick look at [PyPI](https://pypi.org/project/click/) tells me that `8.1.7` is the latest version. -I will then add `click~=8.1`, without the last number, to my requirements file or dependency manager. - -> [!IMPORTANT] -> A code jam project is left unmaintained after the end of the event. If the dependencies aren't pinned, the project will break after any major change in an API. - -## Final words - -> [!IMPORTANT] -> Don't forget to replace this README with an actual description of your project! Images are also welcome! - -We hope this template will be helpful. Good luck in the jam! diff --git a/courageous_comets/__init__.py b/courageous_comets/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..f1c3134 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,17 @@ +# Courageous Comets ☄️ + +Thank you for your interest in our project! + +This is the documentation for the Courageous Comets Discord bot. It was built as part of the Python Discord Summer +Code Jam 2024. + +## Contents + +This documentation is divided into the following sections: + +- [Contributor Guide](./contributor-guide/index.md): How to set up a development environment and contribute to + the project. + +## License + +This project is published under the [MIT license](https://github.com/thijsfranck/courageous-comets/blob/main/LICENSE). diff --git a/docs/contributor-guide/development-environment.md b/docs/contributor-guide/development-environment.md new file mode 100644 index 0000000..b63e402 --- /dev/null +++ b/docs/contributor-guide/development-environment.md @@ -0,0 +1,83 @@ +# Development Environment + +Follow the steps below to set up your development environment. + +!!! NOTE "Prerequisites" + You need to have [Git](https://git-scm.com) installed on your system. + +## Cloning the Repository + +To clone the repository, run the following command: + +```bash +git clone https://github.com/thijsfranck/courageous-comets.git +``` + +Next, open the project in your preferred IDE or navigate to the project directory using the terminal. + +## Environment Setup + +You can set up the development environment using either the [automated](#automated-setup) or [manual](#manual-setup) +setup process. + +### Automated Setup + +The project includes a [development container](https://containers.dev) to automatically set up your development +environment. + +To get started, refer to the setup guide for your IDE: + +- [Visual Studio Code (recommended)](https://code.visualstudio.com/docs/devcontainers/tutorial) +- [PyCharm](https://www.jetbrains.com/help/pycharm/connect-to-devcontainer.html) + +??? TIP "Cloud Development Environment" + Alternatively, you can use a [GitHub Codespace](https://docs.github.com/en/codespaces/getting-started/quickstart) + to set up your development environment in the cloud. + +### Manual Setup + +If you prefer to set up the development environment manually, follow the steps below. + +!!! NOTE "Prerequisites" + Please ensure [Python 3.12](https://www.python.org) and [Poetry](https://python-poetry.org) are installed + on your system. + +Start by installing the project dependencies using Poetry: + +```bash +poetry install +``` + +Finally, install the pre-commit hooks: + +```bash +poetry run pre-commit install +``` + +## Configuration + +To configure your development environment, create a `.env` file in the project root directory with the following +content: + +```env +DISCORD_TOKEN= +``` + +Replace `` with your Discord bot token. + +!!! DANGER "Security Warning" + Do not commit your `.env` file to version control or share your token with anyone! + +??? QUESTION "Where do I find my Discord bot token?" + You can obtain a Discord bot token from the [Discord Developer Portal](https://discord.com/developers/applications). + +## Running the Documentation Locally + +To view the documentation locally, you can use the following command: + +```bash +poetry run mkdocs serve +``` + +Open your browser and navigate to [`http://localhost:8000`](http://localhost:8000) to view the documentation. +The changes you make to the documentation will be automatically reflected in the browser. diff --git a/docs/contributor-guide/documentation.md b/docs/contributor-guide/documentation.md new file mode 100644 index 0000000..31c97c9 --- /dev/null +++ b/docs/contributor-guide/documentation.md @@ -0,0 +1,79 @@ +# Documentation + +Good code documentation aids understanding and speeds up the development process. Follow the guidelines below +to document your code effectively. + +## What to Document + +Always document the following elements of your code: + +1. **Classes**, including their **attributes and public methods** +2. **Module-level functions and constants** + +Prioritize documenting public methods and attributes (those not starting with an underscore). However, private +methods with complex logic should also be documented for clarity. + +## Docstring Format + +This project uses numpy-style docstrings. Refer to the [style guide](https://numpydoc.readthedocs.io/en/latest/format.html) +for the full specification and detailed examples. + +Here are some examples of how to write good documentation for functions and classes: + +??? EXAMPLE "Function Documentation" + ```python + def example_function(param1: int, param2: str): + """ + One-line summary of the function. + + Detailed functional description of what the function does. Can span + multiple lines. + + Parameters + ---------- + param1 : int + Description of the first parameter. + param2 : str + Description of the second parameter. + + Returns + ------- + bool + Description of the return value. + + Raises + ------ + ValueError + Description of the error. + + Examples + -------- + >>> example_function(1, "test") + True + """ + ... + ``` + +??? EXAMPLE "Class Documentation" + ```python + class Example: + """ + Class-level docstring describing the class. + + Attributes + ---------- + attribute : int + Description of the attribute. + """ + ... + ``` + +## Type Annotations + +Python type annotations are strongly encouraged to improve code readability and maintainability. Use type annotations +for all parameters and return values, as well as class attributes. + +??? QUESTION "What are type annotations?" + Type annotations are a way to specify the expected types of variables, function parameters, and return values + in Python code. They are used to improve code readability and catch type-related errors early. Refer to the + [official documentation](https://docs.python.org/3/library/typing.html) for more information. diff --git a/docs/contributor-guide/index.md b/docs/contributor-guide/index.md new file mode 100644 index 0000000..4900974 --- /dev/null +++ b/docs/contributor-guide/index.md @@ -0,0 +1,11 @@ +# Contributor Guide + +The contributor guide is intended for developers working on this project. It provides instructions on how to +set up a new development environment, along with guidelines on version control, documentation, and testing. + +## Contents + +- [Development Environment](./development-environment.md): How to set up your development environment. +- [Version Control](./version-control.md): How to manage changes using version control. +- [Documentation](./documentation.md): How to write good documentation. +- [Testing](./testing.md): How to test the application. diff --git a/docs/contributor-guide/testing.md b/docs/contributor-guide/testing.md new file mode 100644 index 0000000..b7c615e --- /dev/null +++ b/docs/contributor-guide/testing.md @@ -0,0 +1,99 @@ +# Testing + +Automated tests are key to our success, since they allow us to catch bugs early, run sections of code in isolation, +and accelerate our development pace. + +## Structure + +Test modules should be located in the same directory as the module they cover. Test modules should be named +`test__*.py` (e.g.,`test__example.py`). Individual test methods within those modules should be prefixed with +`test__` (e.g., `test__my_function`). + +??? EXAMPLE "Test Module Structure" + ```plaintext + project_root/ + ├── courageous_comets/ + │ ├── __init__.py + │ ├── example.py + │ └── test__example.py + └── ... + ``` + +## Running Tests + +We use the [`pytest`](https://docs.pytest.org) framework for writing and running our tests. To run the tests, +use the following command from the root of the project: + +```bash +poetry run pytest +``` + +This command will discover and run all the tests modules that match the pattern `test__*.py`. + +??? TIP "Running Tests in your IDE" + Most modern IDEs have built-in support for running tests. You can run tests directly from your IDE, which + can be more convenient than running them from the command line. + + - [Visual Studio Code](https://code.visualstudio.com/docs/python/testing) + - [PyCharm](https://www.jetbrains.com/help/pycharm/pytest.html) + + The development container is pre-configured for using `pytest` in Visual Studio Code. + +## What to Test + +Unit tests should cover the following aspects of your code: + +- Input validation +- Correctness of output (or outcome) given a valid input +- Error handling + +??? TIP "Consider Edge Cases" + When writing tests, consider edge cases such as invalid inputs and unexpected behavior. These are often the + areas where bugs are most likely to occur. + +Some parts of the code may be more critical than others. Focus on writing tests for the most critical parts of +the codebase, such as complex algorithms, core functionality or user-facing features. + +## Writing Tests + +Each test case should be self-contained and independent of other tests. This means that each test should set up +its own data and clean up after itself. Avoid relying on the state of other tests or the order in which tests +are run. + +When writing tests, follow these guidelines: + +- Use descriptive test names that clearly indicate what is being tested. +- Limit each test to a single logical concept. +- Use the `assert` statement to check the expected outcome of the test. +- Aim for one `assert` statement per test. +- Use [fixtures](https://docs.pytest.org/en/latest/explanation/fixtures.html) to set up common data or resources. + +??? EXAMPLE "Example Tests" + The `examples` folder includes sample tests that you can use as a base for your own test. + +## Unit Testing and Type Annotations + +You can reduce the need for unit tests by indicating the expected types of input arguments and return values as +type annotations. While they don't replace unit tests, type annotations can reduce the number of tests you might +need to write, particularly those related to input validation. + +For instance, consider the following function without type annotations: + +???+ EXAMPLE "Function Without Type Annotations" + ```python + def add(a, b): + return a + b + ``` + +Without type annotations, you might write multiple tests to ensure that the function behaves correctly with different +types of input, like strings, integers, or floats. But with type annotations: + +???+ EXAMPLE "Function With Type Annotations" + ```python + def add(a: int, b: int) -> int: + return a + b + ``` + +The function's expected behavior is clearer. You know that both `a` and `b` should be integers, and the return +value will also be an integer. With these type annotations in place, there's less need to write unit tests checking +for behaviors with non-integer inputs since the static type checker can catch those mistakes for you. diff --git a/docs/contributor-guide/version-control.md b/docs/contributor-guide/version-control.md new file mode 100644 index 0000000..5440b15 --- /dev/null +++ b/docs/contributor-guide/version-control.md @@ -0,0 +1,223 @@ +# Version Control + +When making changes to the project, follow these guidelines. + +## Branching + +Always create a new branch for your changes. This makes it easier to handle multiple contributions simultaneously. + +First, pull the latest changes from the `main` branch: + +```bash +git pull main +``` + +Next, create a new branch with the following command: + +```bash +git checkout -b "" +``` + +Replace `` with a short, descriptive name for your branch. For example, `add-uptime-command`. + +## Commits + +Commits should follow the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) specification. +This helps maintain a clean and structured commit history. + +Try to keep your commits focused on a single task. If you need to make multiple changes, create separate commits +for each change. + +??? EXAMPLE "Conventional Commit Format" + Here's an example of a good commit message: + + ```plaintext + feat: add uptime command + + Add a new command to display the bot's uptime. + ``` + +??? TIP "Use Commitizen" + The workspace includes [Commitizen](https://commitizen-tools.github.io/commitizen/) to help you write conventional + commit messages. Run the following command to create a commit message interactively: + + ```bash + poetry run cz commit + ``` + +### Automated Checks + +The project includes pre-commit hooks to ensure your code meets the quality standards. These hooks run automatically +before each commit. + +The pre-commit hooks include: + +- Linting and formatting with [Ruff](https://docs.astral.sh/ruff/) +- Commit message validation with [Commitlint](https://commitlint.js.org) + +??? QUESTION "What if the pre-commit hooks fail?" + If the pre-commit hooks fail, you will need to address the issues before committing your changes. Follow the + instructions provided by the pre-commit hooks to identify and fix the issues. + +??? QUESTION "How do I run the pre-commit hooks manually?" + Pre-commit hooks can also be run manually using the following command: + + ```bash + poetry run pre-commit + ``` + +The pre-commit hooks are intended to help us keep the codebase maintainable. If there are rules that you believe +are too strict, please discuss them with the team. + +## Pull Requests + +Once you have completed your changes, it's time to create a pull request. A pull request allows your changes to +be reviewed and merged into the `main` branch. + +Before creating a pull request, ensure your branch is up to date with the latest changes from the `main` branch: + +```bash +git pull main +``` + +Next, push your changes to the repository: + +```bash +git push +``` + +Finally, [create a pull request on GitHub](https://github.com/thijsfranck/courageous-comets/compare). Select +your branch as the source and the `main` branch as the base. + +In the pull request description, provide a brief overview of the changes and any relevant information for reviewers. + +??? EXAMPLE "Pull Request Description" + Here's an example of a good pull request description: + + ```plaintext + # feat: add uptime command + + This pull request adds a new uptime command to display the bot's uptime. + + ## Changes + + - Added a new command to display the bot's uptime + - Updated the help command to include information about the new command + + ## Notes + + - The new command is implemented in a separate file for better organization + - The command has been tested locally and works as expected + ``` + +### Automated Checks + +The project includes automated checks to ensure the code meets the quality standards. These checks include: + +- All [pre-commit hooks](#automated-checks) must pass +- Type checking with [Pyright](https://github.com/microsoft/pyright) +- Unit tests with [pytest](https://docs.pytest.org/en/stable/) + +??? QUESTION "What if the automated checks fail?" + If any of the automated checks fail, please address the issues before requesting a review. Feedback from the + automated checks should be available in the pull request checks tab. + +### Code Review + +All pull requests should be reviewed by at least one other team member before merging. The reviewer will provide +feedback and suggestions for improvement. + +Once the reviewer approves the pull request, you can merge it into the `main` branch. + +??? QUESTION "How do I request a review?" + Request a review from a team member by [assigning them as a reviewer](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/requesting-a-pull-request-review) + to your pull request. + +#### Giving Feedback + +When providing feedback on a pull request, be constructive and specific. Point out areas for improvement and suggest +possible solutions. If you have any questions or concerns, don't hesitate to ask the author for clarification. + +A code review should focus on the following aspects: + +- Correctness and functionality +- Code quality and readability +- Adherence to the project guidelines + +??? EXAMPLE "Good Code Review Feedback" + Here are some examples of good code review feedback: + + ```plaintext + - Great work on the new command! The implementation looks good overall. + - I noticed a small typo in the docstring. Could you update it to fix the typo? + - The logic in the new command is a bit complex. Consider breaking it down into smaller functions for clarity. + - The tests cover most of the functionality, but we are missing a test case for edge case X. Could you add a test for that? + ``` + +Always be respectful and considerate when giving feedback. Remember that the goal is to improve the code and help +the author grow as a developer. + +!!! SUCCESS "Be Positive" + Don't forget to acknowledge the positive aspects of the contribution as well! + +## Release + +Releases are managed through [Commitizen](https://commitizen-tools.github.io/commitizen/). To generate a +new release, run the following command: + +```bash +poetry run cz bump +``` + +This command will automatically determine the next version number based on the commit history and generate a +new tag. It will also update the changelog with the latest changes. To push the changes to the repository, run: + +```bash +git push && git push --tags +``` + +The release will trigger a [GitHub actions workflow](#github-actions) to build and publish a new version of the +Docker image and update the documentation. + +??? TIP "Dry Run" + You can perform a dry run to see the changes that will be made without actually committing them: + + ```bash + poetry run cz bump --dry-run + ``` + +??? TIP "Commitizen and Conventional Commits" + Commitizen uses the commit messages to determine the type of changes and generate the release notes. + Make sure to follow the [commit message guidelines](#commits) to ensure accurate release notes. + +### Semantic Versioning + +Tags should be unique and follow the [Semantic Versioning](https://semver.org/) format. +Semantic version numbers consist of three parts: `major.minor.patch`. For example, `1.0.0`. + +To calculate the next version number, follow these guidelines: + +- For *bug fixes* or *minor improvements*, increment the patch version. +- For *new features* or *significant improvements*, increment the minor version. +- For **breaking changes**, increment the major version. + +??? QUESTION "What is a breaking change?" + A breaking change requires users to change the way they use the software. Examples include removal of features + or backwards-incompatible API changes. + +??? EXAMPLE "Semantic Versioning" + Here are some examples of version increments: + + - Bug fixes: `1.0.0` -> `1.0.1` + - New features: `1.0.1` -> `1.1.0` + - Breaking changes: `1.1.0` -> `2.0.0` + +### GitHub Actions + +A GitHub actions workflow will automatically build and publish a new version of the Docker image when a new tag +is pushed to the repository. + +The updated image will be available on the [GitHub Container Registry](https://github.com/thijsfranck/courageous-comets/pkgs/container/courageous-comets) +with both the release tag and the `latest` tag. + +The GitHub actions workflow also updates the documentation to reflect the new release. diff --git a/docs/stylesheets/table.css b/docs/stylesheets/table.css new file mode 100644 index 0000000..95cff73 --- /dev/null +++ b/docs/stylesheets/table.css @@ -0,0 +1,11 @@ +/* + * Make tables full width by default + */ + + .md-typeset__table { + min-width: 100%; +} + +.md-typeset table:not([class]) { + display: table; +} diff --git a/docs/stylesheets/tabs.css b/docs/stylesheets/tabs.css new file mode 100644 index 0000000..7f99089 --- /dev/null +++ b/docs/stylesheets/tabs.css @@ -0,0 +1,56 @@ +.tabbed-set { + position: relative; + display: flex; + flex-wrap: wrap; + margin: 1em 0; + border-radius: 0.1rem; + border: 1px solid #ddd; + padding: .5rem; +} + +.tabbed-set>input { + display: none; +} + +.tabbed-set label { + width: auto; + padding: 0.9375em 1.25em 0.78125em; + font-weight: 700; + font-size: 0.84em; + white-space: nowrap; + border-bottom: 0.15rem solid transparent; + border-top-left-radius: 0.1rem; + border-top-right-radius: 0.1rem; + cursor: pointer; + transition: background-color 250ms, color 250ms; +} + +.tabbed-set .tabbed-content { + width: 100%; + display: none; + box-shadow: 0 -.05rem #ddd; + padding: 24px; +} + +.tabbed-set input { + position: absolute; + opacity: 0; +} + +.tabbed-set input:checked:nth-child(n+1)+label { + color: #2980b9; + border-color: #2980b9; +} + +@media screen { + .tabbed-set input:nth-child(n+1):checked+label+.tabbed-content { + order: 99; + display: block; + } +} + +@media print { + .tabbed-content { + display: contents; + } +} diff --git a/examples/test__example.py b/examples/test__example.py new file mode 100644 index 0000000..0242673 --- /dev/null +++ b/examples/test__example.py @@ -0,0 +1,76 @@ +""" +The `test__example` module contains example tests for a Python project. + +For guidelines on writing good tests, see the contributor guide. +""" + +import pytest + + +@pytest.fixture() +def example_data() -> list[int]: + """ + Fixture that returns a list of integers for testing. + + Returns + ------- + list[int] + A list of integers. + """ + return [1, 2, 3, 4, 5] + + +def test__addition() -> None: + """ + Example of a basic test case. + + Asserts + ------- + - 1 + 1 is equal to 2. + """ + assert 1 + 1 == 2 + + +def test__sum_with_fixture(example_data: list[int]) -> None: + """ + Example of a test case that uses a fixture. + + Parameters + ---------- + example_data: list[int] + A list of integers. + + Asserts + ------- + - The sum of the example data is 15. + """ + assert sum(example_data) == 15 + + +@pytest.mark.parametrize( + ("input_data", "expected_output"), + [ + ([1, 2, 3], 6), + ([4, 5, 6], 15), + ([], 0), + ], +) +def test__sum_with_parameterization( + input_data: list[int], + expected_output: int, +) -> None: + """ + Example of a parameterized test case. + + Parameters + ---------- + input_data: list[int] + A list of integers to sum. + expected_output: int + The expected sum of the input list. + + Asserts + ------- + - The sum of the input data is equal to the expected output. + """ + assert sum(input_data) == expected_output diff --git a/mkdocs.yaml b/mkdocs.yaml new file mode 100644 index 0000000..8b4ad66 --- /dev/null +++ b/mkdocs.yaml @@ -0,0 +1,71 @@ +copyright: © 2024 Courageous Comets ☄️ +repo_name: thijsfranck/courageous-comets +repo_url: https://github.com/thijsfranck/courageous-comets +site_name: Courageous Comets ☄️ +site_url: https://thijsfranck.github.io/courageous-comets/ + +theme: + name: material + search: true + + features: + - content.code.copy + - navigation.indexes + - navigation.instant + - navigation.instant.progress + - navigation.sections + + icon: + logo: fontawesome/brands/discord + repo: fontawesome/brands/github + + palette: + - media: "(prefers-color-scheme)" + toggle: + icon: material/brightness-auto + name: Switch to light mode + - media: "(prefers-color-scheme: light)" + scheme: default + toggle: + icon: material/brightness-7 + name: Switch to dark mode + - media: "(prefers-color-scheme: dark)" + scheme: slate + toggle: + icon: material/brightness-4 + name: Switch to system preference + +nav: + - Contributor Guide: + - contributor-guide/index.md + - Development Environment: contributor-guide/development-environment.md + - Version Control: contributor-guide/version-control.md + - Documentation: contributor-guide/documentation.md + - Testing: contributor-guide/testing.md + +markdown_extensions: + - admonition + - attr_list + - pymdownx.details + - pymdownx.snippets + - pymdownx.superfences + - pymdownx.tabbed + - toc: + permalink: true + +plugins: + - mike + - search + +extra: + generator: false + social: + - icon: fontawesome/brands/github + link: https://github.com/thijsfranck/courageous-comets + version: + alias: true + provider: mike + +extra_css: + - stylesheets/tabs.css + - stylesheets/table.css diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..ca9a55f --- /dev/null +++ b/poetry.lock @@ -0,0 +1,1173 @@ +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. + +[[package]] +name = "argcomplete" +version = "3.3.0" +description = "Bash tab completion for argparse" +optional = false +python-versions = ">=3.8" +files = [ + {file = "argcomplete-3.3.0-py3-none-any.whl", hash = "sha256:c168c3723482c031df3c207d4ba8fa702717ccb9fc0bfe4117166c1f537b4a54"}, + {file = "argcomplete-3.3.0.tar.gz", hash = "sha256:fd03ff4a5b9e6580569d34b273f741e85cd9e072f3feeeee3eba4891c70eda62"}, +] + +[package.extras] +test = ["coverage", "mypy", "pexpect", "ruff", "wheel"] + +[[package]] +name = "babel" +version = "2.15.0" +description = "Internationalization utilities" +optional = false +python-versions = ">=3.8" +files = [ + {file = "Babel-2.15.0-py3-none-any.whl", hash = "sha256:08706bdad8d0a3413266ab61bd6c34d0c28d6e1e7badf40a2cebe67644e2e1fb"}, + {file = "babel-2.15.0.tar.gz", hash = "sha256:8daf0e265d05768bc6c7a314cf1321e9a123afc328cc635c18622a2f30a04413"}, +] + +[package.extras] +dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] + +[[package]] +name = "certifi" +version = "2024.7.4" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, + {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, +] + +[[package]] +name = "cfgv" +version = "3.4.0" +description = "Validate configuration and produce human readable error messages." +optional = false +python-versions = ">=3.8" +files = [ + {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, + {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.3.2" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, +] + +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "commitizen" +version = "3.27.0" +description = "Python commitizen client tool" +optional = false +python-versions = ">=3.8" +files = [ + {file = "commitizen-3.27.0-py3-none-any.whl", hash = "sha256:11948fa563d5ad5464baf09eaacff3cf8cbade1ca029ed9c4978f2227f033130"}, + {file = "commitizen-3.27.0.tar.gz", hash = "sha256:5874d0c7e8e1be3b75b1b0a2269cffe3dd5c843b860d84b0bdbb9ea86e3474b8"}, +] + +[package.dependencies] +argcomplete = ">=1.12.1,<3.4" +charset-normalizer = ">=2.1.0,<4" +colorama = ">=0.4.1,<0.5.0" +decli = ">=0.6.0,<0.7.0" +importlib_metadata = ">=4.13,<8" +jinja2 = ">=2.10.3" +packaging = ">=19" +pyyaml = ">=3.08" +questionary = ">=2.0,<3.0" +termcolor = ">=1.1,<3" +tomlkit = ">=0.5.3,<1.0.0" + +[[package]] +name = "decli" +version = "0.6.2" +description = "Minimal, easy-to-use, declarative cli tool" +optional = false +python-versions = ">=3.7" +files = [ + {file = "decli-0.6.2-py3-none-any.whl", hash = "sha256:2fc84106ce9a8f523ed501ca543bdb7e416c064917c12a59ebdc7f311a97b7ed"}, + {file = "decli-0.6.2.tar.gz", hash = "sha256:36f71eb55fd0093895efb4f416ec32b7f6e00147dda448e3365cf73ceab42d6f"}, +] + +[[package]] +name = "distlib" +version = "0.3.8" +description = "Distribution utilities" +optional = false +python-versions = "*" +files = [ + {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"}, + {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, +] + +[[package]] +name = "filelock" +version = "3.15.4" +description = "A platform independent file lock." +optional = false +python-versions = ">=3.8" +files = [ + {file = "filelock-3.15.4-py3-none-any.whl", hash = "sha256:6ca1fffae96225dab4c6eaf1c4f4f28cd2568d3ec2a44e15a08520504de468e7"}, + {file = "filelock-3.15.4.tar.gz", hash = "sha256:2207938cbc1844345cb01a5a95524dae30f0ce089eba5b00378295a17e3e90cb"}, +] + +[package.extras] +docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-asyncio (>=0.21)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)", "virtualenv (>=20.26.2)"] +typing = ["typing-extensions (>=4.8)"] + +[[package]] +name = "ghp-import" +version = "2.1.0" +description = "Copy your docs directly to the gh-pages branch." +optional = false +python-versions = "*" +files = [ + {file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"}, + {file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"}, +] + +[package.dependencies] +python-dateutil = ">=2.8.1" + +[package.extras] +dev = ["flake8", "markdown", "twine", "wheel"] + +[[package]] +name = "identify" +version = "2.6.0" +description = "File identification library for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "identify-2.6.0-py2.py3-none-any.whl", hash = "sha256:e79ae4406387a9d300332b5fd366d8994f1525e8414984e1a59e058b2eda2dd0"}, + {file = "identify-2.6.0.tar.gz", hash = "sha256:cb171c685bdc31bcc4c1734698736a7d5b6c8bf2e0c15117f4d469c8640ae5cf"}, +] + +[package.extras] +license = ["ukkonen"] + +[[package]] +name = "idna" +version = "3.7" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, + {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, +] + +[[package]] +name = "importlib-metadata" +version = "7.2.1" +description = "Read metadata from Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "importlib_metadata-7.2.1-py3-none-any.whl", hash = "sha256:ffef94b0b66046dd8ea2d619b701fe978d9264d38f3998bc4c27ec3b146a87c8"}, + {file = "importlib_metadata-7.2.1.tar.gz", hash = "sha256:509ecb2ab77071db5137c655e24ceb3eee66e7bbc6574165d0d114d9fc4bbe68"}, +] + +[package.dependencies] +zipp = ">=0.5" + +[package.extras] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +perf = ["ipython"] +test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"] + +[[package]] +name = "importlib-resources" +version = "6.4.0" +description = "Read resources from Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "importlib_resources-6.4.0-py3-none-any.whl", hash = "sha256:50d10f043df931902d4194ea07ec57960f66a80449ff867bfe782b4c486ba78c"}, + {file = "importlib_resources-6.4.0.tar.gz", hash = "sha256:cdb2b453b8046ca4e3798eb1d84f3cce1446a0e8e7b5ef4efb600f19fc398145"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["jaraco.test (>=5.4)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)", "zipp (>=3.17)"] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "jinja2" +version = "3.1.4" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +files = [ + {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, + {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "markdown" +version = "3.6" +description = "Python implementation of John Gruber's Markdown." +optional = false +python-versions = ">=3.8" +files = [ + {file = "Markdown-3.6-py3-none-any.whl", hash = "sha256:48f276f4d8cfb8ce6527c8f79e2ee29708508bf4d40aa410fbc3b4ee832c850f"}, + {file = "Markdown-3.6.tar.gz", hash = "sha256:ed4f41f6daecbeeb96e576ce414c41d2d876daa9a16cb35fa8ed8c2ddfad0224"}, +] + +[package.extras] +docs = ["mdx-gh-links (>=0.2)", "mkdocs (>=1.5)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python]"] +testing = ["coverage", "pyyaml"] + +[[package]] +name = "markupsafe" +version = "2.1.5" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.7" +files = [ + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, + {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, +] + +[[package]] +name = "mergedeep" +version = "1.3.4" +description = "A deep merge function for 🐍." +optional = false +python-versions = ">=3.6" +files = [ + {file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"}, + {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"}, +] + +[[package]] +name = "mike" +version = "2.1.2" +description = "Manage multiple versions of your MkDocs-powered documentation" +optional = false +python-versions = "*" +files = [ + {file = "mike-2.1.2-py3-none-any.whl", hash = "sha256:d61d9b423ab412d634ca2bd520136d5114e3cc73f4bbd1aa6a0c6625c04918c0"}, + {file = "mike-2.1.2.tar.gz", hash = "sha256:d59cc8054c50f9c8a046cfd47f9b700cf9ff1b2b19f420bd8812ca6f94fa8bd3"}, +] + +[package.dependencies] +importlib-metadata = "*" +importlib-resources = "*" +jinja2 = ">=2.7" +mkdocs = ">=1.0" +pyparsing = ">=3.0" +pyyaml = ">=5.1" +pyyaml-env-tag = "*" +verspec = "*" + +[package.extras] +dev = ["coverage", "flake8 (>=3.0)", "flake8-quotes", "shtab"] +test = ["coverage", "flake8 (>=3.0)", "flake8-quotes", "shtab"] + +[[package]] +name = "mkdocs" +version = "1.6.0" +description = "Project documentation with Markdown." +optional = false +python-versions = ">=3.8" +files = [ + {file = "mkdocs-1.6.0-py3-none-any.whl", hash = "sha256:1eb5cb7676b7d89323e62b56235010216319217d4af5ddc543a91beb8d125ea7"}, + {file = "mkdocs-1.6.0.tar.gz", hash = "sha256:a73f735824ef83a4f3bcb7a231dcab23f5a838f88b7efc54a0eef5fbdbc3c512"}, +] + +[package.dependencies] +click = ">=7.0" +colorama = {version = ">=0.4", markers = "platform_system == \"Windows\""} +ghp-import = ">=1.0" +jinja2 = ">=2.11.1" +markdown = ">=3.3.6" +markupsafe = ">=2.0.1" +mergedeep = ">=1.3.4" +mkdocs-get-deps = ">=0.2.0" +packaging = ">=20.5" +pathspec = ">=0.11.1" +pyyaml = ">=5.1" +pyyaml-env-tag = ">=0.1" +watchdog = ">=2.0" + +[package.extras] +i18n = ["babel (>=2.9.0)"] +min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-import (==1.0)", "importlib-metadata (==4.4)", "jinja2 (==2.11.1)", "markdown (==3.3.6)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "mkdocs-get-deps (==0.2.0)", "packaging (==20.5)", "pathspec (==0.11.1)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "watchdog (==2.0)"] + +[[package]] +name = "mkdocs-get-deps" +version = "0.2.0" +description = "MkDocs extension that lists all dependencies according to a mkdocs.yml file" +optional = false +python-versions = ">=3.8" +files = [ + {file = "mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134"}, + {file = "mkdocs_get_deps-0.2.0.tar.gz", hash = "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c"}, +] + +[package.dependencies] +mergedeep = ">=1.3.4" +platformdirs = ">=2.2.0" +pyyaml = ">=5.1" + +[[package]] +name = "mkdocs-material" +version = "9.5.29" +description = "Documentation that simply works" +optional = false +python-versions = ">=3.8" +files = [ + {file = "mkdocs_material-9.5.29-py3-none-any.whl", hash = "sha256:afc1f508e2662ded95f0a35a329e8a5acd73ee88ca07ba73836eb6fcdae5d8b4"}, + {file = "mkdocs_material-9.5.29.tar.gz", hash = "sha256:3e977598ec15a4ddad5c4dfc9e08edab6023edb51e88f0729bd27be77e3d322a"}, +] + +[package.dependencies] +babel = ">=2.10,<3.0" +colorama = ">=0.4,<1.0" +jinja2 = ">=3.0,<4.0" +markdown = ">=3.2,<4.0" +mkdocs = ">=1.6,<2.0" +mkdocs-material-extensions = ">=1.3,<2.0" +paginate = ">=0.5,<1.0" +pygments = ">=2.16,<3.0" +pymdown-extensions = ">=10.2,<11.0" +regex = ">=2022.4" +requests = ">=2.26,<3.0" + +[package.extras] +git = ["mkdocs-git-committers-plugin-2 (>=1.1,<2.0)", "mkdocs-git-revision-date-localized-plugin (>=1.2.4,<2.0)"] +imaging = ["cairosvg (>=2.6,<3.0)", "pillow (>=10.2,<11.0)"] +recommended = ["mkdocs-minify-plugin (>=0.7,<1.0)", "mkdocs-redirects (>=1.2,<2.0)", "mkdocs-rss-plugin (>=1.6,<2.0)"] + +[[package]] +name = "mkdocs-material-extensions" +version = "1.3.1" +description = "Extension pack for Python Markdown and MkDocs Material." +optional = false +python-versions = ">=3.8" +files = [ + {file = "mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31"}, + {file = "mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443"}, +] + +[[package]] +name = "nodeenv" +version = "1.9.1" +description = "Node.js virtual environment builder" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, + {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, +] + +[[package]] +name = "packaging" +version = "24.1" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, +] + +[[package]] +name = "paginate" +version = "0.5.6" +description = "Divides large result sets into pages for easier browsing" +optional = false +python-versions = "*" +files = [ + {file = "paginate-0.5.6.tar.gz", hash = "sha256:5e6007b6a9398177a7e1648d04fdd9f8c9766a1a945bceac82f1929e8c78af2d"}, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, +] + +[[package]] +name = "platformdirs" +version = "4.2.2" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +optional = false +python-versions = ">=3.8" +files = [ + {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, + {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, +] + +[package.extras] +docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] +type = ["mypy (>=1.8)"] + +[[package]] +name = "pluggy" +version = "1.5.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "pre-commit" +version = "3.7.1" +description = "A framework for managing and maintaining multi-language pre-commit hooks." +optional = false +python-versions = ">=3.9" +files = [ + {file = "pre_commit-3.7.1-py2.py3-none-any.whl", hash = "sha256:fae36fd1d7ad7d6a5a1c0b0d5adb2ed1a3bda5a21bf6c3e5372073d7a11cd4c5"}, + {file = "pre_commit-3.7.1.tar.gz", hash = "sha256:8ca3ad567bc78a4972a3f1a477e94a79d4597e8140a6e0b651c5e33899c3654a"}, +] + +[package.dependencies] +cfgv = ">=2.0.0" +identify = ">=1.0.0" +nodeenv = ">=0.11.1" +pyyaml = ">=5.1" +virtualenv = ">=20.10.0" + +[[package]] +name = "prompt-toolkit" +version = "3.0.36" +description = "Library for building powerful interactive command lines in Python" +optional = false +python-versions = ">=3.6.2" +files = [ + {file = "prompt_toolkit-3.0.36-py3-none-any.whl", hash = "sha256:aa64ad242a462c5ff0363a7b9cfe696c20d55d9fc60c11fd8e632d064804d305"}, + {file = "prompt_toolkit-3.0.36.tar.gz", hash = "sha256:3e163f254bef5a03b146397d7c1963bd3e2812f0964bb9a24e6ec761fd28db63"}, +] + +[package.dependencies] +wcwidth = "*" + +[[package]] +name = "pygments" +version = "2.18.0" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, + {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, +] + +[package.extras] +windows-terminal = ["colorama (>=0.4.6)"] + +[[package]] +name = "pymdown-extensions" +version = "10.8.1" +description = "Extension pack for Python Markdown." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pymdown_extensions-10.8.1-py3-none-any.whl", hash = "sha256:f938326115884f48c6059c67377c46cf631c733ef3629b6eed1349989d1b30cb"}, + {file = "pymdown_extensions-10.8.1.tar.gz", hash = "sha256:3ab1db5c9e21728dabf75192d71471f8e50f216627e9a1fa9535ecb0231b9940"}, +] + +[package.dependencies] +markdown = ">=3.6" +pyyaml = "*" + +[package.extras] +extra = ["pygments (>=2.12)"] + +[[package]] +name = "pyparsing" +version = "3.1.2" +description = "pyparsing module - Classes and methods to define and execute parsing grammars" +optional = false +python-versions = ">=3.6.8" +files = [ + {file = "pyparsing-3.1.2-py3-none-any.whl", hash = "sha256:f9db75911801ed778fe61bb643079ff86601aca99fcae6345aa67292038fb742"}, + {file = "pyparsing-3.1.2.tar.gz", hash = "sha256:a1bac0ce561155ecc3ed78ca94d3c9378656ad4c94c1270de543f621420f94ad"}, +] + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] + +[[package]] +name = "pytest" +version = "8.2.2" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-8.2.2-py3-none-any.whl", hash = "sha256:c434598117762e2bd304e526244f67bf66bbd7b5d6cf22138be51ff661980343"}, + {file = "pytest-8.2.2.tar.gz", hash = "sha256:de4bb8104e201939ccdc688b27a89a7be2079b22e2bd2b07f806b6ba71117977"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=1.5,<2.0" + +[package.extras] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pytest-asyncio" +version = "0.23.7" +description = "Pytest support for asyncio" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest_asyncio-0.23.7-py3-none-any.whl", hash = "sha256:009b48127fbe44518a547bddd25611551b0e43ccdbf1e67d12479f569832c20b"}, + {file = "pytest_asyncio-0.23.7.tar.gz", hash = "sha256:5f5c72948f4c49e7db4f29f2521d4031f1c27f86e57b046126654083d4770268"}, +] + +[package.dependencies] +pytest = ">=7.0.0,<9" + +[package.extras] +docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] +testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] + +[[package]] +name = "pytest-mock" +version = "3.14.0" +description = "Thin-wrapper around the mock package for easier use with pytest" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0"}, + {file = "pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f"}, +] + +[package.dependencies] +pytest = ">=6.2.5" + +[package.extras] +dev = ["pre-commit", "pytest-asyncio", "tox"] + +[[package]] +name = "pytest-sugar" +version = "1.0.0" +description = "pytest-sugar is a plugin for pytest that changes the default look and feel of pytest (e.g. progressbar, show tests that fail instantly)." +optional = false +python-versions = "*" +files = [ + {file = "pytest-sugar-1.0.0.tar.gz", hash = "sha256:6422e83258f5b0c04ce7c632176c7732cab5fdb909cb39cca5c9139f81276c0a"}, + {file = "pytest_sugar-1.0.0-py3-none-any.whl", hash = "sha256:70ebcd8fc5795dc457ff8b69d266a4e2e8a74ae0c3edc749381c64b5246c8dfd"}, +] + +[package.dependencies] +packaging = ">=21.3" +pytest = ">=6.2.0" +termcolor = ">=2.1.0" + +[package.extras] +dev = ["black", "flake8", "pre-commit"] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, +] + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "pyyaml" +version = "6.0.1" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, + {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, + {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, + {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, + {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, + {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, + {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, + {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, + {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, + {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, + {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, + {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, + {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, +] + +[[package]] +name = "pyyaml-env-tag" +version = "0.1" +description = "A custom YAML tag for referencing environment variables in YAML files. " +optional = false +python-versions = ">=3.6" +files = [ + {file = "pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"}, + {file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"}, +] + +[package.dependencies] +pyyaml = "*" + +[[package]] +name = "questionary" +version = "2.0.1" +description = "Python library to build pretty command line user prompts ⭐️" +optional = false +python-versions = ">=3.8" +files = [ + {file = "questionary-2.0.1-py3-none-any.whl", hash = "sha256:8ab9a01d0b91b68444dff7f6652c1e754105533f083cbe27597c8110ecc230a2"}, + {file = "questionary-2.0.1.tar.gz", hash = "sha256:bcce898bf3dbb446ff62830c86c5c6fb9a22a54146f0f5597d3da43b10d8fc8b"}, +] + +[package.dependencies] +prompt_toolkit = ">=2.0,<=3.0.36" + +[[package]] +name = "regex" +version = "2024.5.15" +description = "Alternative regular expression module, to replace re." +optional = false +python-versions = ">=3.8" +files = [ + {file = "regex-2024.5.15-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a81e3cfbae20378d75185171587cbf756015ccb14840702944f014e0d93ea09f"}, + {file = "regex-2024.5.15-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7b59138b219ffa8979013be7bc85bb60c6f7b7575df3d56dc1e403a438c7a3f6"}, + {file = "regex-2024.5.15-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a0bd000c6e266927cb7a1bc39d55be95c4b4f65c5be53e659537537e019232b1"}, + {file = "regex-2024.5.15-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5eaa7ddaf517aa095fa8da0b5015c44d03da83f5bd49c87961e3c997daed0de7"}, + {file = "regex-2024.5.15-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba68168daedb2c0bab7fd7e00ced5ba90aebf91024dea3c88ad5063c2a562cca"}, + {file = "regex-2024.5.15-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6e8d717bca3a6e2064fc3a08df5cbe366369f4b052dcd21b7416e6d71620dca1"}, + {file = "regex-2024.5.15-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1337b7dbef9b2f71121cdbf1e97e40de33ff114801263b275aafd75303bd62b5"}, + {file = "regex-2024.5.15-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f9ebd0a36102fcad2f03696e8af4ae682793a5d30b46c647eaf280d6cfb32796"}, + {file = "regex-2024.5.15-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9efa1a32ad3a3ea112224897cdaeb6aa00381627f567179c0314f7b65d354c62"}, + {file = "regex-2024.5.15-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:1595f2d10dff3d805e054ebdc41c124753631b6a471b976963c7b28543cf13b0"}, + {file = "regex-2024.5.15-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b802512f3e1f480f41ab5f2cfc0e2f761f08a1f41092d6718868082fc0d27143"}, + {file = "regex-2024.5.15-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:a0981022dccabca811e8171f913de05720590c915b033b7e601f35ce4ea7019f"}, + {file = "regex-2024.5.15-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:19068a6a79cf99a19ccefa44610491e9ca02c2be3305c7760d3831d38a467a6f"}, + {file = "regex-2024.5.15-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1b5269484f6126eee5e687785e83c6b60aad7663dafe842b34691157e5083e53"}, + {file = "regex-2024.5.15-cp310-cp310-win32.whl", hash = "sha256:ada150c5adfa8fbcbf321c30c751dc67d2f12f15bd183ffe4ec7cde351d945b3"}, + {file = "regex-2024.5.15-cp310-cp310-win_amd64.whl", hash = "sha256:ac394ff680fc46b97487941f5e6ae49a9f30ea41c6c6804832063f14b2a5a145"}, + {file = "regex-2024.5.15-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f5b1dff3ad008dccf18e652283f5e5339d70bf8ba7c98bf848ac33db10f7bc7a"}, + {file = "regex-2024.5.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c6a2b494a76983df8e3d3feea9b9ffdd558b247e60b92f877f93a1ff43d26656"}, + {file = "regex-2024.5.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a32b96f15c8ab2e7d27655969a23895eb799de3665fa94349f3b2fbfd547236f"}, + {file = "regex-2024.5.15-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:10002e86e6068d9e1c91eae8295ef690f02f913c57db120b58fdd35a6bb1af35"}, + {file = "regex-2024.5.15-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ec54d5afa89c19c6dd8541a133be51ee1017a38b412b1321ccb8d6ddbeb4cf7d"}, + {file = "regex-2024.5.15-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:10e4ce0dca9ae7a66e6089bb29355d4432caed736acae36fef0fdd7879f0b0cb"}, + {file = "regex-2024.5.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e507ff1e74373c4d3038195fdd2af30d297b4f0950eeda6f515ae3d84a1770f"}, + {file = "regex-2024.5.15-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d1f059a4d795e646e1c37665b9d06062c62d0e8cc3c511fe01315973a6542e40"}, + {file = "regex-2024.5.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0721931ad5fe0dda45d07f9820b90b2148ccdd8e45bb9e9b42a146cb4f695649"}, + {file = "regex-2024.5.15-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:833616ddc75ad595dee848ad984d067f2f31be645d603e4d158bba656bbf516c"}, + {file = "regex-2024.5.15-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:287eb7f54fc81546346207c533ad3c2c51a8d61075127d7f6d79aaf96cdee890"}, + {file = "regex-2024.5.15-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:19dfb1c504781a136a80ecd1fff9f16dddf5bb43cec6871778c8a907a085bb3d"}, + {file = "regex-2024.5.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:119af6e56dce35e8dfb5222573b50c89e5508d94d55713c75126b753f834de68"}, + {file = "regex-2024.5.15-cp311-cp311-win32.whl", hash = "sha256:1c1c174d6ec38d6c8a7504087358ce9213d4332f6293a94fbf5249992ba54efa"}, + {file = "regex-2024.5.15-cp311-cp311-win_amd64.whl", hash = "sha256:9e717956dcfd656f5055cc70996ee2cc82ac5149517fc8e1b60261b907740201"}, + {file = "regex-2024.5.15-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:632b01153e5248c134007209b5c6348a544ce96c46005d8456de1d552455b014"}, + {file = "regex-2024.5.15-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e64198f6b856d48192bf921421fdd8ad8eb35e179086e99e99f711957ffedd6e"}, + {file = "regex-2024.5.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68811ab14087b2f6e0fc0c2bae9ad689ea3584cad6917fc57be6a48bbd012c49"}, + {file = "regex-2024.5.15-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8ec0c2fea1e886a19c3bee0cd19d862b3aa75dcdfb42ebe8ed30708df64687a"}, + {file = "regex-2024.5.15-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d0c0c0003c10f54a591d220997dd27d953cd9ccc1a7294b40a4be5312be8797b"}, + {file = "regex-2024.5.15-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2431b9e263af1953c55abbd3e2efca67ca80a3de8a0437cb58e2421f8184717a"}, + {file = "regex-2024.5.15-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a605586358893b483976cffc1723fb0f83e526e8f14c6e6614e75919d9862cf"}, + {file = "regex-2024.5.15-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:391d7f7f1e409d192dba8bcd42d3e4cf9e598f3979cdaed6ab11288da88cb9f2"}, + {file = "regex-2024.5.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9ff11639a8d98969c863d4617595eb5425fd12f7c5ef6621a4b74b71ed8726d5"}, + {file = "regex-2024.5.15-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4eee78a04e6c67e8391edd4dad3279828dd66ac4b79570ec998e2155d2e59fd5"}, + {file = "regex-2024.5.15-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8fe45aa3f4aa57faabbc9cb46a93363edd6197cbc43523daea044e9ff2fea83e"}, + {file = "regex-2024.5.15-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:d0a3d8d6acf0c78a1fff0e210d224b821081330b8524e3e2bc5a68ef6ab5803d"}, + {file = "regex-2024.5.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c486b4106066d502495b3025a0a7251bf37ea9540433940a23419461ab9f2a80"}, + {file = "regex-2024.5.15-cp312-cp312-win32.whl", hash = "sha256:c49e15eac7c149f3670b3e27f1f28a2c1ddeccd3a2812cba953e01be2ab9b5fe"}, + {file = "regex-2024.5.15-cp312-cp312-win_amd64.whl", hash = "sha256:673b5a6da4557b975c6c90198588181029c60793835ce02f497ea817ff647cb2"}, + {file = "regex-2024.5.15-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:87e2a9c29e672fc65523fb47a90d429b70ef72b901b4e4b1bd42387caf0d6835"}, + {file = "regex-2024.5.15-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c3bea0ba8b73b71b37ac833a7f3fd53825924165da6a924aec78c13032f20850"}, + {file = "regex-2024.5.15-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bfc4f82cabe54f1e7f206fd3d30fda143f84a63fe7d64a81558d6e5f2e5aaba9"}, + {file = "regex-2024.5.15-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5bb9425fe881d578aeca0b2b4b3d314ec88738706f66f219c194d67179337cb"}, + {file = "regex-2024.5.15-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:64c65783e96e563103d641760664125e91bd85d8e49566ee560ded4da0d3e704"}, + {file = "regex-2024.5.15-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cf2430df4148b08fb4324b848672514b1385ae3807651f3567871f130a728cc3"}, + {file = "regex-2024.5.15-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5397de3219a8b08ae9540c48f602996aa6b0b65d5a61683e233af8605c42b0f2"}, + {file = "regex-2024.5.15-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:455705d34b4154a80ead722f4f185b04c4237e8e8e33f265cd0798d0e44825fa"}, + {file = "regex-2024.5.15-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b2b6f1b3bb6f640c1a92be3bbfbcb18657b125b99ecf141fb3310b5282c7d4ed"}, + {file = "regex-2024.5.15-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:3ad070b823ca5890cab606c940522d05d3d22395d432f4aaaf9d5b1653e47ced"}, + {file = "regex-2024.5.15-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:5b5467acbfc153847d5adb21e21e29847bcb5870e65c94c9206d20eb4e99a384"}, + {file = "regex-2024.5.15-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:e6662686aeb633ad65be2a42b4cb00178b3fbf7b91878f9446075c404ada552f"}, + {file = "regex-2024.5.15-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:2b4c884767504c0e2401babe8b5b7aea9148680d2e157fa28f01529d1f7fcf67"}, + {file = "regex-2024.5.15-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:3cd7874d57f13bf70078f1ff02b8b0aa48d5b9ed25fc48547516c6aba36f5741"}, + {file = "regex-2024.5.15-cp38-cp38-win32.whl", hash = "sha256:e4682f5ba31f475d58884045c1a97a860a007d44938c4c0895f41d64481edbc9"}, + {file = "regex-2024.5.15-cp38-cp38-win_amd64.whl", hash = "sha256:d99ceffa25ac45d150e30bd9ed14ec6039f2aad0ffa6bb87a5936f5782fc1569"}, + {file = "regex-2024.5.15-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:13cdaf31bed30a1e1c2453ef6015aa0983e1366fad2667657dbcac7b02f67133"}, + {file = "regex-2024.5.15-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cac27dcaa821ca271855a32188aa61d12decb6fe45ffe3e722401fe61e323cd1"}, + {file = "regex-2024.5.15-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7dbe2467273b875ea2de38ded4eba86cbcbc9a1a6d0aa11dcf7bd2e67859c435"}, + {file = "regex-2024.5.15-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64f18a9a3513a99c4bef0e3efd4c4a5b11228b48aa80743be822b71e132ae4f5"}, + {file = "regex-2024.5.15-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d347a741ea871c2e278fde6c48f85136c96b8659b632fb57a7d1ce1872547600"}, + {file = "regex-2024.5.15-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1878b8301ed011704aea4c806a3cadbd76f84dece1ec09cc9e4dc934cfa5d4da"}, + {file = "regex-2024.5.15-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4babf07ad476aaf7830d77000874d7611704a7fcf68c9c2ad151f5d94ae4bfc4"}, + {file = "regex-2024.5.15-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:35cb514e137cb3488bce23352af3e12fb0dbedd1ee6e60da053c69fb1b29cc6c"}, + {file = "regex-2024.5.15-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cdd09d47c0b2efee9378679f8510ee6955d329424c659ab3c5e3a6edea696294"}, + {file = "regex-2024.5.15-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:72d7a99cd6b8f958e85fc6ca5b37c4303294954eac1376535b03c2a43eb72629"}, + {file = "regex-2024.5.15-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:a094801d379ab20c2135529948cb84d417a2169b9bdceda2a36f5f10977ebc16"}, + {file = "regex-2024.5.15-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:c0c18345010870e58238790a6779a1219b4d97bd2e77e1140e8ee5d14df071aa"}, + {file = "regex-2024.5.15-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:16093f563098448ff6b1fa68170e4acbef94e6b6a4e25e10eae8598bb1694b5d"}, + {file = "regex-2024.5.15-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e38a7d4e8f633a33b4c7350fbd8bad3b70bf81439ac67ac38916c4a86b465456"}, + {file = "regex-2024.5.15-cp39-cp39-win32.whl", hash = "sha256:71a455a3c584a88f654b64feccc1e25876066c4f5ef26cd6dd711308aa538694"}, + {file = "regex-2024.5.15-cp39-cp39-win_amd64.whl", hash = "sha256:cab12877a9bdafde5500206d1020a584355a97884dfd388af3699e9137bf7388"}, + {file = "regex-2024.5.15.tar.gz", hash = "sha256:d3ee02d9e5f482cc8309134a91eeaacbdd2261ba111b0fef3748eeb4913e6a2c"}, +] + +[[package]] +name = "requests" +version = "2.32.3" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.8" +files = [ + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "ruff" +version = "0.5.2" +description = "An extremely fast Python linter and code formatter, written in Rust." +optional = false +python-versions = ">=3.7" +files = [ + {file = "ruff-0.5.2-py3-none-linux_armv6l.whl", hash = "sha256:7bab8345df60f9368d5f4594bfb8b71157496b44c30ff035d1d01972e764d3be"}, + {file = "ruff-0.5.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:1aa7acad382ada0189dbe76095cf0a36cd0036779607c397ffdea16517f535b1"}, + {file = "ruff-0.5.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:aec618d5a0cdba5592c60c2dee7d9c865180627f1a4a691257dea14ac1aa264d"}, + {file = "ruff-0.5.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0b62adc5ce81780ff04077e88bac0986363e4a3260ad3ef11ae9c14aa0e67ef"}, + {file = "ruff-0.5.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dc42ebf56ede83cb080a50eba35a06e636775649a1ffd03dc986533f878702a3"}, + {file = "ruff-0.5.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c15c6e9f88c67ffa442681365d11df38afb11059fc44238e71a9d9f1fd51de70"}, + {file = "ruff-0.5.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:d3de9a5960f72c335ef00763d861fc5005ef0644cb260ba1b5a115a102157251"}, + {file = "ruff-0.5.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fe5a968ae933e8f7627a7b2fc8893336ac2be0eb0aace762d3421f6e8f7b7f83"}, + {file = "ruff-0.5.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a04f54a9018f75615ae52f36ea1c5515e356e5d5e214b22609ddb546baef7132"}, + {file = "ruff-0.5.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ed02fb52e3741f0738db5f93e10ae0fb5c71eb33a4f2ba87c9a2fa97462a649"}, + {file = "ruff-0.5.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3cf8fe659f6362530435d97d738eb413e9f090e7e993f88711b0377fbdc99f60"}, + {file = "ruff-0.5.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:237a37e673e9f3cbfff0d2243e797c4862a44c93d2f52a52021c1a1b0899f846"}, + {file = "ruff-0.5.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:2a2949ce7c1cbd8317432ada80fe32156df825b2fd611688814c8557824ef060"}, + {file = "ruff-0.5.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:481af57c8e99da92ad168924fd82220266043c8255942a1cb87958b108ac9335"}, + {file = "ruff-0.5.2-py3-none-win32.whl", hash = "sha256:f1aea290c56d913e363066d83d3fc26848814a1fed3d72144ff9c930e8c7c718"}, + {file = "ruff-0.5.2-py3-none-win_amd64.whl", hash = "sha256:8532660b72b5d94d2a0a7a27ae7b9b40053662d00357bb2a6864dd7e38819084"}, + {file = "ruff-0.5.2-py3-none-win_arm64.whl", hash = "sha256:73439805c5cb68f364d826a5c5c4b6c798ded6b7ebaa4011f01ce6c94e4d5583"}, + {file = "ruff-0.5.2.tar.gz", hash = "sha256:2c0df2d2de685433794a14d8d2e240df619b748fbe3367346baa519d8e6f1ca2"}, +] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "termcolor" +version = "2.4.0" +description = "ANSI color formatting for output in terminal" +optional = false +python-versions = ">=3.8" +files = [ + {file = "termcolor-2.4.0-py3-none-any.whl", hash = "sha256:9297c0df9c99445c2412e832e882a7884038a25617c60cea2ad69488d4040d63"}, + {file = "termcolor-2.4.0.tar.gz", hash = "sha256:aab9e56047c8ac41ed798fa36d892a37aca6b3e9159f3e0c24bc64a9b3ac7b7a"}, +] + +[package.extras] +tests = ["pytest", "pytest-cov"] + +[[package]] +name = "tomlkit" +version = "0.13.0" +description = "Style preserving TOML library" +optional = false +python-versions = ">=3.8" +files = [ + {file = "tomlkit-0.13.0-py3-none-any.whl", hash = "sha256:7075d3042d03b80f603482d69bf0c8f345c2b30e41699fd8883227f89972b264"}, + {file = "tomlkit-0.13.0.tar.gz", hash = "sha256:08ad192699734149f5b97b45f1f18dad7eb1b6d16bc72ad0c2335772650d7b72"}, +] + +[[package]] +name = "urllib3" +version = "2.2.2" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.8" +files = [ + {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, + {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "verspec" +version = "0.1.0" +description = "Flexible version handling" +optional = false +python-versions = "*" +files = [ + {file = "verspec-0.1.0-py3-none-any.whl", hash = "sha256:741877d5633cc9464c45a469ae2a31e801e6dbbaa85b9675d481cda100f11c31"}, + {file = "verspec-0.1.0.tar.gz", hash = "sha256:c4504ca697b2056cdb4bfa7121461f5a0e81809255b41c03dda4ba823637c01e"}, +] + +[package.extras] +test = ["coverage", "flake8 (>=3.7)", "mypy", "pretend", "pytest"] + +[[package]] +name = "virtualenv" +version = "20.26.3" +description = "Virtual Python Environment builder" +optional = false +python-versions = ">=3.7" +files = [ + {file = "virtualenv-20.26.3-py3-none-any.whl", hash = "sha256:8cc4a31139e796e9a7de2cd5cf2489de1217193116a8fd42328f1bd65f434589"}, + {file = "virtualenv-20.26.3.tar.gz", hash = "sha256:4c43a2a236279d9ea36a0d76f98d84bd6ca94ac4e0f4a3b9d46d05e10fea542a"}, +] + +[package.dependencies] +distlib = ">=0.3.7,<1" +filelock = ">=3.12.2,<4" +platformdirs = ">=3.9.1,<5" + +[package.extras] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] + +[[package]] +name = "watchdog" +version = "4.0.1" +description = "Filesystem events monitoring" +optional = false +python-versions = ">=3.8" +files = [ + {file = "watchdog-4.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:da2dfdaa8006eb6a71051795856bedd97e5b03e57da96f98e375682c48850645"}, + {file = "watchdog-4.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e93f451f2dfa433d97765ca2634628b789b49ba8b504fdde5837cdcf25fdb53b"}, + {file = "watchdog-4.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ef0107bbb6a55f5be727cfc2ef945d5676b97bffb8425650dadbb184be9f9a2b"}, + {file = "watchdog-4.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:17e32f147d8bf9657e0922c0940bcde863b894cd871dbb694beb6704cfbd2fb5"}, + {file = "watchdog-4.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:03e70d2df2258fb6cb0e95bbdbe06c16e608af94a3ffbd2b90c3f1e83eb10767"}, + {file = "watchdog-4.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:123587af84260c991dc5f62a6e7ef3d1c57dfddc99faacee508c71d287248459"}, + {file = "watchdog-4.0.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:093b23e6906a8b97051191a4a0c73a77ecc958121d42346274c6af6520dec175"}, + {file = "watchdog-4.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:611be3904f9843f0529c35a3ff3fd617449463cb4b73b1633950b3d97fa4bfb7"}, + {file = "watchdog-4.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:62c613ad689ddcb11707f030e722fa929f322ef7e4f18f5335d2b73c61a85c28"}, + {file = "watchdog-4.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:d4925e4bf7b9bddd1c3de13c9b8a2cdb89a468f640e66fbfabaf735bd85b3e35"}, + {file = "watchdog-4.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cad0bbd66cd59fc474b4a4376bc5ac3fc698723510cbb64091c2a793b18654db"}, + {file = "watchdog-4.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a3c2c317a8fb53e5b3d25790553796105501a235343f5d2bf23bb8649c2c8709"}, + {file = "watchdog-4.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c9904904b6564d4ee8a1ed820db76185a3c96e05560c776c79a6ce5ab71888ba"}, + {file = "watchdog-4.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:667f3c579e813fcbad1b784db7a1aaa96524bed53437e119f6a2f5de4db04235"}, + {file = "watchdog-4.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d10a681c9a1d5a77e75c48a3b8e1a9f2ae2928eda463e8d33660437705659682"}, + {file = "watchdog-4.0.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0144c0ea9997b92615af1d94afc0c217e07ce2c14912c7b1a5731776329fcfc7"}, + {file = "watchdog-4.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:998d2be6976a0ee3a81fb8e2777900c28641fb5bfbd0c84717d89bca0addcdc5"}, + {file = "watchdog-4.0.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e7921319fe4430b11278d924ef66d4daa469fafb1da679a2e48c935fa27af193"}, + {file = "watchdog-4.0.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:f0de0f284248ab40188f23380b03b59126d1479cd59940f2a34f8852db710625"}, + {file = "watchdog-4.0.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bca36be5707e81b9e6ce3208d92d95540d4ca244c006b61511753583c81c70dd"}, + {file = "watchdog-4.0.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:ab998f567ebdf6b1da7dc1e5accfaa7c6992244629c0fdaef062f43249bd8dee"}, + {file = "watchdog-4.0.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:dddba7ca1c807045323b6af4ff80f5ddc4d654c8bce8317dde1bd96b128ed253"}, + {file = "watchdog-4.0.1-py3-none-manylinux2014_armv7l.whl", hash = "sha256:4513ec234c68b14d4161440e07f995f231be21a09329051e67a2118a7a612d2d"}, + {file = "watchdog-4.0.1-py3-none-manylinux2014_i686.whl", hash = "sha256:4107ac5ab936a63952dea2a46a734a23230aa2f6f9db1291bf171dac3ebd53c6"}, + {file = "watchdog-4.0.1-py3-none-manylinux2014_ppc64.whl", hash = "sha256:6e8c70d2cd745daec2a08734d9f63092b793ad97612470a0ee4cbb8f5f705c57"}, + {file = "watchdog-4.0.1-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:f27279d060e2ab24c0aa98363ff906d2386aa6c4dc2f1a374655d4e02a6c5e5e"}, + {file = "watchdog-4.0.1-py3-none-manylinux2014_s390x.whl", hash = "sha256:f8affdf3c0f0466e69f5b3917cdd042f89c8c63aebdb9f7c078996f607cdb0f5"}, + {file = "watchdog-4.0.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:ac7041b385f04c047fcc2951dc001671dee1b7e0615cde772e84b01fbf68ee84"}, + {file = "watchdog-4.0.1-py3-none-win32.whl", hash = "sha256:206afc3d964f9a233e6ad34618ec60b9837d0582b500b63687e34011e15bb429"}, + {file = "watchdog-4.0.1-py3-none-win_amd64.whl", hash = "sha256:7577b3c43e5909623149f76b099ac49a1a01ca4e167d1785c76eb52fa585745a"}, + {file = "watchdog-4.0.1-py3-none-win_ia64.whl", hash = "sha256:d7b9f5f3299e8dd230880b6c55504a1f69cf1e4316275d1b215ebdd8187ec88d"}, + {file = "watchdog-4.0.1.tar.gz", hash = "sha256:eebaacf674fa25511e8867028d281e602ee6500045b57f43b08778082f7f8b44"}, +] + +[package.extras] +watchmedo = ["PyYAML (>=3.10)"] + +[[package]] +name = "wcwidth" +version = "0.2.13" +description = "Measures the displayed width of unicode strings in a terminal" +optional = false +python-versions = "*" +files = [ + {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, + {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, +] + +[[package]] +name = "zipp" +version = "3.19.2" +description = "Backport of pathlib-compatible object wrapper for zip files" +optional = false +python-versions = ">=3.8" +files = [ + {file = "zipp-3.19.2-py3-none-any.whl", hash = "sha256:f091755f667055f2d02b32c53771a7a6c8b47e1fdbc4b72a8b9072b3eef8015c"}, + {file = "zipp-3.19.2.tar.gz", hash = "sha256:bf1dcf6450f873a13e952a29504887c89e6de7506209e5b1bcc3460135d4de19"}, +] + +[package.extras] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"] + +[metadata] +lock-version = "2.0" +python-versions = "~3.12" +content-hash = "cd369216d0ae84d6c53127d132719c08d031890722467bfe2b8d207902d0fc3c" diff --git a/pyproject.toml b/pyproject.toml index 0880be9..9e872a1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,44 +1,73 @@ +[tool.poetry] +name = "courageous-comets" +version = "0.0.0" +authors = ["Courageous Comets"] +description = "" +readme = "docs/README.md" +license = "MIT" +exclude = ["**/test__*.py"] + +[tool.poetry.dependencies] +python = "~3.12" + +[tool.poetry.dev-dependencies] +commitizen = "3.27.0" +mike = "2.1.2" +mkdocs = "1.6.0" +mkdocs-material = "9.5.29" +pre-commit = "3.7.1" +pymdown-extensions = "10.8.1" +pytest = "8.2.2" +pytest-asyncio = "0.23.7" +pytest-mock = "3.14.0" +pytest-sugar = "1.0.0" +ruff = "0.5.2" + +[tool.pyright] +typeCheckingMode = "basic" +pythonVersion = "3.12" +reportUnnecessaryTypeIgnoreComment = "error" + [tool.ruff] -# Increase the line length. This breaks PEP8 but it is way easier to work with. -# The original reason for this limit was a standard vim terminal is only 79 characters, -# but this doesn't really apply anymore. -line-length = 119 -# Target Python 3.12. If you decide to use a different version of Python -# you will need to update this value. +line-length = 100 target-version = "py312" -# Automatically fix auto-fixable issues. -fix = true -# The directory containing the source code. If you choose a different project layout -# you will need to update this value. -src = ["src"] [tool.ruff.lint] -# Enable all linting rules. -select = ["ALL"] -# Ignore some of the most obnoxious linting errors. ignore = [ - # Missing docstrings. - "D100", + # Self and cls do not require annotations. + "ANN101", + "ANN102", + # Module level docstrings don't always make sense "D104", + # Pyright error codes are obnoxiously long. Ignore lint telling you to use them. + "PGH003", + # Documenting every file is a bit redundant for us. + "D100", + # No point in documenting magic methods. "D105", - "D106", + # Makes more sense to use `Parameters` in the main class instead of in the `__init__` . "D107", - # Docstring whitespace. - "D203", - "D213", - # Docstring punctuation. - "D415", - # Docstring quotes. - "D301", - # Builtins. - "A", - # Print statements. - "T20", - # TODOs. - "TD002", - "TD003", - "FIX", - # Annotations. - "ANN101", - "ANN102", + # We do not need cryptographically secure random functions. + "S311", ] +select = ["ALL"] + +[tool.ruff.lint.per-file-ignores] +"**/test__*.py" = ["S101", "PLR2004"] +"examples/**/*.py" = ["INP001"] + +[tool.ruff.lint.pydocstyle] +convention = "numpy" + +[tool.commitizen] +changelog_file = "docs/CHANGELOG.md" +name = "cz_conventional_commits" +tag_format = "v$version" +version_scheme = "semver2" +version_provider = "poetry" +update_changelog_on_bump = true +major_version_zero = true + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/requirements-dev.txt b/requirements-dev.txt deleted file mode 100644 index d529f2e..0000000 --- a/requirements-dev.txt +++ /dev/null @@ -1,6 +0,0 @@ -# This file contains all the development requirements for our linting toolchain. -# Don't forget to pin your dependencies! -# This list will have to be migrated if you wish to use another dependency manager. - -ruff~=0.5.0 -pre-commit~=3.7.1 diff --git a/samples/Pipfile b/samples/Pipfile deleted file mode 100644 index 27673c0..0000000 --- a/samples/Pipfile +++ /dev/null @@ -1,15 +0,0 @@ -# Sample Pipfile. - -[[source]] -url = "https://pypi.org/simple" -verify_ssl = true -name = "pypi" - -[packages] - -[dev-packages] -ruff = "~=0.5.0" -pre-commit = "~=3.7.1" - -[requires] -python_version = "3.12" diff --git a/samples/pyproject.toml b/samples/pyproject.toml deleted file mode 100644 index 835045d..0000000 --- a/samples/pyproject.toml +++ /dev/null @@ -1,19 +0,0 @@ -# Sample poetry configuration. - -[tool.poetry] -name = "Name" -version = "0.1.0" -description = "Description" -authors = ["Author 1 "] -license = "MIT" - -[tool.poetry.dependencies] -python = "3.12.*" - -[tool.poetry.dev-dependencies] -ruff = "~0.5.0" -pre-commit = "~3.7.1" - -[build-system] -requires = ["poetry-core>=1.0.0"] -build-backend = "poetry.core.masonry.api" From 7998a79224d406fd64353cc67dfcbf0ce2fd2f34 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Mon, 15 Jul 2024 20:20:04 +0000 Subject: [PATCH 003/168] ci: ensure actions use bash --- .github/workflows/ci.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 9872e4d..4f34e2c 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -27,6 +27,7 @@ jobs: - name: Set up Pyright run: echo "$(poetry env info --path)/bin" >> $GITHUB_PATH + shell: bash - name: Run type checking uses: jakebailey/pyright-action@v2 @@ -36,3 +37,4 @@ jobs: - name: Run tests run: poetry run pytest + shell: bash From b761ecc5dec5d8965e9826950f6843cebe39aaa4 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Mon, 15 Jul 2024 20:23:01 +0000 Subject: [PATCH 004/168] =?UTF-8?q?bump:=20version=200.0.0=20=E2=86=92=200?= =?UTF-8?q?.0.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/CHANGELOG.md | 1 + pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 docs/CHANGELOG.md diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md new file mode 100644 index 0000000..00da639 --- /dev/null +++ b/docs/CHANGELOG.md @@ -0,0 +1 @@ +## v0.0.1 (2024-07-15) diff --git a/pyproject.toml b/pyproject.toml index 9e872a1..d7e8562 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "courageous-comets" -version = "0.0.0" +version = "0.0.1" authors = ["Courageous Comets"] description = "" readme = "docs/README.md" From b8cb2b10f71a9788638bd91114ce83e90aae1019 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Tue, 16 Jul 2024 12:27:14 +0200 Subject: [PATCH 005/168] docs: add logo and favicon (#1) --- docs/assets/favicon.ico | Bin 0 -> 52894 bytes docs/assets/logo-border.png | Bin 0 -> 145284 bytes docs/assets/logo.png | Bin 0 -> 128579 bytes mkdocs.yaml | 5 +++-- 4 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 docs/assets/favicon.ico create mode 100644 docs/assets/logo-border.png create mode 100644 docs/assets/logo.png diff --git a/docs/assets/favicon.ico b/docs/assets/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..5762dc304a210ffeee527dc09d02e99b84a493fd GIT binary patch literal 52894 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

}^T*Mz59M_< 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 literal 0 HcmV?d00001 diff --git a/docs/assets/logo-border.png b/docs/assets/logo-border.png new file mode 100644 index 0000000000000000000000000000000000000000..cef7363d733a7f04ffae48aa3a5a7cfb6bfa99fb GIT binary patch literal 145284 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

tFUR~^93k^Io87M`wF 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�QPEn_{rvI`*W>ey%{fNmjH;F( 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 literal 0 HcmV?d00001 diff --git a/docs/assets/logo.png b/docs/assets/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..d72d213ae189675fed24681627b8c22a807f8b68 GIT binary patch literal 128579 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

U{tD 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 literal 0 HcmV?d00001 diff --git a/mkdocs.yaml b/mkdocs.yaml index 8b4ad66..d0b73d9 100644 --- a/mkdocs.yaml +++ b/mkdocs.yaml @@ -1,10 +1,12 @@ copyright: © 2024 Courageous Comets ☄️ repo_name: thijsfranck/courageous-comets repo_url: https://github.com/thijsfranck/courageous-comets -site_name: Courageous Comets ☄️ +site_name: Courageous Comets site_url: https://thijsfranck.github.io/courageous-comets/ theme: + favicon: assets/favicon.ico + logo: assets/logo.png name: material search: true @@ -16,7 +18,6 @@ theme: - navigation.sections icon: - logo: fontawesome/brands/discord repo: fontawesome/brands/github palette: From e1270fdbdb0f8e0638f6ab027050513e0eaf27d8 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Tue, 16 Jul 2024 12:28:24 +0200 Subject: [PATCH 006/168] ci: set up secrets management to support development token (#2) --- .devcontainer/devcontainer.json | 1 + .env.lock | 8 ++ .gitignore | 3 + .sops.yaml | 3 + .../development-environment.md | 93 +++++++++++++++++-- docs/stylesheets/tabs.css | 56 ----------- mkdocs.yaml | 4 +- 7 files changed, 100 insertions(+), 68 deletions(-) create mode 100644 .env.lock create mode 100644 .sops.yaml delete mode 100644 docs/stylesheets/tabs.css diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 897ab93..1bbda2c 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -39,6 +39,7 @@ "features": { "ghcr.io/devcontainers-contrib/features/act:1": {}, "ghcr.io/devcontainers-contrib/features/poetry:2": {}, + "ghcr.io/devcontainers-contrib/features/sops:1": {}, "ghcr.io/devcontainers/features/docker-outside-of-docker:1": {}, "ghcr.io/devcontainers/features/python:1": { "installTools": false, diff --git a/.env.lock b/.env.lock new file mode 100644 index 0000000..16426bd --- /dev/null +++ b/.env.lock @@ -0,0 +1,8 @@ +DISCORD_TOKEN=ENC[AES256_GCM,data:TVo8NZfBAJqIAvnJK+OZgGd/3qoUrmrEV+IFMSmqv2v7Ky9Sxa+aULPfTLHsgD0rwidS2fm1evYexFAR9SGzA76lO/wJTyIy,iv:J284ntN9pkjbRMwIXCe9T3FyjfyAJqyued1ztL8RmhM=,tag:QXhXzPdzq3Ge2SOfjtxR4g==,type:str] +sops_lastmodified=2024-07-16T09:00:40Z +sops_mac=ENC[AES256_GCM,data:gxLkvOZxhme7iao6RpHU1iV/8GOQ7hKLrGlmARnqxLf0DciORGBwShju4ZVsJuFKs1uZPR0b4A8UdF0bjz/K27yJn9Xfg9i44lUefY7Gvl5pf/UoIZgVKtZomMmN9p1onemEQRW2hFruB7bm4PoaSRTgM2BMitIt+FQoSZtiEcg=,iv:4jN/x9Sz68nIR50/IeyoCAf+qz959+NlexGRyP/N7sE=,tag:pqpcgmpRnelOgyvWeZZGYg==,type:str] +sops_pgp__list_0__map_created_at=2024-07-16T09:00:06Z +sops_pgp__list_0__map_enc=-----BEGIN PGP MESSAGE-----\n\nhQGMA4xAg/8tRvaHAQv/RD7aND4k9sO78NRhRp+3fi7nClKJsEa2MEN7EuJctNR0\nh8RY6ePCCe7zLJj6pzVuX0Ul7S9WRDQ+Rt0gzmqn77lVsDeB4qwBFKlMj9N+w6bI\nBg6P6ouyyg/BohKyx4CZ9G7ASb6gFyg+vhtmBBKGq86FYFEFyYUpwnuz9RmOb5E6\nZNMqm1zFYA6s3cuYuwUmO1Ot5DE3bX/3PZDaSx61tKtph3i0qc+R/P9ekIOvwrFS\nXtneDFvCmU+kLiRQWHnkUo76ky2zpqON/F+6gViEGOwZCseD7Z/FDRJ99gS+Uv/e\nD1Uxh0JBlmBCKqR3L2iksHP8b25DPxYDy0FaZ0hZXxkyFGzYbw8+r9prf6lGwB5B\njMMYzGQMb/MkdX/ZjcZzTAMPEp0m28LBS4M057anyRPkaEB+6b/iL0xmil2marMA\nF44200XALUi8KYmMkUhStcxHptn1h+hFDup10kn6pA9WiIZXy4JFhxLUoqJrdOXU\nu7WhGd5YImvtl9kZszWS0l4Bm8WwJ4kwa7aQgnLUJR8XMFT4HvmIYA2P5rj9Y9Kw\njuIRQG9AdoA20yNbr0uNxtL5r6GZm3m/Ek/VrKlhWHeUwNfZwYlavzH2jdMbL//2\nkjnUni8aZqypIaE7a3Dy\n=CY+q\n-----END PGP MESSAGE----- +sops_pgp__list_0__map_fp=CFADC47C135DF8E4C14C895097E3177DD2EDD63F +sops_unencrypted_suffix=_unencrypted +sops_version=3.9.0 diff --git a/.gitignore b/.gitignore index 75f0fc0..2d4e1ab 100644 --- a/.gitignore +++ b/.gitignore @@ -172,3 +172,6 @@ pyrightconfig.json # VSCode .vscode/ + +# Secrets +*.asc diff --git a/.sops.yaml b/.sops.yaml new file mode 100644 index 0000000..69af8c1 --- /dev/null +++ b/.sops.yaml @@ -0,0 +1,3 @@ +creation_rules: + - pgp: >- + CFADC47C135DF8E4C14C895097E3177DD2EDD63F diff --git a/docs/contributor-guide/development-environment.md b/docs/contributor-guide/development-environment.md index b63e402..47f077b 100644 --- a/docs/contributor-guide/development-environment.md +++ b/docs/contributor-guide/development-environment.md @@ -42,34 +42,107 @@ If you prefer to set up the development environment manually, follow the steps b Please ensure [Python 3.12](https://www.python.org) and [Poetry](https://python-poetry.org) are installed on your system. +#### Install Dependencies + Start by installing the project dependencies using Poetry: ```bash poetry install ``` -Finally, install the pre-commit hooks: +This will create a virtual environment and install the required dependencies. + +#### Pre-commit Hooks + +Next, install the pre-commit hooks to ensure that your code is formatted and linted before each commit: ```bash poetry run pre-commit install ``` -## Configuration +### Secrets Management + +To use our team's shared Discord bot token, you will need to retrieve it from the `.env.lock` file in the project +root directory. This section will guide you through the process of decrypting the file to access the token. + +??? QUESTION "Can I use my own Discord bot token?" + Yes, you can use your own Discord bot token. + + First, create a new bot account on the [Discord Developer Portal](https://discord.com/developers/applications). + Generate a token for your bot account and create a `.env` file in the project root directory. Add the following + line to the file: + + ```plaintext + DISCORD_TOKEN= + ``` + + If you choose to use your own token, you can skip the steps below. + +#### Install Tools + +First, you will need to install [GnuPG](https://gnupg.org) and [SOPS](https://github.com/getsops/sops) on your +system. Follow the instructions for your operating system below. + +=== "Windows" + + Open a PowerShell terminal and run the following commands: + + ```bash + winget install -e --id Mozilla.SOPS + winget install -e --id GnuPG.GnuPG + ``` + +=== "macOS" + + Open a terminal and run the following command: + + ```bash + brew install sops gnupg + ``` -To configure your development environment, create a `.env` file in the project root directory with the following -content: +=== "Linux" -```env -DISCORD_TOKEN= + Open a terminal and run the following command: + + ```bash + sudo apt-get install -y sops gnupg + ``` + +=== "Development Container" + + If you are using the development container, the tools are already installed! 🎉 + +#### Install Keys + +Next, you will need to import the required keys. Copy the files with the keys into your workspace and import them +using the following commands: + +```bash +gpg --import courageous-comets.pub.asc +gpg --import courageous-comets.sec.asc ``` -Replace `` with your Discord bot token. +The keys will be imported into your keyring. !!! DANGER "Security Warning" - Do not commit your `.env` file to version control or share your token with anyone! + Do not share the keys with anyone! + +??? QUESTION "Where can I find the keys?" + You can download the keys from our private Discord server. -??? QUESTION "Where do I find my Discord bot token?" - You can obtain a Discord bot token from the [Discord Developer Portal](https://discord.com/developers/applications). +#### Decrypt `.env.lock` + +Once you have installed the required keys, you can decrypt the `.env.lock` file using the following command: + +```bash +sops -d --input-type dotenv --output-type dotenv .env.lock > .env +``` + +This will create a decrypted `.env` file at the project root directory. You can now view the contents of the file +and access the Discord bot token. + +!!! DANGER "Security Warning" + Do not commit your decrypted `.env` file to version control or share the token with anyone! ## Running the Documentation Locally diff --git a/docs/stylesheets/tabs.css b/docs/stylesheets/tabs.css deleted file mode 100644 index 7f99089..0000000 --- a/docs/stylesheets/tabs.css +++ /dev/null @@ -1,56 +0,0 @@ -.tabbed-set { - position: relative; - display: flex; - flex-wrap: wrap; - margin: 1em 0; - border-radius: 0.1rem; - border: 1px solid #ddd; - padding: .5rem; -} - -.tabbed-set>input { - display: none; -} - -.tabbed-set label { - width: auto; - padding: 0.9375em 1.25em 0.78125em; - font-weight: 700; - font-size: 0.84em; - white-space: nowrap; - border-bottom: 0.15rem solid transparent; - border-top-left-radius: 0.1rem; - border-top-right-radius: 0.1rem; - cursor: pointer; - transition: background-color 250ms, color 250ms; -} - -.tabbed-set .tabbed-content { - width: 100%; - display: none; - box-shadow: 0 -.05rem #ddd; - padding: 24px; -} - -.tabbed-set input { - position: absolute; - opacity: 0; -} - -.tabbed-set input:checked:nth-child(n+1)+label { - color: #2980b9; - border-color: #2980b9; -} - -@media screen { - .tabbed-set input:nth-child(n+1):checked+label+.tabbed-content { - order: 99; - display: block; - } -} - -@media print { - .tabbed-content { - display: contents; - } -} diff --git a/mkdocs.yaml b/mkdocs.yaml index d0b73d9..c76bdda 100644 --- a/mkdocs.yaml +++ b/mkdocs.yaml @@ -50,7 +50,8 @@ markdown_extensions: - pymdownx.details - pymdownx.snippets - pymdownx.superfences - - pymdownx.tabbed + - pymdownx.tabbed: + alternate_style: true - toc: permalink: true @@ -68,5 +69,4 @@ extra: provider: mike extra_css: - - stylesheets/tabs.css - stylesheets/table.css From 7d68bd1785cdc38600f0cf7feefe241c6af0b491 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Tue, 16 Jul 2024 10:30:11 +0000 Subject: [PATCH 007/168] =?UTF-8?q?bump:=20version=200.0.1=20=E2=86=92=200?= =?UTF-8?q?.0.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/CHANGELOG.md | 2 ++ pyproject.toml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 00da639..bd8b9c5 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1 +1,3 @@ +## v0.0.2 (2024-07-16) + ## v0.0.1 (2024-07-15) diff --git a/pyproject.toml b/pyproject.toml index d7e8562..1e9ba6d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "courageous-comets" -version = "0.0.1" +version = "0.0.2" authors = ["Courageous Comets"] description = "" readme = "docs/README.md" From 105fedd4e0e17b2bb9e423e3abca048c3adb1b10 Mon Sep 17 00:00:00 2001 From: Uchechukwu Orji Date: Tue, 16 Jul 2024 13:42:50 +0100 Subject: [PATCH 008/168] docs: update installation instructions for sop on linux (#3) Co-authored-by: thijsfranck --- .gitignore | 5 +++- .../development-environment.md | 23 +++++++++++++++++-- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 2d4e1ab..ac00087 100644 --- a/.gitignore +++ b/.gitignore @@ -86,7 +86,7 @@ ipython_config.py # pyenv # For a library or package, you might want to ignore these files since the code is # intended to run in multiple environments; otherwise, check them in: -# .python-version +.python-version # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. @@ -175,3 +175,6 @@ pyrightconfig.json # Secrets *.asc + +# Vim +*.vim diff --git a/docs/contributor-guide/development-environment.md b/docs/contributor-guide/development-environment.md index 47f077b..d170950 100644 --- a/docs/contributor-guide/development-environment.md +++ b/docs/contributor-guide/development-environment.md @@ -102,10 +102,29 @@ system. Follow the instructions for your operating system below. === "Linux" - Open a terminal and run the following command: + Download the [SOPS binary](https://github.com/getsops/sops/releases) for your platform. For instance, if + you are on an amd64 architecture: + + ```bash + curl -LO https://github.com/getsops/sops/releases/download/v3.9.0/sops-v3.9.0.linux.amd64 + ``` + + Move the binary into your `PATH`: + + ```bash + mv sops-v3.9.0.linux.amd64 /usr/local/bin/sops + ``` + + Make the binary executable: + + ```bash + chmod +x /usr/local/bin/sops + ``` + + Finally, install GnuPG: ```bash - sudo apt-get install -y sops gnupg + sudo apt-get install -y gnupg ``` === "Development Container" From 099cab86f5104e43d59b3b677b6df791047b32cc Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Tue, 16 Jul 2024 16:55:10 +0200 Subject: [PATCH 009/168] docs: add video installation guide --- docs/contributor-guide/development-environment.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/contributor-guide/development-environment.md b/docs/contributor-guide/development-environment.md index d170950..b436e67 100644 --- a/docs/contributor-guide/development-environment.md +++ b/docs/contributor-guide/development-environment.md @@ -23,9 +23,11 @@ setup process. ### Automated Setup The project includes a [development container](https://containers.dev) to automatically set up your development -environment. +environment. See the video installation guide below: -To get started, refer to the setup guide for your IDE: +https://github.com/user-attachments/assets/703aa245-9e33-44d9-9c79-7432afbeb445 + +For more details, refer to the setup guide for your IDE: - [Visual Studio Code (recommended)](https://code.visualstudio.com/docs/devcontainers/tutorial) - [PyCharm](https://www.jetbrains.com/help/pycharm/connect-to-devcontainer.html) From 150df91c6f34747668bf979e1d2bde6422ccc54b Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Tue, 16 Jul 2024 15:00:37 +0000 Subject: [PATCH 010/168] docs: embed video controls as html elements --- .markdownlint.json | 6 ++++++ docs/contributor-guide/development-environment.md | 4 +++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.markdownlint.json b/.markdownlint.json index 227692d..6f661bd 100644 --- a/.markdownlint.json +++ b/.markdownlint.json @@ -7,6 +7,12 @@ "MD024": { "siblings_only": true }, + "MD033": { + "allowed_elements": [ + "source", + "video" + ] + }, "MD046": false, "default": true } diff --git a/docs/contributor-guide/development-environment.md b/docs/contributor-guide/development-environment.md index b436e67..d74f94a 100644 --- a/docs/contributor-guide/development-environment.md +++ b/docs/contributor-guide/development-environment.md @@ -25,7 +25,9 @@ setup process. The project includes a [development container](https://containers.dev) to automatically set up your development environment. See the video installation guide below: -https://github.com/user-attachments/assets/703aa245-9e33-44d9-9c79-7432afbeb445 + For more details, refer to the setup guide for your IDE: From 07b749f5d2f1906f4c5f28faab49a43a83a5853a Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Tue, 16 Jul 2024 15:01:23 +0000 Subject: [PATCH 011/168] =?UTF-8?q?bump:=20version=200.0.2=20=E2=86=92=200?= =?UTF-8?q?.0.3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/CHANGELOG.md | 2 ++ pyproject.toml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index bd8b9c5..63b1e70 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,3 +1,5 @@ +## v0.0.3 (2024-07-16) + ## v0.0.2 (2024-07-16) ## v0.0.1 (2024-07-15) diff --git a/pyproject.toml b/pyproject.toml index 1e9ba6d..47385f1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "courageous-comets" -version = "0.0.2" +version = "0.0.3" authors = ["Courageous Comets"] description = "" readme = "docs/README.md" From 16514ef28e60ee42bf2fd505fd6ceeed3444dac6 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Tue, 16 Jul 2024 15:25:38 +0000 Subject: [PATCH 012/168] docs: restructure setup guide --- .../development-environment.md | 59 ++++++++++++------- 1 file changed, 39 insertions(+), 20 deletions(-) diff --git a/docs/contributor-guide/development-environment.md b/docs/contributor-guide/development-environment.md index d74f94a..7ab1832 100644 --- a/docs/contributor-guide/development-environment.md +++ b/docs/contributor-guide/development-environment.md @@ -5,16 +5,6 @@ Follow the steps below to set up your development environment. !!! NOTE "Prerequisites" You need to have [Git](https://git-scm.com) installed on your system. -## Cloning the Repository - -To clone the repository, run the following command: - -```bash -git clone https://github.com/thijsfranck/courageous-comets.git -``` - -Next, open the project in your preferred IDE or navigate to the project directory using the terminal. - ## Environment Setup You can set up the development environment using either the [automated](#automated-setup) or [manual](#manual-setup) @@ -23,21 +13,40 @@ setup process. ### Automated Setup The project includes a [development container](https://containers.dev) to automatically set up your development -environment. See the video installation guide below: +environment. + +!!! NOTE "Prerequisites" + [Docker](https://www.docker.com) must be installed on your system to use the development container. + +??? TIP "GitHub Codespaces" + If your system does not support Docker, you can use a [GitHub Codespace](https://docs.github.com/en/codespaces/getting-started/quickstart) + to install a development container in the cloud. + +#### Quick Start + +See the video installation guide below for a step-by-step tutorial on installing the development container with +Visual Studio Code: +First, install the [Remote Development Extension Pack](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.vscode-remote-extensionpack). +Next, open Visual Studio Code and click on icon in the bottom left corner to open the command palette for remote +environments. + +Select the `Clone Repository in Container Volume` command. This will prompt you to select the +repository to clone. Choose the `thijsfranck/courageous-comets` repository or paste the repository URL. + +Once you confirm the selection, the development container will be set up automatically. + +#### Detailed Setup Guide + For more details, refer to the setup guide for your IDE: -- [Visual Studio Code (recommended)](https://code.visualstudio.com/docs/devcontainers/tutorial) +- [Visual Studio Code](https://code.visualstudio.com/docs/devcontainers/tutorial) - [PyCharm](https://www.jetbrains.com/help/pycharm/connect-to-devcontainer.html) -??? TIP "Cloud Development Environment" - Alternatively, you can use a [GitHub Codespace](https://docs.github.com/en/codespaces/getting-started/quickstart) - to set up your development environment in the cloud. - ### Manual Setup If you prefer to set up the development environment manually, follow the steps below. @@ -46,6 +55,16 @@ If you prefer to set up the development environment manually, follow the steps b Please ensure [Python 3.12](https://www.python.org) and [Poetry](https://python-poetry.org) are installed on your system. +#### Clone the Repository + +To clone the repository, run the following command: + +```bash +git clone https://github.com/thijsfranck/courageous-comets.git +``` + +Next, open the project in your preferred IDE or navigate to the project directory using the terminal. + #### Install Dependencies Start by installing the project dependencies using Poetry: @@ -64,7 +83,7 @@ Next, install the pre-commit hooks to ensure that your code is formatted and lin poetry run pre-commit install ``` -### Secrets Management +## Secrets Management To use our team's shared Discord bot token, you will need to retrieve it from the `.env.lock` file in the project root directory. This section will guide you through the process of decrypting the file to access the token. @@ -82,7 +101,7 @@ root directory. This section will guide you through the process of decrypting th If you choose to use your own token, you can skip the steps below. -#### Install Tools +### Install Tools First, you will need to install [GnuPG](https://gnupg.org) and [SOPS](https://github.com/getsops/sops) on your system. Follow the instructions for your operating system below. @@ -135,7 +154,7 @@ system. Follow the instructions for your operating system below. If you are using the development container, the tools are already installed! 🎉 -#### Install Keys +### Install Keys Next, you will need to import the required keys. Copy the files with the keys into your workspace and import them using the following commands: @@ -153,7 +172,7 @@ The keys will be imported into your keyring. ??? QUESTION "Where can I find the keys?" You can download the keys from our private Discord server. -#### Decrypt `.env.lock` +### Decrypt `.env.lock` Once you have installed the required keys, you can decrypt the `.env.lock` file using the following command: From 8886bea17423ebb78c3dbffc21d2389865edb339 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Tue, 16 Jul 2024 15:25:49 +0000 Subject: [PATCH 013/168] =?UTF-8?q?bump:=20version=200.0.3=20=E2=86=92=200?= =?UTF-8?q?.0.4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/CHANGELOG.md | 2 ++ pyproject.toml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 63b1e70..5678e22 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,3 +1,5 @@ +## v0.0.4 (2024-07-16) + ## v0.0.3 (2024-07-16) ## v0.0.2 (2024-07-16) diff --git a/pyproject.toml b/pyproject.toml index 47385f1..d41aa82 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "courageous-comets" -version = "0.0.3" +version = "0.0.4" authors = ["Courageous Comets"] description = "" readme = "docs/README.md" From db63bb8d160de1490c75b2fd2ffe914c08d21b06 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Wed, 17 Jul 2024 10:58:01 +0200 Subject: [PATCH 014/168] ci: switch to age for better secrets management (#4) --- .devcontainer/devcontainer.json | 6 +- .devcontainer/postcreate.sh | 8 ++ .env.lock | 11 ++- .sops.yaml | 4 +- .../development-environment.md | 89 ++++++++++++++----- secrets/.gitignore | 2 + 6 files changed, 88 insertions(+), 32 deletions(-) create mode 100644 secrets/.gitignore diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 1bbda2c..f13a955 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -4,7 +4,8 @@ "dockerfile": "Dockerfile" }, "containerEnv": { - "POETRY_VIRTUALENVS_CREATE": "false" + "POETRY_VIRTUALENVS_CREATE": "false", + "SOPS_AGE_KEY_FILE": "${containerWorkspaceFolder}/secrets/keys.txt" }, "customizations": { "vscode": { @@ -38,6 +39,9 @@ }, "features": { "ghcr.io/devcontainers-contrib/features/act:1": {}, + "ghcr.io/devcontainers-contrib/features/apt-packages": { + "packages": "age" + }, "ghcr.io/devcontainers-contrib/features/poetry:2": {}, "ghcr.io/devcontainers-contrib/features/sops:1": {}, "ghcr.io/devcontainers/features/docker-outside-of-docker:1": {}, diff --git a/.devcontainer/postcreate.sh b/.devcontainer/postcreate.sh index 2628186..06d8386 100644 --- a/.devcontainer/postcreate.sh +++ b/.devcontainer/postcreate.sh @@ -5,3 +5,11 @@ git config --global --add safe.directory $PWD # Install pre-commit hooks poetry run pre-commit install + +# Generate a new secret key if one doesn't exist +if [ ! -f secrets/keys.txt ]; then + echo + echo "Generating a new secret key. Share the following public key with the team:" + age-keygen -o secrets/keys.txt + echo +fi diff --git a/.env.lock b/.env.lock index 16426bd..ff1a349 100644 --- a/.env.lock +++ b/.env.lock @@ -1,8 +1,7 @@ -DISCORD_TOKEN=ENC[AES256_GCM,data:TVo8NZfBAJqIAvnJK+OZgGd/3qoUrmrEV+IFMSmqv2v7Ky9Sxa+aULPfTLHsgD0rwidS2fm1evYexFAR9SGzA76lO/wJTyIy,iv:J284ntN9pkjbRMwIXCe9T3FyjfyAJqyued1ztL8RmhM=,tag:QXhXzPdzq3Ge2SOfjtxR4g==,type:str] -sops_lastmodified=2024-07-16T09:00:40Z -sops_mac=ENC[AES256_GCM,data:gxLkvOZxhme7iao6RpHU1iV/8GOQ7hKLrGlmARnqxLf0DciORGBwShju4ZVsJuFKs1uZPR0b4A8UdF0bjz/K27yJn9Xfg9i44lUefY7Gvl5pf/UoIZgVKtZomMmN9p1onemEQRW2hFruB7bm4PoaSRTgM2BMitIt+FQoSZtiEcg=,iv:4jN/x9Sz68nIR50/IeyoCAf+qz959+NlexGRyP/N7sE=,tag:pqpcgmpRnelOgyvWeZZGYg==,type:str] -sops_pgp__list_0__map_created_at=2024-07-16T09:00:06Z -sops_pgp__list_0__map_enc=-----BEGIN PGP MESSAGE-----\n\nhQGMA4xAg/8tRvaHAQv/RD7aND4k9sO78NRhRp+3fi7nClKJsEa2MEN7EuJctNR0\nh8RY6ePCCe7zLJj6pzVuX0Ul7S9WRDQ+Rt0gzmqn77lVsDeB4qwBFKlMj9N+w6bI\nBg6P6ouyyg/BohKyx4CZ9G7ASb6gFyg+vhtmBBKGq86FYFEFyYUpwnuz9RmOb5E6\nZNMqm1zFYA6s3cuYuwUmO1Ot5DE3bX/3PZDaSx61tKtph3i0qc+R/P9ekIOvwrFS\nXtneDFvCmU+kLiRQWHnkUo76ky2zpqON/F+6gViEGOwZCseD7Z/FDRJ99gS+Uv/e\nD1Uxh0JBlmBCKqR3L2iksHP8b25DPxYDy0FaZ0hZXxkyFGzYbw8+r9prf6lGwB5B\njMMYzGQMb/MkdX/ZjcZzTAMPEp0m28LBS4M057anyRPkaEB+6b/iL0xmil2marMA\nF44200XALUi8KYmMkUhStcxHptn1h+hFDup10kn6pA9WiIZXy4JFhxLUoqJrdOXU\nu7WhGd5YImvtl9kZszWS0l4Bm8WwJ4kwa7aQgnLUJR8XMFT4HvmIYA2P5rj9Y9Kw\njuIRQG9AdoA20yNbr0uNxtL5r6GZm3m/Ek/VrKlhWHeUwNfZwYlavzH2jdMbL//2\nkjnUni8aZqypIaE7a3Dy\n=CY+q\n-----END PGP MESSAGE----- -sops_pgp__list_0__map_fp=CFADC47C135DF8E4C14C895097E3177DD2EDD63F +DISCORD_TOKEN=ENC[AES256_GCM,data:M4ovvlrcqGbUdDRptRI+s+oK+s9xLVez3j7Xlap3f84UW1JfaifOqxUoDjX8TFEc5ejLO+x9VWqU1H9Ftjzedk31ZqBcMApd,iv:hYZpHEOieWCSv9QcycjYk1RGb+xEADxP+azB1Onvus4=,tag:tcJh2Xtny4ZUh7RTt71yuw==,type:str] +sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAyTitITldmenhta3hlVkla\nQnYrTm1zSWlMa2NKeFgza1BwYUNFTm9UZ2l3CmY4ZlpZUy9XWkR4UE5mRVJGN2Yz\nYTZuSGJtMWkzUndyeDVZWHM5SHVUREEKLS0tIHpWUlQvTVBPOG9tUVNrTnI5eHdT\nNWdmalJYZE93dmFXZlJYbGJha1ZQWncK0PgxJnZuM/K4aAtYhZbU1CVuMjuFxoRM\nPyU9wwmCC/FgceUF8WxBOiUTdfmP0u9q50Q7IlGYUbKLCHBl7qgN8w==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_0__map_recipient=age1sgv2yduaugas4travt8tjctmxe0jyge5p0w6ufcfard5umwaxvss2na2ff +sops_lastmodified=2024-07-16T20:02:43Z +sops_mac=ENC[AES256_GCM,data:u7a8Xl6augjT3U1moXPfjwh0P2u5vaRFFs8n46x5YEtdMtBbDwkHdJgCikN963fzT0O4dDPskzXCLgSHGaO8GT2frFdCmru0wZGNYcaPA5EG5QD2e4emehDG22fhU0geYHh59vKXoY6eFmnXoCbfUx39v1TPF2wVlrsXB2LyTNI=,iv:/s3hPMzsYBGP1yuaSYVWb1TALwuqBdJOUJJnfmYC6mY=,tag:LO3wGDuWTI54S4rf8BhvhA==,type:str] sops_unencrypted_suffix=_unencrypted sops_version=3.9.0 diff --git a/.sops.yaml b/.sops.yaml index 69af8c1..d924317 100644 --- a/.sops.yaml +++ b/.sops.yaml @@ -1,3 +1,3 @@ creation_rules: - - pgp: >- - CFADC47C135DF8E4C14C895097E3177DD2EDD63F + - age: >- + age1sgv2yduaugas4travt8tjctmxe0jyge5p0w6ufcfard5umwaxvss2na2ff diff --git a/docs/contributor-guide/development-environment.md b/docs/contributor-guide/development-environment.md index 7ab1832..4000699 100644 --- a/docs/contributor-guide/development-environment.md +++ b/docs/contributor-guide/development-environment.md @@ -103,29 +103,31 @@ root directory. This section will guide you through the process of decrypting th ### Install Tools -First, you will need to install [GnuPG](https://gnupg.org) and [SOPS](https://github.com/getsops/sops) on your -system. Follow the instructions for your operating system below. +First, you will need to install [`age`](https://github.com/FiloSottile/age) and [`SOPS`](https://github.com/getsops/sops) +on your system. Follow the instructions for your operating system below. === "Windows" - Open a PowerShell terminal and run the following commands: + Open a PowerShell terminal and run the following command to install `SOPS`: ```bash winget install -e --id Mozilla.SOPS - winget install -e --id GnuPG.GnuPG ``` + To install `age`, download the [latest binary for Windows](https://github.com/FiloSottile/age/releases) and + add your `age` binary to the system `PATH`. + === "macOS" Open a terminal and run the following command: ```bash - brew install sops gnupg + brew install age sops ``` === "Linux" - Download the [SOPS binary](https://github.com/getsops/sops/releases) for your platform. For instance, if + Download the [`SOPS` binary](https://github.com/getsops/sops/releases) for your platform. For instance, if you are on an amd64 architecture: ```bash @@ -144,47 +146,88 @@ system. Follow the instructions for your operating system below. chmod +x /usr/local/bin/sops ``` - Finally, install GnuPG: + Finally, install `age`: ```bash - sudo apt-get install -y gnupg + sudo apt-get install -y age ``` === "Development Container" If you are using the development container, the tools are already installed! 🎉 -### Install Keys +### Generate Keys -Next, you will need to import the required keys. Copy the files with the keys into your workspace and import them -using the following commands: +Next, you will need to generate a new key pair using `age`. Run the following command from the root directory of +the project: ```bash -gpg --import courageous-comets.pub.asc -gpg --import courageous-comets.sec.asc +age-keygen -o > secrets/keys.txt ``` -The keys will be imported into your keyring. +This will create a new key pair and save it to the `secrets/keys.txt` file. Share your public key with the team +so it can be registered. !!! DANGER "Security Warning" - Do not share the keys with anyone! + Only your public key can be safely shared. Do not share the private key with anyone! + +??? QUESTION "Where can I find my public key?" + You can find your public key in the `secrets/keys.txt` file or in the terminal output after generating the + key pair. + +??? TIP "Development Container Automation" + On initial setup, the key pair is generated automatically in the development container. + +### Registering a new Public Key + +!!! NOTE "Prerequisite" + This step needs to be performed by a team member who already has access to the `.env` file. + +To register a new public key, first extend the `.sops.yaml` file in the project root directory. +Add the public key to the list of `age` keys. Each key is separated by a comma and a newline. + +```yaml +creation_rules: + - age: >- + , + , + +``` + +Next, [encrypt](#encrypting-secrets) the `.env` file with the updated list of keys and push it to the repository. -??? QUESTION "Where can I find the keys?" - You can download the keys from our private Discord server. +### Decrypting Secrets -### Decrypt `.env.lock` +Once your public key is added to the `.env.lock` file, you can decrypt the file to access the Discord bot token. +First, pull the latest changes from the repository: -Once you have installed the required keys, you can decrypt the `.env.lock` file using the following command: +```bash +git pull +``` + +!!! NOTE "Prerequisite" + `SOPS` requires the `SOPS_AGE_KEY_FILE` environment variable to be set to the path of your private key file. + This is automatically set up in the development container. + +Next, run the following command to decrypt the `.env.lock` file: ```bash -sops -d --input-type dotenv --output-type dotenv .env.lock > .env +sops decrypt --input-type dotenv --output-type dotenv .env.lock > .env ``` -This will create a decrypted `.env` file at the project root directory. You can now view the contents of the file -and access the Discord bot token. +This will decrypt the file and save the contents to a new `.env` file in the project root directory. You can now +access the Discord bot token. !!! DANGER "Security Warning" - Do not commit your decrypted `.env` file to version control or share the token with anyone! + Do not commit your decrypted `.env` file to version control or share the contents with anyone! + +### Encrypting Secrets + +To encrypt the `.env` file after making changes, run the following command: + +```bash +sops encrypt .env > .env.lock +``` ## Running the Documentation Locally diff --git a/secrets/.gitignore b/secrets/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/secrets/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore From c68cea5bfc852a2647b85954663dab405c82d4d3 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Wed, 17 Jul 2024 09:03:58 +0000 Subject: [PATCH 015/168] =?UTF-8?q?bump:=20version=200.0.4=20=E2=86=92=200?= =?UTF-8?q?.0.5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/CHANGELOG.md | 2 ++ pyproject.toml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 5678e22..29c1cb0 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,3 +1,5 @@ +## v0.0.5 (2024-07-17) + ## v0.0.4 (2024-07-16) ## v0.0.3 (2024-07-16) diff --git a/pyproject.toml b/pyproject.toml index d41aa82..5e65bd2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "courageous-comets" -version = "0.0.4" +version = "0.0.5" authors = ["Courageous Comets"] description = "" readme = "docs/README.md" From 16081328b49477a838e99043c7351f4ed202fefa Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Wed, 17 Jul 2024 15:34:54 +0000 Subject: [PATCH 016/168] chore: add key to ACL --- .env.lock | 10 ++++++---- .sops.yaml | 3 ++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/.env.lock b/.env.lock index ff1a349..dea69a1 100644 --- a/.env.lock +++ b/.env.lock @@ -1,7 +1,9 @@ -DISCORD_TOKEN=ENC[AES256_GCM,data:M4ovvlrcqGbUdDRptRI+s+oK+s9xLVez3j7Xlap3f84UW1JfaifOqxUoDjX8TFEc5ejLO+x9VWqU1H9Ftjzedk31ZqBcMApd,iv:hYZpHEOieWCSv9QcycjYk1RGb+xEADxP+azB1Onvus4=,tag:tcJh2Xtny4ZUh7RTt71yuw==,type:str] -sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAyTitITldmenhta3hlVkla\nQnYrTm1zSWlMa2NKeFgza1BwYUNFTm9UZ2l3CmY4ZlpZUy9XWkR4UE5mRVJGN2Yz\nYTZuSGJtMWkzUndyeDVZWHM5SHVUREEKLS0tIHpWUlQvTVBPOG9tUVNrTnI5eHdT\nNWdmalJYZE93dmFXZlJYbGJha1ZQWncK0PgxJnZuM/K4aAtYhZbU1CVuMjuFxoRM\nPyU9wwmCC/FgceUF8WxBOiUTdfmP0u9q50Q7IlGYUbKLCHBl7qgN8w==\n-----END AGE ENCRYPTED FILE-----\n +DISCORD_TOKEN=ENC[AES256_GCM,data:rzbi6wbkv9NG1+20UjB1DoRsNQ+HixtgOppxeQX3EXSFpSyWnWUpIvoOdpHFhbFNpSTB22aD/bKlUslifv3UgVPpJjOcnw6F,iv:l7dht/JPpkBalXC78HtouY1HT5ScQS/vNHJNMAgtzh4=,tag:GMARteZ78g39v9NyWS48Hg==,type:str] +sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB5Sjl3N3JvMDlZbWJ6QzJS\nTng4cFNIQS95cTFDRHJnbW1wMGZ2L2VqeG5BClNCUHZkSS81am1laHV6TTJQek5h\nRjBHbjd5cFpjcENqdktRMFRRM1drMVUKLS0tIHFuU2x3TnZQZ0VzV0VrM2xSelI0\nSGFrNFV1dmk0eFJ5N1BzdXRNOVhsbnMKD1orqDcDvuXBqfv84NqvmE9wxPSNHT1Y\nZXC/ubKMPOhalZqjuPrsmIgYiwvthC6R9ThXVXgW2A5x2wYuOhxgXw==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_0__map_recipient=age1sgv2yduaugas4travt8tjctmxe0jyge5p0w6ufcfard5umwaxvss2na2ff -sops_lastmodified=2024-07-16T20:02:43Z -sops_mac=ENC[AES256_GCM,data:u7a8Xl6augjT3U1moXPfjwh0P2u5vaRFFs8n46x5YEtdMtBbDwkHdJgCikN963fzT0O4dDPskzXCLgSHGaO8GT2frFdCmru0wZGNYcaPA5EG5QD2e4emehDG22fhU0geYHh59vKXoY6eFmnXoCbfUx39v1TPF2wVlrsXB2LyTNI=,iv:/s3hPMzsYBGP1yuaSYVWb1TALwuqBdJOUJJnfmYC6mY=,tag:LO3wGDuWTI54S4rf8BhvhA==,type:str] +sops_age__list_1__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBkZXo2b0tlTkpwZzk5YXFX\nbFFUeE1wVVlNU2E0T0hmYmdHUUtkbGliSGdBCkZJUWo0azdqeHJ3R2d1bFZDNlVh\nZHBjZVZmWlJVMjh6Y3V2TzVsTXE4NXcKLS0tIDZRMlVXQUh6eHZER2QrRU5QUm40\nbjlSaEkzb0JPWGhTUExUZUpBZnNkcTAK/25DPX1U6udFrI2cSGDa8sDVpYjGB1SP\nCO4rFl9bhIvIrM3knfoUXu8laD0co8HSQplWXU0rHWvWabr0nySRLQ==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_1__map_recipient=age1lhk7gg022hrfn6z85m2qdmudm8vql4vn90fuyaulvw99fqv8yctq4uh39k +sops_lastmodified=2024-07-17T15:34:25Z +sops_mac=ENC[AES256_GCM,data:nVpT6A743Fs9IwD01dBdNbPlwXhrLzg0IvdnQBiDnWks0I0lPsKHwdhT8EQYL0tSRHVjDJ8rm9tDtG+QRc780JMIyRmKrpr2TroHr8MUH2qqx3/UavqiecmuyJ+zP1Em6Mm0sNYMxhfilWydmSTxoZgHXCMxVFkfOReJt1LV5jo=,iv:B5Fc5rO/GaSGH8D3KO2LU/AfbaBEN7oL/6WrJsvQXgQ=,tag:jIhOP608DzyERN/kXrtpEg==,type:str] sops_unencrypted_suffix=_unencrypted sops_version=3.9.0 diff --git a/.sops.yaml b/.sops.yaml index d924317..c0359b1 100644 --- a/.sops.yaml +++ b/.sops.yaml @@ -1,3 +1,4 @@ creation_rules: - age: >- - age1sgv2yduaugas4travt8tjctmxe0jyge5p0w6ufcfard5umwaxvss2na2ff + age1sgv2yduaugas4travt8tjctmxe0jyge5p0w6ufcfard5umwaxvss2na2ff, + age1lhk7gg022hrfn6z85m2qdmudm8vql4vn90fuyaulvw99fqv8yctq4uh39k From 7ac17a3291b5f129829e9ab9044f51b261ac2afe Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Thu, 18 Jul 2024 22:20:17 +0200 Subject: [PATCH 017/168] ci: set up docker build (#9) --- .github/workflows/publish.yaml | 20 ++++++++++---------- Dockerfile | 21 +++++++++++++++++++++ courageous_comets/__main__.py | 1 + 3 files changed, 32 insertions(+), 10 deletions(-) create mode 100644 Dockerfile create mode 100644 courageous_comets/__main__.py diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index 1cf0033..a2f7f27 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -30,16 +30,16 @@ jobs: python-version: 3.12 poetry-version: 1.8.3 -# - name: Build the Application -# run: poetry build -f wheel -# shell: bash -# -# - name: Build and Publish the Docker Image -# uses: docker/build-push-action@v6 -# with: -# context: . -# push: true -# tags: ghcr.io/thijsfranck/courageous-comets:${{ github.ref_name }},ghcr.io/thijsfranck/courageous-comets:latest + - name: Build the Application + run: poetry build -f wheel + shell: bash + + - name: Build and Publish the Docker Image + uses: docker/build-push-action@v6 + with: + context: . + push: true + tags: ghcr.io/thijsfranck/courageous-comets:${{ github.ref_name }},ghcr.io/thijsfranck/courageous-comets:latest - name: Set up Git user uses: fregante/setup-git-user@v2.0.1 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..2ca2a8b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,21 @@ +# Use the official Python 3.12 slim image as the base image +FROM python:3.12-slim + +# Add a non-root user +RUN adduser --system courageous-comets + +# Set the working directory +WORKDIR /app + +# Copy the wheel file to the working directory +COPY dist/*.whl ./ + +# Install the wheel file and clean up to reduce image size +RUN pip install --no-cache-dir *.whl && \ + rm *.whl + +# Switch to the non-root user +USER courageous-comets + +# Run the application +ENTRYPOINT ["python", "-m", "courageous_comets"] diff --git a/courageous_comets/__main__.py b/courageous_comets/__main__.py new file mode 100644 index 0000000..df858dc --- /dev/null +++ b/courageous_comets/__main__.py @@ -0,0 +1 @@ +# Placeholder - run the main program here From b21efe5f7d460c7c7e2a0fb350fcc025f8eb074e Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Thu, 18 Jul 2024 22:20:49 +0200 Subject: [PATCH 018/168] ci: add redis to devcontainer (#7) --- .devcontainer/devcontainer.json | 23 +++++++++++++++++------ .devcontainer/docker-compose.yaml | 24 ++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 6 deletions(-) create mode 100644 .devcontainer/docker-compose.yaml diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index f13a955..a8db4b9 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,10 +1,8 @@ { - "build": { - "cacheFrom": "ghcr.io/thijsfranck/courageous-comets-devcontainer", - "dockerfile": "Dockerfile" - }, "containerEnv": { "POETRY_VIRTUALENVS_CREATE": "false", + "REDIS_HOST": "localhost", + "REDIS_PORT": "6379", "SOPS_AGE_KEY_FILE": "${containerWorkspaceFolder}/secrets/keys.txt" }, "customizations": { @@ -37,6 +35,7 @@ } } }, + "dockerComposeFile": "docker-compose.yaml", "features": { "ghcr.io/devcontainers-contrib/features/act:1": {}, "ghcr.io/devcontainers-contrib/features/apt-packages": { @@ -51,15 +50,27 @@ } }, "forwardPorts": [ - 8000 + 6379, + 8000, + 8001 ], "name": "Courageous Comets \u2604\ufe0f", "portsAttributes": { + "6379": { + "label": "Redis", + "onAutoForward": "silent" + }, "8000": { "label": "MkDocs", "onAutoForward": "notify" + }, + "8001": { + "label": "RedisInsight", + "onAutoForward": "silent" } }, "postCreateCommand": "bash .devcontainer/postcreate.sh", - "updateContentCommand": "bash .devcontainer/updatecontent.sh" + "service": "dev", + "updateContentCommand": "bash .devcontainer/updatecontent.sh", + "workspaceFolder": "/workspace" } diff --git a/.devcontainer/docker-compose.yaml b/.devcontainer/docker-compose.yaml new file mode 100644 index 0000000..f196600 --- /dev/null +++ b/.devcontainer/docker-compose.yaml @@ -0,0 +1,24 @@ +services: + + # This is the development container + dev: + build: + context: . + dockerfile: Dockerfile + cache_from: + - ghcr.io/thijsfranck/courageous-comets-devcontainer + volumes: + - ..:/workspace + command: 'sleep infinity' + + redis-stack: + image: redis/redis-stack:7.2.0-v11 + environment: + REDIS_PASSWORD: redis + volumes: + - redis-stack-data:/data + network_mode: service:dev + restart: unless-stopped + +volumes: + redis-stack-data: From 9cfcdf56def45c39223aa49655ff7dc7e741d103 Mon Sep 17 00:00:00 2001 From: isaa-ctaylor Date: Thu, 18 Jul 2024 20:38:21 +0000 Subject: [PATCH 019/168] ci: Add age key to .sops.yaml and re-encrypt .env --- .env.lock | 12 +++++++----- .sops.yaml | 3 ++- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/.env.lock b/.env.lock index dea69a1..19f6f5f 100644 --- a/.env.lock +++ b/.env.lock @@ -1,9 +1,11 @@ -DISCORD_TOKEN=ENC[AES256_GCM,data:rzbi6wbkv9NG1+20UjB1DoRsNQ+HixtgOppxeQX3EXSFpSyWnWUpIvoOdpHFhbFNpSTB22aD/bKlUslifv3UgVPpJjOcnw6F,iv:l7dht/JPpkBalXC78HtouY1HT5ScQS/vNHJNMAgtzh4=,tag:GMARteZ78g39v9NyWS48Hg==,type:str] -sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB5Sjl3N3JvMDlZbWJ6QzJS\nTng4cFNIQS95cTFDRHJnbW1wMGZ2L2VqeG5BClNCUHZkSS81am1laHV6TTJQek5h\nRjBHbjd5cFpjcENqdktRMFRRM1drMVUKLS0tIHFuU2x3TnZQZ0VzV0VrM2xSelI0\nSGFrNFV1dmk0eFJ5N1BzdXRNOVhsbnMKD1orqDcDvuXBqfv84NqvmE9wxPSNHT1Y\nZXC/ubKMPOhalZqjuPrsmIgYiwvthC6R9ThXVXgW2A5x2wYuOhxgXw==\n-----END AGE ENCRYPTED FILE-----\n +DISCORD_TOKEN=ENC[AES256_GCM,data:3tRB8k5Yv1VmwJAhG5Bkxv7/ylRBv1eqrXbouZ7jQNUJp+ztXrFBeNUIvI7xoyKCUBaIE/PcCkchbU/Osq9JeB5nI1RKRHC8,iv:jTQ6lPD6jjJ3MWOKKqdkiXXXnGH+CuaxogQ9MtxzOUg=,tag:C6oAqZ9Xgw8B5juWjBR42A==,type:str] +sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBCckZmcmVaMkFwZFB6TWtI\nVGw3YjI1d3FmNUlORFlvQ0tmQmpmanIrOHd3Ckw1dlNBZ25uQTczdm96UTM4VDRU\nYUFKMEl1citaVDZGbWhnUE9kaHByMG8KLS0tIDdyTmlJL0IzL3UvSHI0czNLZFpo\nT3JHT21oZmtvTzhyMFJlMXZML1FwY2cKRYz8DUTACEa+Kft3J1i/iN+wJXVCZrCJ\n1rtpDOx9Ys8GmK54bRQih8k5O2S6u7Eps2zLRWOmMTSQj4d2ZCkO1A==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_0__map_recipient=age1sgv2yduaugas4travt8tjctmxe0jyge5p0w6ufcfard5umwaxvss2na2ff -sops_age__list_1__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBkZXo2b0tlTkpwZzk5YXFX\nbFFUeE1wVVlNU2E0T0hmYmdHUUtkbGliSGdBCkZJUWo0azdqeHJ3R2d1bFZDNlVh\nZHBjZVZmWlJVMjh6Y3V2TzVsTXE4NXcKLS0tIDZRMlVXQUh6eHZER2QrRU5QUm40\nbjlSaEkzb0JPWGhTUExUZUpBZnNkcTAK/25DPX1U6udFrI2cSGDa8sDVpYjGB1SP\nCO4rFl9bhIvIrM3knfoUXu8laD0co8HSQplWXU0rHWvWabr0nySRLQ==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_1__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA5ZGNFNUNjaTNTQ0l5ekNI\nSjJUN1ZLWjdJYkx1UHpQbmhZMHFiOW1qNFFNCnZ4MkhPdWVxcThHUWE5c3FtOGxy\nVTdoeUJoQmtFZEhNSDhudzZib3RpUTQKLS0tIHI3TzdmYmNEZGhnRDRYaFVrTk9P\nWkhKWEhjWHZRVVBjN2FOZzFVUGR5Q1UKh/wkyRmeo4DLKk0yqVA0f1DfhOJpAPCw\nnrkvJ8trH19147od9tDlSQaZo8ipKK0nmijowU6rnS52FjU0hAQm0g==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_1__map_recipient=age1lhk7gg022hrfn6z85m2qdmudm8vql4vn90fuyaulvw99fqv8yctq4uh39k -sops_lastmodified=2024-07-17T15:34:25Z -sops_mac=ENC[AES256_GCM,data:nVpT6A743Fs9IwD01dBdNbPlwXhrLzg0IvdnQBiDnWks0I0lPsKHwdhT8EQYL0tSRHVjDJ8rm9tDtG+QRc780JMIyRmKrpr2TroHr8MUH2qqx3/UavqiecmuyJ+zP1Em6Mm0sNYMxhfilWydmSTxoZgHXCMxVFkfOReJt1LV5jo=,iv:B5Fc5rO/GaSGH8D3KO2LU/AfbaBEN7oL/6WrJsvQXgQ=,tag:jIhOP608DzyERN/kXrtpEg==,type:str] +sops_age__list_2__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBFVEJCQ2xDWE1Ed21icXJy\nMDBSY1E5aW50R01YeDEzcTV3Y1c1eFZpQ2dRCm5xNjhGWTVXR2tPcDBzNnd3bldE\nZXhPSDVGNXM5ZkEvaDFLVU1iUFEvR1EKLS0tIERKSEVJZit0cXhzcEJUNUJlV0h3\nS3B5QlB3SFZIVHJLQUlYNHErTGxuT0EKFCsTjivBASb4wnsTjZwEzzM+YDVWtClp\n/+rRuwHrPSKDwce1PaxyqhuG4/UH7StEk/mCkCLnsUqJyTachVzJGQ==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_2__map_recipient=age1u9a2890jjzd3qak5k9xnjngt4qjjvttwag2yjm9sctf6vuawpu2skzekyn +sops_lastmodified=2024-07-18T20:35:27Z +sops_mac=ENC[AES256_GCM,data:Y8e/+o6z3iDosEydIbz2dbPR160I5SqHTgxcQNHEgQqxPTd9MNxsg8Iz4sygTNlB5ByAFOPm6/EbrFreQ9/ChairtoLWEnyRBuBanFSpB0ZU8a6hLC4Y/0ZQ2YN53/OvuZjBfYaHEsrQxvRTTbY9sp1+6HzbUqMvmMKu6WKz2F4=,iv:jhiy7F8NMtfmv7fqqXNQyewHl4CJzCzEc2sgdNf715c=,tag:/KyvJ5oKtBUZXLNUPxfZpg==,type:str] sops_unencrypted_suffix=_unencrypted sops_version=3.9.0 diff --git a/.sops.yaml b/.sops.yaml index c0359b1..cb11af7 100644 --- a/.sops.yaml +++ b/.sops.yaml @@ -1,4 +1,5 @@ creation_rules: - age: >- age1sgv2yduaugas4travt8tjctmxe0jyge5p0w6ufcfard5umwaxvss2na2ff, - age1lhk7gg022hrfn6z85m2qdmudm8vql4vn90fuyaulvw99fqv8yctq4uh39k + age1lhk7gg022hrfn6z85m2qdmudm8vql4vn90fuyaulvw99fqv8yctq4uh39k, + age1u9a2890jjzd3qak5k9xnjngt4qjjvttwag2yjm9sctf6vuawpu2skzekyn From af36cd24ad96cbe7a6843514d6b01573c80fc998 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Fri, 19 Jul 2024 03:34:17 +0000 Subject: [PATCH 020/168] ci: add docker image metadata --- Dockerfile | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Dockerfile b/Dockerfile index 2ca2a8b..bb49779 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,14 @@ # Use the official Python 3.12 slim image as the base image FROM python:3.12-slim +# Add image metadata +LABEL org.opencontainers.image.authors="Courageous Comets ☄️" +LABEL org.opencontainers.image.description="This application was built by the Courageous Comets team for the Python Discord Summer Code Jam 2024" +LABEL org.opencontainers.image.documentation=https://thijsfranck.github.io/courageous-comets/ +LABEL org.opencontainers.image.licenses=MIT +LABEL org.opencontainers.image.source=https://github.com/thijsfranck/courageous-comets +LABEL org.opencontainers.image.title="Courageous Comets" + # Add a non-root user RUN adduser --system courageous-comets From db8fb4391f38d8a18f68d63a1b665e0f99f5463d Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Fri, 19 Jul 2024 04:32:56 +0000 Subject: [PATCH 021/168] docs: add admin guide and installation link on home page --- .pre-commit-config.yaml | 1 + docs/README.md | 8 ++++ docs/admin-guide/configuration.md | 17 +++++++++ docs/admin-guide/deployment.md | 28 ++++++++++++++ docs/admin-guide/index.md | 10 +++++ .../development-environment.md | 38 ++++++++++++++++++- mkdocs.yaml | 9 ++++- 7 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 docs/admin-guide/configuration.md create mode 100644 docs/admin-guide/deployment.md create mode 100644 docs/admin-guide/index.md diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8ffb973..bf70a4c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -19,6 +19,7 @@ repos: name: Check for TOML syntax errors - id: check-yaml name: Check for YAML syntax errors + args: [--unsafe] - id: end-of-file-fixer name: Ensure files end with a newline - id: pretty-format-json diff --git a/docs/README.md b/docs/README.md index f1c3134..3c24267 100644 --- a/docs/README.md +++ b/docs/README.md @@ -5,10 +5,18 @@ Thank you for your interest in our project! This is the documentation for the Courageous Comets Discord bot. It was built as part of the Python Discord Summer Code Jam 2024. +## How to Install + +Click the button below to install the bot in your Discord server: + + +[Add to Discord :fontawesome-brands-discord:](https://discord.com/oauth2/authorize?client_id=1262672493978714174){ .md-button .md-button--primary } + ## Contents This documentation is divided into the following sections: +- [Administrator Guide](./admin-guide/index.md): How to deploy, configure and manage the bot in production. - [Contributor Guide](./contributor-guide/index.md): How to set up a development environment and contribute to the project. diff --git a/docs/admin-guide/configuration.md b/docs/admin-guide/configuration.md new file mode 100644 index 0000000..14c896c --- /dev/null +++ b/docs/admin-guide/configuration.md @@ -0,0 +1,17 @@ +# Configuration + +The following environment variables are available to configure the application: + +| Variable | Description | Required | Default | +| --------------- | ------------------------------------------------| -------- | ------- | +| `DISCORD_TOKEN` | The Discord bot token. | Yes | - | + +## `DISCORD_TOKEN` + +You can obtain a Discord bot token from the [Discord Developer Portal](https://discord.com/developers/applications). +Your token should have the following scopes: + +- `bot` + +!!! DANGER "Security Warning" + Do not share your token with anyone! diff --git a/docs/admin-guide/deployment.md b/docs/admin-guide/deployment.md new file mode 100644 index 0000000..32e1f33 --- /dev/null +++ b/docs/admin-guide/deployment.md @@ -0,0 +1,28 @@ +# Deployment + +This section provides instructions on how to deploy the application in a production environment. + +!!! NOTE "Prerequisites" + The application is distributed as a [Docker](https://www.docker.com/) image. Please ensure that you have Docker + installed on your system. + +Open your terminal and run the following command to deploy the latest version of the application: + +```bash +docker run -e DISCORD_TOKEN="" ghcr.io/thijsfranck/courageous-comets:latest +``` + +Replace `` with your Discord bot token. The application will start and connect to Discord using the +provided token. + +You can now interact with the application in any Discord server where it has been installed. + +??? QUESTION "Where do I find my Discord bot token?" + See the configuration section for instructions on [how to obtain a Discord bot token](./configuration.md#discord_token). + +??? QUESTION "What other options are available?" + The example above is a minimal configuration for to start the application. Refer to the [configuration](configuration.md) + section for a list of the available options. + +??? QUESTION "Where can I find previous versions of the image?" + Previous versions of the image are available on the [GitHub Container Registry](https://github.com/thijsfranck/courageous-comets/pkgs/container/courageous-comets). diff --git a/docs/admin-guide/index.md b/docs/admin-guide/index.md new file mode 100644 index 0000000..f6a5954 --- /dev/null +++ b/docs/admin-guide/index.md @@ -0,0 +1,10 @@ +# Administrator Guide + +This guide is intended for administrators and developers. It provides instructions on how to deploy and configure +the application, as well as how to manage it in production. + +## Contents + +- [Deployment](./deployment.md): How to deploy the bot in a production environment. +- [Configuration](./configuration.md): How to configure your bot deployment. +- [Changelog](../CHANGELOG.md): The bot's changelog. diff --git a/docs/contributor-guide/development-environment.md b/docs/contributor-guide/development-environment.md index 4000699..b656098 100644 --- a/docs/contributor-guide/development-environment.md +++ b/docs/contributor-guide/development-environment.md @@ -229,7 +229,43 @@ To encrypt the `.env` file after making changes, run the following command: sops encrypt .env > .env.lock ``` -## Running the Documentation Locally +## Running the Application + +With your development environment set up and configured, you can run the application using the following command: + +```bash +poetry run python -m courageous_comets +``` + +The application should now be online and ready to respond to input from your Discord server. + +## Building the Application + +!!! INFO "Production Builds" + Production builds are fully automated. See the [GitHub Actions](./version-control.md#github-actions) section + of the version control page for more information. + +To run a local build for testing, first build the Python package with Poetry: + +```bash +poetry build -f wheel +``` + +This will create a `.whl` file in the `dist` directory. Next, build the Docker image as follows: + +```bash +docker build -t courageous-comets . +``` + +Finally, run the Docker container: + +```bash +docker run -i --env-file .env courageous-comets +``` + +This will run the application just as it would in production, using your local `.env` file. + +## Running the Documentation To view the documentation locally, you can use the following command: diff --git a/mkdocs.yaml b/mkdocs.yaml index c76bdda..cc6396b 100644 --- a/mkdocs.yaml +++ b/mkdocs.yaml @@ -37,7 +37,11 @@ theme: name: Switch to system preference nav: - - Contributor Guide: + - Administrators: + - admin-guide/index.md + - Deployment: admin-guide/deployment.md + - Configuration: admin-guide/configuration.md + - Contributors: - contributor-guide/index.md - Development Environment: contributor-guide/development-environment.md - Version Control: contributor-guide/version-control.md @@ -50,6 +54,9 @@ markdown_extensions: - pymdownx.details - pymdownx.snippets - pymdownx.superfences + - pymdownx.emoji: + emoji_index: !!python/name:material.extensions.emoji.twemoji + emoji_generator: !!python/name:material.extensions.emoji.to_svg - pymdownx.tabbed: alternate_style: true - toc: From f22079fc5d34b4779d462eff3e751501d0668580 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Fri, 19 Jul 2024 04:33:11 +0000 Subject: [PATCH 022/168] =?UTF-8?q?bump:=20version=200.0.5=20=E2=86=92=200?= =?UTF-8?q?.0.6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/CHANGELOG.md | 2 ++ pyproject.toml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 29c1cb0..afc2cf0 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,3 +1,5 @@ +## v0.0.6 (2024-07-19) + ## v0.0.5 (2024-07-17) ## v0.0.4 (2024-07-16) diff --git a/pyproject.toml b/pyproject.toml index 5e65bd2..83d9709 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "courageous-comets" -version = "0.0.5" +version = "0.0.6" authors = ["Courageous Comets"] description = "" readme = "docs/README.md" From 3ce32a7472dfa98bb3d297f32fbae965124d3868 Mon Sep 17 00:00:00 2001 From: Uchechukwu Orji Date: Fri, 19 Jul 2024 14:45:02 +0100 Subject: [PATCH 023/168] Set up global redis connection (#13) Co-authored-by: thijsfranck --- .devcontainer/devcontainer.json | 1 + courageous_comets/__init__.py | 11 ++ courageous_comets/__main__.py | 53 +++++++++- courageous_comets/error.py | 2 + courageous_comets/redis/__init__.py | 0 courageous_comets/settings.py | 10 ++ poetry.lock | 149 +++++++++++++++++++++++++--- pyproject.toml | 1 + 8 files changed, 214 insertions(+), 13 deletions(-) create mode 100644 courageous_comets/error.py create mode 100644 courageous_comets/redis/__init__.py create mode 100644 courageous_comets/settings.py diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index a8db4b9..841079d 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -2,6 +2,7 @@ "containerEnv": { "POETRY_VIRTUALENVS_CREATE": "false", "REDIS_HOST": "localhost", + "REDIS_PASSWORD": "redis", "REDIS_PORT": "6379", "SOPS_AGE_KEY_FILE": "${containerWorkspaceFolder}/secrets/keys.txt" }, diff --git a/courageous_comets/__init__.py b/courageous_comets/__init__.py index e69de29..eb4d034 100644 --- a/courageous_comets/__init__.py +++ b/courageous_comets/__init__.py @@ -0,0 +1,11 @@ +import logging + +from courageous_comets.settings import Settings + +logger = logging.getLogger("courageous_comets") + +if not logger.hasHandlers(): + logger.setLevel(logging.DEBUG if Settings.DEBUG else logging.INFO) + handler = logging.StreamHandler() + handler.setFormatter(logging.Formatter("[%(asctime)s: %(levelname)s] %(message)s")) + logger.addHandler(handler) diff --git a/courageous_comets/__main__.py b/courageous_comets/__main__.py index df858dc..5b1993f 100644 --- a/courageous_comets/__main__.py +++ b/courageous_comets/__main__.py @@ -1 +1,52 @@ -# Placeholder - run the main program here +import asyncio +import sys + +from redis.asyncio import ConnectionError, Redis + +from courageous_comets import logger +from courageous_comets.error import CourageousCometsError +from courageous_comets.settings import Settings + + +async def init_redis() -> Redis: + """Initialize the Redis connection.""" + logger.debug("Connecting to Redis...") + + instance = Redis( + host="", + port=Settings.REDIS_PORT, + password=Settings.REDIS_PASSWORD, + ) + + try: + await instance.ping() + except ConnectionError as e: + message = f"Could not connect to Redis at {Settings.REDIS_HOST}:{Settings.REDIS_PORT}" + raise CourageousCometsError(message) from e + + logger.info( + "Connected to Redis at %s:%s", + Settings.REDIS_HOST, + Settings.REDIS_PORT, + ) + + return instance + + +async def main() -> None: + """Start the appication.""" + redis = await init_redis() + + try: + logger.info("Starting the Discord client...") + finally: + logger.debug("Closing Redis connection...") + await redis.aclose() + logger.info("Application shutdown complete.") + + +try: + asyncio.run(main()) +except CourageousCometsError as e: + logger.critical(e) + sys.exit(1) diff --git a/courageous_comets/error.py b/courageous_comets/error.py new file mode 100644 index 0000000..c9c4282 --- /dev/null +++ b/courageous_comets/error.py @@ -0,0 +1,2 @@ +class CourageousCometsError(Exception): + """Base class for all Courageous Comets exceptions.""" diff --git a/courageous_comets/redis/__init__.py b/courageous_comets/redis/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/courageous_comets/settings.py b/courageous_comets/settings.py new file mode 100644 index 0000000..990dff3 --- /dev/null +++ b/courageous_comets/settings.py @@ -0,0 +1,10 @@ +import os + + +class Settings: + """Shared application configuration.""" + + DEBUG = bool(os.environ.get("DEBUG", default=True)) + REDIS_HOST = os.environ["REDIS_HOST"] + REDIS_PORT = int(os.environ["REDIS_PORT"]) + REDIS_PASSWORD = os.environ["REDIS_PASSWORD"] diff --git a/poetry.lock b/poetry.lock index ca9a55f..b51b3c3 100644 --- a/poetry.lock +++ b/poetry.lock @@ -253,6 +253,124 @@ python-dateutil = ">=2.8.1" [package.extras] dev = ["flake8", "markdown", "twine", "wheel"] +[[package]] +name = "hiredis" +version = "2.3.2" +description = "Python wrapper for hiredis" +optional = false +python-versions = ">=3.7" +files = [ + {file = "hiredis-2.3.2-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:742093f33d374098aa21c1696ac6e4874b52658c870513a297a89265a4d08fe5"}, + {file = "hiredis-2.3.2-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:9e14fb70ca4f7efa924f508975199353bf653f452e4ef0a1e47549e208f943d7"}, + {file = "hiredis-2.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6d7302b4b17fcc1cc727ce84ded7f6be4655701e8d58744f73b09cb9ed2b13df"}, + {file = "hiredis-2.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed63e8b75c193c5e5a8288d9d7b011da076cc314fafc3bfd59ec1d8a750d48c8"}, + {file = "hiredis-2.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6b4edee59dc089bc3948f4f6fba309f51aa2ccce63902364900aa0a553a85e97"}, + {file = "hiredis-2.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6481c3b7673a86276220140456c2a6fbfe8d1fb5c613b4728293c8634134824"}, + {file = "hiredis-2.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:684840b014ce83541a087fcf2d48227196576f56ae3e944d4dfe14c0a3e0ccb7"}, + {file = "hiredis-2.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c4c0bcf786f0eac9593367b6279e9b89534e008edbf116dcd0de956524702c8"}, + {file = "hiredis-2.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:66ab949424ac6504d823cba45c4c4854af5c59306a1531edb43b4dd22e17c102"}, + {file = "hiredis-2.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:322c668ee1c12d6c5750a4b1057e6b4feee2a75b3d25d630922a463cfe5e7478"}, + {file = "hiredis-2.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:bfa73e3f163c6e8b2ec26f22285d717a5f77ab2120c97a2605d8f48b26950dac"}, + {file = "hiredis-2.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:7f39f28ffc65de577c3bc0c7615f149e35bc927802a0f56e612db9b530f316f9"}, + {file = "hiredis-2.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:55ce31bf4711da879b96d511208efb65a6165da4ba91cb3a96d86d5a8d9d23e6"}, + {file = "hiredis-2.3.2-cp310-cp310-win32.whl", hash = "sha256:3dd63d0bbbe75797b743f35d37a4cca7ca7ba35423a0de742ae2985752f20c6d"}, + {file = "hiredis-2.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:ea002656a8d974daaf6089863ab0a306962c8b715db6b10879f98b781a2a5bf5"}, + {file = "hiredis-2.3.2-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:adfbf2e9c38b77d0db2fb32c3bdaea638fa76b4e75847283cd707521ad2475ef"}, + {file = "hiredis-2.3.2-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:80b02d27864ebaf9b153d4b99015342382eeaed651f5591ce6f07e840307c56d"}, + {file = "hiredis-2.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bd40d2e2f82a483de0d0a6dfd8c3895a02e55e5c9949610ecbded18188fd0a56"}, + {file = "hiredis-2.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfa904045d7cebfb0f01dad51352551cce1d873d7c3f80c7ded7d42f8cac8f89"}, + {file = "hiredis-2.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:28bd184b33e0dd6d65816c16521a4ba1ffbe9ff07d66873c42ea4049a62fed83"}, + {file = "hiredis-2.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f70481213373d44614148f0f2e38e7905be3f021902ae5167289413196de4ba4"}, + {file = "hiredis-2.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb8797b528c1ff81eef06713623562b36db3dafa106b59f83a6468df788ff0d1"}, + {file = "hiredis-2.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:02fc71c8333586871602db4774d3a3e403b4ccf6446dc4603ec12df563127cee"}, + {file = "hiredis-2.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0da56915bda1e0a49157191b54d3e27689b70960f0685fdd5c415dacdee2fbed"}, + {file = "hiredis-2.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e2674a5a3168349435b08fa0b82998ed2536eb9acccf7087efe26e4cd088a525"}, + {file = "hiredis-2.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:dc1c3fd49930494a67dcec37d0558d99d84eca8eb3f03b17198424538f2608d7"}, + {file = "hiredis-2.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:14c7b43205e515f538a9defb4e411e0f0576caaeeda76bb9993ed505486f7562"}, + {file = "hiredis-2.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7bac7e02915b970c3723a7a7c5df4ba7a11a3426d2a3f181e041aa506a1ff028"}, + {file = "hiredis-2.3.2-cp311-cp311-win32.whl", hash = "sha256:63a090761ddc3c1f7db5e67aa4e247b4b3bb9890080bdcdadd1b5200b8b89ac4"}, + {file = "hiredis-2.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:70d226ab0306a5b8d408235cabe51d4bf3554c9e8a72d53ce0b3c5c84cf78881"}, + {file = "hiredis-2.3.2-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:5c614552c6bd1d0d907f448f75550f6b24fb56cbfce80c094908b7990cad9702"}, + {file = "hiredis-2.3.2-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:9c431431abf55b64347ddc8df68b3ef840269cb0aa5bc2d26ad9506eb4b1b866"}, + {file = "hiredis-2.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a45857e87e9d2b005e81ddac9d815a33efd26ec67032c366629f023fe64fb415"}, + {file = "hiredis-2.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e138d141ec5a6ec800b6d01ddc3e5561ce1c940215e0eb9960876bfde7186aae"}, + {file = "hiredis-2.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:387f655444d912a963ab68abf64bf6e178a13c8e4aa945cb27388fd01a02e6f1"}, + {file = "hiredis-2.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4852f4bf88f0e2d9bdf91279892f5740ed22ae368335a37a52b92a5c88691140"}, + {file = "hiredis-2.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d711c107e83117129b7f8bd08e9820c43ceec6204fff072a001fd82f6d13db9f"}, + {file = "hiredis-2.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:92830c16885f29163e1c2da1f3c1edb226df1210ec7e8711aaabba3dd0d5470a"}, + {file = "hiredis-2.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:16b01d9ceae265d4ab9547be0cd628ecaff14b3360357a9d30c029e5ae8b7e7f"}, + {file = "hiredis-2.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:5986fb5f380169270a0293bebebd95466a1c85010b4f1afc2727e4d17c452512"}, + {file = "hiredis-2.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:49532d7939cc51f8e99efc326090c54acf5437ed88b9c904cc8015b3c4eda9c9"}, + {file = "hiredis-2.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:8f34801b251ca43ad70691fb08b606a2e55f06b9c9fb1fc18fd9402b19d70f7b"}, + {file = "hiredis-2.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:7298562a49d95570ab1c7fc4051e72824c6a80e907993a21a41ba204223e7334"}, + {file = "hiredis-2.3.2-cp312-cp312-win32.whl", hash = "sha256:e1d86b75de787481b04d112067a4033e1ecfda2a060e50318a74e4e1c9b2948c"}, + {file = "hiredis-2.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:6dbfe1887ffa5cf3030451a56a8f965a9da2fa82b7149357752b67a335a05fc6"}, + {file = "hiredis-2.3.2-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:4fc242e9da4af48714199216eb535b61e8f8d66552c8819e33fc7806bd465a09"}, + {file = "hiredis-2.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e81aa4e9a1fcf604c8c4b51aa5d258e195a6ba81efe1da82dea3204443eba01c"}, + {file = "hiredis-2.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:419780f8583ddb544ffa86f9d44a7fcc183cd826101af4e5ffe535b6765f5f6b"}, + {file = "hiredis-2.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6871306d8b98a15e53a5f289ec1106a3a1d43e7ab6f4d785f95fcef9a7bd9504"}, + {file = "hiredis-2.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88cb0b35b63717ef1e41d62f4f8717166f7c6245064957907cfe177cc144357c"}, + {file = "hiredis-2.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c490191fa1218851f8a80c5a21a05a6f680ac5aebc2e688b71cbfe592f8fec6"}, + {file = "hiredis-2.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:4baf4b579b108062e91bd2a991dc98b9dc3dc06e6288db2d98895eea8acbac22"}, + {file = "hiredis-2.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:e627d8ef5e100556e09fb44c9571a432b10e11596d3c4043500080ca9944a91a"}, + {file = "hiredis-2.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:ba3dc0af0def8c21ce7d903c59ea1e8ec4cb073f25ece9edaec7f92a286cd219"}, + {file = "hiredis-2.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:56e9b7d6051688ca94e68c0c8a54a243f8db841911b683cedf89a29d4de91509"}, + {file = "hiredis-2.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:380e029bb4b1d34cf560fcc8950bf6b57c2ef0c9c8b7c7ac20b7c524a730fadd"}, + {file = "hiredis-2.3.2-cp37-cp37m-win32.whl", hash = "sha256:948d9f2ca7841794dd9b204644963a4bcd69ced4e959b0d4ecf1b8ce994a6daa"}, + {file = "hiredis-2.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:cfa67afe2269b2d203cd1389c00c5bc35a287cd57860441fb0e53b371ea6a029"}, + {file = "hiredis-2.3.2-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:bcbe47da0aebc00a7cfe3ebdcff0373b86ce2b1856251c003e3d69c9db44b5a7"}, + {file = "hiredis-2.3.2-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:f2c9c0d910dd3f7df92f0638e7f65d8edd7f442203caf89c62fc79f11b0b73f8"}, + {file = "hiredis-2.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:01b6c24c0840ac7afafbc4db236fd55f56a9a0919a215c25a238f051781f4772"}, + {file = "hiredis-2.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1f567489f422d40c21e53212a73bef4638d9f21043848150f8544ef1f3a6ad1"}, + {file = "hiredis-2.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:28adecb308293e705e44087a1c2d557a816f032430d8a2a9bb7873902a1c6d48"}, + {file = "hiredis-2.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:27e9619847e9dc70b14b1ad2d0fb4889e7ca18996585c3463cff6c951fd6b10b"}, + {file = "hiredis-2.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a0026cfbf29f07649b0e34509091a2a6016ff8844b127de150efce1c3aff60b"}, + {file = "hiredis-2.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f9de7586522e5da6bee83c9cf0dcccac0857a43249cb4d721a2e312d98a684d1"}, + {file = "hiredis-2.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e58494f282215fc461b06709e9a195a24c12ba09570f25bdf9efb036acc05101"}, + {file = "hiredis-2.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:de3a32b4b76d46f1eb42b24a918d51d8ca52411a381748196241d59a895f7c5c"}, + {file = "hiredis-2.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:1979334ccab21a49c544cd1b8d784ffb2747f99a51cb0bd0976eebb517628382"}, + {file = "hiredis-2.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:0c0773266e1c38a06e7593bd08870ac1503f5f0ce0f5c63f2b4134b090b5d6a4"}, + {file = "hiredis-2.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bd1cee053416183adcc8e6134704c46c60c3f66b8faaf9e65bf76191ca59a2f7"}, + {file = "hiredis-2.3.2-cp38-cp38-win32.whl", hash = "sha256:5341ce3d01ef3c7418a72e370bf028c7aeb16895e79e115fe4c954fff990489e"}, + {file = "hiredis-2.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:8fc7197ff33047ce43a67851ccf190acb5b05c52fd4a001bb55766358f04da68"}, + {file = "hiredis-2.3.2-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:f47775e27388b58ce52f4f972f80e45b13c65113e9e6b6bf60148f893871dc9b"}, + {file = "hiredis-2.3.2-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:9412a06b8a8e09abd6313d96864b6d7713c6003a365995a5c70cfb9209df1570"}, + {file = "hiredis-2.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3020b60e3fc96d08c2a9b011f1c2e2a6bdcc09cb55df93c509b88be5cb791df"}, + {file = "hiredis-2.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:53d0f2c59bce399b8010a21bc779b4f8c32d0f582b2284ac8c98dc7578b27bc4"}, + {file = "hiredis-2.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:57c0d0c7e308ed5280a4900d4468bbfec51f0e1b4cde1deae7d4e639bc6b7766"}, + {file = "hiredis-2.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1d63318ca189fddc7e75f6a4af8eae9c0545863619fb38cfba5f43e81280b286"}, + {file = "hiredis-2.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e741ffe4e2db78a1b9dd6e5d29678ce37fbaaf65dfe132e5b82a794413302ef1"}, + {file = "hiredis-2.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb98038ccd368e0d88bd92ee575c58cfaf33e77f788c36b2a89a84ee1936dc6b"}, + {file = "hiredis-2.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:eae62ed60d53b3561148bcd8c2383e430af38c0deab9f2dd15f8874888ffd26f"}, + {file = "hiredis-2.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ca33c175c1cf60222d9c6d01c38fc17ec3a484f32294af781de30226b003e00f"}, + {file = "hiredis-2.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0c5f6972d2bdee3cd301d5c5438e31195cf1cabf6fd9274491674d4ceb46914d"}, + {file = "hiredis-2.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:a6b54dabfaa5dbaa92f796f0c32819b4636e66aa8e9106c3d421624bd2a2d676"}, + {file = "hiredis-2.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e96cd35df012a17c87ae276196ea8f215e77d6eeca90709eb03999e2d5e3fd8a"}, + {file = "hiredis-2.3.2-cp39-cp39-win32.whl", hash = "sha256:63b99b5ea9fe4f21469fb06a16ca5244307678636f11917359e3223aaeca0b67"}, + {file = "hiredis-2.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:a50c8af811b35b8a43b1590cf890b61ff2233225257a3cad32f43b3ec7ff1b9f"}, + {file = "hiredis-2.3.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7e8bf4444b09419b77ce671088db9f875b26720b5872d97778e2545cd87dba4a"}, + {file = "hiredis-2.3.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bd42d0d45ea47a2f96babd82a659fbc60612ab9423a68e4a8191e538b85542a"}, + {file = "hiredis-2.3.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80441b55edbef868e2563842f5030982b04349408396e5ac2b32025fb06b5212"}, + {file = "hiredis-2.3.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ec444ab8f27562a363672d6a7372bc0700a1bdc9764563c57c5f9efa0e592b5f"}, + {file = "hiredis-2.3.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f9f606e810858207d4b4287b4ef0dc622c2aa469548bf02b59dcc616f134f811"}, + {file = "hiredis-2.3.2-pp37-pypy37_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c3dde4ca00fe9eee3b76209711f1941bb86db42b8a75d7f2249ff9dfc026ab0e"}, + {file = "hiredis-2.3.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4dd676107a1d3c724a56a9d9db38166ad4cf44f924ee701414751bd18a784a0"}, + {file = "hiredis-2.3.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce42649e2676ad783186264d5ffc788a7612ecd7f9effb62d51c30d413a3eefe"}, + {file = "hiredis-2.3.2-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8e3f8b1733078ac663dad57e20060e16389a60ab542f18a97931f3a2a2dd64a4"}, + {file = "hiredis-2.3.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:532a84a82156a82529ec401d1c25d677c6543c791e54a263aa139541c363995f"}, + {file = "hiredis-2.3.2-pp38-pypy38_pp73-macosx_10_15_x86_64.whl", hash = "sha256:4d59f88c4daa36b8c38e59ac7bffed6f5d7f68eaccad471484bf587b28ccc478"}, + {file = "hiredis-2.3.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a91a14dd95e24dc078204b18b0199226ee44644974c645dc54ee7b00c3157330"}, + {file = "hiredis-2.3.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb777a38797c8c7df0444533119570be18d1a4ce5478dffc00c875684df7bfcb"}, + {file = "hiredis-2.3.2-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d47c915897a99d0d34a39fad4be97b4b709ab3d0d3b779ebccf2b6024a8c681e"}, + {file = "hiredis-2.3.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:333b5e04866758b11bda5f5315b4e671d15755fc6ed3b7969721bc6311d0ee36"}, + {file = "hiredis-2.3.2-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c8937f1100435698c18e4da086968c4b5d70e86ea718376f833475ab3277c9aa"}, + {file = "hiredis-2.3.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa45f7d771094b8145af10db74704ab0f698adb682fbf3721d8090f90e42cc49"}, + {file = "hiredis-2.3.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33d5ebc93c39aed4b5bc769f8ce0819bc50e74bb95d57a35f838f1c4378978e0"}, + {file = "hiredis-2.3.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a797d8c7df9944314d309b0d9e1b354e2fa4430a05bb7604da13b6ad291bf959"}, + {file = "hiredis-2.3.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:e15a408f71a6c8c87b364f1f15a6cd9c1baca12bbc47a326ac8ab99ec7ad3c64"}, + {file = "hiredis-2.3.2.tar.gz", hash = "sha256:733e2456b68f3f126ddaf2cd500a33b25146c3676b97ea843665717bda0c5d43"}, +] + [[package]] name = "identify" version = "2.6.0" @@ -798,7 +916,6 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, - {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -806,16 +923,8 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, - {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, - {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, - {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, - {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -832,7 +941,6 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, - {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -840,7 +948,6 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, - {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -874,6 +981,24 @@ files = [ [package.dependencies] prompt_toolkit = ">=2.0,<=3.0.36" +[[package]] +name = "redis" +version = "5.0.7" +description = "Python client for Redis database and key-value store" +optional = false +python-versions = ">=3.7" +files = [ + {file = "redis-5.0.7-py3-none-any.whl", hash = "sha256:0e479e24da960c690be5d9b96d21f7b918a98c0cf49af3b6fafaa0753f93a0db"}, + {file = "redis-5.0.7.tar.gz", hash = "sha256:8f611490b93c8109b50adc317b31bfd84fff31def3475b92e7e80bf39f48175b"}, +] + +[package.dependencies] +hiredis = {version = ">=1.0.0", optional = true, markers = "extra == \"hiredis\""} + +[package.extras] +hiredis = ["hiredis (>=1.0.0)"] +ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)"] + [[package]] name = "regex" version = "2024.5.15" @@ -1170,4 +1295,4 @@ test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", [metadata] lock-version = "2.0" python-versions = "~3.12" -content-hash = "cd369216d0ae84d6c53127d132719c08d031890722467bfe2b8d207902d0fc3c" +content-hash = "3ffb185f024e50189465dd3226753ed6ea91302f0977be7a9135e2e6f3c8a4b1" diff --git a/pyproject.toml b/pyproject.toml index 83d9709..fb6b105 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,6 +9,7 @@ exclude = ["**/test__*.py"] [tool.poetry.dependencies] python = "~3.12" +redis = {extras = ["hiredis"], version = "^5.0.7"} [tool.poetry.dev-dependencies] commitizen = "3.27.0" From f6ceb5ba12b12199b676ea0d5a27f8b02da90867 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Fri, 19 Jul 2024 13:46:10 +0000 Subject: [PATCH 024/168] fix: set redis host from environment variables --- courageous_comets/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/courageous_comets/__main__.py b/courageous_comets/__main__.py index 5b1993f..6582eef 100644 --- a/courageous_comets/__main__.py +++ b/courageous_comets/__main__.py @@ -13,7 +13,7 @@ async def init_redis() -> Redis: logger.debug("Connecting to Redis...") instance = Redis( - host="", + host=Settings.REDIS_HOST, port=Settings.REDIS_PORT, password=Settings.REDIS_PASSWORD, ) From 28f19acd4c925b7a8166a80ea12286a689e19a0c Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Fri, 19 Jul 2024 14:36:07 +0000 Subject: [PATCH 025/168] refactor: improve error handling and logging --- courageous_comets/__init__.py | 5 ++++- courageous_comets/__main__.py | 14 ++++++++++---- courageous_comets/settings.py | 15 +++++++++++---- 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/courageous_comets/__init__.py b/courageous_comets/__init__.py index eb4d034..7a92ab5 100644 --- a/courageous_comets/__init__.py +++ b/courageous_comets/__init__.py @@ -2,10 +2,13 @@ from courageous_comets.settings import Settings +log_level_name = Settings.LOG_LEVEL +log_level = logging.getLevelNamesMapping().get(log_level_name, logging.INFO) + logger = logging.getLogger("courageous_comets") if not logger.hasHandlers(): - logger.setLevel(logging.DEBUG if Settings.DEBUG else logging.INFO) + logger.setLevel(log_level) handler = logging.StreamHandler() handler.setFormatter(logging.Formatter("[%(asctime)s: %(levelname)s] %(message)s")) logger.addHandler(handler) diff --git a/courageous_comets/__main__.py b/courageous_comets/__main__.py index 6582eef..ab8f13e 100644 --- a/courageous_comets/__main__.py +++ b/courageous_comets/__main__.py @@ -1,7 +1,7 @@ import asyncio import sys -from redis.asyncio import ConnectionError, Redis +from redis.asyncio import AuthenticationError, Redis, RedisError from courageous_comets import logger from courageous_comets.error import CourageousCometsError @@ -20,7 +20,10 @@ async def init_redis() -> Redis: try: await instance.ping() - except ConnectionError as e: + except AuthenticationError as e: + message = "Redis authentication failed. Check the password." + raise CourageousCometsError(message) from e + except RedisError as e: message = f"Could not connect to Redis at {Settings.REDIS_HOST}:{Settings.REDIS_PORT}" raise CourageousCometsError(message) from e @@ -35,14 +38,17 @@ async def init_redis() -> Redis: async def main() -> None: """Start the appication.""" + logger.info("Starting the Courageous Comets application ☄️") + redis = await init_redis() try: - logger.info("Starting the Discord client...") + logger.info("Starting the Discord client 🚀") finally: + logger.info("Shutting down gracefully...") logger.debug("Closing Redis connection...") await redis.aclose() - logger.info("Application shutdown complete.") + logger.info("Application shutdown complete. Goodbye! 👋") try: diff --git a/courageous_comets/settings.py b/courageous_comets/settings.py index 990dff3..d708575 100644 --- a/courageous_comets/settings.py +++ b/courageous_comets/settings.py @@ -1,10 +1,17 @@ import os +from courageous_comets.error import CourageousCometsError + class Settings: """Shared application configuration.""" - DEBUG = bool(os.environ.get("DEBUG", default=True)) - REDIS_HOST = os.environ["REDIS_HOST"] - REDIS_PORT = int(os.environ["REDIS_PORT"]) - REDIS_PASSWORD = os.environ["REDIS_PASSWORD"] + LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO") + REDIS_HOST = os.getenv("REDIS_HOST", "localhost") + REDIS_PASSWORD = os.getenv("REDIS_PASSWORD") + + try: + REDIS_PORT = int(os.getenv("REDIS_PORT", "6379")) + except ValueError as e: + message = f"REDIS_PORT must be an integer, got {os.getenv('REDIS_PORT')}" + raise CourageousCometsError(message) from e From ee92d7078537a02782031e6959bc66529d92ee8b Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Fri, 19 Jul 2024 14:36:20 +0000 Subject: [PATCH 026/168] docs: describe configuration options --- docs/admin-guide/configuration.md | 36 ++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/docs/admin-guide/configuration.md b/docs/admin-guide/configuration.md index 14c896c..37a0b0c 100644 --- a/docs/admin-guide/configuration.md +++ b/docs/admin-guide/configuration.md @@ -2,9 +2,13 @@ The following environment variables are available to configure the application: -| Variable | Description | Required | Default | -| --------------- | ------------------------------------------------| -------- | ------- | -| `DISCORD_TOKEN` | The Discord bot token. | Yes | - | +| Variable | Description | Required | Default | +| --------------- | -----------------------| -------- | ----------- | +| `DISCORD_TOKEN` | The Discord bot token. | Yes | - | +| `LOG_LEVEL` | The minimum log level. | No | `INFO` | +| `REDIS_HOST` | The Redis host. | No | `localhost` | +| `REDIS_PORT` | The Redis port. | No | `6379` | +| `REDIS_PASSWORD`| The Redis password. | No | - | ## `DISCORD_TOKEN` @@ -15,3 +19,29 @@ Your token should have the following scopes: !!! DANGER "Security Warning" Do not share your token with anyone! + +## `LOG_LEVEL` + +The minimum log level to display. The following levels are available: + +- `DEBUG` +- `INFO` +- `WARNING` +- `ERROR` +- `CRITICAL` + +## `REDIS_HOST` + +The hostname of the Redis server. Defaults to `localhost`. + +## `REDIS_PORT` + +The port of the Redis server. Defaults to `6379`. + +## `REDIS_PASSWORD` + +The password of the Redis server. Set this variable if your Redis server requires authentication. No password +is set by default. + +!!! DANGER "Security Warning" + Do not share your Redis password with anyone! From 3963ce58690dfdd649379ce451d52737b8631990 Mon Sep 17 00:00:00 2001 From: isaa_ctaylor Date: Fri, 19 Jul 2024 17:00:25 +0100 Subject: [PATCH 027/168] feat: add bot boilerplate Co-authored-by: thijsfranck --- .devcontainer/devcontainer.json | 1 + application.yaml | 3 + courageous_comets/__init__.py | 14 - courageous_comets/__main__.py | 34 +- courageous_comets/client.py | 97 ++++ courageous_comets/cogs/__init__.py | 0 courageous_comets/cogs/boilerplate.py | 34 ++ courageous_comets/cogs/ping.py | 20 + courageous_comets/settings.py | 27 +- poetry.lock | 752 ++++++++++++++++++++++---- pyproject.toml | 5 +- 11 files changed, 839 insertions(+), 148 deletions(-) create mode 100644 application.yaml create mode 100644 courageous_comets/client.py create mode 100644 courageous_comets/cogs/__init__.py create mode 100644 courageous_comets/cogs/boilerplate.py create mode 100644 courageous_comets/cogs/ping.py diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 841079d..7072789 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,5 +1,6 @@ { "containerEnv": { + "BOT_CONFIG_PATH": "${containerWorkspaceFolder}/application.yaml", "POETRY_VIRTUALENVS_CREATE": "false", "REDIS_HOST": "localhost", "REDIS_PASSWORD": "redis", diff --git a/application.yaml b/application.yaml new file mode 100644 index 0000000..c727f13 --- /dev/null +++ b/application.yaml @@ -0,0 +1,3 @@ +cogs: + - courageous_comets.cogs.ping + - jishaku diff --git a/courageous_comets/__init__.py b/courageous_comets/__init__.py index 7a92ab5..e69de29 100644 --- a/courageous_comets/__init__.py +++ b/courageous_comets/__init__.py @@ -1,14 +0,0 @@ -import logging - -from courageous_comets.settings import Settings - -log_level_name = Settings.LOG_LEVEL -log_level = logging.getLevelNamesMapping().get(log_level_name, logging.INFO) - -logger = logging.getLogger("courageous_comets") - -if not logger.hasHandlers(): - logger.setLevel(log_level) - handler = logging.StreamHandler() - handler.setFormatter(logging.Formatter("[%(asctime)s: %(levelname)s] %(message)s")) - logger.addHandler(handler) diff --git a/courageous_comets/__main__.py b/courageous_comets/__main__.py index ab8f13e..600eb96 100644 --- a/courageous_comets/__main__.py +++ b/courageous_comets/__main__.py @@ -1,11 +1,15 @@ import asyncio +import logging import sys +import discord from redis.asyncio import AuthenticationError, Redis, RedisError -from courageous_comets import logger +from courageous_comets import settings +from courageous_comets.client import bot from courageous_comets.error import CourageousCometsError -from courageous_comets.settings import Settings + +logger = logging.getLogger(__name__) async def init_redis() -> Redis: @@ -13,9 +17,9 @@ async def init_redis() -> Redis: logger.debug("Connecting to Redis...") instance = Redis( - host=Settings.REDIS_HOST, - port=Settings.REDIS_PORT, - password=Settings.REDIS_PASSWORD, + host=settings.REDIS_HOST, + port=settings.REDIS_PORT, + password=settings.REDIS_PASSWORD, ) try: @@ -24,13 +28,13 @@ async def init_redis() -> Redis: message = "Redis authentication failed. Check the password." raise CourageousCometsError(message) from e except RedisError as e: - message = f"Could not connect to Redis at {Settings.REDIS_HOST}:{Settings.REDIS_PORT}" + message = f"Could not connect to Redis at {settings.REDIS_HOST}:{settings.REDIS_PORT}" raise CourageousCometsError(message) from e logger.info( "Connected to Redis at %s:%s", - Settings.REDIS_HOST, - Settings.REDIS_PORT, + settings.REDIS_HOST, + settings.REDIS_PORT, ) return instance @@ -38,12 +42,19 @@ async def init_redis() -> Redis: async def main() -> None: """Start the appication.""" + # Let discord.py set up the logging configuration + discord.utils.setup_logging(level=settings.LOG_LEVEL) + logger.info("Starting the Courageous Comets application ☄️") redis = await init_redis() try: logger.info("Starting the Discord client 🚀") + await bot.start(settings.DISCORD_TOKEN or "") + except discord.LoginFailure as e: + message = "Discord login failed. Check the token." + raise CourageousCometsError(message) from e finally: logger.info("Shutting down gracefully...") logger.debug("Closing Redis connection...") @@ -53,6 +64,9 @@ async def main() -> None: try: asyncio.run(main()) -except CourageousCometsError as e: - logger.critical(e) +except (CourageousCometsError, discord.DiscordException) as e: + logging.critical( + "A fatal error occurred while running the bot.", + exc_info=e, + ) sys.exit(1) diff --git a/courageous_comets/client.py b/courageous_comets/client.py new file mode 100644 index 0000000..8facaaf --- /dev/null +++ b/courageous_comets/client.py @@ -0,0 +1,97 @@ +import logging +import typing +from pathlib import Path + +import discord +import yaml +from discord import Intents +from discord.ext import commands + +from courageous_comets import settings + +logger = logging.getLogger(__name__) + +intents = Intents.default() +intents.members = True +intents.message_content = True + +with Path(settings.BOT_CONFIG_PATH).open() as config_file: + CONFIG = yaml.safe_load(config_file) + +bot = commands.Bot(command_prefix=commands.when_mentioned, intents=intents) + + +@bot.event +async def on_ready() -> None: + """Informs when the bot is ready.""" + logger.info("Logged in as %s", bot.user) + + +@bot.event +async def setup_hook() -> None: + """Load all cogs in the config file.""" + for cog in CONFIG["cogs"]: + try: + await bot.load_extension(cog) + logger.info("Loaded cog %s", cog) + except ( + commands.ExtensionNotFound, + commands.ExtensionAlreadyLoaded, + commands.NoEntryPointError, + commands.ExtensionFailed, + ) as e: + logger.exception("Failed to load cog %s", cog, exc_info=e) + + +@bot.command() +@commands.guild_only() +@commands.is_owner() +async def sync( + ctx: commands.Context, + guilds: commands.Greedy[discord.Object], + spec: typing.Literal["~", "*", "^"] | None = None, +) -> None: + """ + Sync to the given scope. + + If no scope is provided and no guilds are given, sync the current tree to the global scope. + `~` - Sync to the current guild. + `*` - Sync to the global scope. + `^` - Remove all non-global commands from the current guild. + No spec - Sync the current tree to the current guild, used mostly for development. + + Parameters + ---------- + guilds: commands.Greedy[discord.Object] + The guilds to sync to. + spec: typing.Literal["~", "*", "^"] | None + The scope to sync to. Defaults to `~`. + """ + async with ctx.typing(): + if not guilds: + if spec == "~": + synced = await ctx.bot.tree.sync(guild=ctx.guild) + elif spec == "*": + synced = await ctx.bot.tree.sync() + elif spec == "^": + ctx.bot.tree.clear_commands(guild=ctx.guild) + await ctx.bot.tree.sync(guild=ctx.guild) + synced = [] + else: + ctx.bot.tree.copy_global_to(guild=ctx.guild) + synced = await ctx.bot.tree.sync(guild=ctx.guild) + + scope = "globally" if spec == "*" else "to the current guild." + await ctx.send(f"Synced {len(synced)} commands {scope}") + + return + + ret = 0 + for guild in guilds: + try: + await ctx.bot.tree.sync(guild=guild) + except discord.HTTPException: + pass + else: + ret += 1 + await ctx.send(f"Synced the tree to {ret}/{len(guilds)}.") diff --git a/courageous_comets/cogs/__init__.py b/courageous_comets/cogs/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/courageous_comets/cogs/boilerplate.py b/courageous_comets/cogs/boilerplate.py new file mode 100644 index 0000000..aff40de --- /dev/null +++ b/courageous_comets/cogs/boilerplate.py @@ -0,0 +1,34 @@ +import discord +from discord import app_commands +from discord.ext import commands + + +# Boilerplate cog code. +# To build a cog: +# 1. Copy this boilerplate to another file in the cogs directory +# 2. Rename the file and class +# 3. Add the file name to config.yaml +class Cog(commands.Cog): + """A boilerplate cog.""" + + def __init__(self, bot: commands.Bot) -> None: + self.bot = bot + + # Slash command handler + # In order for these to be seen in the discord UI, + # they must be synced (sunk?!) using the sync command + @app_commands.command(name="COMMAND_NAME") + async def _command_name(self, interaction: discord.Interaction) -> None: + """Command description.""" + + # Listen for events from discord + # Event reference can be found here: + # https://discordpy.readthedocs.io/en/stable/api.html#discord-api-events + @commands.Cog.listener(name="EVENT_NAME") + async def _event_name(self) -> None: + """Event handler description.""" + + +async def setup(bot: commands.Bot) -> None: + """Load the cog.""" + await bot.add_cog(Cog(bot)) diff --git a/courageous_comets/cogs/ping.py b/courageous_comets/cogs/ping.py new file mode 100644 index 0000000..fc17625 --- /dev/null +++ b/courageous_comets/cogs/ping.py @@ -0,0 +1,20 @@ +import discord +from discord import app_commands +from discord.ext import commands + + +class Ping(commands.Cog): + """A cog containing a simple ping command.""" + + def __init__(self, bot: commands.Bot) -> None: + self.bot = bot + + @app_commands.command(name="ping") + async def ping(self, interaction: discord.Interaction) -> None: + """Ping the bot.""" + await interaction.response.send_message("Pong!") + + +async def setup(bot: commands.Bot) -> None: + """Load the cog.""" + await bot.add_cog(Ping(bot)) diff --git a/courageous_comets/settings.py b/courageous_comets/settings.py index d708575..d786b6f 100644 --- a/courageous_comets/settings.py +++ b/courageous_comets/settings.py @@ -1,17 +1,22 @@ +"""Shared application configuration.""" + +import logging import os -from courageous_comets.error import CourageousCometsError +from dotenv import load_dotenv +from courageous_comets.error import CourageousCometsError -class Settings: - """Shared application configuration.""" +load_dotenv() - LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO") - REDIS_HOST = os.getenv("REDIS_HOST", "localhost") - REDIS_PASSWORD = os.getenv("REDIS_PASSWORD") +BOT_CONFIG_PATH = os.getenv("BOT_CONFIG_PATH", "application.yaml") +DISCORD_TOKEN = os.getenv("DISCORD_TOKEN") +LOG_LEVEL = logging.getLevelNamesMapping().get(os.getenv("LOG_LEVEL", "INFO"), logging.INFO) +REDIS_HOST = os.getenv("REDIS_HOST", "localhost") +REDIS_PASSWORD = os.getenv("REDIS_PASSWORD") - try: - REDIS_PORT = int(os.getenv("REDIS_PORT", "6379")) - except ValueError as e: - message = f"REDIS_PORT must be an integer, got {os.getenv('REDIS_PORT')}" - raise CourageousCometsError(message) from e +try: + REDIS_PORT = int(os.getenv("REDIS_PORT", "6379")) +except ValueError as e: + message = f"REDIS_PORT must be an integer, got {os.getenv('REDIS_PORT')}" + raise CourageousCometsError(message) from e diff --git a/poetry.lock b/poetry.lock index b51b3c3..f0ebba9 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,5 +1,114 @@ # This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +[[package]] +name = "aiohttp" +version = "3.9.5" +description = "Async http client/server framework (asyncio)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "aiohttp-3.9.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fcde4c397f673fdec23e6b05ebf8d4751314fa7c24f93334bf1f1364c1c69ac7"}, + {file = "aiohttp-3.9.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5d6b3f1fabe465e819aed2c421a6743d8debbde79b6a8600739300630a01bf2c"}, + {file = "aiohttp-3.9.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6ae79c1bc12c34082d92bf9422764f799aee4746fd7a392db46b7fd357d4a17a"}, + {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d3ebb9e1316ec74277d19c5f482f98cc65a73ccd5430540d6d11682cd857430"}, + {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84dabd95154f43a2ea80deffec9cb44d2e301e38a0c9d331cc4aa0166fe28ae3"}, + {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c8a02fbeca6f63cb1f0475c799679057fc9268b77075ab7cf3f1c600e81dd46b"}, + {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c26959ca7b75ff768e2776d8055bf9582a6267e24556bb7f7bd29e677932be72"}, + {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:714d4e5231fed4ba2762ed489b4aec07b2b9953cf4ee31e9871caac895a839c0"}, + {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7a6a8354f1b62e15d48e04350f13e726fa08b62c3d7b8401c0a1314f02e3558"}, + {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c413016880e03e69d166efb5a1a95d40f83d5a3a648d16486592c49ffb76d0db"}, + {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ff84aeb864e0fac81f676be9f4685f0527b660f1efdc40dcede3c251ef1e867f"}, + {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ad7f2919d7dac062f24d6f5fe95d401597fbb015a25771f85e692d043c9d7832"}, + {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:702e2c7c187c1a498a4e2b03155d52658fdd6fda882d3d7fbb891a5cf108bb10"}, + {file = "aiohttp-3.9.5-cp310-cp310-win32.whl", hash = "sha256:67c3119f5ddc7261d47163ed86d760ddf0e625cd6246b4ed852e82159617b5fb"}, + {file = "aiohttp-3.9.5-cp310-cp310-win_amd64.whl", hash = "sha256:471f0ef53ccedec9995287f02caf0c068732f026455f07db3f01a46e49d76bbb"}, + {file = "aiohttp-3.9.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e0ae53e33ee7476dd3d1132f932eeb39bf6125083820049d06edcdca4381f342"}, + {file = "aiohttp-3.9.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c088c4d70d21f8ca5c0b8b5403fe84a7bc8e024161febdd4ef04575ef35d474d"}, + {file = "aiohttp-3.9.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:639d0042b7670222f33b0028de6b4e2fad6451462ce7df2af8aee37dcac55424"}, + {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f26383adb94da5e7fb388d441bf09c61e5e35f455a3217bfd790c6b6bc64b2ee"}, + {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66331d00fb28dc90aa606d9a54304af76b335ae204d1836f65797d6fe27f1ca2"}, + {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ff550491f5492ab5ed3533e76b8567f4b37bd2995e780a1f46bca2024223233"}, + {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f22eb3a6c1080d862befa0a89c380b4dafce29dc6cd56083f630073d102eb595"}, + {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a81b1143d42b66ffc40a441379387076243ef7b51019204fd3ec36b9f69e77d6"}, + {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f64fd07515dad67f24b6ea4a66ae2876c01031de91c93075b8093f07c0a2d93d"}, + {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:93e22add827447d2e26d67c9ac0161756007f152fdc5210277d00a85f6c92323"}, + {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:55b39c8684a46e56ef8c8d24faf02de4a2b2ac60d26cee93bc595651ff545de9"}, + {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4715a9b778f4293b9f8ae7a0a7cef9829f02ff8d6277a39d7f40565c737d3771"}, + {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:afc52b8d969eff14e069a710057d15ab9ac17cd4b6753042c407dcea0e40bf75"}, + {file = "aiohttp-3.9.5-cp311-cp311-win32.whl", hash = "sha256:b3df71da99c98534be076196791adca8819761f0bf6e08e07fd7da25127150d6"}, + {file = "aiohttp-3.9.5-cp311-cp311-win_amd64.whl", hash = "sha256:88e311d98cc0bf45b62fc46c66753a83445f5ab20038bcc1b8a1cc05666f428a"}, + {file = "aiohttp-3.9.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:c7a4b7a6cf5b6eb11e109a9755fd4fda7d57395f8c575e166d363b9fc3ec4678"}, + {file = "aiohttp-3.9.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:0a158704edf0abcac8ac371fbb54044f3270bdbc93e254a82b6c82be1ef08f3c"}, + {file = "aiohttp-3.9.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d153f652a687a8e95ad367a86a61e8d53d528b0530ef382ec5aaf533140ed00f"}, + {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82a6a97d9771cb48ae16979c3a3a9a18b600a8505b1115cfe354dfb2054468b4"}, + {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60cdbd56f4cad9f69c35eaac0fbbdf1f77b0ff9456cebd4902f3dd1cf096464c"}, + {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8676e8fd73141ded15ea586de0b7cda1542960a7b9ad89b2b06428e97125d4fa"}, + {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da00da442a0e31f1c69d26d224e1efd3a1ca5bcbf210978a2ca7426dfcae9f58"}, + {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18f634d540dd099c262e9f887c8bbacc959847cfe5da7a0e2e1cf3f14dbf2daf"}, + {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:320e8618eda64e19d11bdb3bd04ccc0a816c17eaecb7e4945d01deee2a22f95f"}, + {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:2faa61a904b83142747fc6a6d7ad8fccff898c849123030f8e75d5d967fd4a81"}, + {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:8c64a6dc3fe5db7b1b4d2b5cb84c4f677768bdc340611eca673afb7cf416ef5a"}, + {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:393c7aba2b55559ef7ab791c94b44f7482a07bf7640d17b341b79081f5e5cd1a"}, + {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c671dc117c2c21a1ca10c116cfcd6e3e44da7fcde37bf83b2be485ab377b25da"}, + {file = "aiohttp-3.9.5-cp312-cp312-win32.whl", hash = "sha256:5a7ee16aab26e76add4afc45e8f8206c95d1d75540f1039b84a03c3b3800dd59"}, + {file = "aiohttp-3.9.5-cp312-cp312-win_amd64.whl", hash = "sha256:5ca51eadbd67045396bc92a4345d1790b7301c14d1848feaac1d6a6c9289e888"}, + {file = "aiohttp-3.9.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:694d828b5c41255e54bc2dddb51a9f5150b4eefa9886e38b52605a05d96566e8"}, + {file = "aiohttp-3.9.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0605cc2c0088fcaae79f01c913a38611ad09ba68ff482402d3410bf59039bfb8"}, + {file = "aiohttp-3.9.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4558e5012ee03d2638c681e156461d37b7a113fe13970d438d95d10173d25f78"}, + {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dbc053ac75ccc63dc3a3cc547b98c7258ec35a215a92bd9f983e0aac95d3d5b"}, + {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4109adee842b90671f1b689901b948f347325045c15f46b39797ae1bf17019de"}, + {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6ea1a5b409a85477fd8e5ee6ad8f0e40bf2844c270955e09360418cfd09abac"}, + {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3c2890ca8c59ee683fd09adf32321a40fe1cf164e3387799efb2acebf090c11"}, + {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3916c8692dbd9d55c523374a3b8213e628424d19116ac4308e434dbf6d95bbdd"}, + {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8d1964eb7617907c792ca00b341b5ec3e01ae8c280825deadbbd678447b127e1"}, + {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d5ab8e1f6bee051a4bf6195e38a5c13e5e161cb7bad83d8854524798bd9fcd6e"}, + {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:52c27110f3862a1afbcb2af4281fc9fdc40327fa286c4625dfee247c3ba90156"}, + {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:7f64cbd44443e80094309875d4f9c71d0401e966d191c3d469cde4642bc2e031"}, + {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8b4f72fbb66279624bfe83fd5eb6aea0022dad8eec62b71e7bf63ee1caadeafe"}, + {file = "aiohttp-3.9.5-cp38-cp38-win32.whl", hash = "sha256:6380c039ec52866c06d69b5c7aad5478b24ed11696f0e72f6b807cfb261453da"}, + {file = "aiohttp-3.9.5-cp38-cp38-win_amd64.whl", hash = "sha256:da22dab31d7180f8c3ac7c7635f3bcd53808f374f6aa333fe0b0b9e14b01f91a"}, + {file = "aiohttp-3.9.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1732102949ff6087589408d76cd6dea656b93c896b011ecafff418c9661dc4ed"}, + {file = "aiohttp-3.9.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c6021d296318cb6f9414b48e6a439a7f5d1f665464da507e8ff640848ee2a58a"}, + {file = "aiohttp-3.9.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:239f975589a944eeb1bad26b8b140a59a3a320067fb3cd10b75c3092405a1372"}, + {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b7b30258348082826d274504fbc7c849959f1989d86c29bc355107accec6cfb"}, + {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd2adf5c87ff6d8b277814a28a535b59e20bfea40a101db6b3bdca7e9926bc24"}, + {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9a3d838441bebcf5cf442700e3963f58b5c33f015341f9ea86dcd7d503c07e2"}, + {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e3a1ae66e3d0c17cf65c08968a5ee3180c5a95920ec2731f53343fac9bad106"}, + {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9c69e77370cce2d6df5d12b4e12bdcca60c47ba13d1cbbc8645dd005a20b738b"}, + {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0cbf56238f4bbf49dab8c2dc2e6b1b68502b1e88d335bea59b3f5b9f4c001475"}, + {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d1469f228cd9ffddd396d9948b8c9cd8022b6d1bf1e40c6f25b0fb90b4f893ed"}, + {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:45731330e754f5811c314901cebdf19dd776a44b31927fa4b4dbecab9e457b0c"}, + {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:3fcb4046d2904378e3aeea1df51f697b0467f2aac55d232c87ba162709478c46"}, + {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8cf142aa6c1a751fcb364158fd710b8a9be874b81889c2bd13aa8893197455e2"}, + {file = "aiohttp-3.9.5-cp39-cp39-win32.whl", hash = "sha256:7b179eea70833c8dee51ec42f3b4097bd6370892fa93f510f76762105568cf09"}, + {file = "aiohttp-3.9.5-cp39-cp39-win_amd64.whl", hash = "sha256:38d80498e2e169bc61418ff36170e0aad0cd268da8b38a17c4cf29d254a8b3f1"}, + {file = "aiohttp-3.9.5.tar.gz", hash = "sha256:edea7d15772ceeb29db4aff55e482d4bcfb6ae160ce144f2682de02f6d693551"}, +] + +[package.dependencies] +aiosignal = ">=1.1.2" +attrs = ">=17.3.0" +frozenlist = ">=1.1.1" +multidict = ">=4.5,<7.0" +yarl = ">=1.0,<2.0" + +[package.extras] +speedups = ["Brotli", "aiodns", "brotlicffi"] + +[[package]] +name = "aiosignal" +version = "1.3.1" +description = "aiosignal: a list of registered asynchronous callbacks" +optional = false +python-versions = ">=3.7" +files = [ + {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, + {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, +] + +[package.dependencies] +frozenlist = ">=1.1.0" + [[package]] name = "argcomplete" version = "3.3.0" @@ -14,6 +123,40 @@ files = [ [package.extras] test = ["coverage", "mypy", "pexpect", "ruff", "wheel"] +[[package]] +name = "astunparse" +version = "1.6.3" +description = "An AST unparser for Python" +optional = false +python-versions = "*" +files = [ + {file = "astunparse-1.6.3-py2.py3-none-any.whl", hash = "sha256:c2652417f2c8b5bb325c885ae329bdf3f86424075c4fd1a128674bc6fba4b8e8"}, + {file = "astunparse-1.6.3.tar.gz", hash = "sha256:5ad93a8456f0d084c3456d059fd9a92cce667963232cbf763eac3bc5b7940872"}, +] + +[package.dependencies] +six = ">=1.6.1,<2.0" +wheel = ">=0.23.0,<1.0" + +[[package]] +name = "attrs" +version = "23.2.0" +description = "Classes Without Boilerplate" +optional = false +python-versions = ">=3.7" +files = [ + {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"}, + {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, +] + +[package.extras] +cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] +dev = ["attrs[tests]", "pre-commit"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] +tests = ["attrs[tests-no-zope]", "zope-interface"] +tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] +tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] + [[package]] name = "babel" version = "2.15.0" @@ -28,6 +171,17 @@ files = [ [package.extras] dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] +[[package]] +name = "braceexpand" +version = "0.1.7" +description = "Bash-style brace expansion for Python" +optional = false +python-versions = "*" +files = [ + {file = "braceexpand-0.1.7-py2.py3-none-any.whl", hash = "sha256:91332d53de7828103dcae5773fb43bc34950b0c8160e35e0f44c4427a3b85014"}, + {file = "braceexpand-0.1.7.tar.gz", hash = "sha256:e6e539bd20eaea53547472ff94f4fb5c3d3bf9d0a89388c4b56663aba765f705"}, +] + [[package]] name = "certifi" version = "2024.7.4" @@ -209,6 +363,26 @@ files = [ {file = "decli-0.6.2.tar.gz", hash = "sha256:36f71eb55fd0093895efb4f416ec32b7f6e00147dda448e3365cf73ceab42d6f"}, ] +[[package]] +name = "discord-py" +version = "2.4.0" +description = "A Python wrapper for the Discord API" +optional = false +python-versions = ">=3.8" +files = [ + {file = "discord.py-2.4.0-py3-none-any.whl", hash = "sha256:b8af6711c70f7e62160bfbecb55be699b5cb69d007426759ab8ab06b1bd77d1d"}, + {file = "discord_py-2.4.0.tar.gz", hash = "sha256:d07cb2a223a185873a1d0ee78b9faa9597e45b3f6186df21a95cec1e9bcdc9a5"}, +] + +[package.dependencies] +aiohttp = ">=3.7.4,<4" + +[package.extras] +docs = ["sphinx (==4.4.0)", "sphinx-inline-tabs (==2023.4.21)", "sphinxcontrib-applehelp (==1.0.4)", "sphinxcontrib-devhelp (==1.0.2)", "sphinxcontrib-htmlhelp (==2.0.1)", "sphinxcontrib-jsmath (==1.0.1)", "sphinxcontrib-qthelp (==1.0.3)", "sphinxcontrib-serializinghtml (==1.1.5)", "sphinxcontrib-trio (==1.1.2)", "sphinxcontrib-websupport (==1.2.4)", "typing-extensions (>=4.3,<5)"] +speed = ["Brotli", "aiodns (>=1.1)", "cchardet (==2.1.7)", "orjson (>=3.5.4)"] +test = ["coverage[toml]", "pytest", "pytest-asyncio", "pytest-cov", "pytest-mock", "typing-extensions (>=4.3,<5)", "tzdata"] +voice = ["PyNaCl (>=1.3.0,<1.6)"] + [[package]] name = "distlib" version = "0.3.8" @@ -236,6 +410,92 @@ docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1 testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-asyncio (>=0.21)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)", "virtualenv (>=20.26.2)"] typing = ["typing-extensions (>=4.8)"] +[[package]] +name = "frozenlist" +version = "1.4.1" +description = "A list-like structure which implements collections.abc.MutableSequence" +optional = false +python-versions = ">=3.8" +files = [ + {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f9aa1878d1083b276b0196f2dfbe00c9b7e752475ed3b682025ff20c1c1f51ac"}, + {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29acab3f66f0f24674b7dc4736477bcd4bc3ad4b896f5f45379a67bce8b96868"}, + {file = "frozenlist-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74fb4bee6880b529a0c6560885fce4dc95936920f9f20f53d99a213f7bf66776"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:590344787a90ae57d62511dd7c736ed56b428f04cd8c161fcc5e7232c130c69a"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:068b63f23b17df8569b7fdca5517edef76171cf3897eb68beb01341131fbd2ad"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c849d495bf5154cd8da18a9eb15db127d4dba2968d88831aff6f0331ea9bd4c"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9750cc7fe1ae3b1611bb8cfc3f9ec11d532244235d75901fb6b8e42ce9229dfe"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9b2de4cf0cdd5bd2dee4c4f63a653c61d2408055ab77b151c1957f221cabf2a"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0633c8d5337cb5c77acbccc6357ac49a1770b8c487e5b3505c57b949b4b82e98"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:27657df69e8801be6c3638054e202a135c7f299267f1a55ed3a598934f6c0d75"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:f9a3ea26252bd92f570600098783d1371354d89d5f6b7dfd87359d669f2109b5"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:4f57dab5fe3407b6c0c1cc907ac98e8a189f9e418f3b6e54d65a718aaafe3950"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e02a0e11cf6597299b9f3bbd3f93d79217cb90cfd1411aec33848b13f5c656cc"}, + {file = "frozenlist-1.4.1-cp310-cp310-win32.whl", hash = "sha256:a828c57f00f729620a442881cc60e57cfcec6842ba38e1b19fd3e47ac0ff8dc1"}, + {file = "frozenlist-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:f56e2333dda1fe0f909e7cc59f021eba0d2307bc6f012a1ccf2beca6ba362439"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a0cb6f11204443f27a1628b0e460f37fb30f624be6051d490fa7d7e26d4af3d0"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b46c8ae3a8f1f41a0d2ef350c0b6e65822d80772fe46b653ab6b6274f61d4a49"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fde5bd59ab5357e3853313127f4d3565fc7dad314a74d7b5d43c22c6a5ed2ced"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:722e1124aec435320ae01ee3ac7bec11a5d47f25d0ed6328f2273d287bc3abb0"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2471c201b70d58a0f0c1f91261542a03d9a5e088ed3dc6c160d614c01649c106"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c757a9dd70d72b076d6f68efdbb9bc943665ae954dad2801b874c8c69e185068"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f146e0911cb2f1da549fc58fc7bcd2b836a44b79ef871980d605ec392ff6b0d2"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9c515e7914626b2a2e1e311794b4c35720a0be87af52b79ff8e1429fc25f19"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c302220494f5c1ebeb0912ea782bcd5e2f8308037b3c7553fad0e48ebad6ad82"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:442acde1e068288a4ba7acfe05f5f343e19fac87bfc96d89eb886b0363e977ec"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:1b280e6507ea8a4fa0c0a7150b4e526a8d113989e28eaaef946cc77ffd7efc0a"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:fe1a06da377e3a1062ae5fe0926e12b84eceb8a50b350ddca72dc85015873f74"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:db9e724bebd621d9beca794f2a4ff1d26eed5965b004a97f1f1685a173b869c2"}, + {file = "frozenlist-1.4.1-cp311-cp311-win32.whl", hash = "sha256:e774d53b1a477a67838a904131c4b0eef6b3d8a651f8b138b04f748fccfefe17"}, + {file = "frozenlist-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:fb3c2db03683b5767dedb5769b8a40ebb47d6f7f45b1b3e3b4b51ec8ad9d9825"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1979bc0aeb89b33b588c51c54ab0161791149f2461ea7c7c946d95d5f93b56ae"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cc7b01b3754ea68a62bd77ce6020afaffb44a590c2289089289363472d13aedb"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9c92be9fd329ac801cc420e08452b70e7aeab94ea4233a4804f0915c14eba9b"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c3894db91f5a489fc8fa6a9991820f368f0b3cbdb9cd8849547ccfab3392d86"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba60bb19387e13597fb059f32cd4d59445d7b18b69a745b8f8e5db0346f33480"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8aefbba5f69d42246543407ed2461db31006b0f76c4e32dfd6f42215a2c41d09"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780d3a35680ced9ce682fbcf4cb9c2bad3136eeff760ab33707b71db84664e3a"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9acbb16f06fe7f52f441bb6f413ebae6c37baa6ef9edd49cdd567216da8600cd"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:23b701e65c7b36e4bf15546a89279bd4d8675faabc287d06bbcfac7d3c33e1e6"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3e0153a805a98f5ada7e09826255ba99fb4f7524bb81bf6b47fb702666484ae1"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:dd9b1baec094d91bf36ec729445f7769d0d0cf6b64d04d86e45baf89e2b9059b"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:1a4471094e146b6790f61b98616ab8e44f72661879cc63fa1049d13ef711e71e"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5667ed53d68d91920defdf4035d1cdaa3c3121dc0b113255124bcfada1cfa1b8"}, + {file = "frozenlist-1.4.1-cp312-cp312-win32.whl", hash = "sha256:beee944ae828747fd7cb216a70f120767fc9f4f00bacae8543c14a6831673f89"}, + {file = "frozenlist-1.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:64536573d0a2cb6e625cf309984e2d873979709f2cf22839bf2d61790b448ad5"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:20b51fa3f588ff2fe658663db52a41a4f7aa6c04f6201449c6c7c476bd255c0d"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:410478a0c562d1a5bcc2f7ea448359fcb050ed48b3c6f6f4f18c313a9bdb1826"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c6321c9efe29975232da3bd0af0ad216800a47e93d763ce64f291917a381b8eb"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48f6a4533887e189dae092f1cf981f2e3885175f7a0f33c91fb5b7b682b6bab6"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6eb73fa5426ea69ee0e012fb59cdc76a15b1283d6e32e4f8dc4482ec67d1194d"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbeb989b5cc29e8daf7f976b421c220f1b8c731cbf22b9130d8815418ea45887"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32453c1de775c889eb4e22f1197fe3bdfe457d16476ea407472b9442e6295f7a"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693945278a31f2086d9bf3df0fe8254bbeaef1fe71e1351c3bd730aa7d31c41b"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1d0ce09d36d53bbbe566fe296965b23b961764c0bcf3ce2fa45f463745c04701"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3a670dc61eb0d0eb7080890c13de3066790f9049b47b0de04007090807c776b0"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:dca69045298ce5c11fd539682cff879cc1e664c245d1c64da929813e54241d11"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a06339f38e9ed3a64e4c4e43aec7f59084033647f908e4259d279a52d3757d09"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b7f2f9f912dca3934c1baec2e4585a674ef16fe00218d833856408c48d5beee7"}, + {file = "frozenlist-1.4.1-cp38-cp38-win32.whl", hash = "sha256:e7004be74cbb7d9f34553a5ce5fb08be14fb33bc86f332fb71cbe5216362a497"}, + {file = "frozenlist-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:5a7d70357e7cee13f470c7883a063aae5fe209a493c57d86eb7f5a6f910fae09"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bfa4a17e17ce9abf47a74ae02f32d014c5e9404b6d9ac7f729e01562bbee601e"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b7e3ed87d4138356775346e6845cccbe66cd9e207f3cd11d2f0b9fd13681359d"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c99169d4ff810155ca50b4da3b075cbde79752443117d89429595c2e8e37fed8"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edb678da49d9f72c9f6c609fbe41a5dfb9a9282f9e6a2253d5a91e0fc382d7c0"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6db4667b187a6742b33afbbaf05a7bc551ffcf1ced0000a571aedbb4aa42fc7b"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55fdc093b5a3cb41d420884cdaf37a1e74c3c37a31f46e66286d9145d2063bd0"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82e8211d69a4f4bc360ea22cd6555f8e61a1bd211d1d5d39d3d228b48c83a897"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89aa2c2eeb20957be2d950b85974b30a01a762f3308cd02bb15e1ad632e22dc7"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d3e0c25a2350080e9319724dede4f31f43a6c9779be48021a7f4ebde8b2d742"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7268252af60904bf52c26173cbadc3a071cece75f873705419c8681f24d3edea"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0c250a29735d4f15321007fb02865f0e6b6a41a6b88f1f523ca1596ab5f50bd5"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:96ec70beabbd3b10e8bfe52616a13561e58fe84c0101dd031dc78f250d5128b9"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:23b2d7679b73fe0e5a4560b672a39f98dfc6f60df63823b0a9970525325b95f6"}, + {file = "frozenlist-1.4.1-cp39-cp39-win32.whl", hash = "sha256:a7496bfe1da7fb1a4e1cc23bb67c58fab69311cc7d32b5a99c2007b4b2a0e932"}, + {file = "frozenlist-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e6a20a581f9ce92d389a8c7d7c3dd47c81fd5d6e655c8dddf341e14aa48659d0"}, + {file = "frozenlist-1.4.1-py3-none-any.whl", hash = "sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7"}, + {file = "frozenlist-1.4.1.tar.gz", hash = "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b"}, +] + [[package]] name = "ghp-import" version = "2.1.0" @@ -255,120 +515,105 @@ dev = ["flake8", "markdown", "twine", "wheel"] [[package]] name = "hiredis" -version = "2.3.2" +version = "3.0.0" description = "Python wrapper for hiredis" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "hiredis-2.3.2-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:742093f33d374098aa21c1696ac6e4874b52658c870513a297a89265a4d08fe5"}, - {file = "hiredis-2.3.2-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:9e14fb70ca4f7efa924f508975199353bf653f452e4ef0a1e47549e208f943d7"}, - {file = "hiredis-2.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6d7302b4b17fcc1cc727ce84ded7f6be4655701e8d58744f73b09cb9ed2b13df"}, - {file = "hiredis-2.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed63e8b75c193c5e5a8288d9d7b011da076cc314fafc3bfd59ec1d8a750d48c8"}, - {file = "hiredis-2.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6b4edee59dc089bc3948f4f6fba309f51aa2ccce63902364900aa0a553a85e97"}, - {file = "hiredis-2.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6481c3b7673a86276220140456c2a6fbfe8d1fb5c613b4728293c8634134824"}, - {file = "hiredis-2.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:684840b014ce83541a087fcf2d48227196576f56ae3e944d4dfe14c0a3e0ccb7"}, - {file = "hiredis-2.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c4c0bcf786f0eac9593367b6279e9b89534e008edbf116dcd0de956524702c8"}, - {file = "hiredis-2.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:66ab949424ac6504d823cba45c4c4854af5c59306a1531edb43b4dd22e17c102"}, - {file = "hiredis-2.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:322c668ee1c12d6c5750a4b1057e6b4feee2a75b3d25d630922a463cfe5e7478"}, - {file = "hiredis-2.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:bfa73e3f163c6e8b2ec26f22285d717a5f77ab2120c97a2605d8f48b26950dac"}, - {file = "hiredis-2.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:7f39f28ffc65de577c3bc0c7615f149e35bc927802a0f56e612db9b530f316f9"}, - {file = "hiredis-2.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:55ce31bf4711da879b96d511208efb65a6165da4ba91cb3a96d86d5a8d9d23e6"}, - {file = "hiredis-2.3.2-cp310-cp310-win32.whl", hash = "sha256:3dd63d0bbbe75797b743f35d37a4cca7ca7ba35423a0de742ae2985752f20c6d"}, - {file = "hiredis-2.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:ea002656a8d974daaf6089863ab0a306962c8b715db6b10879f98b781a2a5bf5"}, - {file = "hiredis-2.3.2-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:adfbf2e9c38b77d0db2fb32c3bdaea638fa76b4e75847283cd707521ad2475ef"}, - {file = "hiredis-2.3.2-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:80b02d27864ebaf9b153d4b99015342382eeaed651f5591ce6f07e840307c56d"}, - {file = "hiredis-2.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bd40d2e2f82a483de0d0a6dfd8c3895a02e55e5c9949610ecbded18188fd0a56"}, - {file = "hiredis-2.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfa904045d7cebfb0f01dad51352551cce1d873d7c3f80c7ded7d42f8cac8f89"}, - {file = "hiredis-2.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:28bd184b33e0dd6d65816c16521a4ba1ffbe9ff07d66873c42ea4049a62fed83"}, - {file = "hiredis-2.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f70481213373d44614148f0f2e38e7905be3f021902ae5167289413196de4ba4"}, - {file = "hiredis-2.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb8797b528c1ff81eef06713623562b36db3dafa106b59f83a6468df788ff0d1"}, - {file = "hiredis-2.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:02fc71c8333586871602db4774d3a3e403b4ccf6446dc4603ec12df563127cee"}, - {file = "hiredis-2.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0da56915bda1e0a49157191b54d3e27689b70960f0685fdd5c415dacdee2fbed"}, - {file = "hiredis-2.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e2674a5a3168349435b08fa0b82998ed2536eb9acccf7087efe26e4cd088a525"}, - {file = "hiredis-2.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:dc1c3fd49930494a67dcec37d0558d99d84eca8eb3f03b17198424538f2608d7"}, - {file = "hiredis-2.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:14c7b43205e515f538a9defb4e411e0f0576caaeeda76bb9993ed505486f7562"}, - {file = "hiredis-2.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7bac7e02915b970c3723a7a7c5df4ba7a11a3426d2a3f181e041aa506a1ff028"}, - {file = "hiredis-2.3.2-cp311-cp311-win32.whl", hash = "sha256:63a090761ddc3c1f7db5e67aa4e247b4b3bb9890080bdcdadd1b5200b8b89ac4"}, - {file = "hiredis-2.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:70d226ab0306a5b8d408235cabe51d4bf3554c9e8a72d53ce0b3c5c84cf78881"}, - {file = "hiredis-2.3.2-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:5c614552c6bd1d0d907f448f75550f6b24fb56cbfce80c094908b7990cad9702"}, - {file = "hiredis-2.3.2-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:9c431431abf55b64347ddc8df68b3ef840269cb0aa5bc2d26ad9506eb4b1b866"}, - {file = "hiredis-2.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a45857e87e9d2b005e81ddac9d815a33efd26ec67032c366629f023fe64fb415"}, - {file = "hiredis-2.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e138d141ec5a6ec800b6d01ddc3e5561ce1c940215e0eb9960876bfde7186aae"}, - {file = "hiredis-2.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:387f655444d912a963ab68abf64bf6e178a13c8e4aa945cb27388fd01a02e6f1"}, - {file = "hiredis-2.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4852f4bf88f0e2d9bdf91279892f5740ed22ae368335a37a52b92a5c88691140"}, - {file = "hiredis-2.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d711c107e83117129b7f8bd08e9820c43ceec6204fff072a001fd82f6d13db9f"}, - {file = "hiredis-2.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:92830c16885f29163e1c2da1f3c1edb226df1210ec7e8711aaabba3dd0d5470a"}, - {file = "hiredis-2.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:16b01d9ceae265d4ab9547be0cd628ecaff14b3360357a9d30c029e5ae8b7e7f"}, - {file = "hiredis-2.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:5986fb5f380169270a0293bebebd95466a1c85010b4f1afc2727e4d17c452512"}, - {file = "hiredis-2.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:49532d7939cc51f8e99efc326090c54acf5437ed88b9c904cc8015b3c4eda9c9"}, - {file = "hiredis-2.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:8f34801b251ca43ad70691fb08b606a2e55f06b9c9fb1fc18fd9402b19d70f7b"}, - {file = "hiredis-2.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:7298562a49d95570ab1c7fc4051e72824c6a80e907993a21a41ba204223e7334"}, - {file = "hiredis-2.3.2-cp312-cp312-win32.whl", hash = "sha256:e1d86b75de787481b04d112067a4033e1ecfda2a060e50318a74e4e1c9b2948c"}, - {file = "hiredis-2.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:6dbfe1887ffa5cf3030451a56a8f965a9da2fa82b7149357752b67a335a05fc6"}, - {file = "hiredis-2.3.2-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:4fc242e9da4af48714199216eb535b61e8f8d66552c8819e33fc7806bd465a09"}, - {file = "hiredis-2.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e81aa4e9a1fcf604c8c4b51aa5d258e195a6ba81efe1da82dea3204443eba01c"}, - {file = "hiredis-2.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:419780f8583ddb544ffa86f9d44a7fcc183cd826101af4e5ffe535b6765f5f6b"}, - {file = "hiredis-2.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6871306d8b98a15e53a5f289ec1106a3a1d43e7ab6f4d785f95fcef9a7bd9504"}, - {file = "hiredis-2.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88cb0b35b63717ef1e41d62f4f8717166f7c6245064957907cfe177cc144357c"}, - {file = "hiredis-2.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c490191fa1218851f8a80c5a21a05a6f680ac5aebc2e688b71cbfe592f8fec6"}, - {file = "hiredis-2.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:4baf4b579b108062e91bd2a991dc98b9dc3dc06e6288db2d98895eea8acbac22"}, - {file = "hiredis-2.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:e627d8ef5e100556e09fb44c9571a432b10e11596d3c4043500080ca9944a91a"}, - {file = "hiredis-2.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:ba3dc0af0def8c21ce7d903c59ea1e8ec4cb073f25ece9edaec7f92a286cd219"}, - {file = "hiredis-2.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:56e9b7d6051688ca94e68c0c8a54a243f8db841911b683cedf89a29d4de91509"}, - {file = "hiredis-2.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:380e029bb4b1d34cf560fcc8950bf6b57c2ef0c9c8b7c7ac20b7c524a730fadd"}, - {file = "hiredis-2.3.2-cp37-cp37m-win32.whl", hash = "sha256:948d9f2ca7841794dd9b204644963a4bcd69ced4e959b0d4ecf1b8ce994a6daa"}, - {file = "hiredis-2.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:cfa67afe2269b2d203cd1389c00c5bc35a287cd57860441fb0e53b371ea6a029"}, - {file = "hiredis-2.3.2-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:bcbe47da0aebc00a7cfe3ebdcff0373b86ce2b1856251c003e3d69c9db44b5a7"}, - {file = "hiredis-2.3.2-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:f2c9c0d910dd3f7df92f0638e7f65d8edd7f442203caf89c62fc79f11b0b73f8"}, - {file = "hiredis-2.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:01b6c24c0840ac7afafbc4db236fd55f56a9a0919a215c25a238f051781f4772"}, - {file = "hiredis-2.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1f567489f422d40c21e53212a73bef4638d9f21043848150f8544ef1f3a6ad1"}, - {file = "hiredis-2.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:28adecb308293e705e44087a1c2d557a816f032430d8a2a9bb7873902a1c6d48"}, - {file = "hiredis-2.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:27e9619847e9dc70b14b1ad2d0fb4889e7ca18996585c3463cff6c951fd6b10b"}, - {file = "hiredis-2.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a0026cfbf29f07649b0e34509091a2a6016ff8844b127de150efce1c3aff60b"}, - {file = "hiredis-2.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f9de7586522e5da6bee83c9cf0dcccac0857a43249cb4d721a2e312d98a684d1"}, - {file = "hiredis-2.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e58494f282215fc461b06709e9a195a24c12ba09570f25bdf9efb036acc05101"}, - {file = "hiredis-2.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:de3a32b4b76d46f1eb42b24a918d51d8ca52411a381748196241d59a895f7c5c"}, - {file = "hiredis-2.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:1979334ccab21a49c544cd1b8d784ffb2747f99a51cb0bd0976eebb517628382"}, - {file = "hiredis-2.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:0c0773266e1c38a06e7593bd08870ac1503f5f0ce0f5c63f2b4134b090b5d6a4"}, - {file = "hiredis-2.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bd1cee053416183adcc8e6134704c46c60c3f66b8faaf9e65bf76191ca59a2f7"}, - {file = "hiredis-2.3.2-cp38-cp38-win32.whl", hash = "sha256:5341ce3d01ef3c7418a72e370bf028c7aeb16895e79e115fe4c954fff990489e"}, - {file = "hiredis-2.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:8fc7197ff33047ce43a67851ccf190acb5b05c52fd4a001bb55766358f04da68"}, - {file = "hiredis-2.3.2-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:f47775e27388b58ce52f4f972f80e45b13c65113e9e6b6bf60148f893871dc9b"}, - {file = "hiredis-2.3.2-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:9412a06b8a8e09abd6313d96864b6d7713c6003a365995a5c70cfb9209df1570"}, - {file = "hiredis-2.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3020b60e3fc96d08c2a9b011f1c2e2a6bdcc09cb55df93c509b88be5cb791df"}, - {file = "hiredis-2.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:53d0f2c59bce399b8010a21bc779b4f8c32d0f582b2284ac8c98dc7578b27bc4"}, - {file = "hiredis-2.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:57c0d0c7e308ed5280a4900d4468bbfec51f0e1b4cde1deae7d4e639bc6b7766"}, - {file = "hiredis-2.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1d63318ca189fddc7e75f6a4af8eae9c0545863619fb38cfba5f43e81280b286"}, - {file = "hiredis-2.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e741ffe4e2db78a1b9dd6e5d29678ce37fbaaf65dfe132e5b82a794413302ef1"}, - {file = "hiredis-2.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb98038ccd368e0d88bd92ee575c58cfaf33e77f788c36b2a89a84ee1936dc6b"}, - {file = "hiredis-2.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:eae62ed60d53b3561148bcd8c2383e430af38c0deab9f2dd15f8874888ffd26f"}, - {file = "hiredis-2.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ca33c175c1cf60222d9c6d01c38fc17ec3a484f32294af781de30226b003e00f"}, - {file = "hiredis-2.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0c5f6972d2bdee3cd301d5c5438e31195cf1cabf6fd9274491674d4ceb46914d"}, - {file = "hiredis-2.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:a6b54dabfaa5dbaa92f796f0c32819b4636e66aa8e9106c3d421624bd2a2d676"}, - {file = "hiredis-2.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e96cd35df012a17c87ae276196ea8f215e77d6eeca90709eb03999e2d5e3fd8a"}, - {file = "hiredis-2.3.2-cp39-cp39-win32.whl", hash = "sha256:63b99b5ea9fe4f21469fb06a16ca5244307678636f11917359e3223aaeca0b67"}, - {file = "hiredis-2.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:a50c8af811b35b8a43b1590cf890b61ff2233225257a3cad32f43b3ec7ff1b9f"}, - {file = "hiredis-2.3.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7e8bf4444b09419b77ce671088db9f875b26720b5872d97778e2545cd87dba4a"}, - {file = "hiredis-2.3.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bd42d0d45ea47a2f96babd82a659fbc60612ab9423a68e4a8191e538b85542a"}, - {file = "hiredis-2.3.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80441b55edbef868e2563842f5030982b04349408396e5ac2b32025fb06b5212"}, - {file = "hiredis-2.3.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ec444ab8f27562a363672d6a7372bc0700a1bdc9764563c57c5f9efa0e592b5f"}, - {file = "hiredis-2.3.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f9f606e810858207d4b4287b4ef0dc622c2aa469548bf02b59dcc616f134f811"}, - {file = "hiredis-2.3.2-pp37-pypy37_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c3dde4ca00fe9eee3b76209711f1941bb86db42b8a75d7f2249ff9dfc026ab0e"}, - {file = "hiredis-2.3.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4dd676107a1d3c724a56a9d9db38166ad4cf44f924ee701414751bd18a784a0"}, - {file = "hiredis-2.3.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce42649e2676ad783186264d5ffc788a7612ecd7f9effb62d51c30d413a3eefe"}, - {file = "hiredis-2.3.2-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8e3f8b1733078ac663dad57e20060e16389a60ab542f18a97931f3a2a2dd64a4"}, - {file = "hiredis-2.3.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:532a84a82156a82529ec401d1c25d677c6543c791e54a263aa139541c363995f"}, - {file = "hiredis-2.3.2-pp38-pypy38_pp73-macosx_10_15_x86_64.whl", hash = "sha256:4d59f88c4daa36b8c38e59ac7bffed6f5d7f68eaccad471484bf587b28ccc478"}, - {file = "hiredis-2.3.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a91a14dd95e24dc078204b18b0199226ee44644974c645dc54ee7b00c3157330"}, - {file = "hiredis-2.3.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb777a38797c8c7df0444533119570be18d1a4ce5478dffc00c875684df7bfcb"}, - {file = "hiredis-2.3.2-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d47c915897a99d0d34a39fad4be97b4b709ab3d0d3b779ebccf2b6024a8c681e"}, - {file = "hiredis-2.3.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:333b5e04866758b11bda5f5315b4e671d15755fc6ed3b7969721bc6311d0ee36"}, - {file = "hiredis-2.3.2-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c8937f1100435698c18e4da086968c4b5d70e86ea718376f833475ab3277c9aa"}, - {file = "hiredis-2.3.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa45f7d771094b8145af10db74704ab0f698adb682fbf3721d8090f90e42cc49"}, - {file = "hiredis-2.3.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33d5ebc93c39aed4b5bc769f8ce0819bc50e74bb95d57a35f838f1c4378978e0"}, - {file = "hiredis-2.3.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a797d8c7df9944314d309b0d9e1b354e2fa4430a05bb7604da13b6ad291bf959"}, - {file = "hiredis-2.3.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:e15a408f71a6c8c87b364f1f15a6cd9c1baca12bbc47a326ac8ab99ec7ad3c64"}, - {file = "hiredis-2.3.2.tar.gz", hash = "sha256:733e2456b68f3f126ddaf2cd500a33b25146c3676b97ea843665717bda0c5d43"}, + {file = "hiredis-3.0.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:4b182791c41c5eb1d9ed736f0ff81694b06937ca14b0d4dadde5dadba7ff6dae"}, + {file = "hiredis-3.0.0-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:13c275b483a052dd645eb2cb60d6380f1f5215e4c22d6207e17b86be6dd87ffa"}, + {file = "hiredis-3.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c1018cc7f12824506f165027eabb302735b49e63af73eb4d5450c66c88f47026"}, + {file = "hiredis-3.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83a29cc7b21b746cb6a480189e49f49b2072812c445e66a9e38d2004d496b81c"}, + {file = "hiredis-3.0.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e241fab6332e8fb5f14af00a4a9c6aefa22f19a336c069b7ddbf28ef8341e8d6"}, + {file = "hiredis-3.0.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1fb8de899f0145d6c4d5d4bd0ee88a78eb980a7ffabd51e9889251b8f58f1785"}, + {file = "hiredis-3.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b23291951959141173eec10f8573538e9349fa27f47a0c34323d1970bf891ee5"}, + {file = "hiredis-3.0.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e421ac9e4b5efc11705a0d5149e641d4defdc07077f748667f359e60dc904420"}, + {file = "hiredis-3.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:77c8006c12154c37691b24ff293c077300c22944018c3ff70094a33e10c1d795"}, + {file = "hiredis-3.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:41afc0d3c18b59eb50970479a9c0e5544fb4b95e3a79cf2fbaece6ddefb926fe"}, + {file = "hiredis-3.0.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:04ccae6dcd9647eae6025425ab64edb4d79fde8b9e6e115ebfabc6830170e3b2"}, + {file = "hiredis-3.0.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:fe91d62b0594db5ea7d23fc2192182b1a7b6973f628a9b8b2e0a42a2be721ac6"}, + {file = "hiredis-3.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:99516d99316062824a24d145d694f5b0d030c80da693ea6f8c4ecf71a251d8bb"}, + {file = "hiredis-3.0.0-cp310-cp310-win32.whl", hash = "sha256:562eaf820de045eb487afaa37e6293fe7eceb5b25e158b5a1974b7e40bf04543"}, + {file = "hiredis-3.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:a1c81c89ed765198da27412aa21478f30d54ef69bf5e4480089d9c3f77b8f882"}, + {file = "hiredis-3.0.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:4664dedcd5933364756d7251a7ea86d60246ccf73a2e00912872dacbfcef8978"}, + {file = "hiredis-3.0.0-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:47de0bbccf4c8a9f99d82d225f7672b9dd690d8fd872007b933ef51a302c9fa6"}, + {file = "hiredis-3.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e43679eca508ba8240d016d8cca9d27342d70184773c15bea78a23c87a1922f1"}, + {file = "hiredis-3.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13c345e7278c210317e77e1934b27b61394fee0dec2e8bd47e71570900f75823"}, + {file = "hiredis-3.0.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00018f22f38530768b73ea86c11f47e8d4df65facd4e562bd78773bd1baef35e"}, + {file = "hiredis-3.0.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ea3a86405baa8eb0d3639ced6926ad03e07113de54cb00fd7510cb0db76a89d"}, + {file = "hiredis-3.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c073848d2b1d5561f3903879ccf4e1a70c9b1e7566c7bdcc98d082fa3e7f0a1d"}, + {file = "hiredis-3.0.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a8dffb5f5b3415a4669d25de48b617fd9d44b0bccfc4c2ab24b06406ecc9ecb"}, + {file = "hiredis-3.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:22c17c96143c2a62dfd61b13803bc5de2ac526b8768d2141c018b965d0333b66"}, + {file = "hiredis-3.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c3ece960008dab66c6b8bb3a1350764677ee7c74ccd6270aaf1b1caf9ccebb46"}, + {file = "hiredis-3.0.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f75999ae00a920f7dce6ecae76fa5e8674a3110e5a75f12c7a2c75ae1af53396"}, + {file = "hiredis-3.0.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e069967cbd5e1900aafc4b5943888f6d34937fc59bf8918a1a546cb729b4b1e4"}, + {file = "hiredis-3.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0aacc0a78e1d94d843a6d191f224a35893e6bdfeb77a4a89264155015c65f126"}, + {file = "hiredis-3.0.0-cp311-cp311-win32.whl", hash = "sha256:719c32147ba29528cb451f037bf837dcdda4ff3ddb6cdb12c4216b0973174718"}, + {file = "hiredis-3.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:bdc144d56333c52c853c31b4e2e52cfbdb22d3da4374c00f5f3d67c42158970f"}, + {file = "hiredis-3.0.0-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:484025d2eb8f6348f7876fc5a2ee742f568915039fcb31b478fd5c242bb0fe3a"}, + {file = "hiredis-3.0.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:fcdb552ffd97151dab8e7bc3ab556dfa1512556b48a367db94b5c20253a35ee1"}, + {file = "hiredis-3.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0bb6f9fd92f147ba11d338ef5c68af4fd2908739c09e51f186e1d90958c68cc1"}, + {file = "hiredis-3.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa86bf9a0ed339ec9e8a9a9d0ae4dccd8671625c83f9f9f2640729b15e07fbfd"}, + {file = "hiredis-3.0.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e194a0d5df9456995d8f510eab9f529213e7326af6b94770abf8f8b7952ddcaa"}, + {file = "hiredis-3.0.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c8a1df39d74ec507d79c7a82c8063eee60bf80537cdeee652f576059b9cdd15c"}, + {file = "hiredis-3.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f91456507427ba36fd81b2ca11053a8e112c775325acc74e993201ea912d63e9"}, + {file = "hiredis-3.0.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9862db92ef67a8a02e0d5370f07d380e14577ecb281b79720e0d7a89aedb9ee5"}, + {file = "hiredis-3.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d10fcd9e0eeab835f492832b2a6edb5940e2f1230155f33006a8dfd3bd2c94e4"}, + {file = "hiredis-3.0.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:48727d7d405d03977d01885f317328dc21d639096308de126c2c4e9950cbd3c9"}, + {file = "hiredis-3.0.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8e0bb6102ebe2efecf8a3292c6660a0e6fac98176af6de67f020bea1c2343717"}, + {file = "hiredis-3.0.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:df274e3abb4df40f4c7274dd3e587dfbb25691826c948bc98d5fead019dfb001"}, + {file = "hiredis-3.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:034925b5fb514f7b11aac38cd55b3fd7e9d3af23bd6497f3f20aa5b8ba58e232"}, + {file = "hiredis-3.0.0-cp312-cp312-win32.whl", hash = "sha256:120f2dda469b28d12ccff7c2230225162e174657b49cf4cd119db525414ae281"}, + {file = "hiredis-3.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:e584fe5f4e6681d8762982be055f1534e0170f6308a7a90f58d737bab12ff6a8"}, + {file = "hiredis-3.0.0-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:122171ff47d96ed8dd4bba6c0e41d8afaba3e8194949f7720431a62aa29d8895"}, + {file = "hiredis-3.0.0-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:ba9fc605ac558f0de67463fb588722878641e6fa1dabcda979e8e69ff581d0bd"}, + {file = "hiredis-3.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a631e2990b8be23178f655cae8ac6c7422af478c420dd54e25f2e26c29e766f1"}, + {file = "hiredis-3.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63482db3fadebadc1d01ad33afa6045ebe2ea528eb77ccaabd33ee7d9c2bad48"}, + {file = "hiredis-3.0.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f669212c390eebfbe03c4e20181f5970b82c5d0a0ad1df1785f7ffbe7d61150"}, + {file = "hiredis-3.0.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a49ef161739f8018c69b371528bdb47d7342edfdee9ddc75a4d8caddf45a6e"}, + {file = "hiredis-3.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98a152052b8878e5e43a2e3a14075218adafc759547c98668a21e9485882696c"}, + {file = "hiredis-3.0.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50a196af0ce657fcde9bf8a0bbe1032e22c64d8fcec2bc926a35e7ff68b3a166"}, + {file = "hiredis-3.0.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:f2f312eef8aafc2255e3585dcf94d5da116c43ef837db91db9ecdc1bc930072d"}, + {file = "hiredis-3.0.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:6ca41fa40fa019cde42c21add74aadd775e71458051a15a352eabeb12eb4d084"}, + {file = "hiredis-3.0.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:6eecb343c70629f5af55a8b3e53264e44fa04e155ef7989de13668a0cb102a90"}, + {file = "hiredis-3.0.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:c3fdad75e7837a475900a1d3a5cc09aa024293c3b0605155da2d42f41bc0e482"}, + {file = "hiredis-3.0.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:8854969e7480e8d61ed7549eb232d95082a743e94138d98d7222ba4e9f7ecacd"}, + {file = "hiredis-3.0.0-cp38-cp38-win32.whl", hash = "sha256:f114a6c86edbf17554672b050cce72abf489fe58d583c7921904d5f1c9691605"}, + {file = "hiredis-3.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:7d99b91e42217d7b4b63354b15b41ce960e27d216783e04c4a350224d55842a4"}, + {file = "hiredis-3.0.0-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:4c6efcbb5687cf8d2aedcc2c3ed4ac6feae90b8547427d417111194873b66b06"}, + {file = "hiredis-3.0.0-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:5b5cff42a522a0d81c2ae7eae5e56d0ee7365e0c4ad50c4de467d8957aff4414"}, + {file = "hiredis-3.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:82f794d564f4bc76b80c50b03267fe5d6589e93f08e66b7a2f674faa2fa76ebc"}, + {file = "hiredis-3.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7a4c1791d7aa7e192f60fe028ae409f18ccdd540f8b1e6aeb0df7816c77e4a4"}, + {file = "hiredis-3.0.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2537b2cd98192323fce4244c8edbf11f3cac548a9d633dbbb12b48702f379f4"}, + {file = "hiredis-3.0.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8fed69bbaa307040c62195a269f82fc3edf46b510a17abb6b30a15d7dab548df"}, + {file = "hiredis-3.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:869f6d5537d243080f44253491bb30aa1ec3c21754003b3bddeadedeb65842b0"}, + {file = "hiredis-3.0.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d435ae89073d7cd51e6b6bf78369c412216261c9c01662e7008ff00978153729"}, + {file = "hiredis-3.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:204b79b30a0e6be0dc2301a4d385bb61472809f09c49f400497f1cdd5a165c66"}, + {file = "hiredis-3.0.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3ea635101b739c12effd189cc19b2671c268abb03013fd1f6321ca29df3ca625"}, + {file = "hiredis-3.0.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:f359175197fd833c8dd7a8c288f1516be45415bb5c939862ab60c2918e1e1943"}, + {file = "hiredis-3.0.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ac6d929cb33dd12ad3424b75725975f0a54b5b12dbff95f2a2d660c510aa106d"}, + {file = "hiredis-3.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:100431e04d25a522ef2c3b94f294c4219c4de3bfc7d557b6253296145a144c11"}, + {file = "hiredis-3.0.0-cp39-cp39-win32.whl", hash = "sha256:e1a9c14ae9573d172dc050a6f63a644457df5d01ec4d35a6a0f097f812930f83"}, + {file = "hiredis-3.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:54a6dd7b478e6eb01ce15b3bb5bf771e108c6c148315bf194eb2ab776a3cac4d"}, + {file = "hiredis-3.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:50da7a9edf371441dfcc56288d790985ee9840d982750580710a9789b8f4a290"}, + {file = "hiredis-3.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9b285ef6bf1581310b0d5e8f6ce64f790a1c40e89c660e1320b35f7515433672"}, + {file = "hiredis-3.0.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0dcfa684966f25b335072115de2f920228a3c2caf79d4bfa2b30f6e4f674a948"}, + {file = "hiredis-3.0.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a41be8af1fd78ca97bc948d789a09b730d1e7587d07ca53af05758f31f4b985d"}, + {file = "hiredis-3.0.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:038756db735e417ab36ee6fd7725ce412385ed2bd0767e8179a4755ea11b804f"}, + {file = "hiredis-3.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:fcecbd39bd42cef905c0b51c9689c39d0cc8b88b1671e7f40d4fb213423aef3a"}, + {file = "hiredis-3.0.0-pp38-pypy38_pp73-macosx_10_15_x86_64.whl", hash = "sha256:a131377493a59fb0f5eaeb2afd49c6540cafcfba5b0b3752bed707be9e7c4eaf"}, + {file = "hiredis-3.0.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:3d22c53f0ec5c18ecb3d92aa9420563b1c5d657d53f01356114978107b00b860"}, + {file = "hiredis-3.0.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c8a91e9520fbc65a799943e5c970ffbcd67905744d8becf2e75f9f0a5e8414f0"}, + {file = "hiredis-3.0.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3dc8043959b50141df58ab4f398e8ae84c6f9e673a2c9407be65fc789138f4a6"}, + {file = "hiredis-3.0.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:51b99cfac514173d7b8abdfe10338193e8a0eccdfe1870b646009d2fb7cbe4b5"}, + {file = "hiredis-3.0.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:fa1fcad89d8a41d8dc10b1e54951ec1e161deabd84ed5a2c95c3c7213bdb3514"}, + {file = "hiredis-3.0.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:898636a06d9bf575d2c594129085ad6b713414038276a4bfc5db7646b8a5be78"}, + {file = "hiredis-3.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:466f836dbcf86de3f9692097a7a01533dc9926986022c6617dc364a402b265c5"}, + {file = "hiredis-3.0.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23142a8af92a13fc1e3f2ca1d940df3dcf2af1d176be41fe8d89e30a837a0b60"}, + {file = "hiredis-3.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:793c80a3d6b0b0e8196a2d5de37a08330125668c8012922685e17aa9108c33ac"}, + {file = "hiredis-3.0.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:467d28112c7faa29b7db743f40803d927c8591e9da02b6ce3d5fadc170a542a2"}, + {file = "hiredis-3.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:dc384874a719c767b50a30750f937af18842ee5e288afba95a5a3ed703b1515a"}, + {file = "hiredis-3.0.0.tar.gz", hash = "sha256:fed8581ae26345dea1f1e0d1a96e05041a727a45e7d8d459164583e23c6ac441"}, ] [[package]] @@ -396,6 +641,23 @@ files = [ {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, ] +[[package]] +name = "import-expression" +version = "1.1.5" +description = "Parses a superset of Python allowing for inline module import expressions" +optional = false +python-versions = "*" +files = [ + {file = "import_expression-1.1.5-py3-none-any.whl", hash = "sha256:f60c3765dbf2f41928b9c6ef79d632209b6705fc8f30e281ed1a492ed026b10f"}, + {file = "import_expression-1.1.5.tar.gz", hash = "sha256:9959588fcfc8dcb144a0725176cfef6c28c7db1fc2d683625025e687516d40c1"}, +] + +[package.dependencies] +astunparse = ">=1.6.3,<2.0.0" + +[package.extras] +test = ["pytest", "pytest-cov"] + [[package]] name = "importlib-metadata" version = "7.2.1" @@ -458,6 +720,31 @@ MarkupSafe = ">=2.0" [package.extras] i18n = ["Babel (>=2.7)"] +[[package]] +name = "jishaku" +version = "2.5.2" +description = "A discord.py extension including useful tools for bot development and debugging." +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "jishaku-2.5.2-py3-none-any.whl", hash = "sha256:87f34942ee44865f5ce08e36723b7c74a313d8a13a4db8a6b7cc12618cc3496c"}, + {file = "jishaku-2.5.2.tar.gz", hash = "sha256:56d38c333036e37481df5e3c9e81d6033b5097738f0d171a81e2752124f0df5c"}, +] + +[package.dependencies] +braceexpand = ">=0.1.7" +click = ">=8.0.1" +import-expression = ">=1.0.0,<2.0.0" + +[package.extras] +discordpy = ["discord.py (>=1.7.3)"] +docs = ["Sphinx (>=4.4.0)", "sphinxcontrib-trio (>=1.1.2)"] +procinfo = ["psutil (>=5.8.0)"] +profiling = ["line-profiler (>=3.5.1)"] +publish = ["Jinja2 (>=3.0.3)"] +test = ["coverage (>=6.3.2)", "flake8 (>=4.0.1)", "isort (>=5.10.1)", "pylint (>=2.11.1)", "pytest (>=7.0.1)", "pytest-asyncio (>=0.18.1)", "pytest-cov (>=3.0.0)", "pytest-mock (>=3.7.0)"] +voice = ["yt-dlp (>=2022.3.8)"] + [[package]] name = "markdown" version = "3.6" @@ -664,6 +951,105 @@ files = [ {file = "mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443"}, ] +[[package]] +name = "multidict" +version = "6.0.5" +description = "multidict implementation" +optional = false +python-versions = ">=3.7" +files = [ + {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:228b644ae063c10e7f324ab1ab6b548bdf6f8b47f3ec234fef1093bc2735e5f9"}, + {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:896ebdcf62683551312c30e20614305f53125750803b614e9e6ce74a96232604"}, + {file = "multidict-6.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:411bf8515f3be9813d06004cac41ccf7d1cd46dfe233705933dd163b60e37600"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d147090048129ce3c453f0292e7697d333db95e52616b3793922945804a433c"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:215ed703caf15f578dca76ee6f6b21b7603791ae090fbf1ef9d865571039ade5"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c6390cf87ff6234643428991b7359b5f59cc15155695deb4eda5c777d2b880f"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fd81c4ebdb4f214161be351eb5bcf385426bf023041da2fd9e60681f3cebae"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3cc2ad10255f903656017363cd59436f2111443a76f996584d1077e43ee51182"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6939c95381e003f54cd4c5516740faba40cf5ad3eeff460c3ad1d3e0ea2549bf"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:220dd781e3f7af2c2c1053da9fa96d9cf3072ca58f057f4c5adaaa1cab8fc442"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:766c8f7511df26d9f11cd3a8be623e59cca73d44643abab3f8c8c07620524e4a"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:fe5d7785250541f7f5019ab9cba2c71169dc7d74d0f45253f8313f436458a4ef"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1c1496e73051918fcd4f58ff2e0f2f3066d1c76a0c6aeffd9b45d53243702cc"}, + {file = "multidict-6.0.5-cp310-cp310-win32.whl", hash = "sha256:7afcdd1fc07befad18ec4523a782cde4e93e0a2bf71239894b8d61ee578c1319"}, + {file = "multidict-6.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:99f60d34c048c5c2fabc766108c103612344c46e35d4ed9ae0673d33c8fb26e8"}, + {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f285e862d2f153a70586579c15c44656f888806ed0e5b56b64489afe4a2dbfba"}, + {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:53689bb4e102200a4fafa9de9c7c3c212ab40a7ab2c8e474491914d2305f187e"}, + {file = "multidict-6.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:612d1156111ae11d14afaf3a0669ebf6c170dbb735e510a7438ffe2369a847fd"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7be7047bd08accdb7487737631d25735c9a04327911de89ff1b26b81745bd4e3"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de170c7b4fe6859beb8926e84f7d7d6c693dfe8e27372ce3b76f01c46e489fcf"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04bde7a7b3de05732a4eb39c94574db1ec99abb56162d6c520ad26f83267de29"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85f67aed7bb647f93e7520633d8f51d3cbc6ab96957c71272b286b2f30dc70ed"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425bf820055005bfc8aa9a0b99ccb52cc2f4070153e34b701acc98d201693733"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d3eb1ceec286eba8220c26f3b0096cf189aea7057b6e7b7a2e60ed36b373b77f"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7901c05ead4b3fb75113fb1dd33eb1253c6d3ee37ce93305acd9d38e0b5f21a4"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e0e79d91e71b9867c73323a3444724d496c037e578a0e1755ae159ba14f4f3d1"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:29bfeb0dff5cb5fdab2023a7a9947b3b4af63e9c47cae2a10ad58394b517fddc"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e030047e85cbcedbfc073f71836d62dd5dadfbe7531cae27789ff66bc551bd5e"}, + {file = "multidict-6.0.5-cp311-cp311-win32.whl", hash = "sha256:2f4848aa3baa109e6ab81fe2006c77ed4d3cd1e0ac2c1fbddb7b1277c168788c"}, + {file = "multidict-6.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:2faa5ae9376faba05f630d7e5e6be05be22913782b927b19d12b8145968a85ea"}, + {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:51d035609b86722963404f711db441cf7134f1889107fb171a970c9701f92e1e"}, + {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cbebcd5bcaf1eaf302617c114aa67569dd3f090dd0ce8ba9e35e9985b41ac35b"}, + {file = "multidict-6.0.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2ffc42c922dbfddb4a4c3b438eb056828719f07608af27d163191cb3e3aa6cc5"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ceb3b7e6a0135e092de86110c5a74e46bda4bd4fbfeeb3a3bcec79c0f861e450"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:79660376075cfd4b2c80f295528aa6beb2058fd289f4c9252f986751a4cd0496"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4428b29611e989719874670fd152b6625500ad6c686d464e99f5aaeeaca175a"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d84a5c3a5f7ce6db1f999fb9438f686bc2e09d38143f2d93d8406ed2dd6b9226"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76c0de87358b192de7ea9649beb392f107dcad9ad27276324c24c91774ca5271"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:79a6d2ba910adb2cbafc95dad936f8b9386e77c84c35bc0add315b856d7c3abb"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:92d16a3e275e38293623ebf639c471d3e03bb20b8ebb845237e0d3664914caef"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:fb616be3538599e797a2017cccca78e354c767165e8858ab5116813146041a24"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:14c2976aa9038c2629efa2c148022ed5eb4cb939e15ec7aace7ca932f48f9ba6"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:435a0984199d81ca178b9ae2c26ec3d49692d20ee29bc4c11a2a8d4514c67eda"}, + {file = "multidict-6.0.5-cp312-cp312-win32.whl", hash = "sha256:9fe7b0653ba3d9d65cbe7698cca585bf0f8c83dbbcc710db9c90f478e175f2d5"}, + {file = "multidict-6.0.5-cp312-cp312-win_amd64.whl", hash = "sha256:01265f5e40f5a17f8241d52656ed27192be03bfa8764d88e8220141d1e4b3556"}, + {file = "multidict-6.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:19fe01cea168585ba0f678cad6f58133db2aa14eccaf22f88e4a6dccadfad8b3"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bf7a982604375a8d49b6cc1b781c1747f243d91b81035a9b43a2126c04766f5"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:107c0cdefe028703fb5dafe640a409cb146d44a6ae201e55b35a4af8e95457dd"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:403c0911cd5d5791605808b942c88a8155c2592e05332d2bf78f18697a5fa15e"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aeaf541ddbad8311a87dd695ed9642401131ea39ad7bc8cf3ef3967fd093b626"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e4972624066095e52b569e02b5ca97dbd7a7ddd4294bf4e7247d52635630dd83"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d946b0a9eb8aaa590df1fe082cee553ceab173e6cb5b03239716338629c50c7a"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b55358304d7a73d7bdf5de62494aaf70bd33015831ffd98bc498b433dfe5b10c"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:a3145cb08d8625b2d3fee1b2d596a8766352979c9bffe5d7833e0503d0f0b5e5"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d65f25da8e248202bd47445cec78e0025c0fe7582b23ec69c3b27a640dd7a8e3"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c9bf56195c6bbd293340ea82eafd0071cb3d450c703d2c93afb89f93b8386ccc"}, + {file = "multidict-6.0.5-cp37-cp37m-win32.whl", hash = "sha256:69db76c09796b313331bb7048229e3bee7928eb62bab5e071e9f7fcc4879caee"}, + {file = "multidict-6.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:fce28b3c8a81b6b36dfac9feb1de115bab619b3c13905b419ec71d03a3fc1423"}, + {file = "multidict-6.0.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76f067f5121dcecf0d63a67f29080b26c43c71a98b10c701b0677e4a065fbd54"}, + {file = "multidict-6.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b82cc8ace10ab5bd93235dfaab2021c70637005e1ac787031f4d1da63d493c1d"}, + {file = "multidict-6.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5cb241881eefd96b46f89b1a056187ea8e9ba14ab88ba632e68d7a2ecb7aadf7"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8e94e6912639a02ce173341ff62cc1201232ab86b8a8fcc05572741a5dc7d93"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09a892e4a9fb47331da06948690ae38eaa2426de97b4ccbfafbdcbe5c8f37ff8"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55205d03e8a598cfc688c71ca8ea5f66447164efff8869517f175ea632c7cb7b"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37b15024f864916b4951adb95d3a80c9431299080341ab9544ed148091b53f50"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2a1dee728b52b33eebff5072817176c172050d44d67befd681609b4746e1c2e"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:edd08e6f2f1a390bf137080507e44ccc086353c8e98c657e666c017718561b89"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:60d698e8179a42ec85172d12f50b1668254628425a6bd611aba022257cac1386"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:3d25f19500588cbc47dc19081d78131c32637c25804df8414463ec908631e453"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:4cc0ef8b962ac7a5e62b9e826bd0cd5040e7d401bc45a6835910ed699037a461"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:eca2e9d0cc5a889850e9bbd68e98314ada174ff6ccd1129500103df7a94a7a44"}, + {file = "multidict-6.0.5-cp38-cp38-win32.whl", hash = "sha256:4a6a4f196f08c58c59e0b8ef8ec441d12aee4125a7d4f4fef000ccb22f8d7241"}, + {file = "multidict-6.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:0275e35209c27a3f7951e1ce7aaf93ce0d163b28948444bec61dd7badc6d3f8c"}, + {file = "multidict-6.0.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e7be68734bd8c9a513f2b0cfd508802d6609da068f40dc57d4e3494cefc92929"}, + {file = "multidict-6.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1d9ea7a7e779d7a3561aade7d596649fbecfa5c08a7674b11b423783217933f9"}, + {file = "multidict-6.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ea1456df2a27c73ce51120fa2f519f1bea2f4a03a917f4a43c8707cf4cbbae1a"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf590b134eb70629e350691ecca88eac3e3b8b3c86992042fb82e3cb1830d5e1"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5c0631926c4f58e9a5ccce555ad7747d9a9f8b10619621f22f9635f069f6233e"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dce1c6912ab9ff5f179eaf6efe7365c1f425ed690b03341911bf4939ef2f3046"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0868d64af83169e4d4152ec612637a543f7a336e4a307b119e98042e852ad9c"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:141b43360bfd3bdd75f15ed811850763555a251e38b2405967f8e25fb43f7d40"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7df704ca8cf4a073334e0427ae2345323613e4df18cc224f647f251e5e75a527"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6214c5a5571802c33f80e6c84713b2c79e024995b9c5897f794b43e714daeec9"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:cd6c8fca38178e12c00418de737aef1261576bd1b6e8c6134d3e729a4e858b38"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:e02021f87a5b6932fa6ce916ca004c4d441509d33bbdbeca70d05dff5e9d2479"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ebd8d160f91a764652d3e51ce0d2956b38efe37c9231cd82cfc0bed2e40b581c"}, + {file = "multidict-6.0.5-cp39-cp39-win32.whl", hash = "sha256:04da1bb8c8dbadf2a18a452639771951c662c5ad03aefe4884775454be322c9b"}, + {file = "multidict-6.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:d6f6d4f185481c9669b9447bf9d9cf3b95a0e9df9d169bbc17e363b7d5487755"}, + {file = "multidict-6.0.5-py3-none-any.whl", hash = "sha256:0d63c74e3d7ab26de115c49bffc92cc77ed23395303d496eae515d4204a625e7"}, + {file = "multidict-6.0.5.tar.gz", hash = "sha256:f7e301075edaf50500f0b341543c41194d8df3ae5caf4702f2095f3ca73dd8da"}, +] + [[package]] name = "nodeenv" version = "1.9.1" @@ -904,6 +1290,20 @@ files = [ [package.dependencies] six = ">=1.5" +[[package]] +name = "python-dotenv" +version = "1.0.1" +description = "Read key-value pairs from a .env file and set them as environment variables" +optional = false +python-versions = ">=3.8" +files = [ + {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, + {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, +] + +[package.extras] +cli = ["click (>=5.0)"] + [[package]] name = "pyyaml" version = "6.0.1" @@ -916,6 +1316,7 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -923,8 +1324,16 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -941,6 +1350,7 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -948,6 +1358,7 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -1277,6 +1688,123 @@ files = [ {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, ] +[[package]] +name = "wheel" +version = "0.43.0" +description = "A built-package format for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "wheel-0.43.0-py3-none-any.whl", hash = "sha256:55c570405f142630c6b9f72fe09d9b67cf1477fcf543ae5b8dcb1f5b7377da81"}, + {file = "wheel-0.43.0.tar.gz", hash = "sha256:465ef92c69fa5c5da2d1cf8ac40559a8c940886afcef87dcf14b9470862f1d85"}, +] + +[package.extras] +test = ["pytest (>=6.0.0)", "setuptools (>=65)"] + +[[package]] +name = "yarl" +version = "1.9.4" +description = "Yet another URL library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a8c1df72eb746f4136fe9a2e72b0c9dc1da1cbd23b5372f94b5820ff8ae30e0e"}, + {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a3a6ed1d525bfb91b3fc9b690c5a21bb52de28c018530ad85093cc488bee2dd2"}, + {file = "yarl-1.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c38c9ddb6103ceae4e4498f9c08fac9b590c5c71b0370f98714768e22ac6fa66"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9e09c9d74f4566e905a0b8fa668c58109f7624db96a2171f21747abc7524234"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8477c1ee4bd47c57d49621a062121c3023609f7a13b8a46953eb6c9716ca392"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5ff2c858f5f6a42c2a8e751100f237c5e869cbde669a724f2062d4c4ef93551"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:357495293086c5b6d34ca9616a43d329317feab7917518bc97a08f9e55648455"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54525ae423d7b7a8ee81ba189f131054defdb122cde31ff17477951464c1691c"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:801e9264d19643548651b9db361ce3287176671fb0117f96b5ac0ee1c3530d53"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e516dc8baf7b380e6c1c26792610230f37147bb754d6426462ab115a02944385"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:7d5aaac37d19b2904bb9dfe12cdb08c8443e7ba7d2852894ad448d4b8f442863"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:54beabb809ffcacbd9d28ac57b0db46e42a6e341a030293fb3185c409e626b8b"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bac8d525a8dbc2a1507ec731d2867025d11ceadcb4dd421423a5d42c56818541"}, + {file = "yarl-1.9.4-cp310-cp310-win32.whl", hash = "sha256:7855426dfbddac81896b6e533ebefc0af2f132d4a47340cee6d22cac7190022d"}, + {file = "yarl-1.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:848cd2a1df56ddbffeb375535fb62c9d1645dde33ca4d51341378b3f5954429b"}, + {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:35a2b9396879ce32754bd457d31a51ff0a9d426fd9e0e3c33394bf4b9036b099"}, + {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c7d56b293cc071e82532f70adcbd8b61909eec973ae9d2d1f9b233f3d943f2c"}, + {file = "yarl-1.9.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d8a1c6c0be645c745a081c192e747c5de06e944a0d21245f4cf7c05e457c36e0"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b3c1ffe10069f655ea2d731808e76e0f452fc6c749bea04781daf18e6039525"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:549d19c84c55d11687ddbd47eeb348a89df9cb30e1993f1b128f4685cd0ebbf8"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7409f968456111140c1c95301cadf071bd30a81cbd7ab829169fb9e3d72eae9"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e23a6d84d9d1738dbc6e38167776107e63307dfc8ad108e580548d1f2c587f42"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8b889777de69897406c9fb0b76cdf2fd0f31267861ae7501d93003d55f54fbe"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:03caa9507d3d3c83bca08650678e25364e1843b484f19986a527630ca376ecce"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e9035df8d0880b2f1c7f5031f33f69e071dfe72ee9310cfc76f7b605958ceb9"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:c0ec0ed476f77db9fb29bca17f0a8fcc7bc97ad4c6c1d8959c507decb22e8572"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:ee04010f26d5102399bd17f8df8bc38dc7ccd7701dc77f4a68c5b8d733406958"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:49a180c2e0743d5d6e0b4d1a9e5f633c62eca3f8a86ba5dd3c471060e352ca98"}, + {file = "yarl-1.9.4-cp311-cp311-win32.whl", hash = "sha256:81eb57278deb6098a5b62e88ad8281b2ba09f2f1147c4767522353eaa6260b31"}, + {file = "yarl-1.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:d1d2532b340b692880261c15aee4dc94dd22ca5d61b9db9a8a361953d36410b1"}, + {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0d2454f0aef65ea81037759be5ca9947539667eecebca092733b2eb43c965a81"}, + {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:44d8ffbb9c06e5a7f529f38f53eda23e50d1ed33c6c869e01481d3fafa6b8142"}, + {file = "yarl-1.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aaaea1e536f98754a6e5c56091baa1b6ce2f2700cc4a00b0d49eca8dea471074"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3777ce5536d17989c91696db1d459574e9a9bd37660ea7ee4d3344579bb6f129"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fc5fc1eeb029757349ad26bbc5880557389a03fa6ada41703db5e068881e5f2"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea65804b5dc88dacd4a40279af0cdadcfe74b3e5b4c897aa0d81cf86927fee78"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa102d6d280a5455ad6a0f9e6d769989638718e938a6a0a2ff3f4a7ff8c62cc4"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09efe4615ada057ba2d30df871d2f668af661e971dfeedf0c159927d48bbeff0"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:008d3e808d03ef28542372d01057fd09168419cdc8f848efe2804f894ae03e51"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6f5cb257bc2ec58f437da2b37a8cd48f666db96d47b8a3115c29f316313654ff"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:992f18e0ea248ee03b5a6e8b3b4738850ae7dbb172cc41c966462801cbf62cf7"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0e9d124c191d5b881060a9e5060627694c3bdd1fe24c5eecc8d5d7d0eb6faabc"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3986b6f41ad22988e53d5778f91855dc0399b043fc8946d4f2e68af22ee9ff10"}, + {file = "yarl-1.9.4-cp312-cp312-win32.whl", hash = "sha256:4b21516d181cd77ebd06ce160ef8cc2a5e9ad35fb1c5930882baff5ac865eee7"}, + {file = "yarl-1.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:a9bd00dc3bc395a662900f33f74feb3e757429e545d831eef5bb280252631984"}, + {file = "yarl-1.9.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:63b20738b5aac74e239622d2fe30df4fca4942a86e31bf47a81a0e94c14df94f"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d7f7de27b8944f1fee2c26a88b4dabc2409d2fea7a9ed3df79b67277644e17"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c74018551e31269d56fab81a728f683667e7c28c04e807ba08f8c9e3bba32f14"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ca06675212f94e7a610e85ca36948bb8fc023e458dd6c63ef71abfd482481aa5"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aef935237d60a51a62b86249839b51345f47564208c6ee615ed2a40878dccdd"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b134fd795e2322b7684155b7855cc99409d10b2e408056db2b93b51a52accc7"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d25039a474c4c72a5ad4b52495056f843a7ff07b632c1b92ea9043a3d9950f6e"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f7d6b36dd2e029b6bcb8a13cf19664c7b8e19ab3a58e0fefbb5b8461447ed5ec"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:957b4774373cf6f709359e5c8c4a0af9f6d7875db657adb0feaf8d6cb3c3964c"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d7eeb6d22331e2fd42fce928a81c697c9ee2d51400bd1a28803965883e13cead"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6a962e04b8f91f8c4e5917e518d17958e3bdee71fd1d8b88cdce74dd0ebbf434"}, + {file = "yarl-1.9.4-cp37-cp37m-win32.whl", hash = "sha256:f3bc6af6e2b8f92eced34ef6a96ffb248e863af20ef4fde9448cc8c9b858b749"}, + {file = "yarl-1.9.4-cp37-cp37m-win_amd64.whl", hash = "sha256:ad4d7a90a92e528aadf4965d685c17dacff3df282db1121136c382dc0b6014d2"}, + {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ec61d826d80fc293ed46c9dd26995921e3a82146feacd952ef0757236fc137be"}, + {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8be9e837ea9113676e5754b43b940b50cce76d9ed7d2461df1af39a8ee674d9f"}, + {file = "yarl-1.9.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bef596fdaa8f26e3d66af846bbe77057237cb6e8efff8cd7cc8dff9a62278bbf"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d47552b6e52c3319fede1b60b3de120fe83bde9b7bddad11a69fb0af7db32f1"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84fc30f71689d7fc9168b92788abc977dc8cefa806909565fc2951d02f6b7d57"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4aa9741085f635934f3a2583e16fcf62ba835719a8b2b28fb2917bb0537c1dfa"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:206a55215e6d05dbc6c98ce598a59e6fbd0c493e2de4ea6cc2f4934d5a18d130"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07574b007ee20e5c375a8fe4a0789fad26db905f9813be0f9fef5a68080de559"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5a2e2433eb9344a163aced6a5f6c9222c0786e5a9e9cac2c89f0b28433f56e23"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6ad6d10ed9b67a382b45f29ea028f92d25bc0bc1daf6c5b801b90b5aa70fb9ec"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:6fe79f998a4052d79e1c30eeb7d6c1c1056ad33300f682465e1b4e9b5a188b78"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a825ec844298c791fd28ed14ed1bffc56a98d15b8c58a20e0e08c1f5f2bea1be"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8619d6915b3b0b34420cf9b2bb6d81ef59d984cb0fde7544e9ece32b4b3043c3"}, + {file = "yarl-1.9.4-cp38-cp38-win32.whl", hash = "sha256:686a0c2f85f83463272ddffd4deb5e591c98aac1897d65e92319f729c320eece"}, + {file = "yarl-1.9.4-cp38-cp38-win_amd64.whl", hash = "sha256:a00862fb23195b6b8322f7d781b0dc1d82cb3bcac346d1e38689370cc1cc398b"}, + {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:604f31d97fa493083ea21bd9b92c419012531c4e17ea6da0f65cacdcf5d0bd27"}, + {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8a854227cf581330ffa2c4824d96e52ee621dd571078a252c25e3a3b3d94a1b1"}, + {file = "yarl-1.9.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ba6f52cbc7809cd8d74604cce9c14868306ae4aa0282016b641c661f981a6e91"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6327976c7c2f4ee6816eff196e25385ccc02cb81427952414a64811037bbc8b"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8397a3817d7dcdd14bb266283cd1d6fc7264a48c186b986f32e86d86d35fbac5"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0381b4ce23ff92f8170080c97678040fc5b08da85e9e292292aba67fdac6c34"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23d32a2594cb5d565d358a92e151315d1b2268bc10f4610d098f96b147370136"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ddb2a5c08a4eaaba605340fdee8fc08e406c56617566d9643ad8bf6852778fc7"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:26a1dc6285e03f3cc9e839a2da83bcbf31dcb0d004c72d0730e755b33466c30e"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:18580f672e44ce1238b82f7fb87d727c4a131f3a9d33a5e0e82b793362bf18b4"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:29e0f83f37610f173eb7e7b5562dd71467993495e568e708d99e9d1944f561ec"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:1f23e4fe1e8794f74b6027d7cf19dc25f8b63af1483d91d595d4a07eca1fb26c"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:db8e58b9d79200c76956cefd14d5c90af54416ff5353c5bfd7cbe58818e26ef0"}, + {file = "yarl-1.9.4-cp39-cp39-win32.whl", hash = "sha256:c7224cab95645c7ab53791022ae77a4509472613e839dab722a72abe5a684575"}, + {file = "yarl-1.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:824d6c50492add5da9374875ce72db7a0733b29c2394890aef23d533106e2b15"}, + {file = "yarl-1.9.4-py3-none-any.whl", hash = "sha256:928cecb0ef9d5a7946eb6ff58417ad2fe9375762382f1bf5c55e61645f2c43ad"}, + {file = "yarl-1.9.4.tar.gz", hash = "sha256:566db86717cf8080b99b58b083b773a908ae40f06681e87e589a976faf8246bf"}, +] + +[package.dependencies] +idna = ">=2.0" +multidict = ">=4.0" + [[package]] name = "zipp" version = "3.19.2" @@ -1295,4 +1823,4 @@ test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", [metadata] lock-version = "2.0" python-versions = "~3.12" -content-hash = "3ffb185f024e50189465dd3226753ed6ea91302f0977be7a9135e2e6f3c8a4b1" +content-hash = "0f512201986af94696f11ee5633d5eb0eb1bb2d775b3b3a76b5308b0f3cd8574" diff --git a/pyproject.toml b/pyproject.toml index fb6b105..5444954 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,10 @@ exclude = ["**/test__*.py"] [tool.poetry.dependencies] python = "~3.12" -redis = {extras = ["hiredis"], version = "^5.0.7"} +discord-py = "^2.4.0" +jishaku = "^2.5.2" +python-dotenv = "^1.0.1" +redis = { extras = ["hiredis"], version = "^5.0.7" } [tool.poetry.dev-dependencies] commitizen = "3.27.0" From c3808b658014ce5a7e5f76714fac0dffd33fefbd Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Fri, 19 Jul 2024 19:05:44 +0200 Subject: [PATCH 028/168] fix: include application config in docker image (#15) Co-authored-by: isaa-ctaylor --- Dockerfile | 4 ++-- poetry.lock | 2 +- pyproject.toml | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index bb49779..bc7d4b7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,8 +15,8 @@ RUN adduser --system courageous-comets # Set the working directory WORKDIR /app -# Copy the wheel file to the working directory -COPY dist/*.whl ./ +# Copy the app config and the wheel file to the working directory and set the permissions +COPY --chown=courageous-comets --chmod=0400 application.yaml dist/*.whl ./ # Install the wheel file and clean up to reduce image size RUN pip install --no-cache-dir *.whl && \ diff --git a/poetry.lock b/poetry.lock index f0ebba9..1a70851 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1823,4 +1823,4 @@ test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", [metadata] lock-version = "2.0" python-versions = "~3.12" -content-hash = "0f512201986af94696f11ee5633d5eb0eb1bb2d775b3b3a76b5308b0f3cd8574" +content-hash = "e98a0974533a45bded47b6f6d1f0da774778f2b01134525a10ed46418e46f620" diff --git a/pyproject.toml b/pyproject.toml index 5444954..2459e09 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,6 +12,7 @@ python = "~3.12" discord-py = "^2.4.0" jishaku = "^2.5.2" python-dotenv = "^1.0.1" +pyyaml = "^6.0.1" redis = { extras = ["hiredis"], version = "^5.0.7" } [tool.poetry.dev-dependencies] From 3b475420aa8b7da70b4b5a1963eefcbd148d4e05 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Sat, 20 Jul 2024 04:52:01 +0000 Subject: [PATCH 029/168] docs: describe new config options --- docs/admin-guide/configuration.md | 49 +++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/docs/admin-guide/configuration.md b/docs/admin-guide/configuration.md index 37a0b0c..dd99177 100644 --- a/docs/admin-guide/configuration.md +++ b/docs/admin-guide/configuration.md @@ -2,15 +2,20 @@ The following environment variables are available to configure the application: -| Variable | Description | Required | Default | -| --------------- | -----------------------| -------- | ----------- | -| `DISCORD_TOKEN` | The Discord bot token. | Yes | - | -| `LOG_LEVEL` | The minimum log level. | No | `INFO` | -| `REDIS_HOST` | The Redis host. | No | `localhost` | -| `REDIS_PORT` | The Redis port. | No | `6379` | -| `REDIS_PASSWORD`| The Redis password. | No | - | +| Variable | Description | Required | Default | +| ------------------------------------- | ------------------------------------------| -------- | ------------------ | +| [`DISCORD_TOKEN`](#discord_token) | The Discord bot token. | Yes | - | +| [`BOT_CONFIG_PATH`](#bot_config_path) | The path to the bot's configuration file. | No | `application.yaml` | +| [`LOG_LEVEL`](#log_level) | The minimum log level. | No | `INFO` | +| [`REDIS_HOST`](#redis_host) | The Redis host. | No | `localhost` | +| [`REDIS_PORT`](#redis_port) | The Redis port. | No | `6379` | +| [`REDIS_PASSWORD`](#redis_password) | The Redis password. | No | - | -## `DISCORD_TOKEN` +## Required Settings + +The following settings are required to start the application: + +### `DISCORD_TOKEN` You can obtain a Discord bot token from the [Discord Developer Portal](https://discord.com/developers/applications). Your token should have the following scopes: @@ -20,7 +25,25 @@ Your token should have the following scopes: !!! DANGER "Security Warning" Do not share your token with anyone! -## `LOG_LEVEL` +## Optional Settings + +The following settings are optional or have default values that can be overridden: + +### `BOT_CONFIG_PATH` + +This specifies the location of the bot's configuration file, which is a YAML file containing the following information: + +```yaml +# List of cogs to load when the bot starts, identified by their package name. +cogs: + - + - +``` + +By default, the application searches for a file named `application.yaml` in the directory from which it is launched. +In the Docker image, this file is located at `/app/application.yaml`. + +### `LOG_LEVEL` The minimum log level to display. The following levels are available: @@ -30,15 +53,17 @@ The minimum log level to display. The following levels are available: - `ERROR` - `CRITICAL` -## `REDIS_HOST` +The default log level is `INFO`. + +### `REDIS_HOST` The hostname of the Redis server. Defaults to `localhost`. -## `REDIS_PORT` +### `REDIS_PORT` The port of the Redis server. Defaults to `6379`. -## `REDIS_PASSWORD` +### `REDIS_PASSWORD` The password of the Redis server. Set this variable if your Redis server requires authentication. No password is set by default. From 32ebf9e9ef3c4f589fd042755415ac9c08fe3592 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Sat, 20 Jul 2024 04:53:40 +0000 Subject: [PATCH 030/168] ci: fix redis password config --- .devcontainer/docker-compose.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.devcontainer/docker-compose.yaml b/.devcontainer/docker-compose.yaml index f196600..73d52f2 100644 --- a/.devcontainer/docker-compose.yaml +++ b/.devcontainer/docker-compose.yaml @@ -14,7 +14,7 @@ services: redis-stack: image: redis/redis-stack:7.2.0-v11 environment: - REDIS_PASSWORD: redis + REDIS_ARGS: "--requirepass redis" volumes: - redis-stack-data:/data network_mode: service:dev From f799dad63cfab2a8fe4212c2fc72d91031dc31a1 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Sat, 20 Jul 2024 04:54:50 +0000 Subject: [PATCH 031/168] fix: improved error handling and settings validation --- courageous_comets/__init__.py | 3 + courageous_comets/__main__.py | 71 ++++++++++++-------- courageous_comets/client.py | 10 +-- courageous_comets/error.py | 2 - courageous_comets/exceptions.py | 36 ++++++++++ courageous_comets/settings.py | 113 +++++++++++++++++++++++++++++--- 6 files changed, 189 insertions(+), 46 deletions(-) delete mode 100644 courageous_comets/error.py create mode 100644 courageous_comets/exceptions.py diff --git a/courageous_comets/__init__.py b/courageous_comets/__init__.py index e69de29..eec4b71 100644 --- a/courageous_comets/__init__.py +++ b/courageous_comets/__init__.py @@ -0,0 +1,3 @@ +from .client import bot + +__all__ = ["bot"] diff --git a/courageous_comets/__main__.py b/courageous_comets/__main__.py index 600eb96..d551e6c 100644 --- a/courageous_comets/__main__.py +++ b/courageous_comets/__main__.py @@ -3,20 +3,30 @@ import sys import discord -from redis.asyncio import AuthenticationError, Redis, RedisError +import redis.asyncio as redis -from courageous_comets import settings -from courageous_comets.client import bot -from courageous_comets.error import CourageousCometsError +from courageous_comets import bot, exceptions, settings -logger = logging.getLogger(__name__) +async def init_redis() -> redis.Redis: + """ + Initialize the Redis connection. -async def init_redis() -> Redis: - """Initialize the Redis connection.""" - logger.debug("Connecting to Redis...") + Returns + ------- + redis.asyncio.Redis + The Redis connection instance. - instance = Redis( + Raises + ------ + courageous_comets.exceptions.AuthenticationError + If the Redis password is incorrect. + courageous_comets.exceptions.DatabaseConnectionError + If the connection to Redis cannot be established. + """ + logging.debug("Connecting to Redis...") + + instance = redis.Redis( host=settings.REDIS_HOST, port=settings.REDIS_PORT, password=settings.REDIS_PASSWORD, @@ -24,14 +34,14 @@ async def init_redis() -> Redis: try: await instance.ping() - except AuthenticationError as e: - message = "Redis authentication failed. Check the password." - raise CourageousCometsError(message) from e - except RedisError as e: + except redis.AuthenticationError as e: + message = "Redis authentication failed. Check the REDIS_PASSWORD environment variable." + raise exceptions.AuthenticationError(message) from e + except redis.RedisError as e: message = f"Could not connect to Redis at {settings.REDIS_HOST}:{settings.REDIS_PORT}" - raise CourageousCometsError(message) from e + raise exceptions.DatabaseConnectionError(message) from e - logger.info( + logging.info( "Connected to Redis at %s:%s", settings.REDIS_HOST, settings.REDIS_PORT, @@ -41,30 +51,37 @@ async def init_redis() -> Redis: async def main() -> None: - """Start the appication.""" - # Let discord.py set up the logging configuration - discord.utils.setup_logging(level=settings.LOG_LEVEL) + """ + Start the appication. + + Handles the setup and teardown of the Discord client and Redis connection. + If a critical error occurs, attempt to shut down gracefully. - logger.info("Starting the Courageous Comets application ☄️") + Raises + ------ + courageous_comets.exceptions.AuthenticationError + If the Discord token is not valid. + """ + logging.info("Starting the Courageous Comets application ☄️") redis = await init_redis() try: - logger.info("Starting the Discord client 🚀") - await bot.start(settings.DISCORD_TOKEN or "") + logging.info("Starting the Discord client 🚀") + await bot.start(settings.DISCORD_TOKEN) except discord.LoginFailure as e: - message = "Discord login failed. Check the token." - raise CourageousCometsError(message) from e + message = "Discord login failed. Check the DISCORD_TOKEN environment variable." + raise exceptions.AuthenticationError(message) from e finally: - logger.info("Shutting down gracefully...") - logger.debug("Closing Redis connection...") + logging.info("Shutting down gracefully...") + logging.debug("Closing Redis connection...") await redis.aclose() - logger.info("Application shutdown complete. Goodbye! 👋") + logging.info("Application shutdown complete. Goodbye! 👋") try: asyncio.run(main()) -except (CourageousCometsError, discord.DiscordException) as e: +except (exceptions.CourageousCometsError, discord.DiscordException) as e: logging.critical( "A fatal error occurred while running the bot.", exc_info=e, diff --git a/courageous_comets/client.py b/courageous_comets/client.py index 8facaaf..ac59465 100644 --- a/courageous_comets/client.py +++ b/courageous_comets/client.py @@ -1,6 +1,5 @@ import logging import typing -from pathlib import Path import discord import yaml @@ -15,7 +14,7 @@ intents.members = True intents.message_content = True -with Path(settings.BOT_CONFIG_PATH).open() as config_file: +with settings.BOT_CONFIG_PATH.open() as config_file: CONFIG = yaml.safe_load(config_file) bot = commands.Bot(command_prefix=commands.when_mentioned, intents=intents) @@ -34,12 +33,7 @@ async def setup_hook() -> None: try: await bot.load_extension(cog) logger.info("Loaded cog %s", cog) - except ( - commands.ExtensionNotFound, - commands.ExtensionAlreadyLoaded, - commands.NoEntryPointError, - commands.ExtensionFailed, - ) as e: + except commands.ExtensionError as e: logger.exception("Failed to load cog %s", cog, exc_info=e) diff --git a/courageous_comets/error.py b/courageous_comets/error.py deleted file mode 100644 index c9c4282..0000000 --- a/courageous_comets/error.py +++ /dev/null @@ -1,2 +0,0 @@ -class CourageousCometsError(Exception): - """Base class for all Courageous Comets exceptions.""" diff --git a/courageous_comets/exceptions.py b/courageous_comets/exceptions.py new file mode 100644 index 0000000..10ebd34 --- /dev/null +++ b/courageous_comets/exceptions.py @@ -0,0 +1,36 @@ +from dataclasses import dataclass + + +class CourageousCometsError(Exception): + """Base class for all Courageous Comets exceptions.""" + + +class AuthenticationError(CourageousCometsError): + """Raised when authentication with an external service fails.""" + + +@dataclass(kw_only=True) +class ConfigurationValueError[T](CourageousCometsError): + """ + Raised when a configuration value is invalid. + + Parameters + ---------- + key: str + The configuration key. + value: T, optional + The invalid value provided. + reason: str + The reason why the value is considered invalid. + """ + + key: str + value: T | None + reason: str + + def __str__(self) -> str: + return f"Invalid value '{self.value}' for configuration key '{self.key}': {self.reason}" + + +class DatabaseConnectionError(CourageousCometsError): + """Raised when a connection to the database cannot be established.""" diff --git a/courageous_comets/settings.py b/courageous_comets/settings.py index d786b6f..f5cc1f4 100644 --- a/courageous_comets/settings.py +++ b/courageous_comets/settings.py @@ -2,21 +2,116 @@ import logging import os +import sys +from pathlib import Path +from discord.utils import setup_logging from dotenv import load_dotenv -from courageous_comets.error import CourageousCometsError +from courageous_comets.exceptions import ConfigurationValueError + +def read_bot_config_path() -> Path: + """ + Read the bot configuration path from the environment. + + Returns + ------- + pathlib.Path + The path to the bot configuration file. + + Raises + ------ + courageous_comets.exceptions.ConfigurationValueError + If the path does not exist. + """ + result = Path(os.getenv("BOT_CONFIG_PATH", "application.yaml")) + + if not result.exists(): + raise ConfigurationValueError( + key="BOT_CONFIG_PATH", + value=os.getenv("BOT_CONFIG_PATH"), + reason=f"No file found at path '{result.resolve()}'", + ) + + return result + + +def read_discord_token() -> str: + """ + Read the Discord token from the environment. + + Returns + ------- + str + The Discord bot token. + + Raises + ------ + courageous_comets.exceptions.ConfigurationValueError + If the token is not found + """ + result = os.getenv("DISCORD_TOKEN", "") + + if not result: + raise ConfigurationValueError( + key="DISCORD_TOKEN", + value=os.getenv("DISCORD_TOKEN"), + reason="No Discord bot token found. Set the DISCORD_TOKEN environment variable.", + ) + + return result + + +def read_redis_port() -> int: + """ + Read the Redis port from the environment. + + Returns + ------- + int + The Redis port number. + + Raises + ------ + courageous_comets.exceptions.ConfigurationValueError + If the port is not a valid port number. + """ + try: + result = int(os.getenv("REDIS_PORT", "6379")) + except ValueError as e: + raise ConfigurationValueError( + key="REDIS_PORT", + value=os.getenv("REDIS_PORT"), + reason="Value must be an integer (e.g., 6379)", + ) from e + + if not (0 <= result <= 65535): # noqa: PLR2004 + raise ConfigurationValueError( + key="REDIS_PORT", + value=result, + reason="Value must be a valid port number (0-65535)", + ) + + return result + + +# Load environment variables from a .env file. If the file does not exist, this does nothing. load_dotenv() -BOT_CONFIG_PATH = os.getenv("BOT_CONFIG_PATH", "application.yaml") -DISCORD_TOKEN = os.getenv("DISCORD_TOKEN") +# Let discord.py set up the logging configuration LOG_LEVEL = logging.getLevelNamesMapping().get(os.getenv("LOG_LEVEL", "INFO"), logging.INFO) -REDIS_HOST = os.getenv("REDIS_HOST", "localhost") -REDIS_PASSWORD = os.getenv("REDIS_PASSWORD") +setup_logging(level=LOG_LEVEL) + try: - REDIS_PORT = int(os.getenv("REDIS_PORT", "6379")) -except ValueError as e: - message = f"REDIS_PORT must be an integer, got {os.getenv('REDIS_PORT')}" - raise CourageousCometsError(message) from e + DISCORD_TOKEN = read_discord_token() + BOT_CONFIG_PATH = read_bot_config_path() + REDIS_HOST = os.getenv("REDIS_HOST", "localhost") + REDIS_PORT = read_redis_port() + REDIS_PASSWORD = os.getenv("REDIS_PASSWORD") +except ConfigurationValueError as e: + logging.critical("Cannot start the application due to configuration errors", exc_info=e) + sys.exit(1) + +logging.info("Configuration loaded successfully") From c25b36f49999aee0a14f80faef36e45f60a73724 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Sat, 20 Jul 2024 04:55:04 +0000 Subject: [PATCH 032/168] =?UTF-8?q?bump:=20version=200.0.6=20=E2=86=92=200?= =?UTF-8?q?.1.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/CHANGELOG.md | 16 ++++++++++++++++ pyproject.toml | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index afc2cf0..4337f35 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,3 +1,19 @@ +## v0.1.0 (2024-07-20) + +### Feat + +- add bot boilerplate + +### Fix + +- improved error handling and settings validation +- include application config in docker image (#15) +- set redis host from environment variables + +### Refactor + +- improve error handling and logging + ## v0.0.6 (2024-07-19) ## v0.0.5 (2024-07-17) diff --git a/pyproject.toml b/pyproject.toml index 2459e09..ef345ad 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "courageous-comets" -version = "0.0.6" +version = "0.1.0" authors = ["Courageous Comets"] description = "" readme = "docs/README.md" From 493108a4f9cde823ec0fe442eafddd1a00fd7685 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Sat, 20 Jul 2024 07:24:25 +0000 Subject: [PATCH 033/168] refactor: improve type hints --- courageous_comets/client.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/courageous_comets/client.py b/courageous_comets/client.py index ac59465..b833e99 100644 --- a/courageous_comets/client.py +++ b/courageous_comets/client.py @@ -1,5 +1,6 @@ import logging import typing +from collections.abc import Collection import discord import yaml @@ -41,8 +42,8 @@ async def setup_hook() -> None: @commands.guild_only() @commands.is_owner() async def sync( - ctx: commands.Context, - guilds: commands.Greedy[discord.Object], + ctx: commands.Context[commands.Bot], + guilds: Collection[discord.Object], spec: typing.Literal["~", "*", "^"] | None = None, ) -> None: """ @@ -63,29 +64,31 @@ async def sync( """ async with ctx.typing(): if not guilds: - if spec == "~": + if spec == "~" and ctx.guild is not None: synced = await ctx.bot.tree.sync(guild=ctx.guild) elif spec == "*": synced = await ctx.bot.tree.sync() - elif spec == "^": + elif spec == "^" and ctx.guild is not None: ctx.bot.tree.clear_commands(guild=ctx.guild) await ctx.bot.tree.sync(guild=ctx.guild) synced = [] - else: + elif ctx.guild is not None: ctx.bot.tree.copy_global_to(guild=ctx.guild) synced = await ctx.bot.tree.sync(guild=ctx.guild) - scope = "globally" if spec == "*" else "to the current guild." - await ctx.send(f"Synced {len(synced)} commands {scope}") + scope = "globally." if spec == "*" else "to the current guild." + await ctx.send(f"Synced {len(synced)} command(s) {scope}") return ret = 0 + for guild in guilds: try: await ctx.bot.tree.sync(guild=guild) - except discord.HTTPException: - pass + except discord.HTTPException as e: + logger.exception("Failed to sync to guild %s", guild, exc_info=e) else: ret += 1 + await ctx.send(f"Synced the tree to {ret}/{len(guilds)}.") From 19111cde1c81dda8b1c2a8f6ec00b26752a01bac Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Sat, 20 Jul 2024 07:24:52 +0000 Subject: [PATCH 034/168] test: add unit tests for core app handlers --- courageous_comets/test__client.py | 222 ++++++++++++++++++++++++++++++ pyproject.toml | 5 + 2 files changed, 227 insertions(+) create mode 100644 courageous_comets/test__client.py diff --git a/courageous_comets/test__client.py b/courageous_comets/test__client.py new file mode 100644 index 0000000..6f3e22a --- /dev/null +++ b/courageous_comets/test__client.py @@ -0,0 +1,222 @@ +from typing import Self + +import discord +import pytest +from discord.ext import commands +from pytest_mock import MockerFixture, MockType + +from .client import bot, intents, logger, on_ready, setup_hook, sync + + +class MockAsyncContextManager: + """Mock an asynchronous context manager.""" + + async def __aenter__(self, *args, **kwargs) -> Self: # noqa: ANN002, ANN003 + return self + + async def __aexit__(self, *args, **kwargs) -> None: # noqa: ANN002, ANN003 + pass + + +@pytest.fixture() +def mock_context(mocker: MockerFixture) -> MockType: + """Create a mock context for testing.""" + ctx = mocker.Mock(spec=commands.Context) + ctx.bot = mocker.Mock() + ctx.bot.tree = mocker.Mock() + ctx.bot.tree.clear_commands = mocker.Mock() + ctx.bot.tree.copy_global_to = mocker.Mock() + ctx.bot.tree.sync = mocker.AsyncMock(return_value=[mocker.Mock()]) + ctx.guild = mocker.Mock() + ctx.send = mocker.AsyncMock() + ctx.typing = mocker.MagicMock(MockAsyncContextManager()) + return ctx + + +def test__bot_has_required_intents() -> None: + """ + Test whether the bot has the required intents. + + Asserts + ------- + - The bot has the required intents. + """ + assert bot.intents == intents + + +@pytest.mark.asyncio() +async def test__on_ready_logs_message(mocker: MockerFixture) -> None: + """ + Test whether the on_ready function logs the expected message. + + Asserts + ------- + - The logger.info function is called with the expected message. + """ + logger_info = mocker.spy(logger, "info") + await on_ready() + logger_info.assert_called_with("Logged in as %s", mocker.ANY) + + +@pytest.mark.asyncio() +async def test__setup_hook_loads_all_cogs(mocker: MockerFixture) -> None: + """ + Test whether the setup_hook function loads all cogs from the config file. + + Asserts + ------- + - The bot.load_extension function is called for each cog in the config file. + """ + cogs = ["cog1", "cog2", "cog3"] + + mocker.patch("courageous_comets.client.CONFIG", {"cogs": cogs}) + load_extension = mocker.patch("discord.ext.commands.Bot.load_extension") + + await setup_hook() + + for cog in cogs: + load_extension.assert_any_call(cog) + + +@pytest.mark.asyncio() +async def test__setup_hook_logs_loaded_cogs(mocker: MockerFixture) -> None: + """ + Test whether the setup_hook function logs the correct message for each cog. + + Asserts + ------- + - The logger.info function is called with the correct message for each cog. + """ + cogs = ["cog1", "cog2", "cog3"] + + mocker.patch("courageous_comets.client.CONFIG", {"cogs": cogs}) + mocker.patch("discord.ext.commands.Bot.load_extension", return_value=None) + logger_info = mocker.spy(logger, "info") + + await setup_hook() + + for cog in cogs: + logger_info.assert_any_call("Loaded cog %s", cog) + + +@pytest.mark.asyncio() +async def test__setup_hook_logs_exception_on_extension_error(mocker: MockerFixture) -> None: + """ + Test whether the setup_hook function logs an exception when an ExtensionError is raised. + + Asserts + ------- + - The logger.exception function is called when an ExtensionError is raised. + """ + mocker.patch("courageous_comets.client.CONFIG", {"cogs": ["cog1"]}) + expected = commands.ExtensionError(name="cog1") + mocker.patch("discord.ext.commands.Bot.load_extension", side_effect=expected) + + logger_exception = mocker.spy(logger, "exception") + + await setup_hook() + + logger_exception.assert_called_with("Failed to load cog %s", "cog1", exc_info=expected) + + +@pytest.mark.asyncio() +async def test__sync_syncs_to_current_guild(mock_context: MockType) -> None: + """ + Test whether the sync command syncs to the current guild. + + Asserts + ------- + - The bot.sync function is called with the expected parameters. + - The ctx.send function is called with the expected message. + """ + await sync(mock_context, [], "~") + + mock_context.bot.tree.sync.assert_called_with(guild=mock_context.guild) + mock_context.send.assert_called_with( + f"Synced {len(mock_context.bot.tree.sync.return_value)} command(s) to the current guild.", + ) + + +@pytest.mark.asyncio() +async def test__sync_syncs_to_global_scope(mock_context: MockType) -> None: + """ + Test whether the sync command syncs to the global scope. + + Asserts + ------- + - The bot.sync function is called with the expected parameters. + - The ctx.send function is called with the expected message + """ + await sync(mock_context, [], "*") + + mock_context.bot.tree.sync.to_have_been_called_with() + mock_context.send.assert_called_with( + f"Synced {len(mock_context.bot.tree.sync.return_value)} command(s) globally.", + ) + + +@pytest.mark.asyncio() +async def test__sync_removes_non_global_commands(mock_context: MockType) -> None: + """ + Test whether the sync command removes non-global commands. + + Asserts + ------- + - The bot.tree.clear_commands function is called with the expected parameters. + - The bot.sync function is called with the expected parameters + - The ctx.send function is called with the expected message + """ + await sync(mock_context, [], "^") + + mock_context.bot.tree.clear_commands.assert_called_with(guild=mock_context.guild) + mock_context.bot.tree.sync.assert_called_with(guild=mock_context.guild) + mock_context.send.assert_called_with("Synced 0 command(s) to the current guild.") + + +@pytest.mark.asyncio() +async def test__sync_syncs_to_given_guilds( + mocker: MockerFixture, + mock_context: MockType, +) -> None: + """ + Test whether the sync command syncs to the given guilds. + + Asserts + ------- + - The bot.sync function is called with the expected parameters. + - The ctx.send function is called with the expected message. + """ + guilds = [mocker.Mock(), mocker.Mock()] + await sync(mock_context, guilds) + + for guild in guilds: + mock_context.bot.tree.sync.assert_any_call(guild=guild) + + mock_context.send.assert_called_with(f"Synced the tree to {len(guilds)}/{len(guilds)}.") + + +@pytest.mark.asyncio() +async def test__sync_logs_exception_on_http_exception( + mocker: MockerFixture, + mock_context: MockType, +) -> None: + """ + Test whether the sync command handles an HTTPException correctly. + + Asserts + ------- + - The logger.exception function is called when an HTTPException is raised. + - The ctx.send function is called with the expected message. + """ + guilds = [mocker.Mock(), mocker.Mock()] + expected = discord.HTTPException(response=mocker.Mock(), message="Failed to sync") + mock_context.bot.tree.sync.side_effect = expected + + logger_exception = mocker.spy(logger, "exception") + + await sync(mock_context, guilds) + + for guild in guilds: + logger_exception.assert_any_call("Failed to sync to guild %s", guild, exc_info=expected) + + mock_context.send.assert_called_with(f"Synced the tree to 0/{len(guilds)}.") diff --git a/pyproject.toml b/pyproject.toml index ef345ad..f98a507 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,6 +33,11 @@ typeCheckingMode = "basic" pythonVersion = "3.12" reportUnnecessaryTypeIgnoreComment = "error" +[tool.pytest.ini_options] +filterwarnings = [ + "ignore:'audioop' is deprecated and slated for removal in Python 3.13", +] + [tool.ruff] line-length = 100 target-version = "py312" From 0e2af063525e9aefaff9123fce2c4f44bc96df40 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Sat, 20 Jul 2024 07:30:37 +0000 Subject: [PATCH 035/168] ci: make discord token available during testing --- .github/workflows/ci.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 4f34e2c..345ecb6 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -12,6 +12,9 @@ jobs: ci: runs-on: ubuntu-latest + env: + DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }} + steps: - name: Checkout code uses: actions/checkout@v4 From 910796c66d8127ebce0ef9f3ab240619fc6089d5 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Sat, 20 Jul 2024 10:51:10 +0000 Subject: [PATCH 036/168] test: fix warning caused by test --- courageous_comets/test__client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/courageous_comets/test__client.py b/courageous_comets/test__client.py index 6f3e22a..78e1d80 100644 --- a/courageous_comets/test__client.py +++ b/courageous_comets/test__client.py @@ -149,7 +149,7 @@ async def test__sync_syncs_to_global_scope(mock_context: MockType) -> None: """ await sync(mock_context, [], "*") - mock_context.bot.tree.sync.to_have_been_called_with() + mock_context.bot.tree.sync.to_have_been_called() mock_context.send.assert_called_with( f"Synced {len(mock_context.bot.tree.sync.return_value)} command(s) globally.", ) From d2f9265a835a99f50243f6023e20060dc0ccbe33 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Sat, 20 Jul 2024 10:54:43 +0000 Subject: [PATCH 037/168] test: fix warning caused by test --- courageous_comets/test__client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/courageous_comets/test__client.py b/courageous_comets/test__client.py index 78e1d80..d8b4fb9 100644 --- a/courageous_comets/test__client.py +++ b/courageous_comets/test__client.py @@ -149,7 +149,7 @@ async def test__sync_syncs_to_global_scope(mock_context: MockType) -> None: """ await sync(mock_context, [], "*") - mock_context.bot.tree.sync.to_have_been_called() + await mock_context.bot.tree.sync.to_have_been_called_with() mock_context.send.assert_called_with( f"Synced {len(mock_context.bot.tree.sync.return_value)} command(s) globally.", ) From 7491fb697740d0974696751b8c105fc9e34f6339 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Sat, 20 Jul 2024 11:01:45 +0000 Subject: [PATCH 038/168] test: assert awaited rather than called --- courageous_comets/test__client.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/courageous_comets/test__client.py b/courageous_comets/test__client.py index d8b4fb9..be3d030 100644 --- a/courageous_comets/test__client.py +++ b/courageous_comets/test__client.py @@ -131,8 +131,8 @@ async def test__sync_syncs_to_current_guild(mock_context: MockType) -> None: """ await sync(mock_context, [], "~") - mock_context.bot.tree.sync.assert_called_with(guild=mock_context.guild) - mock_context.send.assert_called_with( + mock_context.bot.tree.sync.assert_awaited_with(guild=mock_context.guild) + mock_context.send.assert_awaited_with( f"Synced {len(mock_context.bot.tree.sync.return_value)} command(s) to the current guild.", ) @@ -149,8 +149,8 @@ async def test__sync_syncs_to_global_scope(mock_context: MockType) -> None: """ await sync(mock_context, [], "*") - await mock_context.bot.tree.sync.to_have_been_called_with() - mock_context.send.assert_called_with( + mock_context.bot.tree.sync.assert_awaited_with() + mock_context.send.assert_awaited_with( f"Synced {len(mock_context.bot.tree.sync.return_value)} command(s) globally.", ) @@ -169,8 +169,8 @@ async def test__sync_removes_non_global_commands(mock_context: MockType) -> None await sync(mock_context, [], "^") mock_context.bot.tree.clear_commands.assert_called_with(guild=mock_context.guild) - mock_context.bot.tree.sync.assert_called_with(guild=mock_context.guild) - mock_context.send.assert_called_with("Synced 0 command(s) to the current guild.") + mock_context.bot.tree.sync.assert_awaited_with(guild=mock_context.guild) + mock_context.send.assert_awaited_with("Synced 0 command(s) to the current guild.") @pytest.mark.asyncio() @@ -187,12 +187,13 @@ async def test__sync_syncs_to_given_guilds( - The ctx.send function is called with the expected message. """ guilds = [mocker.Mock(), mocker.Mock()] + await sync(mock_context, guilds) for guild in guilds: - mock_context.bot.tree.sync.assert_any_call(guild=guild) + mock_context.bot.tree.sync.assert_any_await(guild=guild) - mock_context.send.assert_called_with(f"Synced the tree to {len(guilds)}/{len(guilds)}.") + mock_context.send.assert_awaited_with(f"Synced the tree to {len(guilds)}/{len(guilds)}.") @pytest.mark.asyncio() @@ -219,4 +220,4 @@ async def test__sync_logs_exception_on_http_exception( for guild in guilds: logger_exception.assert_any_call("Failed to sync to guild %s", guild, exc_info=expected) - mock_context.send.assert_called_with(f"Synced the tree to 0/{len(guilds)}.") + mock_context.send.assert_awaited_with(f"Synced the tree to 0/{len(guilds)}.") From 5602f15e4465bf99d6e89c1589b6a17b844be3f3 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Sat, 20 Jul 2024 11:21:36 +0000 Subject: [PATCH 039/168] test: simplify setup, add missing checks, update docstrings --- courageous_comets/test__client.py | 43 ++++++++++++++++--------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/courageous_comets/test__client.py b/courageous_comets/test__client.py index be3d030..96037c4 100644 --- a/courageous_comets/test__client.py +++ b/courageous_comets/test__client.py @@ -21,14 +21,10 @@ async def __aexit__(self, *args, **kwargs) -> None: # noqa: ANN002, ANN003 @pytest.fixture() def mock_context(mocker: MockerFixture) -> MockType: """Create a mock context for testing.""" - ctx = mocker.Mock(spec=commands.Context) - ctx.bot = mocker.Mock() - ctx.bot.tree = mocker.Mock() - ctx.bot.tree.clear_commands = mocker.Mock() - ctx.bot.tree.copy_global_to = mocker.Mock() + ctx = mocker.MagicMock(spec=commands.Context) + ctx.bot = mocker.MagicMock(spec=commands.Bot) + ctx.bot.tree = mocker.MagicMock(spec=discord.app_commands.CommandTree) ctx.bot.tree.sync = mocker.AsyncMock(return_value=[mocker.Mock()]) - ctx.guild = mocker.Mock() - ctx.send = mocker.AsyncMock() ctx.typing = mocker.MagicMock(MockAsyncContextManager()) return ctx @@ -85,17 +81,19 @@ async def test__setup_hook_logs_loaded_cogs(mocker: MockerFixture) -> None: Asserts ------- + - The bot.load_extension function is awaited for each cog in the config file. - The logger.info function is called with the correct message for each cog. """ cogs = ["cog1", "cog2", "cog3"] - mocker.patch("courageous_comets.client.CONFIG", {"cogs": cogs}) - mocker.patch("discord.ext.commands.Bot.load_extension", return_value=None) + + load_extension = mocker.patch("discord.ext.commands.Bot.load_extension", return_value=None) logger_info = mocker.spy(logger, "info") await setup_hook() for cog in cogs: + load_extension.assert_any_await(cog) logger_info.assert_any_call("Loaded cog %s", cog) @@ -106,16 +104,19 @@ async def test__setup_hook_logs_exception_on_extension_error(mocker: MockerFixtu Asserts ------- + - The bot.load_extension function is awaited for each cog in the config file. - The logger.exception function is called when an ExtensionError is raised. """ - mocker.patch("courageous_comets.client.CONFIG", {"cogs": ["cog1"]}) - expected = commands.ExtensionError(name="cog1") - mocker.patch("discord.ext.commands.Bot.load_extension", side_effect=expected) + cogs = ["cog1"] + mocker.patch("courageous_comets.client.CONFIG", {"cogs": cogs}) + expected = commands.ExtensionError(name="cog1") + load_extension = mocker.patch("discord.ext.commands.Bot.load_extension", side_effect=expected) logger_exception = mocker.spy(logger, "exception") await setup_hook() + load_extension.assert_awaited_with("cog1") logger_exception.assert_called_with("Failed to load cog %s", "cog1", exc_info=expected) @@ -126,8 +127,8 @@ async def test__sync_syncs_to_current_guild(mock_context: MockType) -> None: Asserts ------- - - The bot.sync function is called with the expected parameters. - - The ctx.send function is called with the expected message. + - The bot.sync function is awaited with the expected parameters. + - The ctx.send function is awaited with the expected message. """ await sync(mock_context, [], "~") @@ -144,8 +145,8 @@ async def test__sync_syncs_to_global_scope(mock_context: MockType) -> None: Asserts ------- - - The bot.sync function is called with the expected parameters. - - The ctx.send function is called with the expected message + - The bot.sync function is awaited with the expected parameters. + - The ctx.send function is awaited with the expected message """ await sync(mock_context, [], "*") @@ -163,8 +164,8 @@ async def test__sync_removes_non_global_commands(mock_context: MockType) -> None Asserts ------- - The bot.tree.clear_commands function is called with the expected parameters. - - The bot.sync function is called with the expected parameters - - The ctx.send function is called with the expected message + - The bot.sync function is awaited with the expected parameters + - The ctx.send function is awaited with the expected message """ await sync(mock_context, [], "^") @@ -183,8 +184,8 @@ async def test__sync_syncs_to_given_guilds( Asserts ------- - - The bot.sync function is called with the expected parameters. - - The ctx.send function is called with the expected message. + - The bot.sync function is awaited with the expected parameters. + - The ctx.send function is awaited with the expected message. """ guilds = [mocker.Mock(), mocker.Mock()] @@ -207,7 +208,7 @@ async def test__sync_logs_exception_on_http_exception( Asserts ------- - The logger.exception function is called when an HTTPException is raised. - - The ctx.send function is called with the expected message. + - The ctx.send function is awaited with the expected message. """ guilds = [mocker.Mock(), mocker.Mock()] expected = discord.HTTPException(response=mocker.Mock(), message="Failed to sync") From dbb23d0cabb4f4e85b84c49a787a1d5cccb6879c Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Sat, 20 Jul 2024 15:07:39 +0200 Subject: [PATCH 040/168] feat: download nltk resources on startup (#22) --- .gitignore | 3 ++ Dockerfile | 4 +++ application.yaml | 3 ++ courageous_comets/__main__.py | 39 +++++++++++++++++++++ courageous_comets/exceptions.py | 4 +++ courageous_comets/settings.py | 1 + docs/admin-guide/configuration.md | 6 ++++ poetry.lock | 58 ++++++++++++++++++++++++++++++- pyproject.toml | 1 + 9 files changed, 118 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index ac00087..7fd16ee 100644 --- a/.gitignore +++ b/.gitignore @@ -178,3 +178,6 @@ pyrightconfig.json # Vim *.vim + +# NLTK +nltk_data diff --git a/Dockerfile b/Dockerfile index bc7d4b7..9723e11 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,6 +15,10 @@ RUN adduser --system courageous-comets # Set the working directory WORKDIR /app +# Assign the working directory to the non-root user and set the permissions +RUN chown -R courageous-comets /app && \ + chmod -R 0600 /app + # Copy the app config and the wheel file to the working directory and set the permissions COPY --chown=courageous-comets --chmod=0400 application.yaml dist/*.whl ./ diff --git a/application.yaml b/application.yaml index c727f13..0e7de90 100644 --- a/application.yaml +++ b/application.yaml @@ -1,3 +1,6 @@ cogs: - courageous_comets.cogs.ping - jishaku +nltk: + - stopwords + - vader_lexicon diff --git a/courageous_comets/__main__.py b/courageous_comets/__main__.py index d551e6c..4f1de61 100644 --- a/courageous_comets/__main__.py +++ b/courageous_comets/__main__.py @@ -3,11 +3,49 @@ import sys import discord +import nltk import redis.asyncio as redis +import yaml from courageous_comets import bot, exceptions, settings +async def download_nltk_resource(resource: str, download_dir: str) -> None: + """Download an NLTK resource to the specified directory.""" + logging.debug("Downloading NLTK resource '%s'...", resource) + try: + await asyncio.to_thread( + nltk.download, + resource, + download_dir=download_dir, + quiet=True, + raise_on_error=True, + ) + except ValueError as e: + message = f"Invalid NLTK resource '{resource}'" + raise exceptions.NltkInitializationError(message) from e + + +async def init_nltk() -> None: + """ + Ensure all required NLTK resources are downloaded. + + Downloads the resources specified in the bot configuration file. + """ + with settings.BOT_CONFIG_PATH.open("r") as file: + config = yaml.safe_load(file) + + resources = config.get("nltk", []) + + download_tasks = [ + download_nltk_resource(resource, settings.NLTK_DATA_DIR) for resource in resources + ] + + await asyncio.gather(*download_tasks) + + logging.info("NLTK resources downloaded") + + async def init_redis() -> redis.Redis: """ Initialize the Redis connection. @@ -64,6 +102,7 @@ async def main() -> None: """ logging.info("Starting the Courageous Comets application ☄️") + await init_nltk() redis = await init_redis() try: diff --git a/courageous_comets/exceptions.py b/courageous_comets/exceptions.py index 10ebd34..63acbb9 100644 --- a/courageous_comets/exceptions.py +++ b/courageous_comets/exceptions.py @@ -34,3 +34,7 @@ def __str__(self) -> str: class DatabaseConnectionError(CourageousCometsError): """Raised when a connection to the database cannot be established.""" + + +class NltkInitializationError(CourageousCometsError): + """Raised when the application fails to download the NLTK dependencies on startup.""" diff --git a/courageous_comets/settings.py b/courageous_comets/settings.py index f5cc1f4..6a20067 100644 --- a/courageous_comets/settings.py +++ b/courageous_comets/settings.py @@ -107,6 +107,7 @@ def read_redis_port() -> int: try: DISCORD_TOKEN = read_discord_token() BOT_CONFIG_PATH = read_bot_config_path() + NLTK_DATA_DIR = os.getenv("NLTK_DATA_DIR", "nltk_data") REDIS_HOST = os.getenv("REDIS_HOST", "localhost") REDIS_PORT = read_redis_port() REDIS_PASSWORD = os.getenv("REDIS_PASSWORD") diff --git a/docs/admin-guide/configuration.md b/docs/admin-guide/configuration.md index dd99177..0b8d30d 100644 --- a/docs/admin-guide/configuration.md +++ b/docs/admin-guide/configuration.md @@ -7,6 +7,7 @@ The following environment variables are available to configure the application: | [`DISCORD_TOKEN`](#discord_token) | The Discord bot token. | Yes | - | | [`BOT_CONFIG_PATH`](#bot_config_path) | The path to the bot's configuration file. | No | `application.yaml` | | [`LOG_LEVEL`](#log_level) | The minimum log level. | No | `INFO` | +| [`NLTK_DATA_DIR`](#nltk_data_dir) | The directory containing NLTK data files. | No | `/nltk_data` | | [`REDIS_HOST`](#redis_host) | The Redis host. | No | `localhost` | | [`REDIS_PORT`](#redis_port) | The Redis port. | No | `6379` | | [`REDIS_PASSWORD`](#redis_password) | The Redis password. | No | - | @@ -55,6 +56,11 @@ The minimum log level to display. The following levels are available: The default log level is `INFO`. +### `NLTK_DATA_DIR` + +The directory containing NLTK data files. By default, this is set to `nltk_data` in the directory from which the +application is launched. In the Docker image, this directory is located at `/app/nltk_data`. + ### `REDIS_HOST` The hostname of the Redis server. Defaults to `localhost`. diff --git a/poetry.lock b/poetry.lock index 1a70851..a933e8a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -745,6 +745,17 @@ publish = ["Jinja2 (>=3.0.3)"] test = ["coverage (>=6.3.2)", "flake8 (>=4.0.1)", "isort (>=5.10.1)", "pylint (>=2.11.1)", "pytest (>=7.0.1)", "pytest-asyncio (>=0.18.1)", "pytest-cov (>=3.0.0)", "pytest-mock (>=3.7.0)"] voice = ["yt-dlp (>=2022.3.8)"] +[[package]] +name = "joblib" +version = "1.4.2" +description = "Lightweight pipelining with Python functions" +optional = false +python-versions = ">=3.8" +files = [ + {file = "joblib-1.4.2-py3-none-any.whl", hash = "sha256:06d478d5674cbc267e7496a410ee875abd68e4340feff4490bcb7afb88060ae6"}, + {file = "joblib-1.4.2.tar.gz", hash = "sha256:2382c5816b2636fbd20a09e0f4e9dad4736765fdfb7dca582943b9c1366b3f0e"}, +] + [[package]] name = "markdown" version = "3.6" @@ -1050,6 +1061,31 @@ files = [ {file = "multidict-6.0.5.tar.gz", hash = "sha256:f7e301075edaf50500f0b341543c41194d8df3ae5caf4702f2095f3ca73dd8da"}, ] +[[package]] +name = "nltk" +version = "3.8.1" +description = "Natural Language Toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "nltk-3.8.1-py3-none-any.whl", hash = "sha256:fd5c9109f976fa86bcadba8f91e47f5e9293bd034474752e92a520f81c93dda5"}, + {file = "nltk-3.8.1.zip", hash = "sha256:1834da3d0682cba4f2cede2f9aad6b0fafb6461ba451db0efb6f9c39798d64d3"}, +] + +[package.dependencies] +click = "*" +joblib = "*" +regex = ">=2021.8.3" +tqdm = "*" + +[package.extras] +all = ["matplotlib", "numpy", "pyparsing", "python-crfsuite", "requests", "scikit-learn", "scipy", "twython"] +corenlp = ["requests"] +machine-learning = ["numpy", "python-crfsuite", "scikit-learn", "scipy"] +plot = ["matplotlib"] +tgrep = ["pyparsing"] +twitter = ["twython"] + [[package]] name = "nodeenv" version = "1.9.1" @@ -1582,6 +1618,26 @@ files = [ {file = "tomlkit-0.13.0.tar.gz", hash = "sha256:08ad192699734149f5b97b45f1f18dad7eb1b6d16bc72ad0c2335772650d7b72"}, ] +[[package]] +name = "tqdm" +version = "4.66.4" +description = "Fast, Extensible Progress Meter" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tqdm-4.66.4-py3-none-any.whl", hash = "sha256:b75ca56b413b030bc3f00af51fd2c1a1a5eac6a0c1cca83cbb37a5c52abce644"}, + {file = "tqdm-4.66.4.tar.gz", hash = "sha256:e4d936c9de8727928f3be6079590e97d9abfe8d39a590be678eb5919ffc186bb"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[package.extras] +dev = ["pytest (>=6)", "pytest-cov", "pytest-timeout", "pytest-xdist"] +notebook = ["ipywidgets (>=6)"] +slack = ["slack-sdk"] +telegram = ["requests"] + [[package]] name = "urllib3" version = "2.2.2" @@ -1823,4 +1879,4 @@ test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", [metadata] lock-version = "2.0" python-versions = "~3.12" -content-hash = "e98a0974533a45bded47b6f6d1f0da774778f2b01134525a10ed46418e46f620" +content-hash = "41c4396e0eb0c4b6c7faa1024f39cea166058996cca3e4a043c1e703ef6bc01c" diff --git a/pyproject.toml b/pyproject.toml index f98a507..502f820 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,6 +14,7 @@ jishaku = "^2.5.2" python-dotenv = "^1.0.1" pyyaml = "^6.0.1" redis = { extras = ["hiredis"], version = "^5.0.7" } +nltk = "^3.8.1" [tool.poetry.dev-dependencies] commitizen = "3.27.0" From fe67b139e02414cbed480b11718824e3bbfdcd2a Mon Sep 17 00:00:00 2001 From: Uchechukwu Orji Date: Sat, 20 Jul 2024 14:51:28 +0100 Subject: [PATCH 041/168] feat: add api to store word frequency on redis (#23) --- courageous_comets/redis/keys.py | 46 +++++++++++++++++++++++++++++ courageous_comets/redis/messages.py | 21 +++++++++++++ courageous_comets/settings.py | 11 +++++-- courageous_comets/words.py | 25 ++++++++++++++++ 4 files changed, 101 insertions(+), 2 deletions(-) create mode 100644 courageous_comets/redis/keys.py create mode 100644 courageous_comets/redis/messages.py create mode 100644 courageous_comets/words.py diff --git a/courageous_comets/redis/keys.py b/courageous_comets/redis/keys.py new file mode 100644 index 0000000..b028d9d --- /dev/null +++ b/courageous_comets/redis/keys.py @@ -0,0 +1,46 @@ +from collections.abc import Callable +from functools import wraps +from typing import Any + +from courageous_comets import settings + + +def prefix_key(func: Callable[..., str]) -> Callable[..., str]: + """Prefix return values of methods with settings.REDIS_KEYS_PREFIX.""" + + @wraps(func) + def prefixed_method(self: object, *args: Any, **kwargs: Any) -> str: # noqa: ANN401 + key = func(self, *args, **kwargs) + return f"{settings.REDIS_KEYS_PREFIX}:{key}" + + return prefixed_method + + +class KeySchema: + """A class to generate key names for Redis data structures. + + This class contains a reference to all possible key names used + by the application. + """ + + def __init__(self, prefix: str = settings.REDIS_KEYS_PREFIX) -> None: + self.prefix = prefix + + @prefix_key + def guild_messages(self, guild_id: int) -> str: + """Key to messages for a Discord guild. + + Redis type: hash + """ + return f"{guild_id}:messages" + + @prefix_key + def guild_message_tokens(self, guild_id: int) -> str: + """Key to message tokens for a Discord guild. + + Redis type: hash + """ + return f"{guild_id}:messages:tokens" + + +key_schema = KeySchema() diff --git a/courageous_comets/redis/messages.py b/courageous_comets/redis/messages.py new file mode 100644 index 0000000..63c70e0 --- /dev/null +++ b/courageous_comets/redis/messages.py @@ -0,0 +1,21 @@ +from redis.asyncio import Redis + +from courageous_comets.redis.keys import key_schema + + +async def update_message_tokens( + redis: Redis, + guild_id: int, + words: dict[str, int], +) -> dict[str, int]: + """Update the frequency of each word in a guild.""" + async with redis.pipeline() as pipe: + for word, frequency in words.items(): + pipe.hincrby(key_schema.guild_message_tokens(guild_id), word, frequency) + new_frequencies: list[int] = await pipe.execute() + # Given dictionary keys are iterated in insertion order, execution of the + # instructions are evaluated in the same order, thus, the return values are + # in the same order. + return { # noqa: C416 + word: frequency for word, frequency in zip(words.keys(), new_frequencies, strict=False) + } diff --git a/courageous_comets/settings.py b/courageous_comets/settings.py index 6a20067..02c7f32 100644 --- a/courageous_comets/settings.py +++ b/courageous_comets/settings.py @@ -100,7 +100,10 @@ def read_redis_port() -> int: load_dotenv() # Let discord.py set up the logging configuration -LOG_LEVEL = logging.getLevelNamesMapping().get(os.getenv("LOG_LEVEL", "INFO"), logging.INFO) +LOG_LEVEL = logging.getLevelNamesMapping().get( + os.getenv("LOG_LEVEL", "INFO"), + logging.INFO, +) setup_logging(level=LOG_LEVEL) @@ -111,8 +114,12 @@ def read_redis_port() -> int: REDIS_HOST = os.getenv("REDIS_HOST", "localhost") REDIS_PORT = read_redis_port() REDIS_PASSWORD = os.getenv("REDIS_PASSWORD") + REDIS_KEYS_PREFIX = os.getenv("REDIS_KEYS_PREFIX", "courageous_comets") except ConfigurationValueError as e: - logging.critical("Cannot start the application due to configuration errors", exc_info=e) + logging.critical( + "Cannot start the application due to configuration errors", + exc_info=e, + ) sys.exit(1) logging.info("Configuration loaded successfully") diff --git a/courageous_comets/words.py b/courageous_comets/words.py new file mode 100644 index 0000000..0d8d138 --- /dev/null +++ b/courageous_comets/words.py @@ -0,0 +1,25 @@ +from collections import defaultdict + +from nltk.stem.snowball import SnowballStemmer, stopwords +from nltk.tokenize import word_tokenize + +STOP_WORDS = set(stopwords.words("english")) + + +def tokenize_sentence(sentence: str) -> list[str]: + """Split a sentence into tokens. + + The tokenizer applies stemming to the words that make up the sentence + using the Snowball stemmer and removes stopwords. + """ + stemmer = SnowballStemmer("english") + stemmed_words = [stemmer.stem(token) for token in word_tokenize(sentence)] + return [word for word in stemmed_words if len(word) > 1 and word not in STOP_WORDS] + + +def word_frequency(words: list[str]) -> dict[str, int]: + """Count the number of times each word appears in words.""" + frequency: dict[str, int] = defaultdict(int) + for word in words: + frequency[word] += 1 + return frequency From 13ed85b9f81d75f4ce9fa3e16eeb60aebef8344b Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Sat, 20 Jul 2024 19:23:24 +0200 Subject: [PATCH 042/168] feat: add sentiment analysis (#24) --- .github/workflows/ci.yaml | 1 + conftest.py | 26 ++++ courageous_comets/redis/keys.py | 15 +++ courageous_comets/sentiment.py | 127 ++++++++++++++++++ courageous_comets/settings.py | 2 +- courageous_comets/test__sentiment.py | 188 +++++++++++++++++++++++++++ 6 files changed, 358 insertions(+), 1 deletion(-) create mode 100644 conftest.py create mode 100644 courageous_comets/sentiment.py create mode 100644 courageous_comets/test__sentiment.py diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 345ecb6..230a573 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -14,6 +14,7 @@ jobs: env: DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }} + NLTK_DATA: ${{ github.workspace }}/nltk_data steps: - name: Checkout code diff --git a/conftest.py b/conftest.py new file mode 100644 index 0000000..98b373e --- /dev/null +++ b/conftest.py @@ -0,0 +1,26 @@ +import os +from pathlib import Path + +import nltk +import pytest +import yaml + + +@pytest.fixture(scope="session") +def application_config() -> dict: + """Load the application configuration for testing.""" + with Path("application.yaml").open("r") as file: + return yaml.safe_load(file) + + +@pytest.fixture(scope="session", autouse=True) +def _load_nltk_data(application_config: dict) -> None: + """Load the NLTK data for testing.""" + resources = application_config.get("nltk", []) + + for resource in resources: + nltk.download( + resource, + quiet=True, + download_dir=os.getenv("NLTK_DATA", "nltk_data"), + ) diff --git a/courageous_comets/redis/keys.py b/courageous_comets/redis/keys.py index b028d9d..1134130 100644 --- a/courageous_comets/redis/keys.py +++ b/courageous_comets/redis/keys.py @@ -42,5 +42,20 @@ def guild_message_tokens(self, guild_id: int) -> str: """ return f"{guild_id}:messages:tokens" + @prefix_key + def sentiment_tokens( + self, + guild_id: int, + channel_id: int, + user_id: int, + message_id: int, + ) -> str: + """ + Key to sentiment tokens for a message. + + Redis type: hash + """ + return f"{guild_id}:{channel_id}:{user_id}:{message_id}:sentiment" + key_schema = KeySchema() diff --git a/courageous_comets/sentiment.py b/courageous_comets/sentiment.py new file mode 100644 index 0000000..32a59fa --- /dev/null +++ b/courageous_comets/sentiment.py @@ -0,0 +1,127 @@ +import logging +from typing import TypedDict, cast + +from discord import Message +from nltk.sentiment import SentimentIntensityAnalyzer +from redis.asyncio import Redis + +from courageous_comets.redis.keys import key_schema + +MAX_MESSAGE_LENGTH = 256 + +logger = logging.getLogger(__name__) + + +class SentimentResult(TypedDict): + """Result of sentiment analysis.""" + + neg: float + neu: float + pos: float + compound: float + + +def calculate_sentiment(content: str, key: str) -> SentimentResult: + """ + Calculate the sentiment of a message. + + Uses the NLTK sentiment intensity analyzer to calculate the sentiment of a message. + + Messages can be up to 256 characters long. If a message is longer than 256 characters, + it will be truncated. + + Parameters + ---------- + content : str + The message content to analyze. + + Returns + ------- + SentimentResult + The sentiment of the message. + """ + truncated = content[:MAX_MESSAGE_LENGTH] + + if truncated != content: + logger.warning("Truncated message %s to %s characters", key, MAX_MESSAGE_LENGTH) + + sia = SentimentIntensityAnalyzer() + result = sia.polarity_scores(truncated) + + return cast(SentimentResult, result) + + +async def store_sentiment(message: Message, redis: Redis) -> None: + """ + Calculate the sentiment of a message and store the result in Redis. + + A message must have a guild, channel, author and message ID. If any of these are missing, + the message will be ignored. + + Empty messages will also be ignored. + + Parameters + ---------- + message : discord.Message + The message to process. + + Returns + ------- + SentimentResult + The sentiment of the message. + """ + # Ignore empty messages + if not message.content: + return + + # Extract the IDs from the message + guild_id = message.guild.id if message.guild else 0 + channel_id = message.channel.id if message.channel else 0 + user_id = message.author.id if message.author else 0 + message_id = message.id if message.id else 0 + + # Ignore messages without all required IDs + if not all((guild_id, channel_id, user_id, message_id)): + return + + # Construct the Redis key + key = key_schema.sentiment_tokens( + guild_id=guild_id, + channel_id=channel_id, + user_id=user_id, + message_id=message_id, + ) + + # Calculate the sentiment + sentiment = calculate_sentiment(message.content, key) + + # Store the sentiment in Redis + await redis.hset(key, mapping=sentiment) # type: ignore + + logger.info("Stored sentiment for message %s", key) + + +async def get_sentiment(key: str, redis: Redis) -> SentimentResult: + """ + Retrieve the sentiment of a message from Redis. + + Parameters + ---------- + key : str + The Redis key to retrieve the sentiment from. + redis : redis.asyncio.Redis + The Redis connection instance. + + Returns + ------- + SentimentResult + The sentiment of the message. + """ + neg, neu, pos, compound = await redis.hmget(key, "neg", "neu", "pos", "compound") # type: ignore + + return { + "neg": float(neg or 0), + "neu": float(neu or 0), + "pos": float(pos or 0), + "compound": float(compound or 0), + } diff --git a/courageous_comets/settings.py b/courageous_comets/settings.py index 02c7f32..cb46999 100644 --- a/courageous_comets/settings.py +++ b/courageous_comets/settings.py @@ -110,7 +110,7 @@ def read_redis_port() -> int: try: DISCORD_TOKEN = read_discord_token() BOT_CONFIG_PATH = read_bot_config_path() - NLTK_DATA_DIR = os.getenv("NLTK_DATA_DIR", "nltk_data") + NLTK_DATA_DIR = os.getenv("NLTK_DATA", "nltk_data") REDIS_HOST = os.getenv("REDIS_HOST", "localhost") REDIS_PORT = read_redis_port() REDIS_PASSWORD = os.getenv("REDIS_PASSWORD") diff --git a/courageous_comets/test__sentiment.py b/courageous_comets/test__sentiment.py new file mode 100644 index 0000000..6a24aab --- /dev/null +++ b/courageous_comets/test__sentiment.py @@ -0,0 +1,188 @@ +from unittest.mock import Mock + +import pytest +from pytest_mock import MockerFixture, MockType +from redis.asyncio import Redis + +from .sentiment import ( + MAX_MESSAGE_LENGTH, + calculate_sentiment, + get_sentiment, + logger, + store_sentiment, +) + + +@pytest.fixture() +def redis(mocker: MockerFixture) -> MockerFixture: + """Create a mock Redis instance for testing.""" + mock = mocker.AsyncMock(spec=Redis) + mock.hset = mocker.AsyncMock() + mock.hmget = mocker.AsyncMock() + return mock + + +def test__calculate_sentiment_analyzes_sentiment_of_given_text( + *, + mocker: MockerFixture, +) -> None: + """ + Test whether the sentiment calculation analyzes the sentiment of the given text. + + Asserts + ------- + - The function produces sentiment scores for the given text. + """ + result = calculate_sentiment("I love this product!", "test") + + assert result == { + "neg": mocker.ANY, + "neu": mocker.ANY, + "pos": mocker.ANY, + "compound": mocker.ANY, + } + + +@pytest.mark.parametrize( + ("message", "expected"), + [ + ("a" * MAX_MESSAGE_LENGTH, False), + ("a" * (MAX_MESSAGE_LENGTH + 1), True), + ], +) +def test__calculate_sentiment_truncates_long_messages( + *, + mocker: MockerFixture, + message: str, + expected: bool, +) -> None: + """ + Test whether the sentiment calculation truncates long messages. + + Asserts + ------- + - The function truncates messages longer than 256 characters. + """ + logger_warning = mocker.spy(logger, "warning") + calculate_sentiment(message, "test") + assert logger_warning.called == expected + + +@pytest.mark.asyncio() +async def test__store_sentiment_calculates_and_stores_sentiment( + *, + mocker: MockerFixture, + redis: MockType, +) -> None: + """ + Test whether the store sentiment function calculates and stores the sentiment of a message. + + Asserts + ------- + - The sentiment is calculated for the message content. + - The sentiment is stored in the database. + """ + message = mocker.Mock( + content="I love this product!", + guild=Mock(id=1), + channel=Mock(id=1), + author=Mock(id=1), + id=1, + ) + + await store_sentiment(message, redis) + + redis.hset.assert_awaited_with( + "courageous_comets:1:1:1:1:sentiment", + mapping={ + "neg": mocker.ANY, + "neu": mocker.ANY, + "pos": mocker.ANY, + "compound": mocker.ANY, + }, + ) + + +@pytest.mark.asyncio() +async def test__store_sentiment_ignores_empty_messages( + *, + mocker: MockerFixture, + redis: MockType, +) -> None: + """ + Test whether the store sentiment function ignores empty messages. + + Asserts + ------- + - The database is not updated when the message is empty. + """ + message = mocker.Mock(content="") + await store_sentiment(message, redis) + redis.hset.assert_not_awaited() + + +@pytest.mark.parametrize( + "message", + [ + Mock(content="test", guild=None, channel=None, author=None, id=1), + Mock(content="test", guild=Mock(id=1), channel=None, author=None, id=1), + Mock(content="test", guild=Mock(id=1), channel=Mock(id=1), author=None, id=1), + ], +) +@pytest.mark.asyncio() +async def test__store_sentiment_ignores_messages_without_ids( + *, + redis: MockType, + message: MockType, +) -> None: + """ + Test whether the store sentiment function ignores messages without IDs. + + Asserts + ------- + - The database is not updated when the message is missing IDs. + """ + await store_sentiment(message, redis) + redis.hset.assert_not_awaited() + + +@pytest.mark.asyncio() +async def test__get_sentiment_retrieves_sentiment_from_redis( + *, + redis: MockType, +) -> None: + """ + Test whether the get sentiment function retrieves the sentiment of a message from Redis. + + Asserts + ------- + - The sentiment is retrieved from the database. + """ + key = "courageous_comets:1:1:1:1:sentiment" + redis.hmget.return_value = ["1.0", "1.0", "1.0", "1.0"] + + result = await get_sentiment(key, redis) + + redis.hmget.assert_awaited_with(key, "neg", "neu", "pos", "compound") + assert result == {"neg": 1.0, "neu": 1.0, "pos": 1.0, "compound": 1.0} + + +@pytest.mark.asyncio() +async def test__get_sentiment_handles_missing_sentiment( + *, + redis: MockType, +) -> None: + """ + Test whether the get sentiment function handles missing sentiment in Redis. + + Asserts + ------- + - The function returns default sentiment values when the sentiment is missing. + """ + key = "courageous_comets:1:1:1:1:sentiment" + redis.hmget.return_value = [None, None, None, None] + + result = await get_sentiment(key, redis) + + redis.hmget.assert_awaited_with(key, "neg", "neu", "pos", "compound") + assert result == {"neg": 0.0, "neu": 0.0, "pos": 0.0, "compound": 0.0} From ed4efd27547593fd520908bbe87db335061a68e8 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Sat, 20 Jul 2024 17:29:39 +0000 Subject: [PATCH 043/168] fix: add logging and remove return section from docstring --- courageous_comets/sentiment.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/courageous_comets/sentiment.py b/courageous_comets/sentiment.py index 32a59fa..4e77d65 100644 --- a/courageous_comets/sentiment.py +++ b/courageous_comets/sentiment.py @@ -64,14 +64,10 @@ async def store_sentiment(message: Message, redis: Redis) -> None: ---------- message : discord.Message The message to process. - - Returns - ------- - SentimentResult - The sentiment of the message. """ # Ignore empty messages if not message.content: + logger.warning("Ignoring empty message %s", message.id) return # Extract the IDs from the message @@ -82,6 +78,7 @@ async def store_sentiment(message: Message, redis: Redis) -> None: # Ignore messages without all required IDs if not all((guild_id, channel_id, user_id, message_id)): + logger.warning("Ignoring message %s with missing IDs", message.id) return # Construct the Redis key From 001977c58758eca4cbaa7317a149a3d6520a1ae6 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Sat, 20 Jul 2024 17:43:51 +0000 Subject: [PATCH 044/168] refactor: update docstrings --- courageous_comets/sentiment.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/courageous_comets/sentiment.py b/courageous_comets/sentiment.py index 4e77d65..88609da 100644 --- a/courageous_comets/sentiment.py +++ b/courageous_comets/sentiment.py @@ -34,6 +34,8 @@ def calculate_sentiment(content: str, key: str) -> SentimentResult: ---------- content : str The message content to analyze. + key : str + The Redis key for the message. Used for logging. Returns ------- @@ -64,6 +66,8 @@ async def store_sentiment(message: Message, redis: Redis) -> None: ---------- message : discord.Message The message to process. + redis : redis.asyncio.Redis + The Redis connection instance. """ # Ignore empty messages if not message.content: From d2461571f817d484aa331ff7b61bae4391100fe5 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Sat, 20 Jul 2024 17:45:30 +0000 Subject: [PATCH 045/168] docs: update NLTK_DATA description --- docs/admin-guide/configuration.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/admin-guide/configuration.md b/docs/admin-guide/configuration.md index 0b8d30d..a5d42fc 100644 --- a/docs/admin-guide/configuration.md +++ b/docs/admin-guide/configuration.md @@ -7,7 +7,7 @@ The following environment variables are available to configure the application: | [`DISCORD_TOKEN`](#discord_token) | The Discord bot token. | Yes | - | | [`BOT_CONFIG_PATH`](#bot_config_path) | The path to the bot's configuration file. | No | `application.yaml` | | [`LOG_LEVEL`](#log_level) | The minimum log level. | No | `INFO` | -| [`NLTK_DATA_DIR`](#nltk_data_dir) | The directory containing NLTK data files. | No | `/nltk_data` | +| [`NLTK_DATA`](#nltk_data) | The directory containing NLTK data files. | No | `/nltk_data` | | [`REDIS_HOST`](#redis_host) | The Redis host. | No | `localhost` | | [`REDIS_PORT`](#redis_port) | The Redis port. | No | `6379` | | [`REDIS_PASSWORD`](#redis_password) | The Redis password. | No | - | @@ -56,7 +56,7 @@ The minimum log level to display. The following levels are available: The default log level is `INFO`. -### `NLTK_DATA_DIR` +### `NLTK_DATA` The directory containing NLTK data files. By default, this is set to `nltk_data` in the directory from which the application is launched. In the Docker image, this directory is located at `/app/nltk_data`. From dd874ec7ba3201119c699ce7bfb708c2c49b7e00 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Sat, 20 Jul 2024 18:20:53 +0000 Subject: [PATCH 046/168] ci: set environment variables --- .devcontainer/devcontainer.json | 1 + .github/workflows/ci.yaml | 1 + 2 files changed, 2 insertions(+) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 7072789..7852d2b 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,6 +1,7 @@ { "containerEnv": { "BOT_CONFIG_PATH": "${containerWorkspaceFolder}/application.yaml", + "NLTK_DATA": "${containerWorkspaceFolder}/nltk_data", "POETRY_VIRTUALENVS_CREATE": "false", "REDIS_HOST": "localhost", "REDIS_PASSWORD": "redis", diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 230a573..5435c35 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -13,6 +13,7 @@ jobs: runs-on: ubuntu-latest env: + BOT_CONFIG_PATH: ${{ github.workspace }}/application.yaml DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }} NLTK_DATA: ${{ github.workspace }}/nltk_data From 24715aab3ad704c2b9d20d46dbc95d5c4f64dbb8 Mon Sep 17 00:00:00 2001 From: Uchechukwu Orji Date: Sat, 20 Jul 2024 21:00:19 +0100 Subject: [PATCH 047/168] feat: use pydantic to model Redis hashes (#25) Co-authored-by: thijsfranck --- courageous_comets/__main__.py | 13 ++ courageous_comets/models.py | 38 ++++ courageous_comets/redis/schema.py | 26 +++ courageous_comets/sentiment.py | 30 ++- courageous_comets/test__sentiment.py | 39 ++-- poetry.lock | 307 +++++++++++++++++++++++++-- pyproject.toml | 2 + 7 files changed, 412 insertions(+), 43 deletions(-) create mode 100644 courageous_comets/models.py create mode 100644 courageous_comets/redis/schema.py diff --git a/courageous_comets/__main__.py b/courageous_comets/__main__.py index 4f1de61..951c70e 100644 --- a/courageous_comets/__main__.py +++ b/courageous_comets/__main__.py @@ -6,8 +6,10 @@ import nltk import redis.asyncio as redis import yaml +from redisvl.index import AsyncSearchIndex from courageous_comets import bot, exceptions, settings +from courageous_comets.redis import schema async def download_nltk_resource(resource: str, download_dir: str) -> None: @@ -88,6 +90,15 @@ async def init_redis() -> redis.Redis: return instance +async def create_indexes(redis: redis.Redis) -> None: + """Create search indexes on Redis.""" + logging.debug("Creating indexes on redis...") + message_index = AsyncSearchIndex.from_dict(schema.MESSAGE_SCHEMA) + message_index.set_client(redis) + await message_index.create(overwrite=True) + logging.info("Created indexes on Redis.") + + async def main() -> None: """ Start the appication. @@ -104,6 +115,8 @@ async def main() -> None: await init_nltk() redis = await init_redis() + # Create the search indexes on Redis + await create_indexes(redis) try: logging.info("Starting the Discord client 🚀") diff --git a/courageous_comets/models.py b/courageous_comets/models.py new file mode 100644 index 0000000..8f68a77 --- /dev/null +++ b/courageous_comets/models.py @@ -0,0 +1,38 @@ +import datetime +from typing import Annotated + +import pydantic +from pydantic import PlainSerializer + +UnixTimestamp = Annotated[ + datetime.datetime, + PlainSerializer(lambda t: t.timestamp(), return_type=float), +] + + +class BaseModel(pydantic.BaseModel): + """Base for model definitions.""" + + # Redis tags need to be strings, thus, coerce integers passed during creation to string + model_config = pydantic.ConfigDict(extra="ignore", coerce_numbers_to_str=True) + + +class Message(BaseModel): + """Redis model of a Discord message.""" + + message_id: str + channel_id: str + guild_id: str + timestamp: UnixTimestamp + user_id: str + content: str + embedding: bytes + + +class SentimentResult(BaseModel): + """Result of sentiment analysis.""" + + neg: float + neu: float + pos: float + compound: float diff --git a/courageous_comets/redis/schema.py b/courageous_comets/redis/schema.py new file mode 100644 index 0000000..e82f982 --- /dev/null +++ b/courageous_comets/redis/schema.py @@ -0,0 +1,26 @@ +from courageous_comets import settings + +MESSAGE_SCHEMA = { + "index": { + "name": "message_idx", + "prefix": settings.REDIS_KEYS_PREFIX, + }, + "fields": [ + {"name": "content", "type": "text"}, + {"name": "user_id", "type": "tag"}, + {"name": "message_id", "type": "tag"}, + {"name": "channel_id", "type": "tag"}, + {"name": "guild_id", "type": "tag"}, + {"name": "timestamp", "type": "numeric", "attrs": {"sortable": True}}, + { + "name": "embedding", + "type": "vector", + "attrs": { + "dims": 384, + "distance_metric": "cosine", + "algorithm": "hnsw", + "datatype": "float32", + }, + }, + ], +} diff --git a/courageous_comets/sentiment.py b/courageous_comets/sentiment.py index 88609da..33f1489 100644 --- a/courageous_comets/sentiment.py +++ b/courageous_comets/sentiment.py @@ -1,10 +1,10 @@ import logging -from typing import TypedDict, cast from discord import Message from nltk.sentiment import SentimentIntensityAnalyzer from redis.asyncio import Redis +from courageous_comets.models import SentimentResult from courageous_comets.redis.keys import key_schema MAX_MESSAGE_LENGTH = 256 @@ -12,15 +12,6 @@ logger = logging.getLogger(__name__) -class SentimentResult(TypedDict): - """Result of sentiment analysis.""" - - neg: float - neu: float - pos: float - compound: float - - def calculate_sentiment(content: str, key: str) -> SentimentResult: """ Calculate the sentiment of a message. @@ -50,7 +41,7 @@ def calculate_sentiment(content: str, key: str) -> SentimentResult: sia = SentimentIntensityAnalyzer() result = sia.polarity_scores(truncated) - return cast(SentimentResult, result) + return SentimentResult.model_validate(result) async def store_sentiment(message: Message, redis: Redis) -> None: @@ -97,7 +88,10 @@ async def store_sentiment(message: Message, redis: Redis) -> None: sentiment = calculate_sentiment(message.content, key) # Store the sentiment in Redis - await redis.hset(key, mapping=sentiment) # type: ignore + await redis.hset( + key, + mapping=sentiment.model_dump(mode="json"), + ) # pyright: ignore[reportGeneralTypeIssues] logger.info("Stored sentiment for message %s", key) @@ -120,9 +114,9 @@ async def get_sentiment(key: str, redis: Redis) -> SentimentResult: """ neg, neu, pos, compound = await redis.hmget(key, "neg", "neu", "pos", "compound") # type: ignore - return { - "neg": float(neg or 0), - "neu": float(neu or 0), - "pos": float(pos or 0), - "compound": float(compound or 0), - } + return SentimentResult( + neg=float(neg or 0), + neu=float(neu or 0), + pos=float(pos or 0), + compound=float(compound or 0), + ) diff --git a/courageous_comets/test__sentiment.py b/courageous_comets/test__sentiment.py index 6a24aab..fe29e7d 100644 --- a/courageous_comets/test__sentiment.py +++ b/courageous_comets/test__sentiment.py @@ -4,6 +4,8 @@ from pytest_mock import MockerFixture, MockType from redis.asyncio import Redis +from courageous_comets.models import SentimentResult + from .sentiment import ( MAX_MESSAGE_LENGTH, calculate_sentiment, @@ -22,10 +24,7 @@ def redis(mocker: MockerFixture) -> MockerFixture: return mock -def test__calculate_sentiment_analyzes_sentiment_of_given_text( - *, - mocker: MockerFixture, -) -> None: +def test__calculate_sentiment_analyzes_sentiment_of_given_text(mocker: MockerFixture) -> None: """ Test whether the sentiment calculation analyzes the sentiment of the given text. @@ -33,14 +32,14 @@ def test__calculate_sentiment_analyzes_sentiment_of_given_text( ------- - The function produces sentiment scores for the given text. """ + expected = SentimentResult.model_construct( + neg=mocker.ANY, + neu=mocker.ANY, + pos=mocker.ANY, + compound=mocker.ANY, + ) result = calculate_sentiment("I love this product!", "test") - - assert result == { - "neg": mocker.ANY, - "neu": mocker.ANY, - "pos": mocker.ANY, - "compound": mocker.ANY, - } + assert result == expected @pytest.mark.parametrize( @@ -161,10 +160,17 @@ async def test__get_sentiment_retrieves_sentiment_from_redis( key = "courageous_comets:1:1:1:1:sentiment" redis.hmget.return_value = ["1.0", "1.0", "1.0", "1.0"] + expected = SentimentResult( + neg=1.0, + neu=1.0, + pos=1.0, + compound=1.0, + ) + result = await get_sentiment(key, redis) redis.hmget.assert_awaited_with(key, "neg", "neu", "pos", "compound") - assert result == {"neg": 1.0, "neu": 1.0, "pos": 1.0, "compound": 1.0} + assert result == expected @pytest.mark.asyncio() @@ -182,7 +188,14 @@ async def test__get_sentiment_handles_missing_sentiment( key = "courageous_comets:1:1:1:1:sentiment" redis.hmget.return_value = [None, None, None, None] + expected = SentimentResult( + neg=0.0, + neu=0.0, + pos=0.0, + compound=0.0, + ) + result = await get_sentiment(key, redis) redis.hmget.assert_awaited_with(key, "neg", "neu", "pos", "compound") - assert result == {"neg": 0.0, "neu": 0.0, "pos": 0.0, "compound": 0.0} + assert expected == result diff --git a/poetry.lock b/poetry.lock index a933e8a..b32d644 100644 --- a/poetry.lock +++ b/poetry.lock @@ -109,6 +109,17 @@ files = [ [package.dependencies] frozenlist = ">=1.1.0" +[[package]] +name = "annotated-types" +version = "0.7.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.8" +files = [ + {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, + {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, +] + [[package]] name = "argcomplete" version = "3.3.0" @@ -328,6 +339,23 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "coloredlogs" +version = "15.0.1" +description = "Colored terminal output for Python's logging module" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "coloredlogs-15.0.1-py2.py3-none-any.whl", hash = "sha256:612ee75c546f53e92e70049c9dbfcc18c935a2b9a53b66085ce9ef6a6e5c0934"}, + {file = "coloredlogs-15.0.1.tar.gz", hash = "sha256:7c991aa71a4577af2f82600d8f8f3a89f936baeaf9b50a9c197da014e5bf16b0"}, +] + +[package.dependencies] +humanfriendly = ">=9.1" + +[package.extras] +cron = ["capturer (>=2.4)"] + [[package]] name = "commitizen" version = "3.27.0" @@ -616,6 +644,20 @@ files = [ {file = "hiredis-3.0.0.tar.gz", hash = "sha256:fed8581ae26345dea1f1e0d1a96e05041a727a45e7d8d459164583e23c6ac441"}, ] +[[package]] +name = "humanfriendly" +version = "10.0" +description = "Human friendly output for text interfaces using Python" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "humanfriendly-10.0-py2.py3-none-any.whl", hash = "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477"}, + {file = "humanfriendly-10.0.tar.gz", hash = "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc"}, +] + +[package.dependencies] +pyreadline3 = {version = "*", markers = "sys_platform == \"win32\" and python_version >= \"3.8\""} + [[package]] name = "identify" version = "2.6.0" @@ -1097,6 +1139,60 @@ files = [ {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, ] +[[package]] +name = "numpy" +version = "2.0.0" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = ">=3.9" +files = [ + {file = "numpy-2.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:04494f6ec467ccb5369d1808570ae55f6ed9b5809d7f035059000a37b8d7e86f"}, + {file = "numpy-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2635dbd200c2d6faf2ef9a0d04f0ecc6b13b3cad54f7c67c61155138835515d2"}, + {file = "numpy-2.0.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:0a43f0974d501842866cc83471bdb0116ba0dffdbaac33ec05e6afed5b615238"}, + {file = "numpy-2.0.0-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:8d83bb187fb647643bd56e1ae43f273c7f4dbcdf94550d7938cfc32566756514"}, + {file = "numpy-2.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79e843d186c8fb1b102bef3e2bc35ef81160ffef3194646a7fdd6a73c6b97196"}, + {file = "numpy-2.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d7696c615765091cc5093f76fd1fa069870304beaccfd58b5dcc69e55ef49c1"}, + {file = "numpy-2.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b4c76e3d4c56f145d41b7b6751255feefae92edbc9a61e1758a98204200f30fc"}, + {file = "numpy-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:acd3a644e4807e73b4e1867b769fbf1ce8c5d80e7caaef0d90dcdc640dfc9787"}, + {file = "numpy-2.0.0-cp310-cp310-win32.whl", hash = "sha256:cee6cc0584f71adefe2c908856ccc98702baf95ff80092e4ca46061538a2ba98"}, + {file = "numpy-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:ed08d2703b5972ec736451b818c2eb9da80d66c3e84aed1deeb0c345fefe461b"}, + {file = "numpy-2.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ad0c86f3455fbd0de6c31a3056eb822fc939f81b1618f10ff3406971893b62a5"}, + {file = "numpy-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e7f387600d424f91576af20518334df3d97bc76a300a755f9a8d6e4f5cadd289"}, + {file = "numpy-2.0.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:34f003cb88b1ba38cb9a9a4a3161c1604973d7f9d5552c38bc2f04f829536609"}, + {file = "numpy-2.0.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:b6f6a8f45d0313db07d6d1d37bd0b112f887e1369758a5419c0370ba915b3871"}, + {file = "numpy-2.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f64641b42b2429f56ee08b4f427a4d2daf916ec59686061de751a55aafa22e4"}, + {file = "numpy-2.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a7039a136017eaa92c1848152827e1424701532ca8e8967fe480fe1569dae581"}, + {file = "numpy-2.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:46e161722e0f619749d1cd892167039015b2c2817296104487cd03ed4a955995"}, + {file = "numpy-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0e50842b2295ba8414c8c1d9d957083d5dfe9e16828b37de883f51fc53c4016f"}, + {file = "numpy-2.0.0-cp311-cp311-win32.whl", hash = "sha256:2ce46fd0b8a0c947ae047d222f7136fc4d55538741373107574271bc00e20e8f"}, + {file = "numpy-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:fbd6acc766814ea6443628f4e6751d0da6593dae29c08c0b2606164db026970c"}, + {file = "numpy-2.0.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:354f373279768fa5a584bac997de6a6c9bc535c482592d7a813bb0c09be6c76f"}, + {file = "numpy-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4d2f62e55a4cd9c58c1d9a1c9edaedcd857a73cb6fda875bf79093f9d9086f85"}, + {file = "numpy-2.0.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:1e72728e7501a450288fc8e1f9ebc73d90cfd4671ebbd631f3e7857c39bd16f2"}, + {file = "numpy-2.0.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:84554fc53daa8f6abf8e8a66e076aff6ece62de68523d9f665f32d2fc50fd66e"}, + {file = "numpy-2.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c73aafd1afca80afecb22718f8700b40ac7cab927b8abab3c3e337d70e10e5a2"}, + {file = "numpy-2.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49d9f7d256fbc804391a7f72d4a617302b1afac1112fac19b6c6cec63fe7fe8a"}, + {file = "numpy-2.0.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0ec84b9ba0654f3b962802edc91424331f423dcf5d5f926676e0150789cb3d95"}, + {file = "numpy-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:feff59f27338135776f6d4e2ec7aeeac5d5f7a08a83e80869121ef8164b74af9"}, + {file = "numpy-2.0.0-cp312-cp312-win32.whl", hash = "sha256:c5a59996dc61835133b56a32ebe4ef3740ea5bc19b3983ac60cc32be5a665d54"}, + {file = "numpy-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:a356364941fb0593bb899a1076b92dfa2029f6f5b8ba88a14fd0984aaf76d0df"}, + {file = "numpy-2.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e61155fae27570692ad1d327e81c6cf27d535a5d7ef97648a17d922224b216de"}, + {file = "numpy-2.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4554eb96f0fd263041baf16cf0881b3f5dafae7a59b1049acb9540c4d57bc8cb"}, + {file = "numpy-2.0.0-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:903703372d46bce88b6920a0cd86c3ad82dae2dbef157b5fc01b70ea1cfc430f"}, + {file = "numpy-2.0.0-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:3e8e01233d57639b2e30966c63d36fcea099d17c53bf424d77f088b0f4babd86"}, + {file = "numpy-2.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cde1753efe513705a0c6d28f5884e22bdc30438bf0085c5c486cdaff40cd67a"}, + {file = "numpy-2.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:821eedb7165ead9eebdb569986968b541f9908979c2da8a4967ecac4439bae3d"}, + {file = "numpy-2.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9a1712c015831da583b21c5bfe15e8684137097969c6d22e8316ba66b5baabe4"}, + {file = "numpy-2.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9c27f0946a3536403efb0e1c28def1ae6730a72cd0d5878db38824855e3afc44"}, + {file = "numpy-2.0.0-cp39-cp39-win32.whl", hash = "sha256:63b92c512d9dbcc37f9d81b123dec99fdb318ba38c8059afc78086fe73820275"}, + {file = "numpy-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:3f6bed7f840d44c08ebdb73b1825282b801799e325bcbdfa6bc5c370e5aecc65"}, + {file = "numpy-2.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9416a5c2e92ace094e9f0082c5fd473502c91651fb896bc17690d6fc475128d6"}, + {file = "numpy-2.0.0-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:17067d097ed036636fa79f6a869ac26df7db1ba22039d962422506640314933a"}, + {file = "numpy-2.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38ecb5b0582cd125f67a629072fed6f83562d9dd04d7e03256c9829bdec027ad"}, + {file = "numpy-2.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:cef04d068f5fb0518a77857953193b6bb94809a806bd0a14983a8f12ada060c9"}, + {file = "numpy-2.0.0.tar.gz", hash = "sha256:cf5d1c9e6837f8af9f92b6bd3e86d513cdc11f60fd62185cc49ec7d1aba34864"}, +] + [[package]] name = "packaging" version = "24.1" @@ -1192,6 +1288,126 @@ files = [ [package.dependencies] wcwidth = "*" +[[package]] +name = "pydantic" +version = "2.8.2" +description = "Data validation using Python type hints" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic-2.8.2-py3-none-any.whl", hash = "sha256:73ee9fddd406dc318b885c7a2eab8a6472b68b8fb5ba8150949fc3db939f23c8"}, + {file = "pydantic-2.8.2.tar.gz", hash = "sha256:6f62c13d067b0755ad1c21a34bdd06c0c12625a22b0fc09c6b149816604f7c2a"}, +] + +[package.dependencies] +annotated-types = ">=0.4.0" +pydantic-core = "2.20.1" +typing-extensions = {version = ">=4.6.1", markers = "python_version < \"3.13\""} + +[package.extras] +email = ["email-validator (>=2.0.0)"] + +[[package]] +name = "pydantic-core" +version = "2.20.1" +description = "Core functionality for Pydantic validation and serialization" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic_core-2.20.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3acae97ffd19bf091c72df4d726d552c473f3576409b2a7ca36b2f535ffff4a3"}, + {file = "pydantic_core-2.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41f4c96227a67a013e7de5ff8f20fb496ce573893b7f4f2707d065907bffdbd6"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f239eb799a2081495ea659d8d4a43a8f42cd1fe9ff2e7e436295c38a10c286a"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53e431da3fc53360db73eedf6f7124d1076e1b4ee4276b36fb25514544ceb4a3"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1f62b2413c3a0e846c3b838b2ecd6c7a19ec6793b2a522745b0869e37ab5bc1"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d41e6daee2813ecceea8eda38062d69e280b39df793f5a942fa515b8ed67953"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d482efec8b7dc6bfaedc0f166b2ce349df0011f5d2f1f25537ced4cfc34fd98"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e93e1a4b4b33daed65d781a57a522ff153dcf748dee70b40c7258c5861e1768a"}, + {file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7c4ea22b6739b162c9ecaaa41d718dfad48a244909fe7ef4b54c0b530effc5a"}, + {file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4f2790949cf385d985a31984907fecb3896999329103df4e4983a4a41e13e840"}, + {file = "pydantic_core-2.20.1-cp310-none-win32.whl", hash = "sha256:5e999ba8dd90e93d57410c5e67ebb67ffcaadcea0ad973240fdfd3a135506250"}, + {file = "pydantic_core-2.20.1-cp310-none-win_amd64.whl", hash = "sha256:512ecfbefef6dac7bc5eaaf46177b2de58cdf7acac8793fe033b24ece0b9566c"}, + {file = "pydantic_core-2.20.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d2a8fa9d6d6f891f3deec72f5cc668e6f66b188ab14bb1ab52422fe8e644f312"}, + {file = "pydantic_core-2.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:175873691124f3d0da55aeea1d90660a6ea7a3cfea137c38afa0a5ffabe37b88"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37eee5b638f0e0dcd18d21f59b679686bbd18917b87db0193ae36f9c23c355fc"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25e9185e2d06c16ee438ed39bf62935ec436474a6ac4f9358524220f1b236e43"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:150906b40ff188a3260cbee25380e7494ee85048584998c1e66df0c7a11c17a6"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ad4aeb3e9a97286573c03df758fc7627aecdd02f1da04516a86dc159bf70121"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3f3ed29cd9f978c604708511a1f9c2fdcb6c38b9aae36a51905b8811ee5cbf1"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0dae11d8f5ded51699c74d9548dcc5938e0804cc8298ec0aa0da95c21fff57b"}, + {file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:faa6b09ee09433b87992fb5a2859efd1c264ddc37280d2dd5db502126d0e7f27"}, + {file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9dc1b507c12eb0481d071f3c1808f0529ad41dc415d0ca11f7ebfc666e66a18b"}, + {file = "pydantic_core-2.20.1-cp311-none-win32.whl", hash = "sha256:fa2fddcb7107e0d1808086ca306dcade7df60a13a6c347a7acf1ec139aa6789a"}, + {file = "pydantic_core-2.20.1-cp311-none-win_amd64.whl", hash = "sha256:40a783fb7ee353c50bd3853e626f15677ea527ae556429453685ae32280c19c2"}, + {file = "pydantic_core-2.20.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:595ba5be69b35777474fa07f80fc260ea71255656191adb22a8c53aba4479231"}, + {file = "pydantic_core-2.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a4f55095ad087474999ee28d3398bae183a66be4823f753cd7d67dd0153427c9"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9aa05d09ecf4c75157197f27cdc9cfaeb7c5f15021c6373932bf3e124af029f"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e97fdf088d4b31ff4ba35db26d9cc472ac7ef4a2ff2badeabf8d727b3377fc52"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc633a9fe1eb87e250b5c57d389cf28998e4292336926b0b6cdaee353f89a237"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d573faf8eb7e6b1cbbcb4f5b247c60ca8be39fe2c674495df0eb4318303137fe"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26dc97754b57d2fd00ac2b24dfa341abffc380b823211994c4efac7f13b9e90e"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:33499e85e739a4b60c9dac710c20a08dc73cb3240c9a0e22325e671b27b70d24"}, + {file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bebb4d6715c814597f85297c332297c6ce81e29436125ca59d1159b07f423eb1"}, + {file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:516d9227919612425c8ef1c9b869bbbee249bc91912c8aaffb66116c0b447ebd"}, + {file = "pydantic_core-2.20.1-cp312-none-win32.whl", hash = "sha256:469f29f9093c9d834432034d33f5fe45699e664f12a13bf38c04967ce233d688"}, + {file = "pydantic_core-2.20.1-cp312-none-win_amd64.whl", hash = "sha256:035ede2e16da7281041f0e626459bcae33ed998cca6a0a007a5ebb73414ac72d"}, + {file = "pydantic_core-2.20.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:0827505a5c87e8aa285dc31e9ec7f4a17c81a813d45f70b1d9164e03a813a686"}, + {file = "pydantic_core-2.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:19c0fa39fa154e7e0b7f82f88ef85faa2a4c23cc65aae2f5aea625e3c13c735a"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa223cd1e36b642092c326d694d8bf59b71ddddc94cdb752bbbb1c5c91d833b"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c336a6d235522a62fef872c6295a42ecb0c4e1d0f1a3e500fe949415761b8a19"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7eb6a0587eded33aeefea9f916899d42b1799b7b14b8f8ff2753c0ac1741edac"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:70c8daf4faca8da5a6d655f9af86faf6ec2e1768f4b8b9d0226c02f3d6209703"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9fa4c9bf273ca41f940bceb86922a7667cd5bf90e95dbb157cbb8441008482c"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:11b71d67b4725e7e2a9f6e9c0ac1239bbc0c48cce3dc59f98635efc57d6dac83"}, + {file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:270755f15174fb983890c49881e93f8f1b80f0b5e3a3cc1394a255706cabd203"}, + {file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c81131869240e3e568916ef4c307f8b99583efaa60a8112ef27a366eefba8ef0"}, + {file = "pydantic_core-2.20.1-cp313-none-win32.whl", hash = "sha256:b91ced227c41aa29c672814f50dbb05ec93536abf8f43cd14ec9521ea09afe4e"}, + {file = "pydantic_core-2.20.1-cp313-none-win_amd64.whl", hash = "sha256:65db0f2eefcaad1a3950f498aabb4875c8890438bc80b19362cf633b87a8ab20"}, + {file = "pydantic_core-2.20.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:4745f4ac52cc6686390c40eaa01d48b18997cb130833154801a442323cc78f91"}, + {file = "pydantic_core-2.20.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a8ad4c766d3f33ba8fd692f9aa297c9058970530a32c728a2c4bfd2616d3358b"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41e81317dd6a0127cabce83c0c9c3fbecceae981c8391e6f1dec88a77c8a569a"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:04024d270cf63f586ad41fff13fde4311c4fc13ea74676962c876d9577bcc78f"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eaad4ff2de1c3823fddf82f41121bdf453d922e9a238642b1dedb33c4e4f98ad"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:26ab812fa0c845df815e506be30337e2df27e88399b985d0bb4e3ecfe72df31c"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c5ebac750d9d5f2706654c638c041635c385596caf68f81342011ddfa1e5598"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2aafc5a503855ea5885559eae883978c9b6d8c8993d67766ee73d82e841300dd"}, + {file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4868f6bd7c9d98904b748a2653031fc9c2f85b6237009d475b1008bfaeb0a5aa"}, + {file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa2f457b4af386254372dfa78a2eda2563680d982422641a85f271c859df1987"}, + {file = "pydantic_core-2.20.1-cp38-none-win32.whl", hash = "sha256:225b67a1f6d602de0ce7f6c1c3ae89a4aa25d3de9be857999e9124f15dab486a"}, + {file = "pydantic_core-2.20.1-cp38-none-win_amd64.whl", hash = "sha256:6b507132dcfc0dea440cce23ee2182c0ce7aba7054576efc65634f080dbe9434"}, + {file = "pydantic_core-2.20.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:b03f7941783b4c4a26051846dea594628b38f6940a2fdc0df00b221aed39314c"}, + {file = "pydantic_core-2.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1eedfeb6089ed3fad42e81a67755846ad4dcc14d73698c120a82e4ccf0f1f9f6"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:635fee4e041ab9c479e31edda27fcf966ea9614fff1317e280d99eb3e5ab6fe2"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:77bf3ac639c1ff567ae3b47f8d4cc3dc20f9966a2a6dd2311dcc055d3d04fb8a"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ed1b0132f24beeec5a78b67d9388656d03e6a7c837394f99257e2d55b461611"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6514f963b023aeee506678a1cf821fe31159b925c4b76fe2afa94cc70b3222b"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10d4204d8ca33146e761c79f83cc861df20e7ae9f6487ca290a97702daf56006"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2d036c7187b9422ae5b262badb87a20a49eb6c5238b2004e96d4da1231badef1"}, + {file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9ebfef07dbe1d93efb94b4700f2d278494e9162565a54f124c404a5656d7ff09"}, + {file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6b9d9bb600328a1ce523ab4f454859e9d439150abb0906c5a1983c146580ebab"}, + {file = "pydantic_core-2.20.1-cp39-none-win32.whl", hash = "sha256:784c1214cb6dd1e3b15dd8b91b9a53852aed16671cc3fbe4786f4f1db07089e2"}, + {file = "pydantic_core-2.20.1-cp39-none-win_amd64.whl", hash = "sha256:d2fe69c5434391727efa54b47a1e7986bb0186e72a41b203df8f5b0a19a4f669"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a45f84b09ac9c3d35dfcf6a27fd0634d30d183205230a0ebe8373a0e8cfa0906"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d02a72df14dfdbaf228424573a07af10637bd490f0901cee872c4f434a735b94"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2b27e6af28f07e2f195552b37d7d66b150adbaa39a6d327766ffd695799780f"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:084659fac3c83fd674596612aeff6041a18402f1e1bc19ca39e417d554468482"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:242b8feb3c493ab78be289c034a1f659e8826e2233786e36f2893a950a719bb6"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:38cf1c40a921d05c5edc61a785c0ddb4bed67827069f535d794ce6bcded919fc"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e0bbdd76ce9aa5d4209d65f2b27fc6e5ef1312ae6c5333c26db3f5ade53a1e99"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:254ec27fdb5b1ee60684f91683be95e5133c994cc54e86a0b0963afa25c8f8a6"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:407653af5617f0757261ae249d3fba09504d7a71ab36ac057c938572d1bc9331"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:c693e916709c2465b02ca0ad7b387c4f8423d1db7b4649c551f27a529181c5ad"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b5ff4911aea936a47d9376fd3ab17e970cc543d1b68921886e7f64bd28308d1"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:177f55a886d74f1808763976ac4efd29b7ed15c69f4d838bbd74d9d09cf6fa86"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:964faa8a861d2664f0c7ab0c181af0bea66098b1919439815ca8803ef136fc4e"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:4dd484681c15e6b9a977c785a345d3e378d72678fd5f1f3c0509608da24f2ac0"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f6d6cff3538391e8486a431569b77921adfcdef14eb18fbf19b7c0a5294d4e6a"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a6d511cc297ff0883bc3708b465ff82d7560193169a8b93260f74ecb0a5e08a7"}, + {file = "pydantic_core-2.20.1.tar.gz", hash = "sha256:26ca695eeee5f9f1aeeb211ffc12f10bcb6f71e2989988fda61dabd65db878d4"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" + [[package]] name = "pygments" version = "2.18.0" @@ -1238,6 +1454,17 @@ files = [ [package.extras] diagrams = ["jinja2", "railroad-diagrams"] +[[package]] +name = "pyreadline3" +version = "3.4.1" +description = "A python implementation of GNU readline." +optional = false +python-versions = "*" +files = [ + {file = "pyreadline3-3.4.1-py3-none-any.whl", hash = "sha256:b0efb6516fd4fb07b45949053826a62fa4cb353db5be2bbb4a7aa1fdd1e345fb"}, + {file = "pyreadline3-3.4.1.tar.gz", hash = "sha256:6f3d1f7b8a31ba32b73917cefc1f28cc660562f39aea8646d30bd6eff21f7bae"}, +] + [[package]] name = "pytest" version = "8.2.2" @@ -1352,7 +1579,6 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, - {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -1360,16 +1586,8 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, - {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, - {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, - {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, - {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -1386,7 +1604,6 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, - {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -1394,7 +1611,6 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, - {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -1446,6 +1662,33 @@ hiredis = {version = ">=1.0.0", optional = true, markers = "extra == \"hiredis\" hiredis = ["hiredis (>=1.0.0)"] ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)"] +[[package]] +name = "redisvl" +version = "0.2.3" +description = "Python client library and CLI for using Redis as a vector database" +optional = false +python-versions = "<4.0,>=3.9" +files = [ + {file = "redisvl-0.2.3-py3-none-any.whl", hash = "sha256:0739889de62f45345b501fbd6aea03b7350e25dc078c9154c0bc9c32d67e5eb1"}, + {file = "redisvl-0.2.3.tar.gz", hash = "sha256:13e07a3f70550f74b424ab3734171661a9199c9d1a9f6e001c38ccc422e71f83"}, +] + +[package.dependencies] +coloredlogs = "*" +numpy = "*" +pydantic = ">=2,<3" +pyyaml = "*" +redis = ">=5.0.0" +tabulate = ">=0.9.0,<1" +tenacity = ">=8.2.2" + +[package.extras] +cohere = ["cohere (>=4.44)"] +google-cloud-aiplatform = ["google-cloud-aiplatform (>=1.26)"] +mistralai = ["mistralai (>=0.2.0)"] +openai = ["openai (>=1.13.0)"] +sentence-transformers = ["sentence-transformers (>=2.2.2)"] + [[package]] name = "regex" version = "2024.5.15" @@ -1593,6 +1836,35 @@ files = [ {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] +[[package]] +name = "tabulate" +version = "0.9.0" +description = "Pretty-print tabular data" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f"}, + {file = "tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c"}, +] + +[package.extras] +widechars = ["wcwidth"] + +[[package]] +name = "tenacity" +version = "8.5.0" +description = "Retry code until it succeeds" +optional = false +python-versions = ">=3.8" +files = [ + {file = "tenacity-8.5.0-py3-none-any.whl", hash = "sha256:b594c2a5945830c267ce6b79a166228323ed52718f30302c1359836112346687"}, + {file = "tenacity-8.5.0.tar.gz", hash = "sha256:8bc6c0c8a09b31e6cad13c47afbed1a567518250a9a171418582ed8d9c20ca78"}, +] + +[package.extras] +doc = ["reno", "sphinx"] +test = ["pytest", "tornado (>=4.5)", "typeguard"] + [[package]] name = "termcolor" version = "2.4.0" @@ -1638,6 +1910,17 @@ notebook = ["ipywidgets (>=6)"] slack = ["slack-sdk"] telegram = ["requests"] +[[package]] +name = "typing-extensions" +version = "4.12.2" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, +] + [[package]] name = "urllib3" version = "2.2.2" @@ -1879,4 +2162,4 @@ test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", [metadata] lock-version = "2.0" python-versions = "~3.12" -content-hash = "41c4396e0eb0c4b6c7faa1024f39cea166058996cca3e4a043c1e703ef6bc01c" +content-hash = "a12ab2befe68ba8f6a918f5568b9a308af7c3d954e982a39eacb4053088f726f" diff --git a/pyproject.toml b/pyproject.toml index 502f820..4a7c3e6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,6 +15,8 @@ python-dotenv = "^1.0.1" pyyaml = "^6.0.1" redis = { extras = ["hiredis"], version = "^5.0.7" } nltk = "^3.8.1" +pydantic = "^2.8.2" +redisvl = {extras = ["hiredis"], version = "^0.2.3"} [tool.poetry.dev-dependencies] commitizen = "3.27.0" From 6b3a3ace120d1cabe921a6c777ecf75710a0b21b Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Sun, 21 Jul 2024 06:49:37 +0000 Subject: [PATCH 048/168] refactor: improve docs, types and fix some linter issues --- courageous_comets/client.py | 6 +++-- courageous_comets/exceptions.py | 8 +++---- courageous_comets/models.py | 36 +++++++++++++++++++++++++++-- courageous_comets/redis/keys.py | 33 ++++++++++++++++++-------- courageous_comets/redis/messages.py | 32 +++++++++++++++++++------ courageous_comets/sentiment.py | 4 ++-- 6 files changed, 93 insertions(+), 26 deletions(-) diff --git a/courageous_comets/client.py b/courageous_comets/client.py index b833e99..698075f 100644 --- a/courageous_comets/client.py +++ b/courageous_comets/client.py @@ -57,9 +57,11 @@ async def sync( Parameters ---------- - guilds: commands.Greedy[discord.Object] + ctx : commands.Context[commands.Bot] + The context of the command. + guilds : Collection[discord.Object] The guilds to sync to. - spec: typing.Literal["~", "*", "^"] | None + spec : typing.Literal["~", "*", "^"] | None The scope to sync to. Defaults to `~`. """ async with ctx.typing(): diff --git a/courageous_comets/exceptions.py b/courageous_comets/exceptions.py index 63acbb9..c4eacd8 100644 --- a/courageous_comets/exceptions.py +++ b/courageous_comets/exceptions.py @@ -14,13 +14,13 @@ class ConfigurationValueError[T](CourageousCometsError): """ Raised when a configuration value is invalid. - Parameters + Attributes ---------- - key: str + key : str The configuration key. - value: T, optional + value : T, optional The invalid value provided. - reason: str + reason : str The reason why the value is considered invalid. """ diff --git a/courageous_comets/models.py b/courageous_comets/models.py index 8f68a77..8bbb36a 100644 --- a/courageous_comets/models.py +++ b/courageous_comets/models.py @@ -18,7 +18,26 @@ class BaseModel(pydantic.BaseModel): class Message(BaseModel): - """Redis model of a Discord message.""" + """ + Redis model of a Discord message. + + Attributes + ---------- + message_id : str + The ID of the message. + channel_id : str + The ID of the channel the message was sent in. + guild_id : str + The ID of the guild the message was sent in. + timestamp : UnixTimestamp + The timestamp when the message was sent. + user_id : str + The ID of the user who sent the message. + content : str + The content of the message. + embedding : bytes + The embedding of the message. + """ message_id: str channel_id: str @@ -30,7 +49,20 @@ class Message(BaseModel): class SentimentResult(BaseModel): - """Result of sentiment analysis.""" + """ + Result of sentiment analysis. + + Attributes + ---------- + neg : float + The negative sentiment score. + neu : float + The neutral sentiment score. + pos : float + The positive sentiment score. + compound : float + The compound sentiment score. + """ neg: float neu: float diff --git a/courageous_comets/redis/keys.py b/courageous_comets/redis/keys.py index 1134130..1a8a419 100644 --- a/courageous_comets/redis/keys.py +++ b/courageous_comets/redis/keys.py @@ -1,31 +1,46 @@ from collections.abc import Callable from functools import wraps -from typing import Any +from typing import ParamSpec from courageous_comets import settings +P = ParamSpec("P") +Prefixable = Callable[P, str] -def prefix_key(func: Callable[..., str]) -> Callable[..., str]: - """Prefix return values of methods with settings.REDIS_KEYS_PREFIX.""" + +def prefix_key(func: Prefixable[P]) -> Prefixable[P]: + """ + Prefix the key returned by the decorated function with the Redis key prefix. + + This function is intended to be used as a decorator. + + Parameters + ---------- + func : courageous_comets.redis.keys.Prefixable[P] + The function to decorate. + + Returns + ------- + courageous_comets.redis.keys.Prefixable[P] + The decorated function. + """ @wraps(func) - def prefixed_method(self: object, *args: Any, **kwargs: Any) -> str: # noqa: ANN401 - key = func(self, *args, **kwargs) + def prefixed_method(*args: P.args, **kwargs: P.kwargs) -> str: + key = func(*args, **kwargs) return f"{settings.REDIS_KEYS_PREFIX}:{key}" return prefixed_method class KeySchema: - """A class to generate key names for Redis data structures. + """ + A class to generate key names for Redis data structures. This class contains a reference to all possible key names used by the application. """ - def __init__(self, prefix: str = settings.REDIS_KEYS_PREFIX) -> None: - self.prefix = prefix - @prefix_key def guild_messages(self, guild_id: int) -> str: """Key to messages for a Discord guild. diff --git a/courageous_comets/redis/messages.py b/courageous_comets/redis/messages.py index 63c70e0..2f4581b 100644 --- a/courageous_comets/redis/messages.py +++ b/courageous_comets/redis/messages.py @@ -8,14 +8,32 @@ async def update_message_tokens( guild_id: int, words: dict[str, int], ) -> dict[str, int]: - """Update the frequency of each word in a guild.""" + """ + Update the word frequencies for a guild's messages in the database. + + Parameters + ---------- + redis : Redis + The Redis connection instance. + guild_id : int + The ID of the guild. + words : dict[str, int] + The words and their frequencies to update. + + Returns + ------- + dict[str, int] + The updated word frequencies. + """ async with redis.pipeline() as pipe: + # Queue the increment operations for each word for word, frequency in words.items(): pipe.hincrby(key_schema.guild_message_tokens(guild_id), word, frequency) + + # Execute the pipeline new_frequencies: list[int] = await pipe.execute() - # Given dictionary keys are iterated in insertion order, execution of the - # instructions are evaluated in the same order, thus, the return values are - # in the same order. - return { # noqa: C416 - word: frequency for word, frequency in zip(words.keys(), new_frequencies, strict=False) - } + + # Given dictionary keys are iterated in insertion order, execution of the + # instructions are evaluated in the same order, thus, the return values are + # in the same order. + return dict(zip(words.keys(), new_frequencies, strict=False)) diff --git a/courageous_comets/sentiment.py b/courageous_comets/sentiment.py index 33f1489..dc683b8 100644 --- a/courageous_comets/sentiment.py +++ b/courageous_comets/sentiment.py @@ -30,7 +30,7 @@ def calculate_sentiment(content: str, key: str) -> SentimentResult: Returns ------- - SentimentResult + courageous_comets.models.SentimentResult The sentiment of the message. """ truncated = content[:MAX_MESSAGE_LENGTH] @@ -109,7 +109,7 @@ async def get_sentiment(key: str, redis: Redis) -> SentimentResult: Returns ------- - SentimentResult + courageous_comets.models.SentimentResult The sentiment of the message. """ neg, neu, pos, compound = await redis.hmget(key, "neg", "neu", "pos", "compound") # type: ignore From dbfe6062e991382f07db4feea1860b9f9a63b484 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Sun, 21 Jul 2024 06:55:10 +0000 Subject: [PATCH 049/168] fix: control max number of concurrent downloads --- courageous_comets/__main__.py | 62 +++++++++++++++++-------------- courageous_comets/settings.py | 43 +++++++++++++++++---- docs/admin-guide/configuration.md | 24 +++++++----- 3 files changed, 85 insertions(+), 44 deletions(-) diff --git a/courageous_comets/__main__.py b/courageous_comets/__main__.py index 951c70e..5d8325f 100644 --- a/courageous_comets/__main__.py +++ b/courageous_comets/__main__.py @@ -12,20 +12,21 @@ from courageous_comets.redis import schema -async def download_nltk_resource(resource: str, download_dir: str) -> None: +async def download_nltk_resource(resource: str, semaphore: asyncio.Semaphore) -> None: """Download an NLTK resource to the specified directory.""" - logging.debug("Downloading NLTK resource '%s'...", resource) - try: - await asyncio.to_thread( - nltk.download, - resource, - download_dir=download_dir, - quiet=True, - raise_on_error=True, - ) - except ValueError as e: - message = f"Invalid NLTK resource '{resource}'" - raise exceptions.NltkInitializationError(message) from e + async with semaphore: + logging.debug("Downloading NLTK resource '%s'...", resource) + try: + await asyncio.to_thread( + nltk.download, + resource, + download_dir=settings.NLTK_DATA_DIR, + quiet=True, + raise_on_error=True, + ) + except ValueError as e: + message = f"Invalid NLTK resource '{resource}'" + raise exceptions.NltkInitializationError(message) from e async def init_nltk() -> None: @@ -39,15 +40,26 @@ async def init_nltk() -> None: resources = config.get("nltk", []) - download_tasks = [ - download_nltk_resource(resource, settings.NLTK_DATA_DIR) for resource in resources - ] + semaphore = asyncio.Semaphore(settings.NLTK_DOWNLOAD_CONCURRENCY) + download_tasks = [download_nltk_resource(resource, semaphore) for resource in resources] await asyncio.gather(*download_tasks) logging.info("NLTK resources downloaded") +async def create_indexes(redis: redis.Redis) -> None: + """Create search indexes on Redis.""" + logging.debug("Creating indexes on redis...") + + message_index = AsyncSearchIndex.from_dict(schema.MESSAGE_SCHEMA) + message_index.set_client(redis) + + await message_index.create(overwrite=True) + + logging.info("Created indexes on Redis") + + async def init_redis() -> redis.Redis: """ Initialize the Redis connection. @@ -72,6 +84,7 @@ async def init_redis() -> redis.Redis: password=settings.REDIS_PASSWORD, ) + # Check if the connection is successful try: await instance.ping() except redis.AuthenticationError as e: @@ -87,16 +100,12 @@ async def init_redis() -> redis.Redis: settings.REDIS_PORT, ) - return instance + # Create search indexes on Redis + await create_indexes(instance) + logging.info("Redis initialization complete") -async def create_indexes(redis: redis.Redis) -> None: - """Create search indexes on Redis.""" - logging.debug("Creating indexes on redis...") - message_index = AsyncSearchIndex.from_dict(schema.MESSAGE_SCHEMA) - message_index.set_client(redis) - await message_index.create(overwrite=True) - logging.info("Created indexes on Redis.") + return instance async def main() -> None: @@ -113,10 +122,9 @@ async def main() -> None: """ logging.info("Starting the Courageous Comets application ☄️") - await init_nltk() + # Initialize dependencies redis = await init_redis() - # Create the search indexes on Redis - await create_indexes(redis) + await init_nltk() try: logging.info("Starting the Discord client 🚀") diff --git a/courageous_comets/settings.py b/courageous_comets/settings.py index cb46999..5c7be41 100644 --- a/courageous_comets/settings.py +++ b/courageous_comets/settings.py @@ -63,29 +63,55 @@ def read_discord_token() -> str: return result -def read_redis_port() -> int: +def read_int(key: str, default: int) -> int: """ - Read the Redis port from the environment. + Read an integer value from the environment. + + Parameters + ---------- + key : str + The environment variable key. + default : int + The default value to use if the environment variable is not set. Returns ------- int - The Redis port number. + The integer value. Raises ------ courageous_comets.exceptions.ConfigurationValueError - If the port is not a valid port number. + If the value is not a valid integer. """ try: - result = int(os.getenv("REDIS_PORT", "6379")) + result = int(os.getenv(key, str(default))) except ValueError as e: raise ConfigurationValueError( - key="REDIS_PORT", - value=os.getenv("REDIS_PORT"), - reason="Value must be an integer (e.g., 6379)", + key=key, + value=os.getenv(key), + reason="Value must be an integer", ) from e + return result + + +def read_redis_port() -> int: + """ + Read the Redis port from the environment. + + Returns + ------- + int + The Redis port number. + + Raises + ------ + courageous_comets.exceptions.ConfigurationValueError + If the port is not a valid port number. + """ + result = read_int("REDIS_PORT", 6379) + if not (0 <= result <= 65535): # noqa: PLR2004 raise ConfigurationValueError( key="REDIS_PORT", @@ -111,6 +137,7 @@ def read_redis_port() -> int: DISCORD_TOKEN = read_discord_token() BOT_CONFIG_PATH = read_bot_config_path() NLTK_DATA_DIR = os.getenv("NLTK_DATA", "nltk_data") + NLTK_DOWNLOAD_CONCURRENCY = read_int("NLTK_DOWNLOAD_CONCURRENCY", 3) REDIS_HOST = os.getenv("REDIS_HOST", "localhost") REDIS_PORT = read_redis_port() REDIS_PASSWORD = os.getenv("REDIS_PASSWORD") diff --git a/docs/admin-guide/configuration.md b/docs/admin-guide/configuration.md index a5d42fc..29c8b8b 100644 --- a/docs/admin-guide/configuration.md +++ b/docs/admin-guide/configuration.md @@ -2,15 +2,16 @@ The following environment variables are available to configure the application: -| Variable | Description | Required | Default | -| ------------------------------------- | ------------------------------------------| -------- | ------------------ | -| [`DISCORD_TOKEN`](#discord_token) | The Discord bot token. | Yes | - | -| [`BOT_CONFIG_PATH`](#bot_config_path) | The path to the bot's configuration file. | No | `application.yaml` | -| [`LOG_LEVEL`](#log_level) | The minimum log level. | No | `INFO` | -| [`NLTK_DATA`](#nltk_data) | The directory containing NLTK data files. | No | `/nltk_data` | -| [`REDIS_HOST`](#redis_host) | The Redis host. | No | `localhost` | -| [`REDIS_PORT`](#redis_port) | The Redis port. | No | `6379` | -| [`REDIS_PASSWORD`](#redis_password) | The Redis password. | No | - | +| Variable | Description | Required | Default | +| --------------------------------------------------------- | --------------------------------------------------------------------- | -------- | ------------------ | +| [`DISCORD_TOKEN`](#discord_token) | The Discord bot token. | Yes | - | +| [`BOT_CONFIG_PATH`](#bot_config_path) | The path to the bot's configuration file. | No | `application.yaml` | +| [`LOG_LEVEL`](#log_level) | The minimum log level. | No | `INFO` | +| [`NLTK_DATA`](#nltk_data) | The directory containing NLTK data files. | No | `nltk_data` | +| [`NLTK_DOWNLOAD_CONCURRENCY`](#nltk_download_concurrency) | The maximum number of concurrent downloads when installing NLTK data. | No | `3` | +| [`REDIS_HOST`](#redis_host) | The Redis host. | No | `localhost` | +| [`REDIS_PORT`](#redis_port) | The Redis port. | No | `6379` | +| [`REDIS_PASSWORD`](#redis_password) | The Redis password. | No | - | ## Required Settings @@ -61,6 +62,11 @@ The default log level is `INFO`. The directory containing NLTK data files. By default, this is set to `nltk_data` in the directory from which the application is launched. In the Docker image, this directory is located at `/app/nltk_data`. +### `NLTK_DOWNLOAD_CONCURRENCY` + +The application automatically downloads NLTK data files on startup. This setting controls the number of concurrent +downloads. By default, this is set to `3`. + ### `REDIS_HOST` The hostname of the Redis server. Defaults to `localhost`. From f242b604a4e60fa0b1b0dd3476cc909d203766fd Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Sun, 21 Jul 2024 10:01:18 +0200 Subject: [PATCH 050/168] fix: handle contractions in tokenizer (#26) --- application.yaml | 1 + courageous_comets/test__words.py | 93 ++++++++++++++++++++++++++++++++ courageous_comets/words.py | 64 +++++++++++++++++----- poetry.lock | 90 ++++++++++++++++++++++++++++++- pyproject.toml | 1 + 5 files changed, 235 insertions(+), 14 deletions(-) create mode 100644 courageous_comets/test__words.py diff --git a/application.yaml b/application.yaml index 0e7de90..0b563fb 100644 --- a/application.yaml +++ b/application.yaml @@ -2,5 +2,6 @@ cogs: - courageous_comets.cogs.ping - jishaku nltk: + - punkt - stopwords - vader_lexicon diff --git a/courageous_comets/test__words.py b/courageous_comets/test__words.py new file mode 100644 index 0000000..a36267e --- /dev/null +++ b/courageous_comets/test__words.py @@ -0,0 +1,93 @@ +import pytest + +from courageous_comets.words import tokenize_sentence, word_frequency + + +@pytest.mark.parametrize( + ("sentence", "expected"), + [ + ( + "The quick brown fox jumps over the lazy dog", + ["quick", "brown", "fox", "jump", "lazi", "dog"], + ), + ( + "Hello, world!", + ["hello", "world"], + ), + ( + "I'm sorry, Dave. I'm afraid I can't do that.", + ["sorri", "dave", "afraid"], + ), + ( + "I've been waiting for you, Obi-Wan. We meet again at last.", + ["wait", "obi-wan", "meet", "last"], + ), + ( + "I don't like sand. It's coarse and rough and irritating and it gets everywhere.", + ["like", "sand", "coars", "rough", "irrit", "get", "everywher"], + ), + ( + "You were the chosen one! It was said that you would destroy the Sith, not join them.", + ["chosen", "one", "said", "would", "destroy", "sith", "join"], + ), + ( + "Bring balance to the Force, not leave it in darkness.", + ["bring", "balanc", "forc", "leav", "dark"], + ), + ( + "I'm gonna wreck it!", + ["go", "wreck"], + ), + ( + "I'm gonna wreck it! I'm gonna wreck it!", + ["go", "wreck", "go", "wreck"], + ), + ( + "", # Empty string + [], + ), + ], +) +def test__tokenize_sentence(sentence: str, expected: list[str]) -> None: + """ + Test whether a sentence is tokenized correctly. + + Asserts + ------- + - Contractions are expanded. + - The sentence is split into words. + - Punctuation is removed. + - The words are stemmed. + - Stopwords and words with a length of 1 are removed. + """ + result = tokenize_sentence(sentence) + assert result == expected + + +@pytest.mark.parametrize( + ("words", "expected"), + [ + ( + ["go", "wreck"], + {"go": 1, "wreck": 1}, + ), + ( + ["go", "wreck", "go", "wreck"], + {"go": 2, "wreck": 2}, + ), + ( + [], + {}, + ), + ], +) +def test__word_frequency(words: list[str], expected: dict[str, int]) -> None: + """ + Test whether the frequency of words is calculated correctly. + + Asserts + ------- + - The frequency of each word is counted. + """ + result = word_frequency(words) + assert result == expected diff --git a/courageous_comets/words.py b/courageous_comets/words.py index 0d8d138..8caf111 100644 --- a/courageous_comets/words.py +++ b/courageous_comets/words.py @@ -1,25 +1,63 @@ -from collections import defaultdict +from collections import Counter +import contractions from nltk.stem.snowball import SnowballStemmer, stopwords from nltk.tokenize import word_tokenize -STOP_WORDS = set(stopwords.words("english")) - def tokenize_sentence(sentence: str) -> list[str]: - """Split a sentence into tokens. + """ + Split a sentence into tokens. + + The tokenizer applies the following steps: + + - Expand contractions. + - Break the sentence into words. + - Stem the words. + - Remove stopwords and words with a length of 1. - The tokenizer applies stemming to the words that make up the sentence - using the Snowball stemmer and removes stopwords. + Parameters + ---------- + sentence : str + The sentence to tokenize. + + Returns + ------- + list[str] + The tokens derived from the sentence. + + Notes + ----- + The tokenizer is intended to be used with English text. """ + # Expand contractions + expanded = contractions.fix(sentence) + + # Break the sentence into words + words = word_tokenize(expanded) + + # Stem the words stemmer = SnowballStemmer("english") - stemmed_words = [stemmer.stem(token) for token in word_tokenize(sentence)] - return [word for word in stemmed_words if len(word) > 1 and word not in STOP_WORDS] + stemmed_words = [stemmer.stem(word) for word in words] + + # Remove stopwords and words with a length of 1 + stop_words = set(stopwords.words("english")) + + return [word for word in stemmed_words if len(word) > 1 and word not in stop_words] def word_frequency(words: list[str]) -> dict[str, int]: - """Count the number of times each word appears in words.""" - frequency: dict[str, int] = defaultdict(int) - for word in words: - frequency[word] += 1 - return frequency + """ + Count the number of times each word appears in the given list of words. + + Parameters + ---------- + words : list[str] + The list of words to count the frequency of. + + Returns + ------- + dict[str, int] + The frequency of each word in words. + """ + return Counter(words) diff --git a/poetry.lock b/poetry.lock index b32d644..bfc5425 100644 --- a/poetry.lock +++ b/poetry.lock @@ -120,6 +120,17 @@ files = [ {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, ] +[[package]] +name = "anyascii" +version = "0.3.2" +description = "Unicode to ASCII transliteration" +optional = false +python-versions = ">=3.3" +files = [ + {file = "anyascii-0.3.2-py3-none-any.whl", hash = "sha256:3b3beef6fc43d9036d3b0529050b0c48bfad8bc960e9e562d7223cfb94fe45d4"}, + {file = "anyascii-0.3.2.tar.gz", hash = "sha256:9d5d32ef844fe225b8bc7cba7f950534fae4da27a9bf3a6bea2cb0ea46ce4730"}, +] + [[package]] name = "argcomplete" version = "3.3.0" @@ -380,6 +391,19 @@ questionary = ">=2.0,<3.0" termcolor = ">=1.1,<3" tomlkit = ">=0.5.3,<1.0.0" +[[package]] +name = "contractions" +version = "0.1.73" +description = "Fixes contractions such as `you're` to you `are`" +optional = false +python-versions = "*" +files = [ + {file = "contractions-0.1.73-py2.py3-none-any.whl", hash = "sha256:398cee3b69c37307a50dce4930d961a0f42b48fdae9562df73bed5683008d3bc"}, +] + +[package.dependencies] +textsearch = ">=0.0.21" + [[package]] name = "decli" version = "0.6.2" @@ -1288,6 +1312,44 @@ files = [ [package.dependencies] wcwidth = "*" +[[package]] +name = "pyahocorasick" +version = "2.1.0" +description = "pyahocorasick is a fast and memory efficient library for exact or approximate multi-pattern string search. With the ``ahocorasick.Automaton`` class, you can find multiple key string occurrences at once in some input text. You can use it as a plain dict-like Trie or convert a Trie to an automaton for efficient Aho-Corasick search. And pickle to disk for easy reuse of large automatons. Implemented in C and tested on Python 3.6+. Works on Linux, macOS and Windows. BSD-3-Cause license." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyahocorasick-2.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8c46288044c4f71392efb4f5da0cb8abd160787a8b027afc85079e9c3d7551eb"}, + {file = "pyahocorasick-2.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1f15529c83b8c6e0548d7d3c5631fefa23fba5190e67be49d6c9e24a6358ff9c"}, + {file = "pyahocorasick-2.1.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:581e3d85043f1797543796f021e8d7d48c18e594529b72d86f70ea78abc88fff"}, + {file = "pyahocorasick-2.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c860ad9cb59e56c31aed8a5d1ee9d83a0151277b09198d027ffce213697716ed"}, + {file = "pyahocorasick-2.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:4f8eba88fce34a1d8020638a4a8732c6241a5d85fe12be8669b7495d99d36b6a"}, + {file = "pyahocorasick-2.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d6e0da0a8fc78c694778dced537c1bfb8b2f178ec92a82d81539d2e35a15cba0"}, + {file = "pyahocorasick-2.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:658d55e51c7588a5dba57de674241a16a3c94bf57f3bfd70022c4d7defe2b0f4"}, + {file = "pyahocorasick-2.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9f2728ac77bab807ba65c6ef41be30358ef0c9bb6960c9fe070d43f7024cb91"}, + {file = "pyahocorasick-2.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a58c44c407a45155dc7a3253274b5fd78ab00b579bd5685059610867cdb37142"}, + {file = "pyahocorasick-2.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:d8254d6333df5eb400ed3ec8b24da9e3f5da8e28b94a71392391703a7aac568d"}, + {file = "pyahocorasick-2.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:82b0d20e82cc282fd29324e8df93809cebbffb345055214ce4b7873698df02c8"}, + {file = "pyahocorasick-2.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6dedb9fed92705b742d6aa3d87abb1ec999f57310ef32b962f65f4e42182fe0a"}, + {file = "pyahocorasick-2.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f209796e7d354734781dd883c333596e482c70136fa76a4cb169f383e6c40bca"}, + {file = "pyahocorasick-2.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8337af64c649223cff548c7204dda823e83622d63e5449bc51ae069efb2f240f"}, + {file = "pyahocorasick-2.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:5ebe0d1e15afb782477e3d0aa1dce28ab9dad1200211fb785b9c1cc1208e6f04"}, + {file = "pyahocorasick-2.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:7454ba5fa528958ca9a1bc3143f8e980bd7817ea481f46495e6ffa89675ab93b"}, + {file = "pyahocorasick-2.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3795ac922d21fbfea40a6b3a330762e8b38ce8ba511b1eb15bf9eeb9303b2662"}, + {file = "pyahocorasick-2.1.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:8e92150849a3c13da37e37ca6374fa55960fd5c845029eca02d9b5846b26fe48"}, + {file = "pyahocorasick-2.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:23b183600e2087f16f6c5e6185d61525ad74335f2a5b693dd6d66bba2f6a4b05"}, + {file = "pyahocorasick-2.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:7034b26e145518610651339b8701568a3533a3114b00cf55f22bca80bff58e6d"}, + {file = "pyahocorasick-2.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:36491675a13fe4181a6b3bccfc9032a1a5d03bd3b0a151c06f8865c16ba44b42"}, + {file = "pyahocorasick-2.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:895ab1ff5384ee5325c74cbacafc419e534f1f110b9fb3c544cc56832ecce082"}, + {file = "pyahocorasick-2.1.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:bf4a4b19ac37e9a7087646b8bcc306acd7a91649355d59b866b756068e35d018"}, + {file = "pyahocorasick-2.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f44f96496aa773fc5bf302ddf968dd6b920fab34522f944392af8bde13cbe805"}, + {file = "pyahocorasick-2.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:05b7c2ef52da247efec6fb5a011113b7e943e961e22aaaf757cb9c15083440c9"}, + {file = "pyahocorasick-2.1.0.tar.gz", hash = "sha256:4df4845c1149e9fa4aa33f0f0aa35f5a42957a43a3d6e447c9b44e679e2672ea"}, +] + +[package.extras] +testing = ["pytest", "setuptools", "twine", "wheel"] + [[package]] name = "pydantic" version = "2.8.2" @@ -1579,6 +1641,7 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -1586,8 +1649,16 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -1604,6 +1675,7 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -1611,6 +1683,7 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -1879,6 +1952,21 @@ files = [ [package.extras] tests = ["pytest", "pytest-cov"] +[[package]] +name = "textsearch" +version = "0.0.24" +description = "Find strings/words in text; convenience and C speed" +optional = false +python-versions = "*" +files = [ + {file = "textsearch-0.0.24-py2.py3-none-any.whl", hash = "sha256:1bbc4cc36300fbf0bbaa865500f84e907c85f6a48faf37da6e098407b405ed09"}, + {file = "textsearch-0.0.24.tar.gz", hash = "sha256:2d23b5c3116715b65bccc18bc870ecc236ec8480d48cd5f257cc60bf66bb241a"}, +] + +[package.dependencies] +anyascii = "*" +pyahocorasick = "*" + [[package]] name = "tomlkit" version = "0.13.0" @@ -2162,4 +2250,4 @@ test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", [metadata] lock-version = "2.0" python-versions = "~3.12" -content-hash = "a12ab2befe68ba8f6a918f5568b9a308af7c3d954e982a39eacb4053088f726f" +content-hash = "0f2e9082200eeb36ca502309770dcba911820fc9064cfad802a54b493c0f9ea3" diff --git a/pyproject.toml b/pyproject.toml index 4a7c3e6..125fc85 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,6 +17,7 @@ redis = { extras = ["hiredis"], version = "^5.0.7" } nltk = "^3.8.1" pydantic = "^2.8.2" redisvl = {extras = ["hiredis"], version = "^0.2.3"} +contractions = "^0.1.73" [tool.poetry.dev-dependencies] commitizen = "3.27.0" From 4186e2c5c766f6674e5dc8c9c9f9cbd70dbe418d Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Sun, 21 Jul 2024 14:02:03 +0200 Subject: [PATCH 051/168] refactor: move startup logic into bot class (#27) --- conftest.py | 14 +-- courageous_comets/__main__.py | 133 ++------------------------- courageous_comets/client.py | 83 ++++++++++++++--- courageous_comets/nltk/__init__.py | 3 + courageous_comets/nltk/helpers.py | 48 ++++++++++ courageous_comets/redis/__init__.py | 3 + courageous_comets/redis/helpers.py | 67 ++++++++++++++ courageous_comets/settings.py | 3 - courageous_comets/test__client.py | 56 +++++------ courageous_comets/test__sentiment.py | 5 - pyproject.toml | 3 +- 11 files changed, 233 insertions(+), 185 deletions(-) create mode 100644 courageous_comets/nltk/__init__.py create mode 100644 courageous_comets/nltk/helpers.py create mode 100644 courageous_comets/redis/helpers.py diff --git a/conftest.py b/conftest.py index 98b373e..d51fdff 100644 --- a/conftest.py +++ b/conftest.py @@ -1,10 +1,10 @@ -import os from pathlib import Path -import nltk import pytest import yaml +from courageous_comets.nltk import init_nltk + @pytest.fixture(scope="session") def application_config() -> dict: @@ -14,13 +14,7 @@ def application_config() -> dict: @pytest.fixture(scope="session", autouse=True) -def _load_nltk_data(application_config: dict) -> None: +async def _load_nltk_data(application_config: dict) -> None: """Load the NLTK data for testing.""" resources = application_config.get("nltk", []) - - for resource in resources: - nltk.download( - resource, - quiet=True, - download_dir=os.getenv("NLTK_DATA", "nltk_data"), - ) + await init_nltk(resources) diff --git a/courageous_comets/__main__.py b/courageous_comets/__main__.py index 5d8325f..d841dee 100644 --- a/courageous_comets/__main__.py +++ b/courageous_comets/__main__.py @@ -1,118 +1,15 @@ import asyncio import logging -import sys import discord -import nltk -import redis.asyncio as redis -import yaml -from redisvl.index import AsyncSearchIndex from courageous_comets import bot, exceptions, settings -from courageous_comets.redis import schema - - -async def download_nltk_resource(resource: str, semaphore: asyncio.Semaphore) -> None: - """Download an NLTK resource to the specified directory.""" - async with semaphore: - logging.debug("Downloading NLTK resource '%s'...", resource) - try: - await asyncio.to_thread( - nltk.download, - resource, - download_dir=settings.NLTK_DATA_DIR, - quiet=True, - raise_on_error=True, - ) - except ValueError as e: - message = f"Invalid NLTK resource '{resource}'" - raise exceptions.NltkInitializationError(message) from e - - -async def init_nltk() -> None: - """ - Ensure all required NLTK resources are downloaded. - - Downloads the resources specified in the bot configuration file. - """ - with settings.BOT_CONFIG_PATH.open("r") as file: - config = yaml.safe_load(file) - - resources = config.get("nltk", []) - - semaphore = asyncio.Semaphore(settings.NLTK_DOWNLOAD_CONCURRENCY) - download_tasks = [download_nltk_resource(resource, semaphore) for resource in resources] - - await asyncio.gather(*download_tasks) - - logging.info("NLTK resources downloaded") - - -async def create_indexes(redis: redis.Redis) -> None: - """Create search indexes on Redis.""" - logging.debug("Creating indexes on redis...") - - message_index = AsyncSearchIndex.from_dict(schema.MESSAGE_SCHEMA) - message_index.set_client(redis) - - await message_index.create(overwrite=True) - - logging.info("Created indexes on Redis") - - -async def init_redis() -> redis.Redis: - """ - Initialize the Redis connection. - - Returns - ------- - redis.asyncio.Redis - The Redis connection instance. - - Raises - ------ - courageous_comets.exceptions.AuthenticationError - If the Redis password is incorrect. - courageous_comets.exceptions.DatabaseConnectionError - If the connection to Redis cannot be established. - """ - logging.debug("Connecting to Redis...") - - instance = redis.Redis( - host=settings.REDIS_HOST, - port=settings.REDIS_PORT, - password=settings.REDIS_PASSWORD, - ) - - # Check if the connection is successful - try: - await instance.ping() - except redis.AuthenticationError as e: - message = "Redis authentication failed. Check the REDIS_PASSWORD environment variable." - raise exceptions.AuthenticationError(message) from e - except redis.RedisError as e: - message = f"Could not connect to Redis at {settings.REDIS_HOST}:{settings.REDIS_PORT}" - raise exceptions.DatabaseConnectionError(message) from e - - logging.info( - "Connected to Redis at %s:%s", - settings.REDIS_HOST, - settings.REDIS_PORT, - ) - - # Create search indexes on Redis - await create_indexes(instance) - - logging.info("Redis initialization complete") - - return instance async def main() -> None: """ Start the appication. - Handles the setup and teardown of the Discord client and Redis connection. If a critical error occurs, attempt to shut down gracefully. Raises @@ -121,29 +18,17 @@ async def main() -> None: If the Discord token is not valid. """ logging.info("Starting the Courageous Comets application ☄️") - - # Initialize dependencies - redis = await init_redis() - await init_nltk() - try: - logging.info("Starting the Discord client 🚀") await bot.start(settings.DISCORD_TOKEN) - except discord.LoginFailure as e: - message = "Discord login failed. Check the DISCORD_TOKEN environment variable." - raise exceptions.AuthenticationError(message) from e + except discord.LoginFailure: + logging.critical("Discord login failed. Check the DISCORD_TOKEN environment variable.") + except (exceptions.CourageousCometsError, discord.DiscordException) as e: + logging.critical( + "A fatal error occurred while running the Courageous Comets application.", + exc_info=e, + ) finally: - logging.info("Shutting down gracefully...") - logging.debug("Closing Redis connection...") - await redis.aclose() - logging.info("Application shutdown complete. Goodbye! 👋") + await bot.close() -try: - asyncio.run(main()) -except (exceptions.CourageousCometsError, discord.DiscordException) as e: - logging.critical( - "A fatal error occurred while running the bot.", - exc_info=e, - ) - sys.exit(1) +asyncio.run(main()) diff --git a/courageous_comets/client.py b/courageous_comets/client.py index 698075f..a82c164 100644 --- a/courageous_comets/client.py +++ b/courageous_comets/client.py @@ -1,6 +1,7 @@ import logging import typing from collections.abc import Collection +from typing import override import discord import yaml @@ -8,6 +9,8 @@ from discord.ext import commands from courageous_comets import settings +from courageous_comets.nltk import init_nltk +from courageous_comets.redis import init_redis logger = logging.getLogger(__name__) @@ -18,24 +21,76 @@ with settings.BOT_CONFIG_PATH.open() as config_file: CONFIG = yaml.safe_load(config_file) -bot = commands.Bot(command_prefix=commands.when_mentioned, intents=intents) +class CourageousCometsBot(commands.Bot): + """ + The Courageous Comets Discord bot. + + Attributes + ---------- + redis : redis.asyncio.Redis | None + The Redis connection instance for the bot, or `None` if not connected. + """ + + def __init__(self) -> None: + super().__init__( + command_prefix=commands.when_mentioned, + intents=intents, + ) + self.redis = None + + @override + async def close(self) -> None: + """ + Gracefully shut down the application. + + First closes the Discord client, then the Redis connection if it exists. + + Overrides the `close` method in `discord.ext.commands.Bot`. + """ + logger.info("Gracefully shutting down the application...") + + await super().close() + + if self.redis is not None: + await self.redis.aclose() + logger.info("Closed the Redis connection") -@bot.event -async def on_ready() -> None: - """Informs when the bot is ready.""" - logger.info("Logged in as %s", bot.user) + logger.info("Application shutdown complete. Goodbye! 👋") + + async def on_ready(self) -> None: + """Log a message when the bot is ready.""" + logger.info("Logged in as %s", self.user) + + async def setup_hook(self) -> None: + """ + On startup, initialize the bot. + + Sets up the Redis connection, downloads NLTK resources, and loads all cogs. + """ + logger.info("Initializing the Discord client...") + + self.redis = await init_redis() + + nltk_resources = CONFIG.get("nltk", []) + await init_nltk(nltk_resources) + + cogs = CONFIG.get("cogs", []) + await self.load_cogs(cogs) + + logger.info("Initialization complete 🚀") + + async def load_cogs(self, cogs: list[str]) -> None: + """Load all given cogs.""" + for cog in cogs: + try: + await bot.load_extension(cog) + logger.debug("Loaded cog %s", cog) + except commands.ExtensionError as e: + logger.exception("Failed to load cog %s", cog, exc_info=e) -@bot.event -async def setup_hook() -> None: - """Load all cogs in the config file.""" - for cog in CONFIG["cogs"]: - try: - await bot.load_extension(cog) - logger.info("Loaded cog %s", cog) - except commands.ExtensionError as e: - logger.exception("Failed to load cog %s", cog, exc_info=e) +bot = CourageousCometsBot() @bot.command() diff --git a/courageous_comets/nltk/__init__.py b/courageous_comets/nltk/__init__.py new file mode 100644 index 0000000..3571706 --- /dev/null +++ b/courageous_comets/nltk/__init__.py @@ -0,0 +1,3 @@ +from .helpers import init_nltk + +__all__ = ["init_nltk"] diff --git a/courageous_comets/nltk/helpers.py b/courageous_comets/nltk/helpers.py new file mode 100644 index 0000000..4dd9bd2 --- /dev/null +++ b/courageous_comets/nltk/helpers.py @@ -0,0 +1,48 @@ +import asyncio +import logging +from pathlib import Path + +import nltk + +from courageous_comets import exceptions, settings + +logger = logging.getLogger(__name__) + + +async def download_nltk_resource(resource: str, semaphore: asyncio.Semaphore) -> None: + """Download an NLTK resource to the specified directory.""" + async with semaphore: + logger.debug("Downloading NLTK resource '%s'...", resource) + try: + await asyncio.to_thread( + nltk.download, + resource, + download_dir=settings.NLTK_DATA_DIR, + quiet=True, + raise_on_error=True, + ) + except ValueError as e: + message = f"Invalid NLTK resource '{resource}'" + raise exceptions.NltkInitializationError(message) from e + + +async def init_nltk(resources: list[str]) -> None: + """ + Ensure all required NLTK resources are downloaded. + + Downloads the resources specified in the bot configuration file. + """ + if not any(resources): + logger.debug("No NLTK resources to download") + return + + # Create the NLTK data directory if it does not exist to avoid a race condition when running + # multiple download tasks concurrently + Path(settings.NLTK_DATA_DIR).mkdir(parents=True, exist_ok=True) + + semaphore = asyncio.Semaphore(settings.NLTK_DOWNLOAD_CONCURRENCY) + download_tasks = [download_nltk_resource(resource, semaphore) for resource in resources] + + await asyncio.gather(*download_tasks) + + logger.debug("NLTK resources downloaded") diff --git a/courageous_comets/redis/__init__.py b/courageous_comets/redis/__init__.py index e69de29..ca55dc9 100644 --- a/courageous_comets/redis/__init__.py +++ b/courageous_comets/redis/__init__.py @@ -0,0 +1,3 @@ +from .helpers import init_redis + +__all__ = ["init_redis"] diff --git a/courageous_comets/redis/helpers.py b/courageous_comets/redis/helpers.py new file mode 100644 index 0000000..d4d5980 --- /dev/null +++ b/courageous_comets/redis/helpers.py @@ -0,0 +1,67 @@ +import logging + +import redis.asyncio as redis +from redisvl.index import AsyncSearchIndex + +from courageous_comets import exceptions, settings +from courageous_comets.redis import schema + +logger = logging.getLogger(__name__) + + +async def create_indexes(redis: redis.Redis) -> None: + """Create search indexes on Redis.""" + logger.debug("Creating indexes on redis...") + + message_index = AsyncSearchIndex.from_dict(schema.MESSAGE_SCHEMA) + message_index.set_client(redis) + + await message_index.create(overwrite=True) + + logger.debug("Created indexes on Redis") + + +async def init_redis() -> redis.Redis: + """ + Initialize the Redis connection. + + Returns + ------- + redis.asyncio.Redis + The Redis connection instance. + + Raises + ------ + courageous_comets.exceptions.AuthenticationError + If the Redis password is incorrect. + courageous_comets.exceptions.DatabaseConnectionError + If the connection to Redis cannot be established. + """ + logger.debug("Connecting to Redis...") + + instance = redis.Redis( + host=settings.REDIS_HOST, + port=settings.REDIS_PORT, + password=settings.REDIS_PASSWORD, + ) + + try: + await instance.ping() + except redis.AuthenticationError as e: + message = "Redis authentication failed. Check the REDIS_PASSWORD environment variable." + raise exceptions.AuthenticationError(message) from e + except redis.RedisError as e: + message = f"Could not connect to Redis at {settings.REDIS_HOST}:{settings.REDIS_PORT}" + raise exceptions.DatabaseConnectionError(message) from e + + logger.debug( + "Connected to Redis at %s:%s", + settings.REDIS_HOST, + settings.REDIS_PORT, + ) + + await create_indexes(instance) + + logger.debug("Redis initialization complete") + + return instance diff --git a/courageous_comets/settings.py b/courageous_comets/settings.py index 5c7be41..fe92871 100644 --- a/courageous_comets/settings.py +++ b/courageous_comets/settings.py @@ -5,7 +5,6 @@ import sys from pathlib import Path -from discord.utils import setup_logging from dotenv import load_dotenv from courageous_comets.exceptions import ConfigurationValueError @@ -130,8 +129,6 @@ def read_redis_port() -> int: os.getenv("LOG_LEVEL", "INFO"), logging.INFO, ) -setup_logging(level=LOG_LEVEL) - try: DISCORD_TOKEN = read_discord_token() diff --git a/courageous_comets/test__client.py b/courageous_comets/test__client.py index 96037c4..e05bb6f 100644 --- a/courageous_comets/test__client.py +++ b/courageous_comets/test__client.py @@ -5,7 +5,7 @@ from discord.ext import commands from pytest_mock import MockerFixture, MockType -from .client import bot, intents, logger, on_ready, setup_hook, sync +from .client import CourageousCometsBot, intents, logger, sync class MockAsyncContextManager: @@ -29,7 +29,13 @@ def mock_context(mocker: MockerFixture) -> MockType: return ctx -def test__bot_has_required_intents() -> None: +@pytest.fixture() +def bot() -> CourageousCometsBot: + """Create a CourageousCometsBot instance for testing.""" + return CourageousCometsBot() + + +def test__bot_has_required_intents(bot: CourageousCometsBot) -> None: """ Test whether the bot has the required intents. @@ -40,8 +46,7 @@ def test__bot_has_required_intents() -> None: assert bot.intents == intents -@pytest.mark.asyncio() -async def test__on_ready_logs_message(mocker: MockerFixture) -> None: +async def test__on_ready_logs_message(bot: CourageousCometsBot, mocker: MockerFixture) -> None: """ Test whether the on_ready function logs the expected message. @@ -50,14 +55,13 @@ async def test__on_ready_logs_message(mocker: MockerFixture) -> None: - The logger.info function is called with the expected message. """ logger_info = mocker.spy(logger, "info") - await on_ready() + await bot.on_ready() logger_info.assert_called_with("Logged in as %s", mocker.ANY) -@pytest.mark.asyncio() -async def test__setup_hook_loads_all_cogs(mocker: MockerFixture) -> None: +async def test__load_cogs_loads_all_cogs(bot: CourageousCometsBot, mocker: MockerFixture) -> None: """ - Test whether the setup_hook function loads all cogs from the config file. + Test whether the load_cogs function loads all cogs from the config file. Asserts ------- @@ -65,42 +69,44 @@ async def test__setup_hook_loads_all_cogs(mocker: MockerFixture) -> None: """ cogs = ["cog1", "cog2", "cog3"] - mocker.patch("courageous_comets.client.CONFIG", {"cogs": cogs}) load_extension = mocker.patch("discord.ext.commands.Bot.load_extension") - await setup_hook() + await bot.load_cogs(cogs) for cog in cogs: load_extension.assert_any_call(cog) -@pytest.mark.asyncio() -async def test__setup_hook_logs_loaded_cogs(mocker: MockerFixture) -> None: +async def test__load_cogs_logs_loaded_cogs( + bot: CourageousCometsBot, + mocker: MockerFixture, +) -> None: """ - Test whether the setup_hook function logs the correct message for each cog. + Test whether the load_cogs function logs the correct message for each cog. Asserts ------- - The bot.load_extension function is awaited for each cog in the config file. - - The logger.info function is called with the correct message for each cog. + - The logger.debug function is called with the correct message for each cog. """ cogs = ["cog1", "cog2", "cog3"] - mocker.patch("courageous_comets.client.CONFIG", {"cogs": cogs}) load_extension = mocker.patch("discord.ext.commands.Bot.load_extension", return_value=None) - logger_info = mocker.spy(logger, "info") + logger_debug = mocker.spy(logger, "debug") - await setup_hook() + await bot.load_cogs(cogs) for cog in cogs: load_extension.assert_any_await(cog) - logger_info.assert_any_call("Loaded cog %s", cog) + logger_debug.assert_any_call("Loaded cog %s", cog) -@pytest.mark.asyncio() -async def test__setup_hook_logs_exception_on_extension_error(mocker: MockerFixture) -> None: +async def test__load_cogs_logs_exception_on_extension_error( + bot: CourageousCometsBot, + mocker: MockerFixture, +) -> None: """ - Test whether the setup_hook function logs an exception when an ExtensionError is raised. + Test whether the load_cogs function logs an exception when an ExtensionError is raised. Asserts ------- @@ -108,19 +114,17 @@ async def test__setup_hook_logs_exception_on_extension_error(mocker: MockerFixtu - The logger.exception function is called when an ExtensionError is raised. """ cogs = ["cog1"] - mocker.patch("courageous_comets.client.CONFIG", {"cogs": cogs}) expected = commands.ExtensionError(name="cog1") load_extension = mocker.patch("discord.ext.commands.Bot.load_extension", side_effect=expected) logger_exception = mocker.spy(logger, "exception") - await setup_hook() + await bot.load_cogs(cogs) load_extension.assert_awaited_with("cog1") logger_exception.assert_called_with("Failed to load cog %s", "cog1", exc_info=expected) -@pytest.mark.asyncio() async def test__sync_syncs_to_current_guild(mock_context: MockType) -> None: """ Test whether the sync command syncs to the current guild. @@ -138,7 +142,6 @@ async def test__sync_syncs_to_current_guild(mock_context: MockType) -> None: ) -@pytest.mark.asyncio() async def test__sync_syncs_to_global_scope(mock_context: MockType) -> None: """ Test whether the sync command syncs to the global scope. @@ -156,7 +159,6 @@ async def test__sync_syncs_to_global_scope(mock_context: MockType) -> None: ) -@pytest.mark.asyncio() async def test__sync_removes_non_global_commands(mock_context: MockType) -> None: """ Test whether the sync command removes non-global commands. @@ -174,7 +176,6 @@ async def test__sync_removes_non_global_commands(mock_context: MockType) -> None mock_context.send.assert_awaited_with("Synced 0 command(s) to the current guild.") -@pytest.mark.asyncio() async def test__sync_syncs_to_given_guilds( mocker: MockerFixture, mock_context: MockType, @@ -197,7 +198,6 @@ async def test__sync_syncs_to_given_guilds( mock_context.send.assert_awaited_with(f"Synced the tree to {len(guilds)}/{len(guilds)}.") -@pytest.mark.asyncio() async def test__sync_logs_exception_on_http_exception( mocker: MockerFixture, mock_context: MockType, diff --git a/courageous_comets/test__sentiment.py b/courageous_comets/test__sentiment.py index fe29e7d..bf7f463 100644 --- a/courageous_comets/test__sentiment.py +++ b/courageous_comets/test__sentiment.py @@ -67,7 +67,6 @@ def test__calculate_sentiment_truncates_long_messages( assert logger_warning.called == expected -@pytest.mark.asyncio() async def test__store_sentiment_calculates_and_stores_sentiment( *, mocker: MockerFixture, @@ -102,7 +101,6 @@ async def test__store_sentiment_calculates_and_stores_sentiment( ) -@pytest.mark.asyncio() async def test__store_sentiment_ignores_empty_messages( *, mocker: MockerFixture, @@ -128,7 +126,6 @@ async def test__store_sentiment_ignores_empty_messages( Mock(content="test", guild=Mock(id=1), channel=Mock(id=1), author=None, id=1), ], ) -@pytest.mark.asyncio() async def test__store_sentiment_ignores_messages_without_ids( *, redis: MockType, @@ -145,7 +142,6 @@ async def test__store_sentiment_ignores_messages_without_ids( redis.hset.assert_not_awaited() -@pytest.mark.asyncio() async def test__get_sentiment_retrieves_sentiment_from_redis( *, redis: MockType, @@ -173,7 +169,6 @@ async def test__get_sentiment_retrieves_sentiment_from_redis( assert result == expected -@pytest.mark.asyncio() async def test__get_sentiment_handles_missing_sentiment( *, redis: MockType, diff --git a/pyproject.toml b/pyproject.toml index 125fc85..08a9109 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,7 @@ pyyaml = "^6.0.1" redis = { extras = ["hiredis"], version = "^5.0.7" } nltk = "^3.8.1" pydantic = "^2.8.2" -redisvl = {extras = ["hiredis"], version = "^0.2.3"} +redisvl = { extras = ["hiredis"], version = "^0.2.3" } contractions = "^0.1.73" [tool.poetry.dev-dependencies] @@ -38,6 +38,7 @@ pythonVersion = "3.12" reportUnnecessaryTypeIgnoreComment = "error" [tool.pytest.ini_options] +asyncio_mode = "auto" filterwarnings = [ "ignore:'audioop' is deprecated and slated for removal in Python 3.13", ] From f4d70da715390756abf9fe762b886e4abdb8355d Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Sun, 21 Jul 2024 15:05:28 +0200 Subject: [PATCH 052/168] feat: setup transformer models (#28) Co-authored-by: Uchechukwu Orji --- .devcontainer/devcontainer.json | 4 + .github/workflows/ci.yaml | 17 + .gitignore | 3 + Dockerfile | 6 + application.yaml | 2 + conftest.py | 8 + courageous_comets/client.py | 11 +- courageous_comets/exceptions.py | 4 + courageous_comets/settings.py | 5 + courageous_comets/transformers/__init__.py | 3 + courageous_comets/transformers/helpers.py | 57 ++ docs/admin-guide/configuration.md | 32 +- poetry.lock | 893 ++++++++++++++++++++- pyproject.toml | 3 + 14 files changed, 1036 insertions(+), 12 deletions(-) create mode 100644 courageous_comets/transformers/__init__.py create mode 100644 courageous_comets/transformers/helpers.py diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 7852d2b..c5d274a 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -6,6 +6,7 @@ "REDIS_HOST": "localhost", "REDIS_PASSWORD": "redis", "REDIS_PORT": "6379", + "SENTENCE_TRANSFORMERS_HOME": "${containerWorkspaceFolder}/hf_data", "SOPS_AGE_KEY_FILE": "${containerWorkspaceFolder}/secrets/keys.txt" }, "customizations": { @@ -18,6 +19,9 @@ "-ms-python.autopep8" ], "settings": { + "[markdown]": { + "editor.defaultFormatter": "DavidAnson.vscode-markdownlint" + }, "[python]": { "editor.defaultFormatter": "charliermarsh.ruff" }, diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 5435c35..9d58477 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -16,11 +16,28 @@ jobs: BOT_CONFIG_PATH: ${{ github.workspace }}/application.yaml DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }} NLTK_DATA: ${{ github.workspace }}/nltk_data + SENTENCE_TRANSFORMERS_HOME: ${{ github.workspace }}/hf_data steps: - name: Checkout code uses: actions/checkout@v4 + - name: Set up caching for NLTK_DATA + uses: actions/cache@v4 + with: + path: ${{ env.NLTK_DATA }} + key: ${{ runner.os }}-nltk-data-${{ hashFiles(env.BOT_CONFIG_PATH) }} + restore-keys: | + ${{ runner.os }}-nltk-data- + + - name: Set up caching for SENTENCE_TRANSFORMERS_HOME + uses: actions/cache@v4 + with: + path: ${{ env.SENTENCE_TRANSFORMERS_HOME }} + key: ${{ runner.os }}-hf-data-${{ hashFiles(env.BOT_CONFIG_PATH) }} + restore-keys: | + ${{ runner.os }}-hf-data- + - name: Setup Python, Poetry and dependencies uses: packetcoders/action-setup-cache-python-poetry@main with: diff --git a/.gitignore b/.gitignore index 7fd16ee..9768057 100644 --- a/.gitignore +++ b/.gitignore @@ -181,3 +181,6 @@ pyrightconfig.json # NLTK nltk_data + +# Huggingface +hf_data diff --git a/Dockerfile b/Dockerfile index 9723e11..4a5d560 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,6 +9,12 @@ LABEL org.opencontainers.image.licenses=MIT LABEL org.opencontainers.image.source=https://github.com/thijsfranck/courageous-comets LABEL org.opencontainers.image.title="Courageous Comets" +# Set default environment variables +ENV BOT_CONFIG_PATH=/app/application.yaml +ENV LOG_LEVEL=INFO +ENV NLTK_DATA=/app/nltk_data +ENV SENTENCE_TRANSFORMERS_HOME=/app/hf_data + # Add a non-root user RUN adduser --system courageous-comets diff --git a/application.yaml b/application.yaml index 0b563fb..c557940 100644 --- a/application.yaml +++ b/application.yaml @@ -5,3 +5,5 @@ nltk: - punkt - stopwords - vader_lexicon +transformers: + - sentence-transformers/all-MiniLM-L6-v2 diff --git a/conftest.py b/conftest.py index d51fdff..192181e 100644 --- a/conftest.py +++ b/conftest.py @@ -4,6 +4,7 @@ import yaml from courageous_comets.nltk import init_nltk +from courageous_comets.transformers import init_transformers @pytest.fixture(scope="session") @@ -18,3 +19,10 @@ async def _load_nltk_data(application_config: dict) -> None: """Load the NLTK data for testing.""" resources = application_config.get("nltk", []) await init_nltk(resources) + + +@pytest.fixture(scope="session", autouse=True) +async def _load_transformers(application_config: dict) -> None: + """Load the transformers for testing.""" + transformers = application_config.get("transformers", []) + await init_transformers(transformers) diff --git a/courageous_comets/client.py b/courageous_comets/client.py index a82c164..ba1dffb 100644 --- a/courageous_comets/client.py +++ b/courageous_comets/client.py @@ -11,6 +11,7 @@ from courageous_comets import settings from courageous_comets.nltk import init_nltk from courageous_comets.redis import init_redis +from courageous_comets.transformers import init_transformers logger = logging.getLogger(__name__) @@ -66,7 +67,12 @@ async def setup_hook(self) -> None: """ On startup, initialize the bot. - Sets up the Redis connection, downloads NLTK resources, and loads all cogs. + Performs the following setup actions: + + - Connect to Redis. + - Load the NLTK resources. + - Load the transformers. + - Load the cogs. """ logger.info("Initializing the Discord client...") @@ -75,6 +81,9 @@ async def setup_hook(self) -> None: nltk_resources = CONFIG.get("nltk", []) await init_nltk(nltk_resources) + transformers = CONFIG.get("transformers", []) + await init_transformers(transformers) + cogs = CONFIG.get("cogs", []) await self.load_cogs(cogs) diff --git a/courageous_comets/exceptions.py b/courageous_comets/exceptions.py index c4eacd8..7c6cc3e 100644 --- a/courageous_comets/exceptions.py +++ b/courageous_comets/exceptions.py @@ -38,3 +38,7 @@ class DatabaseConnectionError(CourageousCometsError): class NltkInitializationError(CourageousCometsError): """Raised when the application fails to download the NLTK dependencies on startup.""" + + +class HuggingFaceModelDownloadError(CourageousCometsError): + """Raised when the application fails to download a huggingface model on startup.""" diff --git a/courageous_comets/settings.py b/courageous_comets/settings.py index fe92871..c78741d 100644 --- a/courageous_comets/settings.py +++ b/courageous_comets/settings.py @@ -139,6 +139,11 @@ def read_redis_port() -> int: REDIS_PORT = read_redis_port() REDIS_PASSWORD = os.getenv("REDIS_PASSWORD") REDIS_KEYS_PREFIX = os.getenv("REDIS_KEYS_PREFIX", "courageous_comets") + SENTENCE_TRANSFORMERS_HOME = os.getenv( + "SENTENCE_TRANSFORMERS_HOME", + "hf_data", + ) + SENTENCE_TRANSFORMERS_CONCURRENCY = read_int("SENTENCE_TRANSFORMERS_CONCURRENCY", 3) except ConfigurationValueError as e: logging.critical( "Cannot start the application due to configuration errors", diff --git a/courageous_comets/transformers/__init__.py b/courageous_comets/transformers/__init__.py new file mode 100644 index 0000000..8667502 --- /dev/null +++ b/courageous_comets/transformers/__init__.py @@ -0,0 +1,3 @@ +from .helpers import init_transformers + +__all__ = ["init_transformers"] diff --git a/courageous_comets/transformers/helpers.py b/courageous_comets/transformers/helpers.py new file mode 100644 index 0000000..4715043 --- /dev/null +++ b/courageous_comets/transformers/helpers.py @@ -0,0 +1,57 @@ +import asyncio +import logging +from pathlib import Path + +from sentence_transformers import SentenceTransformer + +from courageous_comets import settings + +logger = logging.getLogger(__name__) + + +async def download_transformer(resource: str, semaphore: asyncio.Semaphore) -> None: + """ + Download a transformer to the specified directory. + + Parameters + ---------- + resource : str + The transformer to download. + semaphore : asyncio.Semaphore + The semaphore to use for concurrency control. + """ + logger.debug("Downloading transformer %r...", resource) + + async with semaphore: + await asyncio.to_thread( + SentenceTransformer, + model_name_or_path=resource, + cache_folder=settings.SENTENCE_TRANSFORMERS_HOME, + ) + + logger.debug("Transformer %r downloaded", resource) + + +async def init_transformers(resources: list[str]) -> None: + """ + Ensure all required transformers are downloaded. + + Parameters + ---------- + resources : list[str] + The list of transformers to download. + """ + if not any(resources): + logger.debug("No transformers to download") + return + + # Create the Huggingface data directory if it does not exist to avoid a race condition when + # running multiple download tasks concurrently + Path(settings.SENTENCE_TRANSFORMERS_HOME).mkdir(parents=True, exist_ok=True) + + semaphore = asyncio.Semaphore(settings.SENTENCE_TRANSFORMERS_CONCURRENCY) + download_tasks = [download_transformer(resource, semaphore) for resource in resources] + + await asyncio.gather(*download_tasks) + + logger.debug("Transformers downloaded") diff --git a/docs/admin-guide/configuration.md b/docs/admin-guide/configuration.md index 29c8b8b..f987d91 100644 --- a/docs/admin-guide/configuration.md +++ b/docs/admin-guide/configuration.md @@ -2,16 +2,18 @@ The following environment variables are available to configure the application: -| Variable | Description | Required | Default | -| --------------------------------------------------------- | --------------------------------------------------------------------- | -------- | ------------------ | -| [`DISCORD_TOKEN`](#discord_token) | The Discord bot token. | Yes | - | -| [`BOT_CONFIG_PATH`](#bot_config_path) | The path to the bot's configuration file. | No | `application.yaml` | -| [`LOG_LEVEL`](#log_level) | The minimum log level. | No | `INFO` | -| [`NLTK_DATA`](#nltk_data) | The directory containing NLTK data files. | No | `nltk_data` | -| [`NLTK_DOWNLOAD_CONCURRENCY`](#nltk_download_concurrency) | The maximum number of concurrent downloads when installing NLTK data. | No | `3` | -| [`REDIS_HOST`](#redis_host) | The Redis host. | No | `localhost` | -| [`REDIS_PORT`](#redis_port) | The Redis port. | No | `6379` | -| [`REDIS_PASSWORD`](#redis_password) | The Redis password. | No | - | +| Variable | Description | Required | Default | +| ------------------------------------------------------------------------- | ------------------------------------------------------------------------ | -------- | ------------------ | +| [`DISCORD_TOKEN`](#discord_token) | The Discord bot token. | Yes | - | +| [`BOT_CONFIG_PATH`](#bot_config_path) | The path to the bot's configuration file. | No | `application.yaml` | +| [`LOG_LEVEL`](#log_level) | The minimum log level. | No | `INFO` | +| [`NLTK_DATA`](#nltk_data) | The directory containing NLTK data files. | No | `nltk_data` | +| [`NLTK_DOWNLOAD_CONCURRENCY`](#nltk_download_concurrency) | The maximum number of concurrent downloads when installing NLTK data. | No | `3` | +| [`REDIS_HOST`](#redis_host) | The Redis host. | No | `localhost` | +| [`REDIS_PORT`](#redis_port) | The Redis port. | No | `6379` | +| [`REDIS_PASSWORD`](#redis_password) | The Redis password. | No | - | +| [`SENTENCE TRANSFORMERS_HOME`](#sentence_transformers_home) | The directory containing Sentence Transformers data files. | No | `hf_data` | +| [`SENTENCE_TRANSFORMERS_CONCURRENCY`](#sentence_transformers_concurrency) | The maximum number of concurrent downloads when installing transformers. | No | `3` | ## Required Settings @@ -82,3 +84,13 @@ is set by default. !!! DANGER "Security Warning" Do not share your Redis password with anyone! + +### `SENTENCE_TRANSFORMERS_HOME` + +The directory containing Sentence Transformers data files. By default, this is set to `hf_data` in the directory +from which the application is launched. In the Docker image, this directory is located at `/app/hf_data`. + +### `SENTENCE_TRANSFORMERS_CONCURRENCY` + +The maximum number of concurrent downloads when installing Sentence Transformers models. By default, this is set +to `3`. diff --git a/poetry.lock b/poetry.lock index bfc5425..787e021 100644 --- a/poetry.lock +++ b/poetry.lock @@ -548,6 +548,45 @@ files = [ {file = "frozenlist-1.4.1.tar.gz", hash = "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b"}, ] +[[package]] +name = "fsspec" +version = "2024.6.1" +description = "File-system specification" +optional = false +python-versions = ">=3.8" +files = [ + {file = "fsspec-2024.6.1-py3-none-any.whl", hash = "sha256:3cb443f8bcd2efb31295a5b9fdb02aee81d8452c80d28f97a6d0959e6cee101e"}, + {file = "fsspec-2024.6.1.tar.gz", hash = "sha256:fad7d7e209dd4c1208e3bbfda706620e0da5142bebbd9c384afb95b07e798e49"}, +] + +[package.extras] +abfs = ["adlfs"] +adl = ["adlfs"] +arrow = ["pyarrow (>=1)"] +dask = ["dask", "distributed"] +dev = ["pre-commit", "ruff"] +doc = ["numpydoc", "sphinx", "sphinx-design", "sphinx-rtd-theme", "yarl"] +dropbox = ["dropbox", "dropboxdrivefs", "requests"] +full = ["adlfs", "aiohttp (!=4.0.0a0,!=4.0.0a1)", "dask", "distributed", "dropbox", "dropboxdrivefs", "fusepy", "gcsfs", "libarchive-c", "ocifs", "panel", "paramiko", "pyarrow (>=1)", "pygit2", "requests", "s3fs", "smbprotocol", "tqdm"] +fuse = ["fusepy"] +gcs = ["gcsfs"] +git = ["pygit2"] +github = ["requests"] +gs = ["gcsfs"] +gui = ["panel"] +hdfs = ["pyarrow (>=1)"] +http = ["aiohttp (!=4.0.0a0,!=4.0.0a1)"] +libarchive = ["libarchive-c"] +oci = ["ocifs"] +s3 = ["s3fs"] +sftp = ["paramiko"] +smb = ["smbprotocol"] +ssh = ["paramiko"] +test = ["aiohttp (!=4.0.0a0,!=4.0.0a1)", "numpy", "pytest", "pytest-asyncio (!=0.22.0)", "pytest-benchmark", "pytest-cov", "pytest-mock", "pytest-recording", "pytest-rerunfailures", "requests"] +test-downstream = ["aiobotocore (>=2.5.4,<3.0.0)", "dask-expr", "dask[dataframe,test]", "moto[server] (>4,<5)", "pytest-timeout", "xarray"] +test-full = ["adlfs", "aiohttp (!=4.0.0a0,!=4.0.0a1)", "cloudpickle", "dask", "distributed", "dropbox", "dropboxdrivefs", "fastparquet", "fusepy", "gcsfs", "jinja2", "kerchunk", "libarchive-c", "lz4", "notebook", "numpy", "ocifs", "pandas", "panel", "paramiko", "pyarrow", "pyarrow (>=1)", "pyftpdlib", "pygit2", "pytest", "pytest-asyncio (!=0.22.0)", "pytest-benchmark", "pytest-cov", "pytest-mock", "pytest-recording", "pytest-rerunfailures", "python-snappy", "requests", "smbprotocol", "tqdm", "urllib3", "zarr", "zstandard"] +tqdm = ["tqdm"] + [[package]] name = "ghp-import" version = "2.1.0" @@ -668,6 +707,40 @@ files = [ {file = "hiredis-3.0.0.tar.gz", hash = "sha256:fed8581ae26345dea1f1e0d1a96e05041a727a45e7d8d459164583e23c6ac441"}, ] +[[package]] +name = "huggingface-hub" +version = "0.24.0" +description = "Client library to download and publish models, datasets and other repos on the huggingface.co hub" +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "huggingface_hub-0.24.0-py3-none-any.whl", hash = "sha256:7ad92edefb93d8145c061f6df8d99df2ff85f8379ba5fac8a95aca0642afa5d7"}, + {file = "huggingface_hub-0.24.0.tar.gz", hash = "sha256:6c7092736b577d89d57b3cdfea026f1b0dc2234ae783fa0d59caf1bf7d52dfa7"}, +] + +[package.dependencies] +filelock = "*" +fsspec = ">=2023.5.0" +packaging = ">=20.9" +pyyaml = ">=5.1" +requests = "*" +tqdm = ">=4.42.1" +typing-extensions = ">=3.7.4.3" + +[package.extras] +all = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "fastapi", "gradio", "jedi", "minijinja (>=1.0)", "mypy (==1.5.1)", "numpy", "pytest (>=8.1.1,<8.2.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "ruff (>=0.5.0)", "soundfile", "types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)", "urllib3 (<2.0)"] +cli = ["InquirerPy (==0.3.4)"] +dev = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "fastapi", "gradio", "jedi", "minijinja (>=1.0)", "mypy (==1.5.1)", "numpy", "pytest (>=8.1.1,<8.2.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "ruff (>=0.5.0)", "soundfile", "types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)", "urllib3 (<2.0)"] +fastai = ["fastai (>=2.4)", "fastcore (>=1.3.27)", "toml"] +hf-transfer = ["hf-transfer (>=0.1.4)"] +inference = ["aiohttp", "minijinja (>=1.0)"] +quality = ["mypy (==1.5.1)", "ruff (>=0.5.0)"] +tensorflow = ["graphviz", "pydot", "tensorflow"] +tensorflow-testing = ["keras (<3.0)", "tensorflow"] +testing = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "fastapi", "gradio", "jedi", "minijinja (>=1.0)", "numpy", "pytest (>=8.1.1,<8.2.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "soundfile", "urllib3 (<2.0)"] +torch = ["safetensors[torch]", "torch"] +typing = ["types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)"] + [[package]] name = "humanfriendly" version = "10.0" @@ -769,6 +842,20 @@ files = [ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] +[[package]] +name = "intel-openmp" +version = "2021.4.0" +description = "Intel OpenMP* Runtime Library" +optional = false +python-versions = "*" +files = [ + {file = "intel_openmp-2021.4.0-py2.py3-none-macosx_10_15_x86_64.macosx_11_0_x86_64.whl", hash = "sha256:41c01e266a7fdb631a7609191709322da2bbf24b252ba763f125dd651bcc7675"}, + {file = "intel_openmp-2021.4.0-py2.py3-none-manylinux1_i686.whl", hash = "sha256:3b921236a38384e2016f0f3d65af6732cf2c12918087128a9163225451e776f2"}, + {file = "intel_openmp-2021.4.0-py2.py3-none-manylinux1_x86_64.whl", hash = "sha256:e2240ab8d01472fed04f3544a878cda5da16c26232b7ea1b59132dbfb48b186e"}, + {file = "intel_openmp-2021.4.0-py2.py3-none-win32.whl", hash = "sha256:6e863d8fd3d7e8ef389d52cf97a50fe2afe1a19247e8c0d168ce021546f96fc9"}, + {file = "intel_openmp-2021.4.0-py2.py3-none-win_amd64.whl", hash = "sha256:eef4c8bcc8acefd7f5cd3b9384dbf73d59e2c99fc56545712ded913f43c4a94f"}, +] + [[package]] name = "jinja2" version = "3.1.4" @@ -1028,6 +1115,41 @@ files = [ {file = "mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443"}, ] +[[package]] +name = "mkl" +version = "2021.4.0" +description = "Intel® oneAPI Math Kernel Library" +optional = false +python-versions = "*" +files = [ + {file = "mkl-2021.4.0-py2.py3-none-macosx_10_15_x86_64.macosx_11_0_x86_64.whl", hash = "sha256:67460f5cd7e30e405b54d70d1ed3ca78118370b65f7327d495e9c8847705e2fb"}, + {file = "mkl-2021.4.0-py2.py3-none-manylinux1_i686.whl", hash = "sha256:636d07d90e68ccc9630c654d47ce9fdeb036bb46e2b193b3a9ac8cfea683cce5"}, + {file = "mkl-2021.4.0-py2.py3-none-manylinux1_x86_64.whl", hash = "sha256:398dbf2b0d12acaf54117a5210e8f191827f373d362d796091d161f610c1ebfb"}, + {file = "mkl-2021.4.0-py2.py3-none-win32.whl", hash = "sha256:439c640b269a5668134e3dcbcea4350459c4a8bc46469669b2d67e07e3d330e8"}, + {file = "mkl-2021.4.0-py2.py3-none-win_amd64.whl", hash = "sha256:ceef3cafce4c009dd25f65d7ad0d833a0fbadc3d8903991ec92351fe5de1e718"}, +] + +[package.dependencies] +intel-openmp = "==2021.*" +tbb = "==2021.*" + +[[package]] +name = "mpmath" +version = "1.3.0" +description = "Python library for arbitrary-precision floating-point arithmetic" +optional = false +python-versions = "*" +files = [ + {file = "mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c"}, + {file = "mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f"}, +] + +[package.extras] +develop = ["codecov", "pycodestyle", "pytest (>=4.6)", "pytest-cov", "wheel"] +docs = ["sphinx"] +gmpy = ["gmpy2 (>=2.1.0a4)"] +tests = ["pytest (>=4.6)"] + [[package]] name = "multidict" version = "6.0.5" @@ -1127,6 +1249,24 @@ files = [ {file = "multidict-6.0.5.tar.gz", hash = "sha256:f7e301075edaf50500f0b341543c41194d8df3ae5caf4702f2095f3ca73dd8da"}, ] +[[package]] +name = "networkx" +version = "3.3" +description = "Python package for creating and manipulating graphs and networks" +optional = false +python-versions = ">=3.10" +files = [ + {file = "networkx-3.3-py3-none-any.whl", hash = "sha256:28575580c6ebdaf4505b22c6256a2b9de86b316dc63ba9e93abde3d78dfdbcf2"}, + {file = "networkx-3.3.tar.gz", hash = "sha256:0c127d8b2f4865f59ae9cb8aafcd60b5c70f3241ebd66f7defad7c4ab90126c9"}, +] + +[package.extras] +default = ["matplotlib (>=3.6)", "numpy (>=1.23)", "pandas (>=1.4)", "scipy (>=1.9,!=1.11.0,!=1.11.1)"] +developer = ["changelist (==0.5)", "mypy (>=1.1)", "pre-commit (>=3.2)", "rtoml"] +doc = ["myst-nb (>=1.0)", "numpydoc (>=1.7)", "pillow (>=9.4)", "pydata-sphinx-theme (>=0.14)", "sphinx (>=7)", "sphinx-gallery (>=0.14)", "texext (>=0.6.7)"] +extra = ["lxml (>=4.6)", "pydot (>=2.0)", "pygraphviz (>=1.12)", "sympy (>=1.10)"] +test = ["pytest (>=7.2)", "pytest-cov (>=4.0)"] + [[package]] name = "nltk" version = "3.8.1" @@ -1217,6 +1357,149 @@ files = [ {file = "numpy-2.0.0.tar.gz", hash = "sha256:cf5d1c9e6837f8af9f92b6bd3e86d513cdc11f60fd62185cc49ec7d1aba34864"}, ] +[[package]] +name = "nvidia-cublas-cu12" +version = "12.1.3.1" +description = "CUBLAS native runtime libraries" +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_cublas_cu12-12.1.3.1-py3-none-manylinux1_x86_64.whl", hash = "sha256:ee53ccca76a6fc08fb9701aa95b6ceb242cdaab118c3bb152af4e579af792728"}, + {file = "nvidia_cublas_cu12-12.1.3.1-py3-none-win_amd64.whl", hash = "sha256:2b964d60e8cf11b5e1073d179d85fa340c120e99b3067558f3cf98dd69d02906"}, +] + +[[package]] +name = "nvidia-cuda-cupti-cu12" +version = "12.1.105" +description = "CUDA profiling tools runtime libs." +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_cuda_cupti_cu12-12.1.105-py3-none-manylinux1_x86_64.whl", hash = "sha256:e54fde3983165c624cb79254ae9818a456eb6e87a7fd4d56a2352c24ee542d7e"}, + {file = "nvidia_cuda_cupti_cu12-12.1.105-py3-none-win_amd64.whl", hash = "sha256:bea8236d13a0ac7190bd2919c3e8e6ce1e402104276e6f9694479e48bb0eb2a4"}, +] + +[[package]] +name = "nvidia-cuda-nvrtc-cu12" +version = "12.1.105" +description = "NVRTC native runtime libraries" +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_cuda_nvrtc_cu12-12.1.105-py3-none-manylinux1_x86_64.whl", hash = "sha256:339b385f50c309763ca65456ec75e17bbefcbbf2893f462cb8b90584cd27a1c2"}, + {file = "nvidia_cuda_nvrtc_cu12-12.1.105-py3-none-win_amd64.whl", hash = "sha256:0a98a522d9ff138b96c010a65e145dc1b4850e9ecb75a0172371793752fd46ed"}, +] + +[[package]] +name = "nvidia-cuda-runtime-cu12" +version = "12.1.105" +description = "CUDA Runtime native Libraries" +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_cuda_runtime_cu12-12.1.105-py3-none-manylinux1_x86_64.whl", hash = "sha256:6e258468ddf5796e25f1dc591a31029fa317d97a0a94ed93468fc86301d61e40"}, + {file = "nvidia_cuda_runtime_cu12-12.1.105-py3-none-win_amd64.whl", hash = "sha256:dfb46ef84d73fababab44cf03e3b83f80700d27ca300e537f85f636fac474344"}, +] + +[[package]] +name = "nvidia-cudnn-cu12" +version = "8.9.2.26" +description = "cuDNN runtime libraries" +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_cudnn_cu12-8.9.2.26-py3-none-manylinux1_x86_64.whl", hash = "sha256:5ccb288774fdfb07a7e7025ffec286971c06d8d7b4fb162525334616d7629ff9"}, +] + +[package.dependencies] +nvidia-cublas-cu12 = "*" + +[[package]] +name = "nvidia-cufft-cu12" +version = "11.0.2.54" +description = "CUFFT native runtime libraries" +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_cufft_cu12-11.0.2.54-py3-none-manylinux1_x86_64.whl", hash = "sha256:794e3948a1aa71fd817c3775866943936774d1c14e7628c74f6f7417224cdf56"}, + {file = "nvidia_cufft_cu12-11.0.2.54-py3-none-win_amd64.whl", hash = "sha256:d9ac353f78ff89951da4af698f80870b1534ed69993f10a4cf1d96f21357e253"}, +] + +[[package]] +name = "nvidia-curand-cu12" +version = "10.3.2.106" +description = "CURAND native runtime libraries" +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_curand_cu12-10.3.2.106-py3-none-manylinux1_x86_64.whl", hash = "sha256:9d264c5036dde4e64f1de8c50ae753237c12e0b1348738169cd0f8a536c0e1e0"}, + {file = "nvidia_curand_cu12-10.3.2.106-py3-none-win_amd64.whl", hash = "sha256:75b6b0c574c0037839121317e17fd01f8a69fd2ef8e25853d826fec30bdba74a"}, +] + +[[package]] +name = "nvidia-cusolver-cu12" +version = "11.4.5.107" +description = "CUDA solver native runtime libraries" +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_cusolver_cu12-11.4.5.107-py3-none-manylinux1_x86_64.whl", hash = "sha256:8a7ec542f0412294b15072fa7dab71d31334014a69f953004ea7a118206fe0dd"}, + {file = "nvidia_cusolver_cu12-11.4.5.107-py3-none-win_amd64.whl", hash = "sha256:74e0c3a24c78612192a74fcd90dd117f1cf21dea4822e66d89e8ea80e3cd2da5"}, +] + +[package.dependencies] +nvidia-cublas-cu12 = "*" +nvidia-cusparse-cu12 = "*" +nvidia-nvjitlink-cu12 = "*" + +[[package]] +name = "nvidia-cusparse-cu12" +version = "12.1.0.106" +description = "CUSPARSE native runtime libraries" +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_cusparse_cu12-12.1.0.106-py3-none-manylinux1_x86_64.whl", hash = "sha256:f3b50f42cf363f86ab21f720998517a659a48131e8d538dc02f8768237bd884c"}, + {file = "nvidia_cusparse_cu12-12.1.0.106-py3-none-win_amd64.whl", hash = "sha256:b798237e81b9719373e8fae8d4f091b70a0cf09d9d85c95a557e11df2d8e9a5a"}, +] + +[package.dependencies] +nvidia-nvjitlink-cu12 = "*" + +[[package]] +name = "nvidia-nccl-cu12" +version = "2.20.5" +description = "NVIDIA Collective Communication Library (NCCL) Runtime" +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_nccl_cu12-2.20.5-py3-none-manylinux2014_aarch64.whl", hash = "sha256:1fc150d5c3250b170b29410ba682384b14581db722b2531b0d8d33c595f33d01"}, + {file = "nvidia_nccl_cu12-2.20.5-py3-none-manylinux2014_x86_64.whl", hash = "sha256:057f6bf9685f75215d0c53bf3ac4a10b3e6578351de307abad9e18a99182af56"}, +] + +[[package]] +name = "nvidia-nvjitlink-cu12" +version = "12.5.82" +description = "Nvidia JIT LTO Library" +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_nvjitlink_cu12-12.5.82-py3-none-manylinux2014_aarch64.whl", hash = "sha256:98103729cc5226e13ca319a10bbf9433bbbd44ef64fe72f45f067cacc14b8d27"}, + {file = "nvidia_nvjitlink_cu12-12.5.82-py3-none-manylinux2014_x86_64.whl", hash = "sha256:f9b37bc5c8cf7509665cb6ada5aaa0ce65618f2332b7d3e78e9790511f111212"}, + {file = "nvidia_nvjitlink_cu12-12.5.82-py3-none-win_amd64.whl", hash = "sha256:e782564d705ff0bf61ac3e1bf730166da66dd2fe9012f111ede5fc49b64ae697"}, +] + +[[package]] +name = "nvidia-nvtx-cu12" +version = "12.1.105" +description = "NVIDIA Tools Extension" +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_nvtx_cu12-12.1.105-py3-none-manylinux1_x86_64.whl", hash = "sha256:dc21cf308ca5691e7c04d962e213f8a4aa9bbfa23d95412f452254c2caeb09e5"}, + {file = "nvidia_nvtx_cu12-12.1.105-py3-none-win_amd64.whl", hash = "sha256:65f4d98982b31b60026e0e6de73fbdfc09d08a96f4656dd3665ca616a11e1e82"}, +] + [[package]] name = "packaging" version = "24.1" @@ -1249,6 +1532,103 @@ files = [ {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, ] +[[package]] +name = "pillow" +version = "10.4.0" +description = "Python Imaging Library (Fork)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pillow-10.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e"}, + {file = "pillow-10.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:543f3dc61c18dafb755773efc89aae60d06b6596a63914107f75459cf984164d"}, + {file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7928ecbf1ece13956b95d9cbcfc77137652b02763ba384d9ab508099a2eca856"}, + {file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4d49b85c4348ea0b31ea63bc75a9f3857869174e2bf17e7aba02945cd218e6f"}, + {file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:6c762a5b0997f5659a5ef2266abc1d8851ad7749ad9a6a5506eb23d314e4f46b"}, + {file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a985e028fc183bf12a77a8bbf36318db4238a3ded7fa9df1b9a133f1cb79f8fc"}, + {file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:812f7342b0eee081eaec84d91423d1b4650bb9828eb53d8511bcef8ce5aecf1e"}, + {file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ac1452d2fbe4978c2eec89fb5a23b8387aba707ac72810d9490118817d9c0b46"}, + {file = "pillow-10.4.0-cp310-cp310-win32.whl", hash = "sha256:bcd5e41a859bf2e84fdc42f4edb7d9aba0a13d29a2abadccafad99de3feff984"}, + {file = "pillow-10.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:ecd85a8d3e79cd7158dec1c9e5808e821feea088e2f69a974db5edf84dc53141"}, + {file = "pillow-10.4.0-cp310-cp310-win_arm64.whl", hash = "sha256:ff337c552345e95702c5fde3158acb0625111017d0e5f24bf3acdb9cc16b90d1"}, + {file = "pillow-10.4.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0a9ec697746f268507404647e531e92889890a087e03681a3606d9b920fbee3c"}, + {file = "pillow-10.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe91cb65544a1321e631e696759491ae04a2ea11d36715eca01ce07284738be"}, + {file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dc6761a6efc781e6a1544206f22c80c3af4c8cf461206d46a1e6006e4429ff3"}, + {file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e84b6cc6a4a3d76c153a6b19270b3526a5a8ed6b09501d3af891daa2a9de7d6"}, + {file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:bbc527b519bd3aa9d7f429d152fea69f9ad37c95f0b02aebddff592688998abe"}, + {file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:76a911dfe51a36041f2e756b00f96ed84677cdeb75d25c767f296c1c1eda1319"}, + {file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59291fb29317122398786c2d44427bbd1a6d7ff54017075b22be9d21aa59bd8d"}, + {file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:416d3a5d0e8cfe4f27f574362435bc9bae57f679a7158e0096ad2beb427b8696"}, + {file = "pillow-10.4.0-cp311-cp311-win32.whl", hash = "sha256:7086cc1d5eebb91ad24ded9f58bec6c688e9f0ed7eb3dbbf1e4800280a896496"}, + {file = "pillow-10.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cbed61494057c0f83b83eb3a310f0bf774b09513307c434d4366ed64f4128a91"}, + {file = "pillow-10.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:f5f0c3e969c8f12dd2bb7e0b15d5c468b51e5017e01e2e867335c81903046a22"}, + {file = "pillow-10.4.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:673655af3eadf4df6b5457033f086e90299fdd7a47983a13827acf7459c15d94"}, + {file = "pillow-10.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:866b6942a92f56300012f5fbac71f2d610312ee65e22f1aa2609e491284e5597"}, + {file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29dbdc4207642ea6aad70fbde1a9338753d33fb23ed6956e706936706f52dd80"}, + {file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf2342ac639c4cf38799a44950bbc2dfcb685f052b9e262f446482afaf4bffca"}, + {file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f5b92f4d70791b4a67157321c4e8225d60b119c5cc9aee8ecf153aace4aad4ef"}, + {file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:86dcb5a1eb778d8b25659d5e4341269e8590ad6b4e8b44d9f4b07f8d136c414a"}, + {file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:780c072c2e11c9b2c7ca37f9a2ee8ba66f44367ac3e5c7832afcfe5104fd6d1b"}, + {file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37fb69d905be665f68f28a8bba3c6d3223c8efe1edf14cc4cfa06c241f8c81d9"}, + {file = "pillow-10.4.0-cp312-cp312-win32.whl", hash = "sha256:7dfecdbad5c301d7b5bde160150b4db4c659cee2b69589705b6f8a0c509d9f42"}, + {file = "pillow-10.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:1d846aea995ad352d4bdcc847535bd56e0fd88d36829d2c90be880ef1ee4668a"}, + {file = "pillow-10.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:e553cad5179a66ba15bb18b353a19020e73a7921296a7979c4a2b7f6a5cd57f9"}, + {file = "pillow-10.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8bc1a764ed8c957a2e9cacf97c8b2b053b70307cf2996aafd70e91a082e70df3"}, + {file = "pillow-10.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6209bb41dc692ddfee4942517c19ee81b86c864b626dbfca272ec0f7cff5d9fb"}, + {file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bee197b30783295d2eb680b311af15a20a8b24024a19c3a26431ff83eb8d1f70"}, + {file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ef61f5dd14c300786318482456481463b9d6b91ebe5ef12f405afbba77ed0be"}, + {file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:297e388da6e248c98bc4a02e018966af0c5f92dfacf5a5ca22fa01cb3179bca0"}, + {file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:e4db64794ccdf6cb83a59d73405f63adbe2a1887012e308828596100a0b2f6cc"}, + {file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd2880a07482090a3bcb01f4265f1936a903d70bc740bfcb1fd4e8a2ffe5cf5a"}, + {file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b35b21b819ac1dbd1233317adeecd63495f6babf21b7b2512d244ff6c6ce309"}, + {file = "pillow-10.4.0-cp313-cp313-win32.whl", hash = "sha256:551d3fd6e9dc15e4c1eb6fc4ba2b39c0c7933fa113b220057a34f4bb3268a060"}, + {file = "pillow-10.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:030abdbe43ee02e0de642aee345efa443740aa4d828bfe8e2eb11922ea6a21ea"}, + {file = "pillow-10.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:5b001114dd152cfd6b23befeb28d7aee43553e2402c9f159807bf55f33af8a8d"}, + {file = "pillow-10.4.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8d4d5063501b6dd4024b8ac2f04962d661222d120381272deea52e3fc52d3736"}, + {file = "pillow-10.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7c1ee6f42250df403c5f103cbd2768a28fe1a0ea1f0f03fe151c8741e1469c8b"}, + {file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15e02e9bb4c21e39876698abf233c8c579127986f8207200bc8a8f6bb27acf2"}, + {file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a8d4bade9952ea9a77d0c3e49cbd8b2890a399422258a77f357b9cc9be8d680"}, + {file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:43efea75eb06b95d1631cb784aa40156177bf9dd5b4b03ff38979e048258bc6b"}, + {file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:950be4d8ba92aca4b2bb0741285a46bfae3ca699ef913ec8416c1b78eadd64cd"}, + {file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d7480af14364494365e89d6fddc510a13e5a2c3584cb19ef65415ca57252fb84"}, + {file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:73664fe514b34c8f02452ffb73b7a92c6774e39a647087f83d67f010eb9a0cf0"}, + {file = "pillow-10.4.0-cp38-cp38-win32.whl", hash = "sha256:e88d5e6ad0d026fba7bdab8c3f225a69f063f116462c49892b0149e21b6c0a0e"}, + {file = "pillow-10.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:5161eef006d335e46895297f642341111945e2c1c899eb406882a6c61a4357ab"}, + {file = "pillow-10.4.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0ae24a547e8b711ccaaf99c9ae3cd975470e1a30caa80a6aaee9a2f19c05701d"}, + {file = "pillow-10.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:298478fe4f77a4408895605f3482b6cc6222c018b2ce565c2b6b9c354ac3229b"}, + {file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:134ace6dc392116566980ee7436477d844520a26a4b1bd4053f6f47d096997fd"}, + {file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:930044bb7679ab003b14023138b50181899da3f25de50e9dbee23b61b4de2126"}, + {file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:c76e5786951e72ed3686e122d14c5d7012f16c8303a674d18cdcd6d89557fc5b"}, + {file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b2724fdb354a868ddf9a880cb84d102da914e99119211ef7ecbdc613b8c96b3c"}, + {file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dbc6ae66518ab3c5847659e9988c3b60dc94ffb48ef9168656e0019a93dbf8a1"}, + {file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:06b2f7898047ae93fad74467ec3d28fe84f7831370e3c258afa533f81ef7f3df"}, + {file = "pillow-10.4.0-cp39-cp39-win32.whl", hash = "sha256:7970285ab628a3779aecc35823296a7869f889b8329c16ad5a71e4901a3dc4ef"}, + {file = "pillow-10.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:961a7293b2457b405967af9c77dcaa43cc1a8cd50d23c532e62d48ab6cdd56f5"}, + {file = "pillow-10.4.0-cp39-cp39-win_arm64.whl", hash = "sha256:32cda9e3d601a52baccb2856b8ea1fc213c90b340c542dcef77140dfa3278a9e"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5b4815f2e65b30f5fbae9dfffa8636d992d49705723fe86a3661806e069352d4"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8f0aef4ef59694b12cadee839e2ba6afeab89c0f39a3adc02ed51d109117b8da"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f4727572e2918acaa9077c919cbbeb73bd2b3ebcfe033b72f858fc9fbef0026"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff25afb18123cea58a591ea0244b92eb1e61a1fd497bf6d6384f09bc3262ec3e"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dc3e2db6ba09ffd7d02ae9141cfa0ae23393ee7687248d46a7507b75d610f4f5"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:02a2be69f9c9b8c1e97cf2713e789d4e398c751ecfd9967c18d0ce304efbf885"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0755ffd4a0c6f267cccbae2e9903d95477ca2f77c4fcf3a3a09570001856c8a5"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:a02364621fe369e06200d4a16558e056fe2805d3468350df3aef21e00d26214b"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:1b5dea9831a90e9d0721ec417a80d4cbd7022093ac38a568db2dd78363b00908"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b885f89040bb8c4a1573566bbb2f44f5c505ef6e74cec7ab9068c900047f04b"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87dd88ded2e6d74d31e1e0a99a726a6765cda32d00ba72dc37f0651f306daaa8"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:2db98790afc70118bd0255c2eeb465e9767ecf1f3c25f9a1abb8ffc8cfd1fe0a"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f7baece4ce06bade126fb84b8af1c33439a76d8a6fd818970215e0560ca28c27"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:cfdd747216947628af7b259d274771d84db2268ca062dd5faf373639d00113a3"}, + {file = "pillow-10.4.0.tar.gz", hash = "sha256:166c1cd4d24309b30d61f79f4a9114b7b2313d7450912277855ff5dfd7cd4a06"}, +] + +[package.extras] +docs = ["furo", "olefile", "sphinx (>=7.3)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"] +fpx = ["olefile"] +mic = ["olefile"] +tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] +typing = ["typing-extensions"] +xmp = ["defusedxml"] + [[package]] name = "platformdirs" version = "4.2.2" @@ -1898,6 +2278,240 @@ files = [ {file = "ruff-0.5.2.tar.gz", hash = "sha256:2c0df2d2de685433794a14d8d2e240df619b748fbe3367346baa519d8e6f1ca2"}, ] +[[package]] +name = "safetensors" +version = "0.4.3" +description = "" +optional = false +python-versions = ">=3.7" +files = [ + {file = "safetensors-0.4.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:dcf5705cab159ce0130cd56057f5f3425023c407e170bca60b4868048bae64fd"}, + {file = "safetensors-0.4.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bb4f8c5d0358a31e9a08daeebb68f5e161cdd4018855426d3f0c23bb51087055"}, + {file = "safetensors-0.4.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70a5319ef409e7f88686a46607cbc3c428271069d8b770076feaf913664a07ac"}, + {file = "safetensors-0.4.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fb9c65bd82f9ef3ce4970dc19ee86be5f6f93d032159acf35e663c6bea02b237"}, + {file = "safetensors-0.4.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:edb5698a7bc282089f64c96c477846950358a46ede85a1c040e0230344fdde10"}, + {file = "safetensors-0.4.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:efcc860be094b8d19ac61b452ec635c7acb9afa77beb218b1d7784c6d41fe8ad"}, + {file = "safetensors-0.4.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d88b33980222085dd6001ae2cad87c6068e0991d4f5ccf44975d216db3b57376"}, + {file = "safetensors-0.4.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5fc6775529fb9f0ce2266edd3e5d3f10aab068e49f765e11f6f2a63b5367021d"}, + {file = "safetensors-0.4.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9c6ad011c1b4e3acff058d6b090f1da8e55a332fbf84695cf3100c649cc452d1"}, + {file = "safetensors-0.4.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8c496c5401c1b9c46d41a7688e8ff5b0310a3b9bae31ce0f0ae870e1ea2b8caf"}, + {file = "safetensors-0.4.3-cp310-none-win32.whl", hash = "sha256:38e2a8666178224a51cca61d3cb4c88704f696eac8f72a49a598a93bbd8a4af9"}, + {file = "safetensors-0.4.3-cp310-none-win_amd64.whl", hash = "sha256:393e6e391467d1b2b829c77e47d726f3b9b93630e6a045b1d1fca67dc78bf632"}, + {file = "safetensors-0.4.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:22f3b5d65e440cec0de8edaa672efa888030802e11c09b3d6203bff60ebff05a"}, + {file = "safetensors-0.4.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7c4fa560ebd4522adddb71dcd25d09bf211b5634003f015a4b815b7647d62ebe"}, + {file = "safetensors-0.4.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9afd5358719f1b2cf425fad638fc3c887997d6782da317096877e5b15b2ce93"}, + {file = "safetensors-0.4.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d8c5093206ef4b198600ae484230402af6713dab1bd5b8e231905d754022bec7"}, + {file = "safetensors-0.4.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0b2104df1579d6ba9052c0ae0e3137c9698b2d85b0645507e6fd1813b70931a"}, + {file = "safetensors-0.4.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8cf18888606dad030455d18f6c381720e57fc6a4170ee1966adb7ebc98d4d6a3"}, + {file = "safetensors-0.4.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0bf4f9d6323d9f86eef5567eabd88f070691cf031d4c0df27a40d3b4aaee755b"}, + {file = "safetensors-0.4.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:585c9ae13a205807b63bef8a37994f30c917ff800ab8a1ca9c9b5d73024f97ee"}, + {file = "safetensors-0.4.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:faefeb3b81bdfb4e5a55b9bbdf3d8d8753f65506e1d67d03f5c851a6c87150e9"}, + {file = "safetensors-0.4.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:befdf0167ad626f22f6aac6163477fcefa342224a22f11fdd05abb3995c1783c"}, + {file = "safetensors-0.4.3-cp311-none-win32.whl", hash = "sha256:a7cef55929dcbef24af3eb40bedec35d82c3c2fa46338bb13ecf3c5720af8a61"}, + {file = "safetensors-0.4.3-cp311-none-win_amd64.whl", hash = "sha256:840b7ac0eff5633e1d053cc9db12fdf56b566e9403b4950b2dc85393d9b88d67"}, + {file = "safetensors-0.4.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:22d21760dc6ebae42e9c058d75aa9907d9f35e38f896e3c69ba0e7b213033856"}, + {file = "safetensors-0.4.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d22c1a10dff3f64d0d68abb8298a3fd88ccff79f408a3e15b3e7f637ef5c980"}, + {file = "safetensors-0.4.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1648568667f820b8c48317c7006221dc40aced1869908c187f493838a1362bc"}, + {file = "safetensors-0.4.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:446e9fe52c051aeab12aac63d1017e0f68a02a92a027b901c4f8e931b24e5397"}, + {file = "safetensors-0.4.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fef5d70683643618244a4f5221053567ca3e77c2531e42ad48ae05fae909f542"}, + {file = "safetensors-0.4.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a1f4430cc0c9d6afa01214a4b3919d0a029637df8e09675ceef1ca3f0dfa0df"}, + {file = "safetensors-0.4.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d603846a8585b9432a0fd415db1d4c57c0f860eb4aea21f92559ff9902bae4d"}, + {file = "safetensors-0.4.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a844cdb5d7cbc22f5f16c7e2a0271170750763c4db08381b7f696dbd2c78a361"}, + {file = "safetensors-0.4.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:88887f69f7a00cf02b954cdc3034ffb383b2303bc0ab481d4716e2da51ddc10e"}, + {file = "safetensors-0.4.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ee463219d9ec6c2be1d331ab13a8e0cd50d2f32240a81d498266d77d07b7e71e"}, + {file = "safetensors-0.4.3-cp312-none-win32.whl", hash = "sha256:d0dd4a1db09db2dba0f94d15addc7e7cd3a7b0d393aa4c7518c39ae7374623c3"}, + {file = "safetensors-0.4.3-cp312-none-win_amd64.whl", hash = "sha256:d14d30c25897b2bf19b6fb5ff7e26cc40006ad53fd4a88244fdf26517d852dd7"}, + {file = "safetensors-0.4.3-cp37-cp37m-macosx_10_12_x86_64.whl", hash = "sha256:d1456f814655b224d4bf6e7915c51ce74e389b413be791203092b7ff78c936dd"}, + {file = "safetensors-0.4.3-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:455d538aa1aae4a8b279344a08136d3f16334247907b18a5c3c7fa88ef0d3c46"}, + {file = "safetensors-0.4.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf476bca34e1340ee3294ef13e2c625833f83d096cfdf69a5342475602004f95"}, + {file = "safetensors-0.4.3-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:02ef3a24face643456020536591fbd3c717c5abaa2737ec428ccbbc86dffa7a4"}, + {file = "safetensors-0.4.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7de32d0d34b6623bb56ca278f90db081f85fb9c5d327e3c18fd23ac64f465768"}, + {file = "safetensors-0.4.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a0deb16a1d3ea90c244ceb42d2c6c276059616be21a19ac7101aa97da448faf"}, + {file = "safetensors-0.4.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c59d51f182c729f47e841510b70b967b0752039f79f1de23bcdd86462a9b09ee"}, + {file = "safetensors-0.4.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1f598b713cc1a4eb31d3b3203557ac308acf21c8f41104cdd74bf640c6e538e3"}, + {file = "safetensors-0.4.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:5757e4688f20df083e233b47de43845d1adb7e17b6cf7da5f8444416fc53828d"}, + {file = "safetensors-0.4.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:fe746d03ed8d193674a26105e4f0fe6c726f5bb602ffc695b409eaf02f04763d"}, + {file = "safetensors-0.4.3-cp37-none-win32.whl", hash = "sha256:0d5ffc6a80f715c30af253e0e288ad1cd97a3d0086c9c87995e5093ebc075e50"}, + {file = "safetensors-0.4.3-cp37-none-win_amd64.whl", hash = "sha256:a11c374eb63a9c16c5ed146457241182f310902bd2a9c18255781bb832b6748b"}, + {file = "safetensors-0.4.3-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:b1e31be7945f66be23f4ec1682bb47faa3df34cb89fc68527de6554d3c4258a4"}, + {file = "safetensors-0.4.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:03a4447c784917c9bf01d8f2ac5080bc15c41692202cd5f406afba16629e84d6"}, + {file = "safetensors-0.4.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d244bcafeb1bc06d47cfee71727e775bca88a8efda77a13e7306aae3813fa7e4"}, + {file = "safetensors-0.4.3-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53c4879b9c6bd7cd25d114ee0ef95420e2812e676314300624594940a8d6a91f"}, + {file = "safetensors-0.4.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:74707624b81f1b7f2b93f5619d4a9f00934d5948005a03f2c1845ffbfff42212"}, + {file = "safetensors-0.4.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0d52c958dc210265157573f81d34adf54e255bc2b59ded6218500c9b15a750eb"}, + {file = "safetensors-0.4.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f9568f380f513a60139971169c4a358b8731509cc19112369902eddb33faa4d"}, + {file = "safetensors-0.4.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0d9cd8e1560dfc514b6d7859247dc6a86ad2f83151a62c577428d5102d872721"}, + {file = "safetensors-0.4.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:89f9f17b0dacb913ed87d57afbc8aad85ea42c1085bd5de2f20d83d13e9fc4b2"}, + {file = "safetensors-0.4.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1139eb436fd201c133d03c81209d39ac57e129f5e74e34bb9ab60f8d9b726270"}, + {file = "safetensors-0.4.3-cp38-none-win32.whl", hash = "sha256:d9c289f140a9ae4853fc2236a2ffc9a9f2d5eae0cb673167e0f1b8c18c0961ac"}, + {file = "safetensors-0.4.3-cp38-none-win_amd64.whl", hash = "sha256:622afd28968ef3e9786562d352659a37de4481a4070f4ebac883f98c5836563e"}, + {file = "safetensors-0.4.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:8651c7299cbd8b4161a36cd6a322fa07d39cd23535b144d02f1c1972d0c62f3c"}, + {file = "safetensors-0.4.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e375d975159ac534c7161269de24ddcd490df2157b55c1a6eeace6cbb56903f0"}, + {file = "safetensors-0.4.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:084fc436e317f83f7071fc6a62ca1c513b2103db325cd09952914b50f51cf78f"}, + {file = "safetensors-0.4.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:41a727a7f5e6ad9f1db6951adee21bbdadc632363d79dc434876369a17de6ad6"}, + {file = "safetensors-0.4.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e7dbbde64b6c534548696808a0e01276d28ea5773bc9a2dfb97a88cd3dffe3df"}, + {file = "safetensors-0.4.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bbae3b4b9d997971431c346edbfe6e41e98424a097860ee872721e176040a893"}, + {file = "safetensors-0.4.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01e4b22e3284cd866edeabe4f4d896229495da457229408d2e1e4810c5187121"}, + {file = "safetensors-0.4.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0dd37306546b58d3043eb044c8103a02792cc024b51d1dd16bd3dd1f334cb3ed"}, + {file = "safetensors-0.4.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d8815b5e1dac85fc534a97fd339e12404db557878c090f90442247e87c8aeaea"}, + {file = "safetensors-0.4.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e011cc162503c19f4b1fd63dfcddf73739c7a243a17dac09b78e57a00983ab35"}, + {file = "safetensors-0.4.3-cp39-none-win32.whl", hash = "sha256:01feb3089e5932d7e662eda77c3ecc389f97c0883c4a12b5cfdc32b589a811c3"}, + {file = "safetensors-0.4.3-cp39-none-win_amd64.whl", hash = "sha256:3f9cdca09052f585e62328c1c2923c70f46814715c795be65f0b93f57ec98a02"}, + {file = "safetensors-0.4.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:1b89381517891a7bb7d1405d828b2bf5d75528299f8231e9346b8eba092227f9"}, + {file = "safetensors-0.4.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:cd6fff9e56df398abc5866b19a32124815b656613c1c5ec0f9350906fd798aac"}, + {file = "safetensors-0.4.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:840caf38d86aa7014fe37ade5d0d84e23dcfbc798b8078015831996ecbc206a3"}, + {file = "safetensors-0.4.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9650713b2cfa9537a2baf7dd9fee458b24a0aaaa6cafcea8bdd5fb2b8efdc34"}, + {file = "safetensors-0.4.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e4119532cd10dba04b423e0f86aecb96cfa5a602238c0aa012f70c3a40c44b50"}, + {file = "safetensors-0.4.3-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:e066e8861eef6387b7c772344d1fe1f9a72800e04ee9a54239d460c400c72aab"}, + {file = "safetensors-0.4.3-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:90964917f5b0fa0fa07e9a051fbef100250c04d150b7026ccbf87a34a54012e0"}, + {file = "safetensors-0.4.3-pp37-pypy37_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c41e1893d1206aa7054029681778d9a58b3529d4c807002c156d58426c225173"}, + {file = "safetensors-0.4.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae7613a119a71a497d012ccc83775c308b9c1dab454806291427f84397d852fd"}, + {file = "safetensors-0.4.3-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9bac020faba7f5dc481e881b14b6425265feabb5bfc552551d21189c0eddc3"}, + {file = "safetensors-0.4.3-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:420a98f593ff9930f5822560d14c395ccbc57342ddff3b463bc0b3d6b1951550"}, + {file = "safetensors-0.4.3-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f5e6883af9a68c0028f70a4c19d5a6ab6238a379be36ad300a22318316c00cb0"}, + {file = "safetensors-0.4.3-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:cdd0a3b5da66e7f377474599814dbf5cbf135ff059cc73694de129b58a5e8a2c"}, + {file = "safetensors-0.4.3-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:9bfb92f82574d9e58401d79c70c716985dc049b635fef6eecbb024c79b2c46ad"}, + {file = "safetensors-0.4.3-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:3615a96dd2dcc30eb66d82bc76cda2565f4f7bfa89fcb0e31ba3cea8a1a9ecbb"}, + {file = "safetensors-0.4.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:868ad1b6fc41209ab6bd12f63923e8baeb1a086814cb2e81a65ed3d497e0cf8f"}, + {file = "safetensors-0.4.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7ffba80aa49bd09195145a7fd233a7781173b422eeb995096f2b30591639517"}, + {file = "safetensors-0.4.3-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c0acbe31340ab150423347e5b9cc595867d814244ac14218932a5cf1dd38eb39"}, + {file = "safetensors-0.4.3-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:19bbdf95de2cf64f25cd614c5236c8b06eb2cfa47cbf64311f4b5d80224623a3"}, + {file = "safetensors-0.4.3-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b852e47eb08475c2c1bd8131207b405793bfc20d6f45aff893d3baaad449ed14"}, + {file = "safetensors-0.4.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5d07cbca5b99babb692d76d8151bec46f461f8ad8daafbfd96b2fca40cadae65"}, + {file = "safetensors-0.4.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:1ab6527a20586d94291c96e00a668fa03f86189b8a9defa2cdd34a1a01acc7d5"}, + {file = "safetensors-0.4.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02318f01e332cc23ffb4f6716e05a492c5f18b1d13e343c49265149396284a44"}, + {file = "safetensors-0.4.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec4b52ce9a396260eb9731eb6aea41a7320de22ed73a1042c2230af0212758ce"}, + {file = "safetensors-0.4.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:018b691383026a2436a22b648873ed11444a364324e7088b99cd2503dd828400"}, + {file = "safetensors-0.4.3-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:309b10dbcab63269ecbf0e2ca10ce59223bb756ca5d431ce9c9eeabd446569da"}, + {file = "safetensors-0.4.3-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b277482120df46e27a58082df06a15aebda4481e30a1c21eefd0921ae7e03f65"}, + {file = "safetensors-0.4.3.tar.gz", hash = "sha256:2f85fc50c4e07a21e95c24e07460fe6f7e2859d0ce88092838352b798ce711c2"}, +] + +[package.extras] +all = ["safetensors[jax]", "safetensors[numpy]", "safetensors[paddlepaddle]", "safetensors[pinned-tf]", "safetensors[quality]", "safetensors[testing]", "safetensors[torch]"] +dev = ["safetensors[all]"] +jax = ["flax (>=0.6.3)", "jax (>=0.3.25)", "jaxlib (>=0.3.25)", "safetensors[numpy]"] +mlx = ["mlx (>=0.0.9)"] +numpy = ["numpy (>=1.21.6)"] +paddlepaddle = ["paddlepaddle (>=2.4.1)", "safetensors[numpy]"] +pinned-tf = ["safetensors[numpy]", "tensorflow (==2.11.0)"] +quality = ["black (==22.3)", "click (==8.0.4)", "flake8 (>=3.8.3)", "isort (>=5.5.4)"] +tensorflow = ["safetensors[numpy]", "tensorflow (>=2.11.0)"] +testing = ["h5py (>=3.7.0)", "huggingface-hub (>=0.12.1)", "hypothesis (>=6.70.2)", "pytest (>=7.2.0)", "pytest-benchmark (>=4.0.0)", "safetensors[numpy]", "setuptools-rust (>=1.5.2)"] +torch = ["safetensors[numpy]", "torch (>=1.10)"] + +[[package]] +name = "scikit-learn" +version = "1.5.1" +description = "A set of python modules for machine learning and data mining" +optional = false +python-versions = ">=3.9" +files = [ + {file = "scikit_learn-1.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:781586c414f8cc58e71da4f3d7af311e0505a683e112f2f62919e3019abd3745"}, + {file = "scikit_learn-1.5.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:f5b213bc29cc30a89a3130393b0e39c847a15d769d6e59539cd86b75d276b1a7"}, + {file = "scikit_learn-1.5.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ff4ba34c2abff5ec59c803ed1d97d61b036f659a17f55be102679e88f926fac"}, + {file = "scikit_learn-1.5.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:161808750c267b77b4a9603cf9c93579c7a74ba8486b1336034c2f1579546d21"}, + {file = "scikit_learn-1.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:10e49170691514a94bb2e03787aa921b82dbc507a4ea1f20fd95557862c98dc1"}, + {file = "scikit_learn-1.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:154297ee43c0b83af12464adeab378dee2d0a700ccd03979e2b821e7dd7cc1c2"}, + {file = "scikit_learn-1.5.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:b5e865e9bd59396220de49cb4a57b17016256637c61b4c5cc81aaf16bc123bbe"}, + {file = "scikit_learn-1.5.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:909144d50f367a513cee6090873ae582dba019cb3fca063b38054fa42704c3a4"}, + {file = "scikit_learn-1.5.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:689b6f74b2c880276e365fe84fe4f1befd6a774f016339c65655eaff12e10cbf"}, + {file = "scikit_learn-1.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:9a07f90846313a7639af6a019d849ff72baadfa4c74c778821ae0fad07b7275b"}, + {file = "scikit_learn-1.5.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5944ce1faada31c55fb2ba20a5346b88e36811aab504ccafb9f0339e9f780395"}, + {file = "scikit_learn-1.5.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:0828673c5b520e879f2af6a9e99eee0eefea69a2188be1ca68a6121b809055c1"}, + {file = "scikit_learn-1.5.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:508907e5f81390e16d754e8815f7497e52139162fd69c4fdbd2dfa5d6cc88915"}, + {file = "scikit_learn-1.5.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97625f217c5c0c5d0505fa2af28ae424bd37949bb2f16ace3ff5f2f81fb4498b"}, + {file = "scikit_learn-1.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:da3f404e9e284d2b0a157e1b56b6566a34eb2798205cba35a211df3296ab7a74"}, + {file = "scikit_learn-1.5.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:88e0672c7ac21eb149d409c74cc29f1d611d5158175846e7a9c2427bd12b3956"}, + {file = "scikit_learn-1.5.1-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:7b073a27797a283187a4ef4ee149959defc350b46cbf63a84d8514fe16b69855"}, + {file = "scikit_learn-1.5.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b59e3e62d2be870e5c74af4e793293753565c7383ae82943b83383fdcf5cc5c1"}, + {file = "scikit_learn-1.5.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bd8d3a19d4bd6dc5a7d4f358c8c3a60934dc058f363c34c0ac1e9e12a31421d"}, + {file = "scikit_learn-1.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:5f57428de0c900a98389c4a433d4a3cf89de979b3aa24d1c1d251802aa15e44d"}, + {file = "scikit_learn-1.5.1.tar.gz", hash = "sha256:0ea5d40c0e3951df445721927448755d3fe1d80833b0b7308ebff5d2a45e6414"}, +] + +[package.dependencies] +joblib = ">=1.2.0" +numpy = ">=1.19.5" +scipy = ">=1.6.0" +threadpoolctl = ">=3.1.0" + +[package.extras] +benchmark = ["matplotlib (>=3.3.4)", "memory_profiler (>=0.57.0)", "pandas (>=1.1.5)"] +build = ["cython (>=3.0.10)", "meson-python (>=0.16.0)", "numpy (>=1.19.5)", "scipy (>=1.6.0)"] +docs = ["Pillow (>=7.1.2)", "matplotlib (>=3.3.4)", "memory_profiler (>=0.57.0)", "numpydoc (>=1.2.0)", "pandas (>=1.1.5)", "plotly (>=5.14.0)", "polars (>=0.20.23)", "pooch (>=1.6.0)", "pydata-sphinx-theme (>=0.15.3)", "scikit-image (>=0.17.2)", "seaborn (>=0.9.0)", "sphinx (>=7.3.7)", "sphinx-copybutton (>=0.5.2)", "sphinx-design (>=0.5.0)", "sphinx-gallery (>=0.16.0)", "sphinx-prompt (>=1.4.0)", "sphinx-remove-toctrees (>=1.0.0.post1)", "sphinxcontrib-sass (>=0.3.4)", "sphinxext-opengraph (>=0.9.1)"] +examples = ["matplotlib (>=3.3.4)", "pandas (>=1.1.5)", "plotly (>=5.14.0)", "pooch (>=1.6.0)", "scikit-image (>=0.17.2)", "seaborn (>=0.9.0)"] +install = ["joblib (>=1.2.0)", "numpy (>=1.19.5)", "scipy (>=1.6.0)", "threadpoolctl (>=3.1.0)"] +maintenance = ["conda-lock (==2.5.6)"] +tests = ["black (>=24.3.0)", "matplotlib (>=3.3.4)", "mypy (>=1.9)", "numpydoc (>=1.2.0)", "pandas (>=1.1.5)", "polars (>=0.20.23)", "pooch (>=1.6.0)", "pyamg (>=4.0.0)", "pyarrow (>=12.0.0)", "pytest (>=7.1.2)", "pytest-cov (>=2.9.0)", "ruff (>=0.2.1)", "scikit-image (>=0.17.2)"] + +[[package]] +name = "scipy" +version = "1.14.0" +description = "Fundamental algorithms for scientific computing in Python" +optional = false +python-versions = ">=3.10" +files = [ + {file = "scipy-1.14.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7e911933d54ead4d557c02402710c2396529540b81dd554fc1ba270eb7308484"}, + {file = "scipy-1.14.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:687af0a35462402dd851726295c1a5ae5f987bd6e9026f52e9505994e2f84ef6"}, + {file = "scipy-1.14.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:07e179dc0205a50721022344fb85074f772eadbda1e1b3eecdc483f8033709b7"}, + {file = "scipy-1.14.0-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:6a9c9a9b226d9a21e0a208bdb024c3982932e43811b62d202aaf1bb59af264b1"}, + {file = "scipy-1.14.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:076c27284c768b84a45dcf2e914d4000aac537da74236a0d45d82c6fa4b7b3c0"}, + {file = "scipy-1.14.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42470ea0195336df319741e230626b6225a740fd9dce9642ca13e98f667047c0"}, + {file = "scipy-1.14.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:176c6f0d0470a32f1b2efaf40c3d37a24876cebf447498a4cefb947a79c21e9d"}, + {file = "scipy-1.14.0-cp310-cp310-win_amd64.whl", hash = "sha256:ad36af9626d27a4326c8e884917b7ec321d8a1841cd6dacc67d2a9e90c2f0359"}, + {file = "scipy-1.14.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6d056a8709ccda6cf36cdd2eac597d13bc03dba38360f418560a93050c76a16e"}, + {file = "scipy-1.14.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:f0a50da861a7ec4573b7c716b2ebdcdf142b66b756a0d392c236ae568b3a93fb"}, + {file = "scipy-1.14.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:94c164a9e2498e68308e6e148646e486d979f7fcdb8b4cf34b5441894bdb9caf"}, + {file = "scipy-1.14.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:a7d46c3e0aea5c064e734c3eac5cf9eb1f8c4ceee756262f2c7327c4c2691c86"}, + {file = "scipy-1.14.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9eee2989868e274aae26125345584254d97c56194c072ed96cb433f32f692ed8"}, + {file = "scipy-1.14.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e3154691b9f7ed73778d746da2df67a19d046a6c8087c8b385bc4cdb2cfca74"}, + {file = "scipy-1.14.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c40003d880f39c11c1edbae8144e3813904b10514cd3d3d00c277ae996488cdb"}, + {file = "scipy-1.14.0-cp311-cp311-win_amd64.whl", hash = "sha256:5b083c8940028bb7e0b4172acafda6df762da1927b9091f9611b0bcd8676f2bc"}, + {file = "scipy-1.14.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:bff2438ea1330e06e53c424893ec0072640dac00f29c6a43a575cbae4c99b2b9"}, + {file = "scipy-1.14.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:bbc0471b5f22c11c389075d091d3885693fd3f5e9a54ce051b46308bc787e5d4"}, + {file = "scipy-1.14.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:64b2ff514a98cf2bb734a9f90d32dc89dc6ad4a4a36a312cd0d6327170339eb0"}, + {file = "scipy-1.14.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:7d3da42fbbbb860211a811782504f38ae7aaec9de8764a9bef6b262de7a2b50f"}, + {file = "scipy-1.14.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d91db2c41dd6c20646af280355d41dfa1ec7eead235642178bd57635a3f82209"}, + {file = "scipy-1.14.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a01cc03bcdc777c9da3cfdcc74b5a75caffb48a6c39c8450a9a05f82c4250a14"}, + {file = "scipy-1.14.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:65df4da3c12a2bb9ad52b86b4dcf46813e869afb006e58be0f516bc370165159"}, + {file = "scipy-1.14.0-cp312-cp312-win_amd64.whl", hash = "sha256:4c4161597c75043f7154238ef419c29a64ac4a7c889d588ea77690ac4d0d9b20"}, + {file = "scipy-1.14.0.tar.gz", hash = "sha256:b5923f48cb840380f9854339176ef21763118a7300a88203ccd0bdd26e58527b"}, +] + +[package.dependencies] +numpy = ">=1.23.5,<2.3" + +[package.extras] +dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy (==1.10.0)", "pycodestyle", "pydevtool", "rich-click", "ruff (>=0.0.292)", "types-psutil", "typing_extensions"] +doc = ["jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.13.1)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0)", "sphinx-design (>=0.4.0)"] +test = ["Cython", "array-api-strict", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] + +[[package]] +name = "sentence-transformers" +version = "3.0.1" +description = "Multilingual text embeddings" +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "sentence_transformers-3.0.1-py3-none-any.whl", hash = "sha256:01050cc4053c49b9f5b78f6980b5a72db3fd3a0abb9169b1792ac83875505ee6"}, + {file = "sentence_transformers-3.0.1.tar.gz", hash = "sha256:8a3d2c537cc4d1014ccc20ac92be3d6135420a3bc60ae29a3a8a9b4bb35fbff6"}, +] + +[package.dependencies] +huggingface-hub = ">=0.15.1" +numpy = "*" +Pillow = "*" +scikit-learn = "*" +scipy = "*" +torch = ">=1.11.0" +tqdm = "*" +transformers = ">=4.34.0,<5.0.0" + +[package.extras] +dev = ["accelerate (>=0.20.3)", "datasets", "pre-commit", "pytest", "ruff (>=0.3.0)"] +train = ["accelerate (>=0.20.3)", "datasets"] + [[package]] name = "six" version = "1.16.0" @@ -1909,6 +2523,23 @@ files = [ {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] +[[package]] +name = "sympy" +version = "1.13.1" +description = "Computer algebra system (CAS) in Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "sympy-1.13.1-py3-none-any.whl", hash = "sha256:db36cdc64bf61b9b24578b6f7bab1ecdd2452cf008f34faa33776680c26d66f8"}, + {file = "sympy-1.13.1.tar.gz", hash = "sha256:9cebf7e04ff162015ce31c9c6c9144daa34a93bd082f54fd8f12deca4f47515f"}, +] + +[package.dependencies] +mpmath = ">=1.1.0,<1.4" + +[package.extras] +dev = ["hypothesis (>=6.70.0)", "pytest (>=7.1.0)"] + [[package]] name = "tabulate" version = "0.9.0" @@ -1923,6 +2554,19 @@ files = [ [package.extras] widechars = ["wcwidth"] +[[package]] +name = "tbb" +version = "2021.13.0" +description = "Intel® oneAPI Threading Building Blocks (oneTBB)" +optional = false +python-versions = "*" +files = [ + {file = "tbb-2021.13.0-py2.py3-none-manylinux1_i686.whl", hash = "sha256:a2567725329639519d46d92a2634cf61e76601dac2f777a05686fea546c4fe4f"}, + {file = "tbb-2021.13.0-py2.py3-none-manylinux1_x86_64.whl", hash = "sha256:aaf667e92849adb012b8874d6393282afc318aca4407fc62f912ee30a22da46a"}, + {file = "tbb-2021.13.0-py3-none-win32.whl", hash = "sha256:6669d26703e9943f6164c6407bd4a237a45007e79b8d3832fe6999576eaaa9ef"}, + {file = "tbb-2021.13.0-py3-none-win_amd64.whl", hash = "sha256:3528a53e4bbe64b07a6112b4c5a00ff3c61924ee46c9c68e004a1ac7ad1f09c3"}, +] + [[package]] name = "tenacity" version = "8.5.0" @@ -1967,6 +2611,134 @@ files = [ anyascii = "*" pyahocorasick = "*" +[[package]] +name = "threadpoolctl" +version = "3.5.0" +description = "threadpoolctl" +optional = false +python-versions = ">=3.8" +files = [ + {file = "threadpoolctl-3.5.0-py3-none-any.whl", hash = "sha256:56c1e26c150397e58c4926da8eeee87533b1e32bef131bd4bf6a2f45f3185467"}, + {file = "threadpoolctl-3.5.0.tar.gz", hash = "sha256:082433502dd922bf738de0d8bcc4fdcbf0979ff44c42bd40f5af8a282f6fa107"}, +] + +[[package]] +name = "tokenizers" +version = "0.19.1" +description = "" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tokenizers-0.19.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:952078130b3d101e05ecfc7fc3640282d74ed26bcf691400f872563fca15ac97"}, + {file = "tokenizers-0.19.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:82c8b8063de6c0468f08e82c4e198763e7b97aabfe573fd4cf7b33930ca4df77"}, + {file = "tokenizers-0.19.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f03727225feaf340ceeb7e00604825addef622d551cbd46b7b775ac834c1e1c4"}, + {file = "tokenizers-0.19.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:453e4422efdfc9c6b6bf2eae00d5e323f263fff62b29a8c9cd526c5003f3f642"}, + {file = "tokenizers-0.19.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:02e81bf089ebf0e7f4df34fa0207519f07e66d8491d963618252f2e0729e0b46"}, + {file = "tokenizers-0.19.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b07c538ba956843833fee1190cf769c60dc62e1cf934ed50d77d5502194d63b1"}, + {file = "tokenizers-0.19.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e28cab1582e0eec38b1f38c1c1fb2e56bce5dc180acb1724574fc5f47da2a4fe"}, + {file = "tokenizers-0.19.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b01afb7193d47439f091cd8f070a1ced347ad0f9144952a30a41836902fe09e"}, + {file = "tokenizers-0.19.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7fb297edec6c6841ab2e4e8f357209519188e4a59b557ea4fafcf4691d1b4c98"}, + {file = "tokenizers-0.19.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2e8a3dd055e515df7054378dc9d6fa8c8c34e1f32777fb9a01fea81496b3f9d3"}, + {file = "tokenizers-0.19.1-cp310-none-win32.whl", hash = "sha256:7ff898780a155ea053f5d934925f3902be2ed1f4d916461e1a93019cc7250837"}, + {file = "tokenizers-0.19.1-cp310-none-win_amd64.whl", hash = "sha256:bea6f9947e9419c2fda21ae6c32871e3d398cba549b93f4a65a2d369662d9403"}, + {file = "tokenizers-0.19.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:5c88d1481f1882c2e53e6bb06491e474e420d9ac7bdff172610c4f9ad3898059"}, + {file = "tokenizers-0.19.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ddf672ed719b4ed82b51499100f5417d7d9f6fb05a65e232249268f35de5ed14"}, + {file = "tokenizers-0.19.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:dadc509cc8a9fe460bd274c0e16ac4184d0958117cf026e0ea8b32b438171594"}, + {file = "tokenizers-0.19.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfedf31824ca4915b511b03441784ff640378191918264268e6923da48104acc"}, + {file = "tokenizers-0.19.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac11016d0a04aa6487b1513a3a36e7bee7eec0e5d30057c9c0408067345c48d2"}, + {file = "tokenizers-0.19.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76951121890fea8330d3a0df9a954b3f2a37e3ec20e5b0530e9a0044ca2e11fe"}, + {file = "tokenizers-0.19.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b342d2ce8fc8d00f376af068e3274e2e8649562e3bc6ae4a67784ded6b99428d"}, + {file = "tokenizers-0.19.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d16ff18907f4909dca9b076b9c2d899114dd6abceeb074eca0c93e2353f943aa"}, + {file = "tokenizers-0.19.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:706a37cc5332f85f26efbe2bdc9ef8a9b372b77e4645331a405073e4b3a8c1c6"}, + {file = "tokenizers-0.19.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:16baac68651701364b0289979ecec728546133e8e8fe38f66fe48ad07996b88b"}, + {file = "tokenizers-0.19.1-cp311-none-win32.whl", hash = "sha256:9ed240c56b4403e22b9584ee37d87b8bfa14865134e3e1c3fb4b2c42fafd3256"}, + {file = "tokenizers-0.19.1-cp311-none-win_amd64.whl", hash = "sha256:ad57d59341710b94a7d9dbea13f5c1e7d76fd8d9bcd944a7a6ab0b0da6e0cc66"}, + {file = "tokenizers-0.19.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:621d670e1b1c281a1c9698ed89451395d318802ff88d1fc1accff0867a06f153"}, + {file = "tokenizers-0.19.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d924204a3dbe50b75630bd16f821ebda6a5f729928df30f582fb5aade90c818a"}, + {file = "tokenizers-0.19.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4f3fefdc0446b1a1e6d81cd4c07088ac015665d2e812f6dbba4a06267d1a2c95"}, + {file = "tokenizers-0.19.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9620b78e0b2d52ef07b0d428323fb34e8ea1219c5eac98c2596311f20f1f9266"}, + {file = "tokenizers-0.19.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:04ce49e82d100594715ac1b2ce87d1a36e61891a91de774755f743babcd0dd52"}, + {file = "tokenizers-0.19.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c5c2ff13d157afe413bf7e25789879dd463e5a4abfb529a2d8f8473d8042e28f"}, + {file = "tokenizers-0.19.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3174c76efd9d08f836bfccaca7cfec3f4d1c0a4cf3acbc7236ad577cc423c840"}, + {file = "tokenizers-0.19.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c9d5b6c0e7a1e979bec10ff960fae925e947aab95619a6fdb4c1d8ff3708ce3"}, + {file = "tokenizers-0.19.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a179856d1caee06577220ebcfa332af046d576fb73454b8f4d4b0ba8324423ea"}, + {file = "tokenizers-0.19.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:952b80dac1a6492170f8c2429bd11fcaa14377e097d12a1dbe0ef2fb2241e16c"}, + {file = "tokenizers-0.19.1-cp312-none-win32.whl", hash = "sha256:01d62812454c188306755c94755465505836fd616f75067abcae529c35edeb57"}, + {file = "tokenizers-0.19.1-cp312-none-win_amd64.whl", hash = "sha256:b70bfbe3a82d3e3fb2a5e9b22a39f8d1740c96c68b6ace0086b39074f08ab89a"}, + {file = "tokenizers-0.19.1-cp37-cp37m-macosx_10_12_x86_64.whl", hash = "sha256:bb9dfe7dae85bc6119d705a76dc068c062b8b575abe3595e3c6276480e67e3f1"}, + {file = "tokenizers-0.19.1-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:1f0360cbea28ea99944ac089c00de7b2e3e1c58f479fb8613b6d8d511ce98267"}, + {file = "tokenizers-0.19.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:71e3ec71f0e78780851fef28c2a9babe20270404c921b756d7c532d280349214"}, + {file = "tokenizers-0.19.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b82931fa619dbad979c0ee8e54dd5278acc418209cc897e42fac041f5366d626"}, + {file = "tokenizers-0.19.1-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e8ff5b90eabdcdaa19af697885f70fe0b714ce16709cf43d4952f1f85299e73a"}, + {file = "tokenizers-0.19.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e742d76ad84acbdb1a8e4694f915fe59ff6edc381c97d6dfdd054954e3478ad4"}, + {file = "tokenizers-0.19.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d8c5d59d7b59885eab559d5bc082b2985555a54cda04dda4c65528d90ad252ad"}, + {file = "tokenizers-0.19.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b2da5c32ed869bebd990c9420df49813709e953674c0722ff471a116d97b22d"}, + {file = "tokenizers-0.19.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:638e43936cc8b2cbb9f9d8dde0fe5e7e30766a3318d2342999ae27f68fdc9bd6"}, + {file = "tokenizers-0.19.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:78e769eb3b2c79687d9cb0f89ef77223e8e279b75c0a968e637ca7043a84463f"}, + {file = "tokenizers-0.19.1-cp37-none-win32.whl", hash = "sha256:72791f9bb1ca78e3ae525d4782e85272c63faaef9940d92142aa3eb79f3407a3"}, + {file = "tokenizers-0.19.1-cp37-none-win_amd64.whl", hash = "sha256:f3bbb7a0c5fcb692950b041ae11067ac54826204318922da754f908d95619fbc"}, + {file = "tokenizers-0.19.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:07f9295349bbbcedae8cefdbcfa7f686aa420be8aca5d4f7d1ae6016c128c0c5"}, + {file = "tokenizers-0.19.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:10a707cc6c4b6b183ec5dbfc5c34f3064e18cf62b4a938cb41699e33a99e03c1"}, + {file = "tokenizers-0.19.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6309271f57b397aa0aff0cbbe632ca9d70430839ca3178bf0f06f825924eca22"}, + {file = "tokenizers-0.19.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ad23d37d68cf00d54af184586d79b84075ada495e7c5c0f601f051b162112dc"}, + {file = "tokenizers-0.19.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:427c4f0f3df9109314d4f75b8d1f65d9477033e67ffaec4bca53293d3aca286d"}, + {file = "tokenizers-0.19.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e83a31c9cf181a0a3ef0abad2b5f6b43399faf5da7e696196ddd110d332519ee"}, + {file = "tokenizers-0.19.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c27b99889bd58b7e301468c0838c5ed75e60c66df0d4db80c08f43462f82e0d3"}, + {file = "tokenizers-0.19.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bac0b0eb952412b0b196ca7a40e7dce4ed6f6926489313414010f2e6b9ec2adf"}, + {file = "tokenizers-0.19.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8a6298bde623725ca31c9035a04bf2ef63208d266acd2bed8c2cb7d2b7d53ce6"}, + {file = "tokenizers-0.19.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:08a44864e42fa6d7d76d7be4bec62c9982f6f6248b4aa42f7302aa01e0abfd26"}, + {file = "tokenizers-0.19.1-cp38-none-win32.whl", hash = "sha256:1de5bc8652252d9357a666e609cb1453d4f8e160eb1fb2830ee369dd658e8975"}, + {file = "tokenizers-0.19.1-cp38-none-win_amd64.whl", hash = "sha256:0bcce02bf1ad9882345b34d5bd25ed4949a480cf0e656bbd468f4d8986f7a3f1"}, + {file = "tokenizers-0.19.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:0b9394bd204842a2a1fd37fe29935353742be4a3460b6ccbaefa93f58a8df43d"}, + {file = "tokenizers-0.19.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4692ab92f91b87769d950ca14dbb61f8a9ef36a62f94bad6c82cc84a51f76f6a"}, + {file = "tokenizers-0.19.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6258c2ef6f06259f70a682491c78561d492e885adeaf9f64f5389f78aa49a051"}, + {file = "tokenizers-0.19.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c85cf76561fbd01e0d9ea2d1cbe711a65400092bc52b5242b16cfd22e51f0c58"}, + {file = "tokenizers-0.19.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:670b802d4d82bbbb832ddb0d41df7015b3e549714c0e77f9bed3e74d42400fbe"}, + {file = "tokenizers-0.19.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:85aa3ab4b03d5e99fdd31660872249df5e855334b6c333e0bc13032ff4469c4a"}, + {file = "tokenizers-0.19.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cbf001afbbed111a79ca47d75941e9e5361297a87d186cbfc11ed45e30b5daba"}, + {file = "tokenizers-0.19.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4c89aa46c269e4e70c4d4f9d6bc644fcc39bb409cb2a81227923404dd6f5227"}, + {file = "tokenizers-0.19.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:39c1ec76ea1027438fafe16ecb0fb84795e62e9d643444c1090179e63808c69d"}, + {file = "tokenizers-0.19.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c2a0d47a89b48d7daa241e004e71fb5a50533718897a4cd6235cb846d511a478"}, + {file = "tokenizers-0.19.1-cp39-none-win32.whl", hash = "sha256:61b7fe8886f2e104d4caf9218b157b106207e0f2a4905c9c7ac98890688aabeb"}, + {file = "tokenizers-0.19.1-cp39-none-win_amd64.whl", hash = "sha256:f97660f6c43efd3e0bfd3f2e3e5615bf215680bad6ee3d469df6454b8c6e8256"}, + {file = "tokenizers-0.19.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3b11853f17b54c2fe47742c56d8a33bf49ce31caf531e87ac0d7d13d327c9334"}, + {file = "tokenizers-0.19.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d26194ef6c13302f446d39972aaa36a1dda6450bc8949f5eb4c27f51191375bd"}, + {file = "tokenizers-0.19.1-pp310-pypy310_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e8d1ed93beda54bbd6131a2cb363a576eac746d5c26ba5b7556bc6f964425594"}, + {file = "tokenizers-0.19.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca407133536f19bdec44b3da117ef0d12e43f6d4b56ac4c765f37eca501c7bda"}, + {file = "tokenizers-0.19.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce05fde79d2bc2e46ac08aacbc142bead21614d937aac950be88dc79f9db9022"}, + {file = "tokenizers-0.19.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:35583cd46d16f07c054efd18b5d46af4a2f070a2dd0a47914e66f3ff5efb2b1e"}, + {file = "tokenizers-0.19.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:43350270bfc16b06ad3f6f07eab21f089adb835544417afda0f83256a8bf8b75"}, + {file = "tokenizers-0.19.1-pp37-pypy37_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b4399b59d1af5645bcee2072a463318114c39b8547437a7c2d6a186a1b5a0e2d"}, + {file = "tokenizers-0.19.1-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6852c5b2a853b8b0ddc5993cd4f33bfffdca4fcc5d52f89dd4b8eada99379285"}, + {file = "tokenizers-0.19.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bcd266ae85c3d39df2f7e7d0e07f6c41a55e9a3123bb11f854412952deacd828"}, + {file = "tokenizers-0.19.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ecb2651956eea2aa0a2d099434134b1b68f1c31f9a5084d6d53f08ed43d45ff2"}, + {file = "tokenizers-0.19.1-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:b279ab506ec4445166ac476fb4d3cc383accde1ea152998509a94d82547c8e2a"}, + {file = "tokenizers-0.19.1-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:89183e55fb86e61d848ff83753f64cded119f5d6e1f553d14ffee3700d0a4a49"}, + {file = "tokenizers-0.19.1-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2edbc75744235eea94d595a8b70fe279dd42f3296f76d5a86dde1d46e35f574"}, + {file = "tokenizers-0.19.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:0e64bfde9a723274e9a71630c3e9494ed7b4c0f76a1faacf7fe294cd26f7ae7c"}, + {file = "tokenizers-0.19.1-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0b5ca92bfa717759c052e345770792d02d1f43b06f9e790ca0a1db62838816f3"}, + {file = "tokenizers-0.19.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f8a20266e695ec9d7a946a019c1d5ca4eddb6613d4f466888eee04f16eedb85"}, + {file = "tokenizers-0.19.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63c38f45d8f2a2ec0f3a20073cccb335b9f99f73b3c69483cd52ebc75369d8a1"}, + {file = "tokenizers-0.19.1-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:dd26e3afe8a7b61422df3176e06664503d3f5973b94f45d5c45987e1cb711876"}, + {file = "tokenizers-0.19.1-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:eddd5783a4a6309ce23432353cdb36220e25cbb779bfa9122320666508b44b88"}, + {file = "tokenizers-0.19.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:56ae39d4036b753994476a1b935584071093b55c7a72e3b8288e68c313ca26e7"}, + {file = "tokenizers-0.19.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:f9939ca7e58c2758c01b40324a59c034ce0cebad18e0d4563a9b1beab3018243"}, + {file = "tokenizers-0.19.1-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6c330c0eb815d212893c67a032e9dc1b38a803eccb32f3e8172c19cc69fbb439"}, + {file = "tokenizers-0.19.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec11802450a2487cdf0e634b750a04cbdc1c4d066b97d94ce7dd2cb51ebb325b"}, + {file = "tokenizers-0.19.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2b718f316b596f36e1dae097a7d5b91fc5b85e90bf08b01ff139bd8953b25af"}, + {file = "tokenizers-0.19.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:ed69af290c2b65169f0ba9034d1dc39a5db9459b32f1dd8b5f3f32a3fcf06eab"}, + {file = "tokenizers-0.19.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f8a9c828277133af13f3859d1b6bf1c3cb6e9e1637df0e45312e6b7c2e622b1f"}, + {file = "tokenizers-0.19.1.tar.gz", hash = "sha256:ee59e6680ed0fdbe6b724cf38bd70400a0c1dd623b07ac729087270caeac88e3"}, +] + +[package.dependencies] +huggingface-hub = ">=0.16.4,<1.0" + +[package.extras] +dev = ["tokenizers[testing]"] +docs = ["setuptools-rust", "sphinx", "sphinx-rtd-theme"] +testing = ["black (==22.3)", "datasets", "numpy", "pytest", "requests", "ruff"] + [[package]] name = "tomlkit" version = "0.13.0" @@ -1978,6 +2750,59 @@ files = [ {file = "tomlkit-0.13.0.tar.gz", hash = "sha256:08ad192699734149f5b97b45f1f18dad7eb1b6d16bc72ad0c2335772650d7b72"}, ] +[[package]] +name = "torch" +version = "2.3.1" +description = "Tensors and Dynamic neural networks in Python with strong GPU acceleration" +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "torch-2.3.1-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:605a25b23944be5ab7c3467e843580e1d888b8066e5aaf17ff7bf9cc30001cc3"}, + {file = "torch-2.3.1-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:f2357eb0965583a0954d6f9ad005bba0091f956aef879822274b1bcdb11bd308"}, + {file = "torch-2.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:32b05fe0d1ada7f69c9f86c14ff69b0ef1957a5a54199bacba63d22d8fab720b"}, + {file = "torch-2.3.1-cp310-none-macosx_11_0_arm64.whl", hash = "sha256:7c09a94362778428484bcf995f6004b04952106aee0ef45ff0b4bab484f5498d"}, + {file = "torch-2.3.1-cp311-cp311-manylinux1_x86_64.whl", hash = "sha256:b2ec81b61bb094ea4a9dee1cd3f7b76a44555375719ad29f05c0ca8ef596ad39"}, + {file = "torch-2.3.1-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:490cc3d917d1fe0bd027057dfe9941dc1d6d8e3cae76140f5dd9a7e5bc7130ab"}, + {file = "torch-2.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:5802530783bd465fe66c2df99123c9a54be06da118fbd785a25ab0a88123758a"}, + {file = "torch-2.3.1-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:a7dd4ed388ad1f3d502bf09453d5fe596c7b121de7e0cfaca1e2017782e9bbac"}, + {file = "torch-2.3.1-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:a486c0b1976a118805fc7c9641d02df7afbb0c21e6b555d3bb985c9f9601b61a"}, + {file = "torch-2.3.1-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:224259821fe3e4c6f7edf1528e4fe4ac779c77addaa74215eb0b63a5c474d66c"}, + {file = "torch-2.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:e5fdccbf6f1334b2203a61a0e03821d5845f1421defe311dabeae2fc8fbeac2d"}, + {file = "torch-2.3.1-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:3c333dc2ebc189561514eda06e81df22bf8fb64e2384746b2cb9f04f96d1d4c8"}, + {file = "torch-2.3.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:07e9ba746832b8d069cacb45f312cadd8ad02b81ea527ec9766c0e7404bb3feb"}, + {file = "torch-2.3.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:462d1c07dbf6bb5d9d2f3316fee73a24f3d12cd8dacf681ad46ef6418f7f6626"}, + {file = "torch-2.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:ff60bf7ce3de1d43ad3f6969983f321a31f0a45df3690921720bcad6a8596cc4"}, + {file = "torch-2.3.1-cp38-none-macosx_11_0_arm64.whl", hash = "sha256:bee0bd33dc58aa8fc8a7527876e9b9a0e812ad08122054a5bff2ce5abf005b10"}, + {file = "torch-2.3.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:aaa872abde9a3d4f91580f6396d54888620f4a0b92e3976a6034759df4b961ad"}, + {file = "torch-2.3.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:3d7a7f7ef21a7520510553dc3938b0c57c116a7daee20736a9e25cbc0e832bdc"}, + {file = "torch-2.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:4777f6cefa0c2b5fa87223c213e7b6f417cf254a45e5829be4ccd1b2a4ee1011"}, + {file = "torch-2.3.1-cp39-none-macosx_11_0_arm64.whl", hash = "sha256:2bb5af780c55be68fe100feb0528d2edebace1d55cb2e351de735809ba7391eb"}, +] + +[package.dependencies] +filelock = "*" +fsspec = "*" +jinja2 = "*" +mkl = {version = ">=2021.1.1,<=2021.4.0", markers = "platform_system == \"Windows\""} +networkx = "*" +nvidia-cublas-cu12 = {version = "12.1.3.1", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-cuda-cupti-cu12 = {version = "12.1.105", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-cuda-nvrtc-cu12 = {version = "12.1.105", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-cuda-runtime-cu12 = {version = "12.1.105", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-cudnn-cu12 = {version = "8.9.2.26", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-cufft-cu12 = {version = "11.0.2.54", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-curand-cu12 = {version = "10.3.2.106", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-cusolver-cu12 = {version = "11.4.5.107", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-cusparse-cu12 = {version = "12.1.0.106", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-nccl-cu12 = {version = "2.20.5", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-nvtx-cu12 = {version = "12.1.105", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +sympy = "*" +typing-extensions = ">=4.8.0" + +[package.extras] +opt-einsum = ["opt-einsum (>=3.3)"] +optree = ["optree (>=0.9.1)"] + [[package]] name = "tqdm" version = "4.66.4" @@ -1998,6 +2823,72 @@ notebook = ["ipywidgets (>=6)"] slack = ["slack-sdk"] telegram = ["requests"] +[[package]] +name = "transformers" +version = "4.41.2" +description = "State-of-the-art Machine Learning for JAX, PyTorch and TensorFlow" +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "transformers-4.41.2-py3-none-any.whl", hash = "sha256:05555d20e43f808de1ef211ab64803cdb513170cef70d29a888b589caebefc67"}, + {file = "transformers-4.41.2.tar.gz", hash = "sha256:80a4db216533d573e9cc7388646c31ed9480918feb7c55eb211249cb23567f87"}, +] + +[package.dependencies] +filelock = "*" +huggingface-hub = ">=0.23.0,<1.0" +numpy = ">=1.17" +packaging = ">=20.0" +pyyaml = ">=5.1" +regex = "!=2019.12.17" +requests = "*" +safetensors = ">=0.4.1" +tokenizers = ">=0.19,<0.20" +tqdm = ">=4.27" + +[package.extras] +accelerate = ["accelerate (>=0.21.0)"] +agents = ["Pillow (>=10.0.1,<=15.0)", "accelerate (>=0.21.0)", "datasets (!=2.5.0)", "diffusers", "opencv-python", "sentencepiece (>=0.1.91,!=0.1.92)", "torch"] +all = ["Pillow (>=10.0.1,<=15.0)", "accelerate (>=0.21.0)", "av (==9.2.0)", "codecarbon (==1.2.0)", "decord (==0.6.0)", "flax (>=0.4.1,<=0.7.0)", "jax (>=0.4.1,<=0.4.13)", "jaxlib (>=0.4.1,<=0.4.13)", "kenlm", "keras-nlp (>=0.3.1)", "librosa", "onnxconverter-common", "optax (>=0.0.8,<=0.1.4)", "optuna", "phonemizer", "protobuf", "pyctcdecode (>=0.4.0)", "ray[tune] (>=2.7.0)", "scipy (<1.13.0)", "sentencepiece (>=0.1.91,!=0.1.92)", "sigopt", "tensorflow (>2.9,<2.16)", "tensorflow-text (<2.16)", "tf2onnx", "timm", "tokenizers (>=0.19,<0.20)", "torch", "torchaudio", "torchvision"] +audio = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)"] +codecarbon = ["codecarbon (==1.2.0)"] +deepspeed = ["accelerate (>=0.21.0)", "deepspeed (>=0.9.3)"] +deepspeed-testing = ["GitPython (<3.1.19)", "accelerate (>=0.21.0)", "beautifulsoup4", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "deepspeed (>=0.9.3)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "nltk", "optuna", "parameterized", "protobuf", "psutil", "pydantic", "pytest (>=7.2.0,<8.0.0)", "pytest-rich", "pytest-timeout", "pytest-xdist", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (==0.1.5)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "sentencepiece (>=0.1.91,!=0.1.92)", "tensorboard", "timeout-decorator"] +dev = ["GitPython (<3.1.19)", "Pillow (>=10.0.1,<=15.0)", "accelerate (>=0.21.0)", "av (==9.2.0)", "beautifulsoup4", "codecarbon (==1.2.0)", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "decord (==0.6.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "flax (>=0.4.1,<=0.7.0)", "fugashi (>=1.0)", "ipadic (>=1.0.0,<2.0)", "isort (>=5.5.4)", "jax (>=0.4.1,<=0.4.13)", "jaxlib (>=0.4.1,<=0.4.13)", "kenlm", "keras-nlp (>=0.3.1)", "librosa", "nltk", "onnxconverter-common", "optax (>=0.0.8,<=0.1.4)", "optuna", "parameterized", "phonemizer", "protobuf", "psutil", "pyctcdecode (>=0.4.0)", "pydantic", "pytest (>=7.2.0,<8.0.0)", "pytest-rich", "pytest-timeout", "pytest-xdist", "ray[tune] (>=2.7.0)", "rhoknp (>=1.1.0,<1.3.1)", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (==0.1.5)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "scikit-learn", "scipy (<1.13.0)", "sentencepiece (>=0.1.91,!=0.1.92)", "sigopt", "sudachidict-core (>=20220729)", "sudachipy (>=0.6.6)", "tensorboard", "tensorflow (>2.9,<2.16)", "tensorflow-text (<2.16)", "tf2onnx", "timeout-decorator", "timm", "tokenizers (>=0.19,<0.20)", "torch", "torchaudio", "torchvision", "unidic (>=1.0.2)", "unidic-lite (>=1.0.7)", "urllib3 (<2.0.0)"] +dev-tensorflow = ["GitPython (<3.1.19)", "Pillow (>=10.0.1,<=15.0)", "beautifulsoup4", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "isort (>=5.5.4)", "kenlm", "keras-nlp (>=0.3.1)", "librosa", "nltk", "onnxconverter-common", "onnxruntime (>=1.4.0)", "onnxruntime-tools (>=1.4.2)", "parameterized", "phonemizer", "protobuf", "psutil", "pyctcdecode (>=0.4.0)", "pydantic", "pytest (>=7.2.0,<8.0.0)", "pytest-rich", "pytest-timeout", "pytest-xdist", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (==0.1.5)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "scikit-learn", "sentencepiece (>=0.1.91,!=0.1.92)", "tensorboard", "tensorflow (>2.9,<2.16)", "tensorflow-text (<2.16)", "tf2onnx", "timeout-decorator", "tokenizers (>=0.19,<0.20)", "urllib3 (<2.0.0)"] +dev-torch = ["GitPython (<3.1.19)", "Pillow (>=10.0.1,<=15.0)", "accelerate (>=0.21.0)", "beautifulsoup4", "codecarbon (==1.2.0)", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "fugashi (>=1.0)", "ipadic (>=1.0.0,<2.0)", "isort (>=5.5.4)", "kenlm", "librosa", "nltk", "onnxruntime (>=1.4.0)", "onnxruntime-tools (>=1.4.2)", "optuna", "parameterized", "phonemizer", "protobuf", "psutil", "pyctcdecode (>=0.4.0)", "pydantic", "pytest (>=7.2.0,<8.0.0)", "pytest-rich", "pytest-timeout", "pytest-xdist", "ray[tune] (>=2.7.0)", "rhoknp (>=1.1.0,<1.3.1)", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (==0.1.5)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "scikit-learn", "sentencepiece (>=0.1.91,!=0.1.92)", "sigopt", "sudachidict-core (>=20220729)", "sudachipy (>=0.6.6)", "tensorboard", "timeout-decorator", "timm", "tokenizers (>=0.19,<0.20)", "torch", "torchaudio", "torchvision", "unidic (>=1.0.2)", "unidic-lite (>=1.0.7)", "urllib3 (<2.0.0)"] +flax = ["flax (>=0.4.1,<=0.7.0)", "jax (>=0.4.1,<=0.4.13)", "jaxlib (>=0.4.1,<=0.4.13)", "optax (>=0.0.8,<=0.1.4)", "scipy (<1.13.0)"] +flax-speech = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)"] +ftfy = ["ftfy"] +integrations = ["optuna", "ray[tune] (>=2.7.0)", "sigopt"] +ja = ["fugashi (>=1.0)", "ipadic (>=1.0.0,<2.0)", "rhoknp (>=1.1.0,<1.3.1)", "sudachidict-core (>=20220729)", "sudachipy (>=0.6.6)", "unidic (>=1.0.2)", "unidic-lite (>=1.0.7)"] +modelcreation = ["cookiecutter (==1.7.3)"] +natten = ["natten (>=0.14.6,<0.15.0)"] +onnx = ["onnxconverter-common", "onnxruntime (>=1.4.0)", "onnxruntime-tools (>=1.4.2)", "tf2onnx"] +onnxruntime = ["onnxruntime (>=1.4.0)", "onnxruntime-tools (>=1.4.2)"] +optuna = ["optuna"] +quality = ["GitPython (<3.1.19)", "datasets (!=2.5.0)", "isort (>=5.5.4)", "ruff (==0.1.5)", "urllib3 (<2.0.0)"] +ray = ["ray[tune] (>=2.7.0)"] +retrieval = ["datasets (!=2.5.0)", "faiss-cpu"] +sagemaker = ["sagemaker (>=2.31.0)"] +sentencepiece = ["protobuf", "sentencepiece (>=0.1.91,!=0.1.92)"] +serving = ["fastapi", "pydantic", "starlette", "uvicorn"] +sigopt = ["sigopt"] +sklearn = ["scikit-learn"] +speech = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)", "torchaudio"] +testing = ["GitPython (<3.1.19)", "beautifulsoup4", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "nltk", "parameterized", "psutil", "pydantic", "pytest (>=7.2.0,<8.0.0)", "pytest-rich", "pytest-timeout", "pytest-xdist", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (==0.1.5)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "sentencepiece (>=0.1.91,!=0.1.92)", "tensorboard", "timeout-decorator"] +tf = ["keras-nlp (>=0.3.1)", "onnxconverter-common", "tensorflow (>2.9,<2.16)", "tensorflow-text (<2.16)", "tf2onnx"] +tf-cpu = ["keras (>2.9,<2.16)", "keras-nlp (>=0.3.1)", "onnxconverter-common", "tensorflow-cpu (>2.9,<2.16)", "tensorflow-probability (<2.16)", "tensorflow-text (<2.16)", "tf2onnx"] +tf-speech = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)"] +timm = ["timm"] +tokenizers = ["tokenizers (>=0.19,<0.20)"] +torch = ["accelerate (>=0.21.0)", "torch"] +torch-speech = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)", "torchaudio"] +torch-vision = ["Pillow (>=10.0.1,<=15.0)", "torchvision"] +torchhub = ["filelock", "huggingface-hub (>=0.23.0,<1.0)", "importlib-metadata", "numpy (>=1.17)", "packaging (>=20.0)", "protobuf", "regex (!=2019.12.17)", "requests", "sentencepiece (>=0.1.91,!=0.1.92)", "tokenizers (>=0.19,<0.20)", "torch", "tqdm (>=4.27)"] +video = ["av (==9.2.0)", "decord (==0.6.0)"] +vision = ["Pillow (>=10.0.1,<=15.0)"] + [[package]] name = "typing-extensions" version = "4.12.2" @@ -2250,4 +3141,4 @@ test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", [metadata] lock-version = "2.0" python-versions = "~3.12" -content-hash = "0f2e9082200eeb36ca502309770dcba911820fc9064cfad802a54b493c0f9ea3" +content-hash = "fdbdeb73f326fcd8c71a81ad2d5d1a7bf10bd871fe7565276f0d9941f6604c96" diff --git a/pyproject.toml b/pyproject.toml index 08a9109..42fed1a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,6 +18,7 @@ nltk = "^3.8.1" pydantic = "^2.8.2" redisvl = { extras = ["hiredis"], version = "^0.2.3" } contractions = "^0.1.73" +sentence-transformers = "^3.0.1" [tool.poetry.dev-dependencies] commitizen = "3.27.0" @@ -41,6 +42,8 @@ reportUnnecessaryTypeIgnoreComment = "error" asyncio_mode = "auto" filterwarnings = [ "ignore:'audioop' is deprecated and slated for removal in Python 3.13", + "ignore:invalid escape sequence *", + "ignore:`resume_download` is deprecated and will be removed *", ] [tool.ruff] From eed1ee5c928df5a9571419eb420b220b459abd42 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Sun, 21 Jul 2024 13:17:14 +0000 Subject: [PATCH 053/168] =?UTF-8?q?bump:=20version=200.1.0=20=E2=86=92=200?= =?UTF-8?q?.2.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/CHANGELOG.md | 23 +++++++++++++++++++++++ pyproject.toml | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 4337f35..247bf6f 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,3 +1,26 @@ +## v0.2.0 (2024-07-21) + +### Feat + +- setup transformer models (#28) +- use pydantic to model Redis hashes (#25) +- add sentiment analysis (#24) +- add api to store word frequency on redis (#23) +- download nltk resources on startup (#22) + +### Fix + +- handle contractions in tokenizer (#26) +- control max number of concurrent downloads +- add logging and remove return section from docstring + +### Refactor + +- move startup logic into bot class (#27) +- improve docs, types and fix some linter issues +- update docstrings +- improve type hints + ## v0.1.0 (2024-07-20) ### Feat diff --git a/pyproject.toml b/pyproject.toml index 42fed1a..0815c59 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "courageous-comets" -version = "0.1.0" +version = "0.2.0" authors = ["Courageous Comets"] description = "" readme = "docs/README.md" From 61ec725d0d8e80ae380fe6be1bfab0921c01e34c Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Sun, 21 Jul 2024 13:42:26 +0000 Subject: [PATCH 054/168] docs: link changelog in navigation --- mkdocs.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/mkdocs.yaml b/mkdocs.yaml index cc6396b..199bb41 100644 --- a/mkdocs.yaml +++ b/mkdocs.yaml @@ -41,6 +41,7 @@ nav: - admin-guide/index.md - Deployment: admin-guide/deployment.md - Configuration: admin-guide/configuration.md + - Changelog: CHANGELOG.md - Contributors: - contributor-guide/index.md - Development Environment: contributor-guide/development-environment.md From 5b1a4cbc2d2689347b0547160802f5bba217d906 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Sun, 21 Jul 2024 14:26:28 +0000 Subject: [PATCH 055/168] refactor: remove throws clause from docstring --- courageous_comets/__main__.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/courageous_comets/__main__.py b/courageous_comets/__main__.py index d841dee..a91ec19 100644 --- a/courageous_comets/__main__.py +++ b/courageous_comets/__main__.py @@ -11,11 +11,6 @@ async def main() -> None: Start the appication. If a critical error occurs, attempt to shut down gracefully. - - Raises - ------ - courageous_comets.exceptions.AuthenticationError - If the Discord token is not valid. """ logging.info("Starting the Courageous Comets application ☄️") try: From 9cb6bff95ef5e08bb632ad6fa7b87b6e10a1cf12 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Sun, 21 Jul 2024 15:51:05 +0000 Subject: [PATCH 056/168] fix: suppress warnings from libraries --- courageous_comets/settings.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/courageous_comets/settings.py b/courageous_comets/settings.py index c78741d..f1e2f92 100644 --- a/courageous_comets/settings.py +++ b/courageous_comets/settings.py @@ -3,6 +3,7 @@ import logging import os import sys +import warnings from pathlib import Path from dotenv import load_dotenv @@ -124,6 +125,9 @@ def read_redis_port() -> int: # Load environment variables from a .env file. If the file does not exist, this does nothing. load_dotenv() +# Suppress warnings from libraries +warnings.filterwarnings("ignore") + # Let discord.py set up the logging configuration LOG_LEVEL = logging.getLevelNamesMapping().get( os.getenv("LOG_LEVEL", "INFO"), From 32ce1e7e272cb11c841a73274d47167771facd1e Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Sun, 21 Jul 2024 20:38:03 +0200 Subject: [PATCH 057/168] fix: ensure consistent log output (#29) --- courageous_comets/__main__.py | 3 + courageous_comets/redis/helpers.py | 4 +- courageous_comets/settings.py | 17 ++- poetry.lock | 195 ++++++++++++++++++++++------- pyproject.toml | 22 ++-- 5 files changed, 181 insertions(+), 60 deletions(-) diff --git a/courageous_comets/__main__.py b/courageous_comets/__main__.py index a91ec19..83cb864 100644 --- a/courageous_comets/__main__.py +++ b/courageous_comets/__main__.py @@ -12,6 +12,9 @@ async def main() -> None: If a critical error occurs, attempt to shut down gracefully. """ + # Override logging configuration by dependencies + settings.setup_logging() + logging.info("Starting the Courageous Comets application ☄️") try: await bot.start(settings.DISCORD_TOKEN) diff --git a/courageous_comets/redis/helpers.py b/courageous_comets/redis/helpers.py index d4d5980..16c322a 100644 --- a/courageous_comets/redis/helpers.py +++ b/courageous_comets/redis/helpers.py @@ -54,7 +54,7 @@ async def init_redis() -> redis.Redis: message = f"Could not connect to Redis at {settings.REDIS_HOST}:{settings.REDIS_PORT}" raise exceptions.DatabaseConnectionError(message) from e - logger.debug( + logger.info( "Connected to Redis at %s:%s", settings.REDIS_HOST, settings.REDIS_PORT, @@ -62,6 +62,6 @@ async def init_redis() -> redis.Redis: await create_indexes(instance) - logger.debug("Redis initialization complete") + logger.info("Redis initialization complete") return instance diff --git a/courageous_comets/settings.py b/courageous_comets/settings.py index f1e2f92..fce2519 100644 --- a/courageous_comets/settings.py +++ b/courageous_comets/settings.py @@ -6,6 +6,7 @@ import warnings from pathlib import Path +import coloredlogs from dotenv import load_dotenv from courageous_comets.exceptions import ConfigurationValueError @@ -122,18 +123,32 @@ def read_redis_port() -> int: return result +def setup_logging() -> None: + """Set up logging for the application.""" + coloredlogs.install( + level=LOG_LEVEL, + fmt="{asctime} {levelname:<8} [{name}] {message}", + datefmt="%Y-%m-%d %H:%M:%S", + style="{", + reconfigure=True, + ) + + # Load environment variables from a .env file. If the file does not exist, this does nothing. load_dotenv() # Suppress warnings from libraries warnings.filterwarnings("ignore") -# Let discord.py set up the logging configuration +# Set up logging for the application LOG_LEVEL = logging.getLevelNamesMapping().get( os.getenv("LOG_LEVEL", "INFO"), logging.INFO, ) +setup_logging() + +# Load configuration values from the environment try: DISCORD_TOKEN = read_discord_token() BOT_CONFIG_PATH = read_bot_config_path() diff --git a/poetry.lock b/poetry.lock index 787e021..f7932f1 100644 --- a/poetry.lock +++ b/poetry.lock @@ -215,6 +215,70 @@ files = [ {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, ] +[[package]] +name = "cffi" +version = "1.16.0" +description = "Foreign Function Interface for Python calling C code." +optional = false +python-versions = ">=3.8" +files = [ + {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"}, + {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"}, + {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"}, + {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"}, + {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"}, + {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"}, + {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"}, + {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"}, + {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"}, + {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"}, + {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"}, + {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"}, + {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"}, + {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"}, + {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"}, + {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"}, + {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"}, + {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"}, + {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"}, + {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"}, + {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"}, + {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"}, + {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"}, + {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"}, + {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"}, + {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"}, + {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"}, +] + +[package.dependencies] +pycparser = "*" + [[package]] name = "cfgv" version = "3.4.0" @@ -1305,56 +1369,56 @@ files = [ [[package]] name = "numpy" -version = "2.0.0" +version = "2.0.1" description = "Fundamental package for array computing in Python" optional = false python-versions = ">=3.9" files = [ - {file = "numpy-2.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:04494f6ec467ccb5369d1808570ae55f6ed9b5809d7f035059000a37b8d7e86f"}, - {file = "numpy-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2635dbd200c2d6faf2ef9a0d04f0ecc6b13b3cad54f7c67c61155138835515d2"}, - {file = "numpy-2.0.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:0a43f0974d501842866cc83471bdb0116ba0dffdbaac33ec05e6afed5b615238"}, - {file = "numpy-2.0.0-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:8d83bb187fb647643bd56e1ae43f273c7f4dbcdf94550d7938cfc32566756514"}, - {file = "numpy-2.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79e843d186c8fb1b102bef3e2bc35ef81160ffef3194646a7fdd6a73c6b97196"}, - {file = "numpy-2.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d7696c615765091cc5093f76fd1fa069870304beaccfd58b5dcc69e55ef49c1"}, - {file = "numpy-2.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b4c76e3d4c56f145d41b7b6751255feefae92edbc9a61e1758a98204200f30fc"}, - {file = "numpy-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:acd3a644e4807e73b4e1867b769fbf1ce8c5d80e7caaef0d90dcdc640dfc9787"}, - {file = "numpy-2.0.0-cp310-cp310-win32.whl", hash = "sha256:cee6cc0584f71adefe2c908856ccc98702baf95ff80092e4ca46061538a2ba98"}, - {file = "numpy-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:ed08d2703b5972ec736451b818c2eb9da80d66c3e84aed1deeb0c345fefe461b"}, - {file = "numpy-2.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ad0c86f3455fbd0de6c31a3056eb822fc939f81b1618f10ff3406971893b62a5"}, - {file = "numpy-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e7f387600d424f91576af20518334df3d97bc76a300a755f9a8d6e4f5cadd289"}, - {file = "numpy-2.0.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:34f003cb88b1ba38cb9a9a4a3161c1604973d7f9d5552c38bc2f04f829536609"}, - {file = "numpy-2.0.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:b6f6a8f45d0313db07d6d1d37bd0b112f887e1369758a5419c0370ba915b3871"}, - {file = "numpy-2.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f64641b42b2429f56ee08b4f427a4d2daf916ec59686061de751a55aafa22e4"}, - {file = "numpy-2.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a7039a136017eaa92c1848152827e1424701532ca8e8967fe480fe1569dae581"}, - {file = "numpy-2.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:46e161722e0f619749d1cd892167039015b2c2817296104487cd03ed4a955995"}, - {file = "numpy-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0e50842b2295ba8414c8c1d9d957083d5dfe9e16828b37de883f51fc53c4016f"}, - {file = "numpy-2.0.0-cp311-cp311-win32.whl", hash = "sha256:2ce46fd0b8a0c947ae047d222f7136fc4d55538741373107574271bc00e20e8f"}, - {file = "numpy-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:fbd6acc766814ea6443628f4e6751d0da6593dae29c08c0b2606164db026970c"}, - {file = "numpy-2.0.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:354f373279768fa5a584bac997de6a6c9bc535c482592d7a813bb0c09be6c76f"}, - {file = "numpy-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4d2f62e55a4cd9c58c1d9a1c9edaedcd857a73cb6fda875bf79093f9d9086f85"}, - {file = "numpy-2.0.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:1e72728e7501a450288fc8e1f9ebc73d90cfd4671ebbd631f3e7857c39bd16f2"}, - {file = "numpy-2.0.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:84554fc53daa8f6abf8e8a66e076aff6ece62de68523d9f665f32d2fc50fd66e"}, - {file = "numpy-2.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c73aafd1afca80afecb22718f8700b40ac7cab927b8abab3c3e337d70e10e5a2"}, - {file = "numpy-2.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49d9f7d256fbc804391a7f72d4a617302b1afac1112fac19b6c6cec63fe7fe8a"}, - {file = "numpy-2.0.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0ec84b9ba0654f3b962802edc91424331f423dcf5d5f926676e0150789cb3d95"}, - {file = "numpy-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:feff59f27338135776f6d4e2ec7aeeac5d5f7a08a83e80869121ef8164b74af9"}, - {file = "numpy-2.0.0-cp312-cp312-win32.whl", hash = "sha256:c5a59996dc61835133b56a32ebe4ef3740ea5bc19b3983ac60cc32be5a665d54"}, - {file = "numpy-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:a356364941fb0593bb899a1076b92dfa2029f6f5b8ba88a14fd0984aaf76d0df"}, - {file = "numpy-2.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e61155fae27570692ad1d327e81c6cf27d535a5d7ef97648a17d922224b216de"}, - {file = "numpy-2.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4554eb96f0fd263041baf16cf0881b3f5dafae7a59b1049acb9540c4d57bc8cb"}, - {file = "numpy-2.0.0-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:903703372d46bce88b6920a0cd86c3ad82dae2dbef157b5fc01b70ea1cfc430f"}, - {file = "numpy-2.0.0-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:3e8e01233d57639b2e30966c63d36fcea099d17c53bf424d77f088b0f4babd86"}, - {file = "numpy-2.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cde1753efe513705a0c6d28f5884e22bdc30438bf0085c5c486cdaff40cd67a"}, - {file = "numpy-2.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:821eedb7165ead9eebdb569986968b541f9908979c2da8a4967ecac4439bae3d"}, - {file = "numpy-2.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9a1712c015831da583b21c5bfe15e8684137097969c6d22e8316ba66b5baabe4"}, - {file = "numpy-2.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9c27f0946a3536403efb0e1c28def1ae6730a72cd0d5878db38824855e3afc44"}, - {file = "numpy-2.0.0-cp39-cp39-win32.whl", hash = "sha256:63b92c512d9dbcc37f9d81b123dec99fdb318ba38c8059afc78086fe73820275"}, - {file = "numpy-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:3f6bed7f840d44c08ebdb73b1825282b801799e325bcbdfa6bc5c370e5aecc65"}, - {file = "numpy-2.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9416a5c2e92ace094e9f0082c5fd473502c91651fb896bc17690d6fc475128d6"}, - {file = "numpy-2.0.0-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:17067d097ed036636fa79f6a869ac26df7db1ba22039d962422506640314933a"}, - {file = "numpy-2.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38ecb5b0582cd125f67a629072fed6f83562d9dd04d7e03256c9829bdec027ad"}, - {file = "numpy-2.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:cef04d068f5fb0518a77857953193b6bb94809a806bd0a14983a8f12ada060c9"}, - {file = "numpy-2.0.0.tar.gz", hash = "sha256:cf5d1c9e6837f8af9f92b6bd3e86d513cdc11f60fd62185cc49ec7d1aba34864"}, + {file = "numpy-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0fbb536eac80e27a2793ffd787895242b7f18ef792563d742c2d673bfcb75134"}, + {file = "numpy-2.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:69ff563d43c69b1baba77af455dd0a839df8d25e8590e79c90fcbe1499ebde42"}, + {file = "numpy-2.0.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:1b902ce0e0a5bb7704556a217c4f63a7974f8f43e090aff03fcf262e0b135e02"}, + {file = "numpy-2.0.1-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:f1659887361a7151f89e79b276ed8dff3d75877df906328f14d8bb40bb4f5101"}, + {file = "numpy-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4658c398d65d1b25e1760de3157011a80375da861709abd7cef3bad65d6543f9"}, + {file = "numpy-2.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4127d4303b9ac9f94ca0441138acead39928938660ca58329fe156f84b9f3015"}, + {file = "numpy-2.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e5eeca8067ad04bc8a2a8731183d51d7cbaac66d86085d5f4766ee6bf19c7f87"}, + {file = "numpy-2.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9adbd9bb520c866e1bfd7e10e1880a1f7749f1f6e5017686a5fbb9b72cf69f82"}, + {file = "numpy-2.0.1-cp310-cp310-win32.whl", hash = "sha256:7b9853803278db3bdcc6cd5beca37815b133e9e77ff3d4733c247414e78eb8d1"}, + {file = "numpy-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:81b0893a39bc5b865b8bf89e9ad7807e16717f19868e9d234bdaf9b1f1393868"}, + {file = "numpy-2.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:75b4e316c5902d8163ef9d423b1c3f2f6252226d1aa5cd8a0a03a7d01ffc6268"}, + {file = "numpy-2.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6e4eeb6eb2fced786e32e6d8df9e755ce5be920d17f7ce00bc38fcde8ccdbf9e"}, + {file = "numpy-2.0.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:a1e01dcaab205fbece13c1410253a9eea1b1c9b61d237b6fa59bcc46e8e89343"}, + {file = "numpy-2.0.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:a8fc2de81ad835d999113ddf87d1ea2b0f4704cbd947c948d2f5513deafe5a7b"}, + {file = "numpy-2.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a3d94942c331dd4e0e1147f7a8699a4aa47dffc11bf8a1523c12af8b2e91bbe"}, + {file = "numpy-2.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15eb4eca47d36ec3f78cde0a3a2ee24cf05ca7396ef808dda2c0ddad7c2bde67"}, + {file = "numpy-2.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b83e16a5511d1b1f8a88cbabb1a6f6a499f82c062a4251892d9ad5d609863fb7"}, + {file = "numpy-2.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f87fec1f9bc1efd23f4227becff04bd0e979e23ca50cc92ec88b38489db3b55"}, + {file = "numpy-2.0.1-cp311-cp311-win32.whl", hash = "sha256:36d3a9405fd7c511804dc56fc32974fa5533bdeb3cd1604d6b8ff1d292b819c4"}, + {file = "numpy-2.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:08458fbf403bff5e2b45f08eda195d4b0c9b35682311da5a5a0a0925b11b9bd8"}, + {file = "numpy-2.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6bf4e6f4a2a2e26655717a1983ef6324f2664d7011f6ef7482e8c0b3d51e82ac"}, + {file = "numpy-2.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6fddc5fe258d3328cd8e3d7d3e02234c5d70e01ebe377a6ab92adb14039cb4"}, + {file = "numpy-2.0.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:5daab361be6ddeb299a918a7c0864fa8618af66019138263247af405018b04e1"}, + {file = "numpy-2.0.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:ea2326a4dca88e4a274ba3a4405eb6c6467d3ffbd8c7d38632502eaae3820587"}, + {file = "numpy-2.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:529af13c5f4b7a932fb0e1911d3a75da204eff023ee5e0e79c1751564221a5c8"}, + {file = "numpy-2.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6790654cb13eab303d8402354fabd47472b24635700f631f041bd0b65e37298a"}, + {file = "numpy-2.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cbab9fc9c391700e3e1287666dfd82d8666d10e69a6c4a09ab97574c0b7ee0a7"}, + {file = "numpy-2.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:99d0d92a5e3613c33a5f01db206a33f8fdf3d71f2912b0de1739894668b7a93b"}, + {file = "numpy-2.0.1-cp312-cp312-win32.whl", hash = "sha256:173a00b9995f73b79eb0191129f2455f1e34c203f559dd118636858cc452a1bf"}, + {file = "numpy-2.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:bb2124fdc6e62baae159ebcfa368708867eb56806804d005860b6007388df171"}, + {file = "numpy-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bfc085b28d62ff4009364e7ca34b80a9a080cbd97c2c0630bb5f7f770dae9414"}, + {file = "numpy-2.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8fae4ebbf95a179c1156fab0b142b74e4ba4204c87bde8d3d8b6f9c34c5825ef"}, + {file = "numpy-2.0.1-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:72dc22e9ec8f6eaa206deb1b1355eb2e253899d7347f5e2fae5f0af613741d06"}, + {file = "numpy-2.0.1-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:ec87f5f8aca726117a1c9b7083e7656a9d0d606eec7299cc067bb83d26f16e0c"}, + {file = "numpy-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f682ea61a88479d9498bf2091fdcd722b090724b08b31d63e022adc063bad59"}, + {file = "numpy-2.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8efc84f01c1cd7e34b3fb310183e72fcdf55293ee736d679b6d35b35d80bba26"}, + {file = "numpy-2.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3fdabe3e2a52bc4eff8dc7a5044342f8bd9f11ef0934fcd3289a788c0eb10018"}, + {file = "numpy-2.0.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:24a0e1befbfa14615b49ba9659d3d8818a0f4d8a1c5822af8696706fbda7310c"}, + {file = "numpy-2.0.1-cp39-cp39-win32.whl", hash = "sha256:f9cf5ea551aec449206954b075db819f52adc1638d46a6738253a712d553c7b4"}, + {file = "numpy-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:e9e81fa9017eaa416c056e5d9e71be93d05e2c3c2ab308d23307a8bc4443c368"}, + {file = "numpy-2.0.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:61728fba1e464f789b11deb78a57805c70b2ed02343560456190d0501ba37b0f"}, + {file = "numpy-2.0.1-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:12f5d865d60fb9734e60a60f1d5afa6d962d8d4467c120a1c0cda6eb2964437d"}, + {file = "numpy-2.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eacf3291e263d5a67d8c1a581a8ebbcfd6447204ef58828caf69a5e3e8c75990"}, + {file = "numpy-2.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2c3a346ae20cfd80b6cfd3e60dc179963ef2ea58da5ec074fd3d9e7a1e7ba97f"}, + {file = "numpy-2.0.1.tar.gz", hash = "sha256:485b87235796410c3519a699cfe1faab097e509e90ebb05dcd098db2ae87e7b3"}, ] [[package]] @@ -1730,6 +1794,17 @@ files = [ [package.extras] testing = ["pytest", "setuptools", "twine", "wheel"] +[[package]] +name = "pycparser" +version = "2.22" +description = "C parser in Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, + {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, +] + [[package]] name = "pydantic" version = "2.8.2" @@ -1882,6 +1957,32 @@ pyyaml = "*" [package.extras] extra = ["pygments (>=2.12)"] +[[package]] +name = "pynacl" +version = "1.5.0" +description = "Python binding to the Networking and Cryptography (NaCl) library" +optional = false +python-versions = ">=3.6" +files = [ + {file = "PyNaCl-1.5.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1"}, + {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92"}, + {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a36d4a9dda1f19ce6e03c9a784a2921a4b726b02e1c736600ca9c22029474394"}, + {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0c84947a22519e013607c9be43706dd42513f9e6ae5d39d3613ca1e142fba44d"}, + {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06b8f6fa7f5de8d5d2f7573fe8c863c051225a27b61e6860fd047b1775807858"}, + {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a422368fc821589c228f4c49438a368831cb5bbc0eab5ebe1d7fac9dded6567b"}, + {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:61f642bf2378713e2c2e1de73444a3778e5f0a38be6fee0fe532fe30060282ff"}, + {file = "PyNaCl-1.5.0-cp36-abi3-win32.whl", hash = "sha256:e46dae94e34b085175f8abb3b0aaa7da40767865ac82c928eeb9e57e1ea8a543"}, + {file = "PyNaCl-1.5.0-cp36-abi3-win_amd64.whl", hash = "sha256:20f42270d27e1b6a29f54032090b972d97f0a1b0948cc52392041ef7831fee93"}, + {file = "PyNaCl-1.5.0.tar.gz", hash = "sha256:8ac7448f09ab85811607bdd21ec2464495ac8b7c66d146bf545b0f08fb9220ba"}, +] + +[package.dependencies] +cffi = ">=1.4.1" + +[package.extras] +docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"] +tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"] + [[package]] name = "pyparsing" version = "3.1.2" @@ -3141,4 +3242,4 @@ test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", [metadata] lock-version = "2.0" python-versions = "~3.12" -content-hash = "fdbdeb73f326fcd8c71a81ad2d5d1a7bf10bd871fe7565276f0d9941f6604c96" +content-hash = "5efb4593a78251ed0aff910887e74ac779ffc71d2d967efa4dd66e9617af6f00" diff --git a/pyproject.toml b/pyproject.toml index 0815c59..82514c5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,16 +9,18 @@ exclude = ["**/test__*.py"] [tool.poetry.dependencies] python = "~3.12" -discord-py = "^2.4.0" -jishaku = "^2.5.2" -python-dotenv = "^1.0.1" -pyyaml = "^6.0.1" -redis = { extras = ["hiredis"], version = "^5.0.7" } -nltk = "^3.8.1" -pydantic = "^2.8.2" -redisvl = { extras = ["hiredis"], version = "^0.2.3" } -contractions = "^0.1.73" -sentence-transformers = "^3.0.1" +coloredlogs = "15.0.1" +contractions = "0.1.73" +discord-py = "2.4.0" +jishaku = "2.5.2" +nltk = "3.8.1" +pydantic = "2.8.2" +pynacl = "1.5.0" +python-dotenv = "1.0.1" +pyyaml = "6.0.1" +redis = { extras = ["hiredis"], version = "5.0.7" } +redisvl = { extras = ["hiredis"], version = "0.2.3" } +sentence-transformers = "3.0.1" [tool.poetry.dev-dependencies] commitizen = "3.27.0" From c8cc5700e33e310544b589c4eceb94ce7beae6a1 Mon Sep 17 00:00:00 2001 From: Uchechukwu Orji Date: Sun, 21 Jul 2024 20:17:13 +0100 Subject: [PATCH 058/168] feat: return messages based on similarity score (#30) Co-authored-by: thijsfranck --- .github/workflows/ci.yaml | 12 ++++ conftest.py | 33 ++++++++++ courageous_comets/client.py | 42 ++++++++++++ courageous_comets/enums.py | 9 +++ courageous_comets/models.py | 14 +++- courageous_comets/redis/helpers.py | 1 + courageous_comets/redis/messages.py | 98 ++++++++++++++++++++++++++++ courageous_comets/test__redis.py | 78 ++++++++++++++++++++++ courageous_comets/test__sentiment.py | 11 ++-- courageous_comets/vectorizer.py | 29 ++++++++ 10 files changed, 321 insertions(+), 6 deletions(-) create mode 100644 courageous_comets/enums.py create mode 100644 courageous_comets/test__redis.py create mode 100644 courageous_comets/vectorizer.py diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 9d58477..e69c725 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -12,6 +12,18 @@ jobs: ci: runs-on: ubuntu-latest + services: + redis: + image: redis/redis-stack:7.2.0-v11 + ports: + - 6379:6379 + # Set health checks to wait until redis has started + options: >- + --health-cmd "redis-cli ping" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + env: BOT_CONFIG_PATH: ${{ github.workspace }}/application.yaml DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }} diff --git a/conftest.py b/conftest.py index 192181e..0111e93 100644 --- a/conftest.py +++ b/conftest.py @@ -1,10 +1,15 @@ +from collections.abc import AsyncGenerator from pathlib import Path import pytest import yaml +from redis.asyncio import Redis +from courageous_comets import settings from courageous_comets.nltk import init_nltk +from courageous_comets.redis.helpers import init_redis from courageous_comets.transformers import init_transformers +from courageous_comets.vectorizer import Vectorizer @pytest.fixture(scope="session") @@ -26,3 +31,31 @@ async def _load_transformers(application_config: dict) -> None: """Load the transformers for testing.""" transformers = application_config.get("transformers", []) await init_transformers(transformers) + + +@pytest.fixture(scope="session", autouse=True) +def _patch_redis_keys_prefix() -> None: + """Set the REDIS_KEYS_PREFIX for testing.""" + settings.REDIS_KEYS_PREFIX = settings.REDIS_KEYS_PREFIX + "_test" + + +@pytest.fixture() +async def redis() -> AsyncGenerator[Redis, None]: + """Acquire a connection to the Redis database with teardown.""" + redis = await init_redis() + + yield redis + + # Delete any keys that were created during the test + keys_to_delete = [key async for key in redis.scan_iter(f"{settings.REDIS_KEYS_PREFIX}:*")] + + if keys_to_delete: + await redis.delete(*keys_to_delete) + + await redis.aclose() + + +@pytest.fixture(scope="session") +def vectorizer() -> Vectorizer: + """Set up the vectorizer for encoding messages.""" + return Vectorizer() diff --git a/courageous_comets/client.py b/courageous_comets/client.py index ba1dffb..4373009 100644 --- a/courageous_comets/client.py +++ b/courageous_comets/client.py @@ -9,9 +9,13 @@ from discord.ext import commands from courageous_comets import settings +from courageous_comets.exceptions import CourageousCometsError +from courageous_comets.models import VectorizedMessage from courageous_comets.nltk import init_nltk from courageous_comets.redis import init_redis +from courageous_comets.redis import messages as redis_messages from courageous_comets.transformers import init_transformers +from courageous_comets.vectorizer import Vectorizer logger = logging.getLogger(__name__) @@ -31,6 +35,7 @@ class CourageousCometsBot(commands.Bot): ---------- redis : redis.asyncio.Redis | None The Redis connection instance for the bot, or `None` if not connected. + vectorizer: Vectorizer | None """ def __init__(self) -> None: @@ -39,6 +44,7 @@ def __init__(self) -> None: intents=intents, ) self.redis = None + self.vectorizer: Vectorizer | None = None @override async def close(self) -> None: @@ -72,6 +78,7 @@ async def setup_hook(self) -> None: - Connect to Redis. - Load the NLTK resources. - Load the transformers. + - Set up the vectorizer. - Load the cogs. """ logger.info("Initializing the Discord client...") @@ -84,6 +91,8 @@ async def setup_hook(self) -> None: transformers = CONFIG.get("transformers", []) await init_transformers(transformers) + self.vectorizer = Vectorizer() + cogs = CONFIG.get("cogs", []) await self.load_cogs(cogs) @@ -98,6 +107,39 @@ async def load_cogs(self, cogs: list[str]) -> None: except commands.ExtensionError as e: logger.exception("Failed to load cog %s", cog, exc_info=e) + async def save_message(self, message: discord.Message) -> str: + """Save a message on Redis. + + Parameters + ---------- + redis: Redis + The Redis connection instance. + message : discord.Message + The message to save. + + Returns + ------- + str + The key to the data on Redis. + """ + if self.redis is None or self.vectorizer is None: + error_message = "Redis and Vectorizer not initialized." + raise CourageousCometsError(error_message) + + embedding = await self.vectorizer.embed(message.content) + return await redis_messages.save_message( + self.redis, + VectorizedMessage( + user_id=str(message.author.id), + message_id=str(message.id), + channel_id=str(message.channel.id), + guild_id=message.guild.id, # pyright: ignore + content=message.content, + timestamp=message.created_at, + embedding=embedding, + ), + ) + bot = CourageousCometsBot() diff --git a/courageous_comets/enums.py b/courageous_comets/enums.py new file mode 100644 index 0000000..56005a9 --- /dev/null +++ b/courageous_comets/enums.py @@ -0,0 +1,9 @@ +import enum + + +class StatisticScopeEnum(enum.Enum): + """Scope of statistics results to fetch.""" + + GUILD = enum.auto() + CHANNEL = enum.auto() + USER = enum.auto() diff --git a/courageous_comets/models.py b/courageous_comets/models.py index 8bbb36a..52016a5 100644 --- a/courageous_comets/models.py +++ b/courageous_comets/models.py @@ -35,8 +35,6 @@ class Message(BaseModel): The ID of the user who sent the message. content : str The content of the message. - embedding : bytes - The embedding of the message. """ message_id: str @@ -45,6 +43,18 @@ class Message(BaseModel): timestamp: UnixTimestamp user_id: str content: str + + +class VectorizedMessage(Message): + """Message with vector embedding of content. + + Attributes + ---------- + embedding : bytes + The embedding of the content. + embedding: bytes + """ + embedding: bytes diff --git a/courageous_comets/redis/helpers.py b/courageous_comets/redis/helpers.py index 16c322a..3a95305 100644 --- a/courageous_comets/redis/helpers.py +++ b/courageous_comets/redis/helpers.py @@ -43,6 +43,7 @@ async def init_redis() -> redis.Redis: host=settings.REDIS_HOST, port=settings.REDIS_PORT, password=settings.REDIS_PASSWORD, + decode_responses=True, ) try: diff --git a/courageous_comets/redis/messages.py b/courageous_comets/redis/messages.py index 2f4581b..201b76b 100644 --- a/courageous_comets/redis/messages.py +++ b/courageous_comets/redis/messages.py @@ -1,5 +1,11 @@ from redis.asyncio import Redis +from redisvl.index import AsyncSearchIndex +from redisvl.query import VectorQuery +from redisvl.query.filter import Tag +from courageous_comets import models +from courageous_comets.enums import StatisticScopeEnum +from courageous_comets.redis import schema from courageous_comets.redis.keys import key_schema @@ -37,3 +43,95 @@ async def update_message_tokens( # instructions are evaluated in the same order, thus, the return values are # in the same order. return dict(zip(words.keys(), new_frequencies, strict=False)) + + +async def save_message( + redis: Redis, + message: models.VectorizedMessage, +) -> str: + """Save a message on Redis. + + Parameters + ---------- + redis : Redis + The Redis connection instance. + message : models.VectorizedMessage + The message to save + + Returns + ------- + str + The key to the data on Redis. + """ + index = AsyncSearchIndex.from_dict(schema.MESSAGE_SCHEMA) + index.set_client(redis) + return ( + await index.load( + [message.model_dump()], + keys=[ + key_schema.guild_messages( + message.guild_id, # pyright: ignore + ), + ], + ) + )[0] + + +async def get_similar_messages( + redis: Redis, + message: models.Message, + embedding: bytes, + limit: int = 10, + scope: StatisticScopeEnum = StatisticScopeEnum.GUILD, +) -> list[models.Message]: + """ + Get the messages with similar semantics to the provided message. + + Parameters + ---------- + redis : Redis + The Redis connection instance. + message : models.Message + The comparison message. + embedding: bytes + The vector embedding of the message. + limit : + The number of similar messages to fetch. + scope : enums.StatisticScopeEnum + The scope to limit the search. + + Returns + ------- + list[models.Message] + The messages that are similar semantically. + """ + index = AsyncSearchIndex.from_dict(schema.MESSAGE_SCHEMA) + index.set_client(redis) + # Determine the scope to filter the search + match scope: + case StatisticScopeEnum.GUILD: + filter_expression = Tag("guild_id") == message.guild_id + case StatisticScopeEnum.CHANNEL: + filter_expression = Tag("channel_id") == message.channel_id + case StatisticScopeEnum.USER: + filter_expression = Tag("user_id") == message.user_id + case _: + error_message = f"Unhandled scope: {scope!r}" + raise ValueError(error_message) + + query = VectorQuery( + vector=embedding, + vector_field_name="embedding", + return_fields=[ + "message_id", + "channel_id", + "user_id", + "content", + "guild_id", + "timestamp", + ], + filter_expression=filter_expression, + num_results=limit, + ) + results = await index.query(query) + return [models.Message.model_validate(result) for result in results] diff --git a/courageous_comets/test__redis.py b/courageous_comets/test__redis.py new file mode 100644 index 0000000..7bfcc0b --- /dev/null +++ b/courageous_comets/test__redis.py @@ -0,0 +1,78 @@ +import datetime + +import pytest +import pytest_asyncio +from redis.asyncio import Redis + +from courageous_comets import models +from courageous_comets.redis.keys import key_schema +from courageous_comets.redis.messages import get_similar_messages, save_message +from courageous_comets.vectorizer import Vectorizer + + +@pytest.fixture(scope="session") +def message() -> models.Message: + """Fixture that sets up a message. + + Returns + ------- + models.Message + A Redis model of a Discord message + """ + return models.Message( + message_id="1", + channel_id="1", + guild_id="1", + timestamp=datetime.datetime.fromtimestamp(0, datetime.UTC), + user_id="1", + content="The quick brown fox jumps over the lazy dog.", + ) + + +@pytest_asyncio.fixture(scope="session") +async def vectorized_message( + message: models.Message, + vectorizer: Vectorizer, +) -> models.VectorizedMessage: + """Fixture that creates an embedding vector of contents of message. + + Returns + ------- + models.VectorizedMessage + A message with embedding vector + """ + embedding = await vectorizer.embed(message.content) + return models.VectorizedMessage(**message.model_dump(), embedding=embedding) + + +async def test__save_message( + redis: Redis, + vectorized_message: models.VectorizedMessage, +) -> None: + """ + Tests whether the save_mesage function stores the message on Redis. + + Asserts + ------- + - The returned key is the same as the one constructed by the key_schema + """ + key = await save_message(redis, vectorized_message) + assert key == key_schema.guild_messages(int(vectorized_message.guild_id)) + + +async def test__get_similar_message( + redis: Redis, + message: models.Message, + vectorized_message: models.VectorizedMessage, +) -> None: + """ + Tests that the same message is returned by the get_similar_messages function. + + Asserts + ------- + - The returned message is the same message whose embedding vector was used + """ + await save_message(redis, vectorized_message) + messages = await get_similar_messages(redis, message, vectorized_message.embedding) + assert len(messages) == 1 + assert messages[0].model_dump() == message.model_dump() diff --git a/courageous_comets/test__sentiment.py b/courageous_comets/test__sentiment.py index bf7f463..75ac58a 100644 --- a/courageous_comets/test__sentiment.py +++ b/courageous_comets/test__sentiment.py @@ -4,6 +4,7 @@ from pytest_mock import MockerFixture, MockType from redis.asyncio import Redis +from courageous_comets import settings from courageous_comets.models import SentimentResult from .sentiment import ( @@ -24,7 +25,9 @@ def redis(mocker: MockerFixture) -> MockerFixture: return mock -def test__calculate_sentiment_analyzes_sentiment_of_given_text(mocker: MockerFixture) -> None: +def test__calculate_sentiment_analyzes_sentiment_of_given_text( + mocker: MockerFixture, +) -> None: """ Test whether the sentiment calculation analyzes the sentiment of the given text. @@ -91,7 +94,7 @@ async def test__store_sentiment_calculates_and_stores_sentiment( await store_sentiment(message, redis) redis.hset.assert_awaited_with( - "courageous_comets:1:1:1:1:sentiment", + f"{settings.REDIS_KEYS_PREFIX}:1:1:1:1:sentiment", mapping={ "neg": mocker.ANY, "neu": mocker.ANY, @@ -153,7 +156,7 @@ async def test__get_sentiment_retrieves_sentiment_from_redis( ------- - The sentiment is retrieved from the database. """ - key = "courageous_comets:1:1:1:1:sentiment" + key = f"{settings.REDIS_KEYS_PREFIX}:1:1:1:1:sentiment" redis.hmget.return_value = ["1.0", "1.0", "1.0", "1.0"] expected = SentimentResult( @@ -180,7 +183,7 @@ async def test__get_sentiment_handles_missing_sentiment( ------- - The function returns default sentiment values when the sentiment is missing. """ - key = "courageous_comets:1:1:1:1:sentiment" + key = f"{settings.REDIS_KEYS_PREFIX}:1:1:1:1:sentiment" redis.hmget.return_value = [None, None, None, None] expected = SentimentResult( diff --git a/courageous_comets/vectorizer.py b/courageous_comets/vectorizer.py new file mode 100644 index 0000000..8fd9929 --- /dev/null +++ b/courageous_comets/vectorizer.py @@ -0,0 +1,29 @@ +import asyncio + +from redisvl.utils.vectorize.text.huggingface import HFTextVectorizer + + +class Vectorizer: + """Convert a chunk of text to vector embedding. + + This class uses the Hugging Face sentence transformer to create vector + embeddings. + + Attributes + ---------- + transformer: huggingface.HFTextVectorizer + The Hugging Face sentence transformer + """ + + def __init__(self) -> None: + self.transformer = HFTextVectorizer( + model="sentence-transformers/all-MiniLM-L6-v2", + ) + + async def embed(self, message: str) -> bytes: + """Create a vector embedding of message.""" + return await asyncio.to_thread( + self.transformer.embed, # pyright: ignore + message, + as_buffer=True, + ) From c133fb7b628d2f89ce9e8b8c3270a1248f263c39 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Mon, 22 Jul 2024 08:11:28 +0200 Subject: [PATCH 059/168] test: move tests into separate folder and add integration test example (#31) --- pyproject.toml | 3 +- tests/__init__.py | 0 conftest.py => tests/conftest.py | 19 ----------- tests/courageous_comets/__init__.py | 0 .../courageous_comets}/test__client.py | 2 +- .../courageous_comets}/test__sentiment.py | 3 +- .../courageous_comets}/test__words.py | 0 tests/integrations/__init__.py | 0 tests/integrations/conftest.py | 33 +++++++++++++++++++ tests/integrations/test__bot.py | 30 +++++++++++++++++ .../integrations}/test__redis.py | 0 11 files changed, 67 insertions(+), 23 deletions(-) create mode 100644 tests/__init__.py rename conftest.py => tests/conftest.py (70%) create mode 100644 tests/courageous_comets/__init__.py rename {courageous_comets => tests/courageous_comets}/test__client.py (98%) rename {courageous_comets => tests/courageous_comets}/test__sentiment.py (99%) rename {courageous_comets => tests/courageous_comets}/test__words.py (100%) create mode 100644 tests/integrations/__init__.py create mode 100644 tests/integrations/conftest.py create mode 100644 tests/integrations/test__bot.py rename {courageous_comets => tests/integrations}/test__redis.py (100%) diff --git a/pyproject.toml b/pyproject.toml index 82514c5..d143797 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,6 @@ authors = ["Courageous Comets"] description = "" readme = "docs/README.md" license = "MIT" -exclude = ["**/test__*.py"] [tool.poetry.dependencies] python = "~3.12" @@ -42,10 +41,12 @@ reportUnnecessaryTypeIgnoreComment = "error" [tool.pytest.ini_options] asyncio_mode = "auto" +# Ignore warnings from dependencies filterwarnings = [ "ignore:'audioop' is deprecated and slated for removal in Python 3.13", "ignore:invalid escape sequence *", "ignore:`resume_download` is deprecated and will be removed *", + "ignore:datetime.datetime.utcnow", ] [tool.ruff] diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/conftest.py b/tests/conftest.py similarity index 70% rename from conftest.py rename to tests/conftest.py index 0111e93..dbcbdca 100644 --- a/conftest.py +++ b/tests/conftest.py @@ -1,13 +1,10 @@ -from collections.abc import AsyncGenerator from pathlib import Path import pytest import yaml -from redis.asyncio import Redis from courageous_comets import settings from courageous_comets.nltk import init_nltk -from courageous_comets.redis.helpers import init_redis from courageous_comets.transformers import init_transformers from courageous_comets.vectorizer import Vectorizer @@ -39,22 +36,6 @@ def _patch_redis_keys_prefix() -> None: settings.REDIS_KEYS_PREFIX = settings.REDIS_KEYS_PREFIX + "_test" -@pytest.fixture() -async def redis() -> AsyncGenerator[Redis, None]: - """Acquire a connection to the Redis database with teardown.""" - redis = await init_redis() - - yield redis - - # Delete any keys that were created during the test - keys_to_delete = [key async for key in redis.scan_iter(f"{settings.REDIS_KEYS_PREFIX}:*")] - - if keys_to_delete: - await redis.delete(*keys_to_delete) - - await redis.aclose() - - @pytest.fixture(scope="session") def vectorizer() -> Vectorizer: """Set up the vectorizer for encoding messages.""" diff --git a/tests/courageous_comets/__init__.py b/tests/courageous_comets/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/courageous_comets/test__client.py b/tests/courageous_comets/test__client.py similarity index 98% rename from courageous_comets/test__client.py rename to tests/courageous_comets/test__client.py index e05bb6f..0f2f9a3 100644 --- a/courageous_comets/test__client.py +++ b/tests/courageous_comets/test__client.py @@ -5,7 +5,7 @@ from discord.ext import commands from pytest_mock import MockerFixture, MockType -from .client import CourageousCometsBot, intents, logger, sync +from courageous_comets.client import CourageousCometsBot, intents, logger, sync class MockAsyncContextManager: diff --git a/courageous_comets/test__sentiment.py b/tests/courageous_comets/test__sentiment.py similarity index 99% rename from courageous_comets/test__sentiment.py rename to tests/courageous_comets/test__sentiment.py index 75ac58a..f498a06 100644 --- a/courageous_comets/test__sentiment.py +++ b/tests/courageous_comets/test__sentiment.py @@ -6,8 +6,7 @@ from courageous_comets import settings from courageous_comets.models import SentimentResult - -from .sentiment import ( +from courageous_comets.sentiment import ( MAX_MESSAGE_LENGTH, calculate_sentiment, get_sentiment, diff --git a/courageous_comets/test__words.py b/tests/courageous_comets/test__words.py similarity index 100% rename from courageous_comets/test__words.py rename to tests/courageous_comets/test__words.py diff --git a/tests/integrations/__init__.py b/tests/integrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/integrations/conftest.py b/tests/integrations/conftest.py new file mode 100644 index 0000000..9871600 --- /dev/null +++ b/tests/integrations/conftest.py @@ -0,0 +1,33 @@ +from collections.abc import AsyncGenerator +from typing import cast + +import pytest +from redis.asyncio import Redis + +from courageous_comets import settings +from courageous_comets.client import CourageousCometsBot + + +@pytest.fixture() +async def bot() -> AsyncGenerator[CourageousCometsBot, None]: + """Fixture that sets up and tears down the CourageousCometsBot instance.""" + instance = CourageousCometsBot() + await instance.setup_hook() + yield instance + await instance.close() + + +@pytest.fixture() +async def redis(bot: CourageousCometsBot) -> AsyncGenerator[Redis, None]: + """Acquire a connection to the Redis database with teardown.""" + instance = cast(Redis, bot.redis) + + yield instance + + # Delete any keys that were created during the test + keys_to_delete = [key async for key in instance.scan_iter(f"{settings.REDIS_KEYS_PREFIX}:*")] + + if keys_to_delete: + await instance.delete(*keys_to_delete) + + await instance.aclose() diff --git a/tests/integrations/test__bot.py b/tests/integrations/test__bot.py new file mode 100644 index 0000000..de5f448 --- /dev/null +++ b/tests/integrations/test__bot.py @@ -0,0 +1,30 @@ +import discord +from pytest_mock import MockerFixture +from redis.asyncio import Redis + +from courageous_comets.client import CourageousCometsBot +from courageous_comets.redis.keys import key_schema + + +async def test__save_message(bot: CourageousCometsBot, redis: Redis, mocker: MockerFixture) -> None: + """ + Test whether messages received by the bot are saved to Redis. + + Asserts + ------- + - The message is saved to Redis. + """ + message = mocker.MagicMock(spec=discord.Message) + + message.content = "The quick brown fox jumps over the lazy dog." + message.id = 1 + message.author.id = 1 + message.channel.id = 1 + message.guild.id = 1 + + await bot.save_message(message) + + key = key_schema.guild_messages(1) + key_exists = await redis.exists(key) + + assert key_exists diff --git a/courageous_comets/test__redis.py b/tests/integrations/test__redis.py similarity index 100% rename from courageous_comets/test__redis.py rename to tests/integrations/test__redis.py From f48649f22a88a81c06a9986a986f3d8754d38ebf Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Mon, 22 Jul 2024 09:58:42 +0200 Subject: [PATCH 060/168] feat: add messages cog (#32) --- application.yaml | 1 + courageous_comets/client.py | 64 ++------------ courageous_comets/cogs/messages.py | 87 +++++++++++++++++++ .../{test__bot.py => test__messages.py} | 20 ++++- 4 files changed, 113 insertions(+), 59 deletions(-) create mode 100644 courageous_comets/cogs/messages.py rename tests/integrations/{test__bot.py => test__messages.py} (53%) diff --git a/application.yaml b/application.yaml index c557940..cfe10cc 100644 --- a/application.yaml +++ b/application.yaml @@ -1,4 +1,5 @@ cogs: + - courageous_comets.cogs.messages - courageous_comets.cogs.ping - jishaku nltk: diff --git a/courageous_comets/client.py b/courageous_comets/client.py index 4373009..ab1ebaf 100644 --- a/courageous_comets/client.py +++ b/courageous_comets/client.py @@ -9,13 +9,8 @@ from discord.ext import commands from courageous_comets import settings -from courageous_comets.exceptions import CourageousCometsError -from courageous_comets.models import VectorizedMessage from courageous_comets.nltk import init_nltk from courageous_comets.redis import init_redis -from courageous_comets.redis import messages as redis_messages -from courageous_comets.transformers import init_transformers -from courageous_comets.vectorizer import Vectorizer logger = logging.getLogger(__name__) @@ -35,7 +30,6 @@ class CourageousCometsBot(commands.Bot): ---------- redis : redis.asyncio.Redis | None The Redis connection instance for the bot, or `None` if not connected. - vectorizer: Vectorizer | None """ def __init__(self) -> None: @@ -44,7 +38,6 @@ def __init__(self) -> None: intents=intents, ) self.redis = None - self.vectorizer: Vectorizer | None = None @override async def close(self) -> None: @@ -65,6 +58,15 @@ async def close(self) -> None: logger.info("Application shutdown complete. Goodbye! 👋") + async def load_cogs(self, cogs: list[str]) -> None: + """Load all given cogs.""" + for cog in cogs: + try: + await bot.load_extension(cog) + logger.debug("Loaded cog %s", cog) + except commands.ExtensionError as e: + logger.exception("Failed to load cog %s", cog, exc_info=e) + async def on_ready(self) -> None: """Log a message when the bot is ready.""" logger.info("Logged in as %s", self.user) @@ -77,7 +79,6 @@ async def setup_hook(self) -> None: - Connect to Redis. - Load the NLTK resources. - - Load the transformers. - Set up the vectorizer. - Load the cogs. """ @@ -88,58 +89,11 @@ async def setup_hook(self) -> None: nltk_resources = CONFIG.get("nltk", []) await init_nltk(nltk_resources) - transformers = CONFIG.get("transformers", []) - await init_transformers(transformers) - - self.vectorizer = Vectorizer() - cogs = CONFIG.get("cogs", []) await self.load_cogs(cogs) logger.info("Initialization complete 🚀") - async def load_cogs(self, cogs: list[str]) -> None: - """Load all given cogs.""" - for cog in cogs: - try: - await bot.load_extension(cog) - logger.debug("Loaded cog %s", cog) - except commands.ExtensionError as e: - logger.exception("Failed to load cog %s", cog, exc_info=e) - - async def save_message(self, message: discord.Message) -> str: - """Save a message on Redis. - - Parameters - ---------- - redis: Redis - The Redis connection instance. - message : discord.Message - The message to save. - - Returns - ------- - str - The key to the data on Redis. - """ - if self.redis is None or self.vectorizer is None: - error_message = "Redis and Vectorizer not initialized." - raise CourageousCometsError(error_message) - - embedding = await self.vectorizer.embed(message.content) - return await redis_messages.save_message( - self.redis, - VectorizedMessage( - user_id=str(message.author.id), - message_id=str(message.id), - channel_id=str(message.channel.id), - guild_id=message.guild.id, # pyright: ignore - content=message.content, - timestamp=message.created_at, - embedding=embedding, - ), - ) - bot = CourageousCometsBot() diff --git a/courageous_comets/cogs/messages.py b/courageous_comets/cogs/messages.py new file mode 100644 index 0000000..7d8ae63 --- /dev/null +++ b/courageous_comets/cogs/messages.py @@ -0,0 +1,87 @@ +import logging + +import discord +from discord.ext import commands +from redis.asyncio import Redis + +from courageous_comets.client import CourageousCometsBot +from courageous_comets.models import VectorizedMessage +from courageous_comets.redis import messages +from courageous_comets.vectorizer import Vectorizer + +logger = logging.getLogger(__name__) + + +class Messages(commands.Cog): + """A cog that listens for messages from discord.""" + + def __init__(self, bot: CourageousCometsBot) -> None: + self.bot = bot + self.vectorizer = Vectorizer() + + @commands.Cog.listener(name="on_message") + async def on_message(self, message: discord.Message) -> None: + """ + Save a message to the database. + + If the bot is not connected to Redis, this method does nothing. + + Parameters + ---------- + message : discord.Message + The message to save. + """ + if not self.bot.redis: + return + + await save_message(message, self.bot.redis, self.vectorizer) + + +async def setup(bot: CourageousCometsBot) -> None: + """ + Load the cog. + + Parameters + ---------- + bot : CourageousCometsBot + The bot instance. + """ + await bot.add_cog(Messages(bot)) + + +async def save_message(message: discord.Message, redis: Redis, vectorizer: Vectorizer) -> None: + """ + Save a message on Redis. + + Parameters + ---------- + message : discord.Message + The message to save. + redis: Redis + The Redis connection instance. + vectorizer : Vectorizer + The vectorizer to use to embed the message. + + Returns + ------- + str + The key to the data on Redis. + """ + if not message.guild: + return logger.debug("Ignoring message %s because it's not in a guild", message.id) + + embedding = await vectorizer.embed(message.content) + + vectorized_message = VectorizedMessage( + user_id=str(message.author.id), + message_id=str(message.id), + channel_id=str(message.channel.id), + guild_id=str(message.guild.id), + content=message.content, + timestamp=message.created_at, + embedding=embedding, + ) + + key = await messages.save_message(redis, vectorized_message) + + return logger.info("Saved message %s to Redis with key %s", message.id, key) diff --git a/tests/integrations/test__bot.py b/tests/integrations/test__messages.py similarity index 53% rename from tests/integrations/test__bot.py rename to tests/integrations/test__messages.py index de5f448..e45efab 100644 --- a/tests/integrations/test__bot.py +++ b/tests/integrations/test__messages.py @@ -1,18 +1,30 @@ import discord +import pytest from pytest_mock import MockerFixture from redis.asyncio import Redis from courageous_comets.client import CourageousCometsBot +from courageous_comets.cogs.messages import Messages from courageous_comets.redis.keys import key_schema -async def test__save_message(bot: CourageousCometsBot, redis: Redis, mocker: MockerFixture) -> None: +@pytest.fixture() +def cog(bot: CourageousCometsBot) -> Messages: + """Return an instance of the Messages cog.""" + return Messages(bot) + + +async def test__messages_on_message__message_saved_to_redis( + cog: Messages, + redis: Redis, + mocker: MockerFixture, +) -> None: """ - Test whether messages received by the bot are saved to Redis. + Test whether messages received by the cog are saved to Redis. Asserts ------- - - The message is saved to Redis. + - The message key is present in Redis. """ message = mocker.MagicMock(spec=discord.Message) @@ -22,7 +34,7 @@ async def test__save_message(bot: CourageousCometsBot, redis: Redis, mocker: Moc message.channel.id = 1 message.guild.id = 1 - await bot.save_message(message) + await cog.on_message(message) key = key_schema.guild_messages(1) key_exists = await redis.exists(key) From c64406e398d749bc8a0956e78ba4b6da2f3580df Mon Sep 17 00:00:00 2001 From: Uchechukwu Orji Date: Mon, 22 Jul 2024 11:45:40 +0100 Subject: [PATCH 061/168] fix: save messages with message_id on redis (#33) --- courageous_comets/redis/keys.py | 8 ++++---- courageous_comets/redis/messages.py | 3 ++- tests/courageous_comets/test__sentiment.py | 8 ++++---- tests/integrations/test__messages.py | 2 +- tests/integrations/test__redis.py | 5 ++++- 5 files changed, 15 insertions(+), 11 deletions(-) diff --git a/courageous_comets/redis/keys.py b/courageous_comets/redis/keys.py index 1a8a419..85b2fd7 100644 --- a/courageous_comets/redis/keys.py +++ b/courageous_comets/redis/keys.py @@ -42,12 +42,12 @@ class KeySchema: """ @prefix_key - def guild_messages(self, guild_id: int) -> str: + def guild_messages(self, *, guild_id: int | str, message_id: int | str) -> str: """Key to messages for a Discord guild. Redis type: hash """ - return f"{guild_id}:messages" + return f"messages:{guild_id}:{message_id}" @prefix_key def guild_message_tokens(self, guild_id: int) -> str: @@ -55,7 +55,7 @@ def guild_message_tokens(self, guild_id: int) -> str: Redis type: hash """ - return f"{guild_id}:messages:tokens" + return f"messages:tokens:{guild_id}" @prefix_key def sentiment_tokens( @@ -70,7 +70,7 @@ def sentiment_tokens( Redis type: hash """ - return f"{guild_id}:{channel_id}:{user_id}:{message_id}:sentiment" + return f"sentiment:{guild_id}:{channel_id}:{user_id}:{message_id}" key_schema = KeySchema() diff --git a/courageous_comets/redis/messages.py b/courageous_comets/redis/messages.py index 201b76b..3e63e0f 100644 --- a/courageous_comets/redis/messages.py +++ b/courageous_comets/redis/messages.py @@ -70,7 +70,8 @@ async def save_message( [message.model_dump()], keys=[ key_schema.guild_messages( - message.guild_id, # pyright: ignore + guild_id=message.guild_id, + message_id=message.message_id, ), ], ) diff --git a/tests/courageous_comets/test__sentiment.py b/tests/courageous_comets/test__sentiment.py index f498a06..488fc5a 100644 --- a/tests/courageous_comets/test__sentiment.py +++ b/tests/courageous_comets/test__sentiment.py @@ -4,8 +4,8 @@ from pytest_mock import MockerFixture, MockType from redis.asyncio import Redis -from courageous_comets import settings from courageous_comets.models import SentimentResult +from courageous_comets.redis.keys import key_schema from courageous_comets.sentiment import ( MAX_MESSAGE_LENGTH, calculate_sentiment, @@ -93,7 +93,7 @@ async def test__store_sentiment_calculates_and_stores_sentiment( await store_sentiment(message, redis) redis.hset.assert_awaited_with( - f"{settings.REDIS_KEYS_PREFIX}:1:1:1:1:sentiment", + key_schema.sentiment_tokens(1, 1, 1, 1), mapping={ "neg": mocker.ANY, "neu": mocker.ANY, @@ -155,7 +155,7 @@ async def test__get_sentiment_retrieves_sentiment_from_redis( ------- - The sentiment is retrieved from the database. """ - key = f"{settings.REDIS_KEYS_PREFIX}:1:1:1:1:sentiment" + key = key_schema.sentiment_tokens(1, 1, 1, 1) redis.hmget.return_value = ["1.0", "1.0", "1.0", "1.0"] expected = SentimentResult( @@ -182,7 +182,7 @@ async def test__get_sentiment_handles_missing_sentiment( ------- - The function returns default sentiment values when the sentiment is missing. """ - key = f"{settings.REDIS_KEYS_PREFIX}:1:1:1:1:sentiment" + key = key_schema.sentiment_tokens(1, 1, 1, 1) redis.hmget.return_value = [None, None, None, None] expected = SentimentResult( diff --git a/tests/integrations/test__messages.py b/tests/integrations/test__messages.py index e45efab..ace9ce2 100644 --- a/tests/integrations/test__messages.py +++ b/tests/integrations/test__messages.py @@ -36,7 +36,7 @@ async def test__messages_on_message__message_saved_to_redis( await cog.on_message(message) - key = key_schema.guild_messages(1) + key = key_schema.guild_messages(guild_id=1, message_id=1) key_exists = await redis.exists(key) assert key_exists diff --git a/tests/integrations/test__redis.py b/tests/integrations/test__redis.py index 7bfcc0b..d72c9dc 100644 --- a/tests/integrations/test__redis.py +++ b/tests/integrations/test__redis.py @@ -57,7 +57,10 @@ async def test__save_message( - The returned key is the same as the one constructed by the key_schema """ key = await save_message(redis, vectorized_message) - assert key == key_schema.guild_messages(int(vectorized_message.guild_id)) + assert key == key_schema.guild_messages( + guild_id=vectorized_message.guild_id, + message_id=vectorized_message.message_id, + ) async def test__get_similar_message( From afde5e273902422b9d64419dda60f6f0287ffd12 Mon Sep 17 00:00:00 2001 From: isaa_ctaylor Date: Mon, 22 Jul 2024 12:10:25 +0100 Subject: [PATCH 062/168] refactor: include save_message in Messages cog (#34) Co-authored-by: thijsfranck --- courageous_comets/client.py | 4 +- courageous_comets/cogs/messages.py | 87 +++++++++++++++--------------- 2 files changed, 46 insertions(+), 45 deletions(-) diff --git a/courageous_comets/client.py b/courageous_comets/client.py index ab1ebaf..9e3447c 100644 --- a/courageous_comets/client.py +++ b/courageous_comets/client.py @@ -7,6 +7,7 @@ import yaml from discord import Intents from discord.ext import commands +from redis.asyncio import Redis from courageous_comets import settings from courageous_comets.nltk import init_nltk @@ -32,12 +33,13 @@ class CourageousCometsBot(commands.Bot): The Redis connection instance for the bot, or `None` if not connected. """ + redis: Redis | None = None + def __init__(self) -> None: super().__init__( command_prefix=commands.when_mentioned, intents=intents, ) - self.redis = None @override async def close(self) -> None: diff --git a/courageous_comets/cogs/messages.py b/courageous_comets/cogs/messages.py index 7d8ae63..794097d 100644 --- a/courageous_comets/cogs/messages.py +++ b/courageous_comets/cogs/messages.py @@ -2,7 +2,6 @@ import discord from discord.ext import commands -from redis.asyncio import Redis from courageous_comets.client import CourageousCometsBot from courageous_comets.models import VectorizedMessage @@ -22,9 +21,20 @@ def __init__(self, bot: CourageousCometsBot) -> None: @commands.Cog.listener(name="on_message") async def on_message(self, message: discord.Message) -> None: """ - Save a message to the database. + When a message is received, save it to Redis. - If the bot is not connected to Redis, this method does nothing. + Parameters + ---------- + message : discord.Message + The message to save. + """ + await self.save_message(message) + + async def save_message(self, message: discord.Message) -> None: + """ + Save a message on Redis. + + Ignore messages that are not in a guild or if the bot is not connected to Redis. Parameters ---------- @@ -32,9 +42,36 @@ async def on_message(self, message: discord.Message) -> None: The message to save. """ if not self.bot.redis: - return - - await save_message(message, self.bot.redis, self.vectorizer) + return logger.warning( + "Ignoring message %s because the bot is not connected to Redis", + message.id, + ) + + if not message.guild: + return logger.debug( + "Ignoring message %s because it's not in a guild", + message.id, + ) + + embedding = await self.vectorizer.embed(message.content) + + vectorized_message = VectorizedMessage( + user_id=str(message.author.id), + message_id=str(message.id), + channel_id=str(message.channel.id), + guild_id=str(message.guild.id), + content=message.content, + timestamp=message.created_at, + embedding=embedding, + ) + + key = await messages.save_message(self.bot.redis, vectorized_message) + + return logger.info( + "Saved message %s to Redis with key %s", + message.id, + key, + ) async def setup(bot: CourageousCometsBot) -> None: @@ -47,41 +84,3 @@ async def setup(bot: CourageousCometsBot) -> None: The bot instance. """ await bot.add_cog(Messages(bot)) - - -async def save_message(message: discord.Message, redis: Redis, vectorizer: Vectorizer) -> None: - """ - Save a message on Redis. - - Parameters - ---------- - message : discord.Message - The message to save. - redis: Redis - The Redis connection instance. - vectorizer : Vectorizer - The vectorizer to use to embed the message. - - Returns - ------- - str - The key to the data on Redis. - """ - if not message.guild: - return logger.debug("Ignoring message %s because it's not in a guild", message.id) - - embedding = await vectorizer.embed(message.content) - - vectorized_message = VectorizedMessage( - user_id=str(message.author.id), - message_id=str(message.id), - channel_id=str(message.channel.id), - guild_id=str(message.guild.id), - content=message.content, - timestamp=message.created_at, - embedding=embedding, - ) - - key = await messages.save_message(redis, vectorized_message) - - return logger.info("Saved message %s to Redis with key %s", message.id, key) From 17c84108fd7d206b93dbc7725c5f946056b6679b Mon Sep 17 00:00:00 2001 From: Uchechukwu Orji Date: Mon, 22 Jul 2024 12:11:37 +0100 Subject: [PATCH 063/168] refactor: remove hfvectorizer (#35) --- courageous_comets/vectorizer.py | 19 +++++++++++-------- poetry.lock | 13 +------------ pyproject.toml | 1 + 3 files changed, 13 insertions(+), 20 deletions(-) diff --git a/courageous_comets/vectorizer.py b/courageous_comets/vectorizer.py index 8fd9929..7583532 100644 --- a/courageous_comets/vectorizer.py +++ b/courageous_comets/vectorizer.py @@ -1,6 +1,7 @@ import asyncio -from redisvl.utils.vectorize.text.huggingface import HFTextVectorizer +import numpy as np +from sentence_transformers import SentenceTransformer class Vectorizer: @@ -11,19 +12,21 @@ class Vectorizer: Attributes ---------- - transformer: huggingface.HFTextVectorizer - The Hugging Face sentence transformer + transformer: sentence_transformers.SentenceTransformer + The sentence transformer """ def __init__(self) -> None: - self.transformer = HFTextVectorizer( - model="sentence-transformers/all-MiniLM-L6-v2", + self.transformer = SentenceTransformer( + "sentence-transformers/all-MiniLM-L6-v2", ) async def embed(self, message: str) -> bytes: """Create a vector embedding of message.""" - return await asyncio.to_thread( - self.transformer.embed, # pyright: ignore + embedding = await asyncio.to_thread( + self.transformer.encode, message, - as_buffer=True, ) + return embedding.astype( # pyright: ignore + np.float32, + ).tobytes() diff --git a/poetry.lock b/poetry.lock index f7932f1..a4cf2e2 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2122,7 +2122,6 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, - {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -2130,16 +2129,8 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, - {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, - {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, - {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, - {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -2156,7 +2147,6 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, - {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -2164,7 +2154,6 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, - {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -3242,4 +3231,4 @@ test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", [metadata] lock-version = "2.0" python-versions = "~3.12" -content-hash = "5efb4593a78251ed0aff910887e74ac779ffc71d2d967efa4dd66e9617af6f00" +content-hash = "6f3cea24ee781437f22206b6c78dc82869aacc270306d0950c3771078af09904" diff --git a/pyproject.toml b/pyproject.toml index d143797..72a6b40 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,6 +20,7 @@ pyyaml = "6.0.1" redis = { extras = ["hiredis"], version = "5.0.7" } redisvl = { extras = ["hiredis"], version = "0.2.3" } sentence-transformers = "3.0.1" +numpy = "^2.0.1" [tool.poetry.dev-dependencies] commitizen = "3.27.0" From 0cfd27bc6b1b1f1f2c0419d36216880c1dc3c993 Mon Sep 17 00:00:00 2001 From: Uchechukwu Orji Date: Mon, 22 Jul 2024 15:53:00 +0100 Subject: [PATCH 064/168] feat: replace sentence_transformers with transformers library (#36) Co-authored-by: thijsfranck --- .devcontainer/devcontainer.json | 2 +- .github/workflows/ci.yaml | 18 +- .pre-commit-config.yaml | 12 + Dockerfile | 2 +- courageous_comets/cogs/messages.py | 2 +- courageous_comets/settings.py | 8 +- courageous_comets/transformers/helpers.py | 17 +- courageous_comets/vectorizer.py | 97 +++- docs/admin-guide/configuration.md | 24 +- poetry.lock | 557 ++++------------------ pyproject.toml | 10 +- tests/integrations/test__redis.py | 2 +- 12 files changed, 243 insertions(+), 508 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index c5d274a..d1e1aa8 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,12 +1,12 @@ { "containerEnv": { "BOT_CONFIG_PATH": "${containerWorkspaceFolder}/application.yaml", + "HF_HOME": "${containerWorkspaceFolder}/hf_data", "NLTK_DATA": "${containerWorkspaceFolder}/nltk_data", "POETRY_VIRTUALENVS_CREATE": "false", "REDIS_HOST": "localhost", "REDIS_PASSWORD": "redis", "REDIS_PORT": "6379", - "SENTENCE_TRANSFORMERS_HOME": "${containerWorkspaceFolder}/hf_data", "SOPS_AGE_KEY_FILE": "${containerWorkspaceFolder}/secrets/keys.txt" }, "customizations": { diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index e69c725..3803471 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -28,7 +28,7 @@ jobs: BOT_CONFIG_PATH: ${{ github.workspace }}/application.yaml DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }} NLTK_DATA: ${{ github.workspace }}/nltk_data - SENTENCE_TRANSFORMERS_HOME: ${{ github.workspace }}/hf_data + HF_HOME: ${{ github.workspace }}/hf_data steps: - name: Checkout code @@ -42,10 +42,10 @@ jobs: restore-keys: | ${{ runner.os }}-nltk-data- - - name: Set up caching for SENTENCE_TRANSFORMERS_HOME + - name: Set up caching for HF_HOME uses: actions/cache@v4 with: - path: ${{ env.SENTENCE_TRANSFORMERS_HOME }} + path: ${{ env.HF_HOME }} key: ${{ runner.os }}-hf-data-${{ hashFiles(env.BOT_CONFIG_PATH) }} restore-keys: | ${{ runner.os }}-hf-data- @@ -56,18 +56,12 @@ jobs: python-version: 3.12 poetry-version: 1.8.3 - - name: Run pre-commit hooks - uses: pre-commit/action@v3.0.1 - - - name: Set up Pyright + - name: Update GITHUB_PATH run: echo "$(poetry env info --path)/bin" >> $GITHUB_PATH shell: bash - - name: Run type checking - uses: jakebailey/pyright-action@v2 - with: - version: 1.1.370 - python-version: 3.12 + - name: Run pre-commit hooks + uses: pre-commit/action@v3.0.1 - name: Run tests run: poetry run pytest diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index bf70a4c..7985d64 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -43,3 +43,15 @@ repos: hooks: - id: markdownlint name: Check for Markdown linting errors + + - repo: https://github.com/RobertCraigie/pyright-python + rev: v1.1.372 + hooks: + - id: pyright + name: pyright (system) + description: 'pyright static type checker' + entry: pyright + language: system + 'types_or': [python, pyi] + require_serial: true + minimum_pre_commit_version: '2.9.2' diff --git a/Dockerfile b/Dockerfile index 4a5d560..dc10708 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,7 +13,7 @@ LABEL org.opencontainers.image.title="Courageous Comets" ENV BOT_CONFIG_PATH=/app/application.yaml ENV LOG_LEVEL=INFO ENV NLTK_DATA=/app/nltk_data -ENV SENTENCE_TRANSFORMERS_HOME=/app/hf_data +ENV HF_HOME=/app/hf_data # Add a non-root user RUN adduser --system courageous-comets diff --git a/courageous_comets/cogs/messages.py b/courageous_comets/cogs/messages.py index 794097d..a9bdbdf 100644 --- a/courageous_comets/cogs/messages.py +++ b/courageous_comets/cogs/messages.py @@ -53,7 +53,7 @@ async def save_message(self, message: discord.Message) -> None: message.id, ) - embedding = await self.vectorizer.embed(message.content) + embedding = await self.vectorizer.aencode(message.content) vectorized_message = VectorizedMessage( user_id=str(message.author.id), diff --git a/courageous_comets/settings.py b/courageous_comets/settings.py index fce2519..7228874 100644 --- a/courageous_comets/settings.py +++ b/courageous_comets/settings.py @@ -158,11 +158,13 @@ def setup_logging() -> None: REDIS_PORT = read_redis_port() REDIS_PASSWORD = os.getenv("REDIS_PASSWORD") REDIS_KEYS_PREFIX = os.getenv("REDIS_KEYS_PREFIX", "courageous_comets") - SENTENCE_TRANSFORMERS_HOME = os.getenv( - "SENTENCE_TRANSFORMERS_HOME", + # Huggingface environment variable for caching downloaded models. + # https://huggingface.co/docs/huggingface_hub/v0.24.0/package_reference/environment_variables#hf_home + HF_HOME = os.getenv( + "HF_HOME", "hf_data", ) - SENTENCE_TRANSFORMERS_CONCURRENCY = read_int("SENTENCE_TRANSFORMERS_CONCURRENCY", 3) + HF_DOWNLOAD_CONCURRENCY = read_int("HF_DOWNLOAD_CONCURRENCY", 3) except ConfigurationValueError as e: logging.critical( "Cannot start the application due to configuration errors", diff --git a/courageous_comets/transformers/helpers.py b/courageous_comets/transformers/helpers.py index 4715043..542c407 100644 --- a/courageous_comets/transformers/helpers.py +++ b/courageous_comets/transformers/helpers.py @@ -2,7 +2,7 @@ import logging from pathlib import Path -from sentence_transformers import SentenceTransformer +from transformers import AutoModel, AutoTokenizer from courageous_comets import settings @@ -24,9 +24,14 @@ async def download_transformer(resource: str, semaphore: asyncio.Semaphore) -> N async with semaphore: await asyncio.to_thread( - SentenceTransformer, - model_name_or_path=resource, - cache_folder=settings.SENTENCE_TRANSFORMERS_HOME, + AutoTokenizer.from_pretrained, + pretrained_model_name_or_path=resource, + cache_dir=settings.HF_HOME, + ) + await asyncio.to_thread( + AutoModel.from_pretrained, + pretrained_model_name_or_path=resource, + cache_dir=settings.HF_HOME, ) logger.debug("Transformer %r downloaded", resource) @@ -47,9 +52,9 @@ async def init_transformers(resources: list[str]) -> None: # Create the Huggingface data directory if it does not exist to avoid a race condition when # running multiple download tasks concurrently - Path(settings.SENTENCE_TRANSFORMERS_HOME).mkdir(parents=True, exist_ok=True) + Path(settings.HF_HOME).mkdir(parents=True, exist_ok=True) - semaphore = asyncio.Semaphore(settings.SENTENCE_TRANSFORMERS_CONCURRENCY) + semaphore = asyncio.Semaphore(settings.HF_DOWNLOAD_CONCURRENCY) download_tasks = [download_transformer(resource, semaphore) for resource in resources] await asyncio.gather(*download_tasks) diff --git a/courageous_comets/vectorizer.py b/courageous_comets/vectorizer.py index 7583532..4610ba1 100644 --- a/courageous_comets/vectorizer.py +++ b/courageous_comets/vectorizer.py @@ -1,7 +1,12 @@ import asyncio import numpy as np -from sentence_transformers import SentenceTransformer +import torch +import torch.nn.functional as torch_nn_functional +from torch import Tensor +from transformers import AutoModel, AutoTokenizer + +from courageous_comets import settings class Vectorizer: @@ -12,21 +17,89 @@ class Vectorizer: Attributes ---------- - transformer: sentence_transformers.SentenceTransformer + TRANSFORMER_MODEL: str + The name of the model for training the transformer + tokenizer: transformers.AutoTokenizer + The Hugging Face sentence tokenizer + model: transformers.AutoModel The sentence transformer """ + TRANSFORMER_MODEL_NAME = "sentence-transformers/all-MiniLM-L6-v2" + def __init__(self) -> None: - self.transformer = SentenceTransformer( - "sentence-transformers/all-MiniLM-L6-v2", + self.tokenizer = AutoTokenizer.from_pretrained( + Vectorizer.TRANSFORMER_MODEL_NAME, + cache_dir=settings.HF_HOME, + ) + self.model = AutoModel.from_pretrained( + Vectorizer.TRANSFORMER_MODEL_NAME, + cache_dir=settings.HF_HOME, + ) + + def encode(self, message: str) -> bytes: + """ + Create vector embedding of a message. + + The encoder applies the follwowing steps: + + - Tokenize sentences + - Compute token embeddings + - Perform pooling taking into account the attention mask + - Normalize embeddings using torch.nn.functional + + Adapted from: https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2#usage-huggingface-transformers + + Parameters + ---------- + message: str + The message to generate vector embeddings + + Returns + ------- + bytes + The vector embeddings of the message + """ + + # Mean Pooling - Take attention mask into account for correct averaging + def mean_pooling(model_output: list[Tensor], attention_mask: Tensor) -> Tensor: + token_embeddings = model_output[ + 0 + ] # First element of model_output contains all token embeddings + input_mask_expanded = ( + attention_mask.unsqueeze(-1).expand(token_embeddings.size()).float() + ) + return torch.sum(token_embeddings * input_mask_expanded, 1) / torch.clamp( + input_mask_expanded.sum(1), + min=1e-9, + ) + + # Tokenize sentences + encoded_input = self.tokenizer( + [message], + padding=True, + truncation=True, + return_tensors="pt", ) - async def embed(self, message: str) -> bytes: - """Create a vector embedding of message.""" - embedding = await asyncio.to_thread( - self.transformer.encode, - message, + # Compute token embeddings + with torch.no_grad(): + model_output = self.model(**encoded_input) + + # Perform pooling + sentence_embeddings = mean_pooling( + model_output, + encoded_input["attention_mask"], + ) + + # Normalize embeddings + return ( + torch_nn_functional.normalize(sentence_embeddings, p=2, dim=1) + .numpy() + .astype(np.float32) + .tobytes() ) - return embedding.astype( # pyright: ignore - np.float32, - ).tobytes() + + async def aencode(self, message: str) -> bytes: + """Create a vector embedding of message asynchronously.""" + return await asyncio.to_thread(self.encode, message) diff --git a/docs/admin-guide/configuration.md b/docs/admin-guide/configuration.md index f987d91..0395f44 100644 --- a/docs/admin-guide/configuration.md +++ b/docs/admin-guide/configuration.md @@ -6,14 +6,14 @@ The following environment variables are available to configure the application: | ------------------------------------------------------------------------- | ------------------------------------------------------------------------ | -------- | ------------------ | | [`DISCORD_TOKEN`](#discord_token) | The Discord bot token. | Yes | - | | [`BOT_CONFIG_PATH`](#bot_config_path) | The path to the bot's configuration file. | No | `application.yaml` | +| [`HF_DOWNLOAD_CONCURRENCY`](#hf_download_concurrency) | The maximum number of concurrent downloads when installing transformers. | No | `3` | +| [`HF_HOME`](#hf_home) | The directory containing Huggingface Transformers data files. | No | `hf_data` | | [`LOG_LEVEL`](#log_level) | The minimum log level. | No | `INFO` | | [`NLTK_DATA`](#nltk_data) | The directory containing NLTK data files. | No | `nltk_data` | | [`NLTK_DOWNLOAD_CONCURRENCY`](#nltk_download_concurrency) | The maximum number of concurrent downloads when installing NLTK data. | No | `3` | | [`REDIS_HOST`](#redis_host) | The Redis host. | No | `localhost` | | [`REDIS_PORT`](#redis_port) | The Redis port. | No | `6379` | | [`REDIS_PASSWORD`](#redis_password) | The Redis password. | No | - | -| [`SENTENCE TRANSFORMERS_HOME`](#sentence_transformers_home) | The directory containing Sentence Transformers data files. | No | `hf_data` | -| [`SENTENCE_TRANSFORMERS_CONCURRENCY`](#sentence_transformers_concurrency) | The maximum number of concurrent downloads when installing transformers. | No | `3` | ## Required Settings @@ -47,6 +47,16 @@ cogs: By default, the application searches for a file named `application.yaml` in the directory from which it is launched. In the Docker image, this file is located at `/app/application.yaml`. +### `HF_DOWNLOAD_CONCURRENCY` + +The maximum number of concurrent downloads when installing Huggingface Transformers models. By default, this +is set to `3`. + +### `HF_HOME` + +The directory containing Huggingface Transformers data files. By default, this is set to `hf_data` in the directory +from which the application is launched. In the Docker image, this directory is located at `/app/hf_data`. + ### `LOG_LEVEL` The minimum log level to display. The following levels are available: @@ -84,13 +94,3 @@ is set by default. !!! DANGER "Security Warning" Do not share your Redis password with anyone! - -### `SENTENCE_TRANSFORMERS_HOME` - -The directory containing Sentence Transformers data files. By default, this is set to `hf_data` in the directory -from which the application is launched. In the Docker image, this directory is located at `/app/hf_data`. - -### `SENTENCE_TRANSFORMERS_CONCURRENCY` - -The maximum number of concurrent downloads when installing Sentence Transformers models. By default, this is set -to `3`. diff --git a/poetry.lock b/poetry.lock index a4cf2e2..1104237 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1369,199 +1369,47 @@ files = [ [[package]] name = "numpy" -version = "2.0.1" +version = "1.26.4" description = "Fundamental package for array computing in Python" optional = false python-versions = ">=3.9" files = [ - {file = "numpy-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0fbb536eac80e27a2793ffd787895242b7f18ef792563d742c2d673bfcb75134"}, - {file = "numpy-2.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:69ff563d43c69b1baba77af455dd0a839df8d25e8590e79c90fcbe1499ebde42"}, - {file = "numpy-2.0.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:1b902ce0e0a5bb7704556a217c4f63a7974f8f43e090aff03fcf262e0b135e02"}, - {file = "numpy-2.0.1-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:f1659887361a7151f89e79b276ed8dff3d75877df906328f14d8bb40bb4f5101"}, - {file = "numpy-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4658c398d65d1b25e1760de3157011a80375da861709abd7cef3bad65d6543f9"}, - {file = "numpy-2.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4127d4303b9ac9f94ca0441138acead39928938660ca58329fe156f84b9f3015"}, - {file = "numpy-2.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e5eeca8067ad04bc8a2a8731183d51d7cbaac66d86085d5f4766ee6bf19c7f87"}, - {file = "numpy-2.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9adbd9bb520c866e1bfd7e10e1880a1f7749f1f6e5017686a5fbb9b72cf69f82"}, - {file = "numpy-2.0.1-cp310-cp310-win32.whl", hash = "sha256:7b9853803278db3bdcc6cd5beca37815b133e9e77ff3d4733c247414e78eb8d1"}, - {file = "numpy-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:81b0893a39bc5b865b8bf89e9ad7807e16717f19868e9d234bdaf9b1f1393868"}, - {file = "numpy-2.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:75b4e316c5902d8163ef9d423b1c3f2f6252226d1aa5cd8a0a03a7d01ffc6268"}, - {file = "numpy-2.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6e4eeb6eb2fced786e32e6d8df9e755ce5be920d17f7ce00bc38fcde8ccdbf9e"}, - {file = "numpy-2.0.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:a1e01dcaab205fbece13c1410253a9eea1b1c9b61d237b6fa59bcc46e8e89343"}, - {file = "numpy-2.0.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:a8fc2de81ad835d999113ddf87d1ea2b0f4704cbd947c948d2f5513deafe5a7b"}, - {file = "numpy-2.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a3d94942c331dd4e0e1147f7a8699a4aa47dffc11bf8a1523c12af8b2e91bbe"}, - {file = "numpy-2.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15eb4eca47d36ec3f78cde0a3a2ee24cf05ca7396ef808dda2c0ddad7c2bde67"}, - {file = "numpy-2.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b83e16a5511d1b1f8a88cbabb1a6f6a499f82c062a4251892d9ad5d609863fb7"}, - {file = "numpy-2.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f87fec1f9bc1efd23f4227becff04bd0e979e23ca50cc92ec88b38489db3b55"}, - {file = "numpy-2.0.1-cp311-cp311-win32.whl", hash = "sha256:36d3a9405fd7c511804dc56fc32974fa5533bdeb3cd1604d6b8ff1d292b819c4"}, - {file = "numpy-2.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:08458fbf403bff5e2b45f08eda195d4b0c9b35682311da5a5a0a0925b11b9bd8"}, - {file = "numpy-2.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6bf4e6f4a2a2e26655717a1983ef6324f2664d7011f6ef7482e8c0b3d51e82ac"}, - {file = "numpy-2.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6fddc5fe258d3328cd8e3d7d3e02234c5d70e01ebe377a6ab92adb14039cb4"}, - {file = "numpy-2.0.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:5daab361be6ddeb299a918a7c0864fa8618af66019138263247af405018b04e1"}, - {file = "numpy-2.0.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:ea2326a4dca88e4a274ba3a4405eb6c6467d3ffbd8c7d38632502eaae3820587"}, - {file = "numpy-2.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:529af13c5f4b7a932fb0e1911d3a75da204eff023ee5e0e79c1751564221a5c8"}, - {file = "numpy-2.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6790654cb13eab303d8402354fabd47472b24635700f631f041bd0b65e37298a"}, - {file = "numpy-2.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cbab9fc9c391700e3e1287666dfd82d8666d10e69a6c4a09ab97574c0b7ee0a7"}, - {file = "numpy-2.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:99d0d92a5e3613c33a5f01db206a33f8fdf3d71f2912b0de1739894668b7a93b"}, - {file = "numpy-2.0.1-cp312-cp312-win32.whl", hash = "sha256:173a00b9995f73b79eb0191129f2455f1e34c203f559dd118636858cc452a1bf"}, - {file = "numpy-2.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:bb2124fdc6e62baae159ebcfa368708867eb56806804d005860b6007388df171"}, - {file = "numpy-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bfc085b28d62ff4009364e7ca34b80a9a080cbd97c2c0630bb5f7f770dae9414"}, - {file = "numpy-2.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8fae4ebbf95a179c1156fab0b142b74e4ba4204c87bde8d3d8b6f9c34c5825ef"}, - {file = "numpy-2.0.1-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:72dc22e9ec8f6eaa206deb1b1355eb2e253899d7347f5e2fae5f0af613741d06"}, - {file = "numpy-2.0.1-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:ec87f5f8aca726117a1c9b7083e7656a9d0d606eec7299cc067bb83d26f16e0c"}, - {file = "numpy-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f682ea61a88479d9498bf2091fdcd722b090724b08b31d63e022adc063bad59"}, - {file = "numpy-2.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8efc84f01c1cd7e34b3fb310183e72fcdf55293ee736d679b6d35b35d80bba26"}, - {file = "numpy-2.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3fdabe3e2a52bc4eff8dc7a5044342f8bd9f11ef0934fcd3289a788c0eb10018"}, - {file = "numpy-2.0.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:24a0e1befbfa14615b49ba9659d3d8818a0f4d8a1c5822af8696706fbda7310c"}, - {file = "numpy-2.0.1-cp39-cp39-win32.whl", hash = "sha256:f9cf5ea551aec449206954b075db819f52adc1638d46a6738253a712d553c7b4"}, - {file = "numpy-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:e9e81fa9017eaa416c056e5d9e71be93d05e2c3c2ab308d23307a8bc4443c368"}, - {file = "numpy-2.0.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:61728fba1e464f789b11deb78a57805c70b2ed02343560456190d0501ba37b0f"}, - {file = "numpy-2.0.1-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:12f5d865d60fb9734e60a60f1d5afa6d962d8d4467c120a1c0cda6eb2964437d"}, - {file = "numpy-2.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eacf3291e263d5a67d8c1a581a8ebbcfd6447204ef58828caf69a5e3e8c75990"}, - {file = "numpy-2.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2c3a346ae20cfd80b6cfd3e60dc179963ef2ea58da5ec074fd3d9e7a1e7ba97f"}, - {file = "numpy-2.0.1.tar.gz", hash = "sha256:485b87235796410c3519a699cfe1faab097e509e90ebb05dcd098db2ae87e7b3"}, -] - -[[package]] -name = "nvidia-cublas-cu12" -version = "12.1.3.1" -description = "CUBLAS native runtime libraries" -optional = false -python-versions = ">=3" -files = [ - {file = "nvidia_cublas_cu12-12.1.3.1-py3-none-manylinux1_x86_64.whl", hash = "sha256:ee53ccca76a6fc08fb9701aa95b6ceb242cdaab118c3bb152af4e579af792728"}, - {file = "nvidia_cublas_cu12-12.1.3.1-py3-none-win_amd64.whl", hash = "sha256:2b964d60e8cf11b5e1073d179d85fa340c120e99b3067558f3cf98dd69d02906"}, -] - -[[package]] -name = "nvidia-cuda-cupti-cu12" -version = "12.1.105" -description = "CUDA profiling tools runtime libs." -optional = false -python-versions = ">=3" -files = [ - {file = "nvidia_cuda_cupti_cu12-12.1.105-py3-none-manylinux1_x86_64.whl", hash = "sha256:e54fde3983165c624cb79254ae9818a456eb6e87a7fd4d56a2352c24ee542d7e"}, - {file = "nvidia_cuda_cupti_cu12-12.1.105-py3-none-win_amd64.whl", hash = "sha256:bea8236d13a0ac7190bd2919c3e8e6ce1e402104276e6f9694479e48bb0eb2a4"}, -] - -[[package]] -name = "nvidia-cuda-nvrtc-cu12" -version = "12.1.105" -description = "NVRTC native runtime libraries" -optional = false -python-versions = ">=3" -files = [ - {file = "nvidia_cuda_nvrtc_cu12-12.1.105-py3-none-manylinux1_x86_64.whl", hash = "sha256:339b385f50c309763ca65456ec75e17bbefcbbf2893f462cb8b90584cd27a1c2"}, - {file = "nvidia_cuda_nvrtc_cu12-12.1.105-py3-none-win_amd64.whl", hash = "sha256:0a98a522d9ff138b96c010a65e145dc1b4850e9ecb75a0172371793752fd46ed"}, -] - -[[package]] -name = "nvidia-cuda-runtime-cu12" -version = "12.1.105" -description = "CUDA Runtime native Libraries" -optional = false -python-versions = ">=3" -files = [ - {file = "nvidia_cuda_runtime_cu12-12.1.105-py3-none-manylinux1_x86_64.whl", hash = "sha256:6e258468ddf5796e25f1dc591a31029fa317d97a0a94ed93468fc86301d61e40"}, - {file = "nvidia_cuda_runtime_cu12-12.1.105-py3-none-win_amd64.whl", hash = "sha256:dfb46ef84d73fababab44cf03e3b83f80700d27ca300e537f85f636fac474344"}, -] - -[[package]] -name = "nvidia-cudnn-cu12" -version = "8.9.2.26" -description = "cuDNN runtime libraries" -optional = false -python-versions = ">=3" -files = [ - {file = "nvidia_cudnn_cu12-8.9.2.26-py3-none-manylinux1_x86_64.whl", hash = "sha256:5ccb288774fdfb07a7e7025ffec286971c06d8d7b4fb162525334616d7629ff9"}, -] - -[package.dependencies] -nvidia-cublas-cu12 = "*" - -[[package]] -name = "nvidia-cufft-cu12" -version = "11.0.2.54" -description = "CUFFT native runtime libraries" -optional = false -python-versions = ">=3" -files = [ - {file = "nvidia_cufft_cu12-11.0.2.54-py3-none-manylinux1_x86_64.whl", hash = "sha256:794e3948a1aa71fd817c3775866943936774d1c14e7628c74f6f7417224cdf56"}, - {file = "nvidia_cufft_cu12-11.0.2.54-py3-none-win_amd64.whl", hash = "sha256:d9ac353f78ff89951da4af698f80870b1534ed69993f10a4cf1d96f21357e253"}, -] - -[[package]] -name = "nvidia-curand-cu12" -version = "10.3.2.106" -description = "CURAND native runtime libraries" -optional = false -python-versions = ">=3" -files = [ - {file = "nvidia_curand_cu12-10.3.2.106-py3-none-manylinux1_x86_64.whl", hash = "sha256:9d264c5036dde4e64f1de8c50ae753237c12e0b1348738169cd0f8a536c0e1e0"}, - {file = "nvidia_curand_cu12-10.3.2.106-py3-none-win_amd64.whl", hash = "sha256:75b6b0c574c0037839121317e17fd01f8a69fd2ef8e25853d826fec30bdba74a"}, -] - -[[package]] -name = "nvidia-cusolver-cu12" -version = "11.4.5.107" -description = "CUDA solver native runtime libraries" -optional = false -python-versions = ">=3" -files = [ - {file = "nvidia_cusolver_cu12-11.4.5.107-py3-none-manylinux1_x86_64.whl", hash = "sha256:8a7ec542f0412294b15072fa7dab71d31334014a69f953004ea7a118206fe0dd"}, - {file = "nvidia_cusolver_cu12-11.4.5.107-py3-none-win_amd64.whl", hash = "sha256:74e0c3a24c78612192a74fcd90dd117f1cf21dea4822e66d89e8ea80e3cd2da5"}, -] - -[package.dependencies] -nvidia-cublas-cu12 = "*" -nvidia-cusparse-cu12 = "*" -nvidia-nvjitlink-cu12 = "*" - -[[package]] -name = "nvidia-cusparse-cu12" -version = "12.1.0.106" -description = "CUSPARSE native runtime libraries" -optional = false -python-versions = ">=3" -files = [ - {file = "nvidia_cusparse_cu12-12.1.0.106-py3-none-manylinux1_x86_64.whl", hash = "sha256:f3b50f42cf363f86ab21f720998517a659a48131e8d538dc02f8768237bd884c"}, - {file = "nvidia_cusparse_cu12-12.1.0.106-py3-none-win_amd64.whl", hash = "sha256:b798237e81b9719373e8fae8d4f091b70a0cf09d9d85c95a557e11df2d8e9a5a"}, -] - -[package.dependencies] -nvidia-nvjitlink-cu12 = "*" - -[[package]] -name = "nvidia-nccl-cu12" -version = "2.20.5" -description = "NVIDIA Collective Communication Library (NCCL) Runtime" -optional = false -python-versions = ">=3" -files = [ - {file = "nvidia_nccl_cu12-2.20.5-py3-none-manylinux2014_aarch64.whl", hash = "sha256:1fc150d5c3250b170b29410ba682384b14581db722b2531b0d8d33c595f33d01"}, - {file = "nvidia_nccl_cu12-2.20.5-py3-none-manylinux2014_x86_64.whl", hash = "sha256:057f6bf9685f75215d0c53bf3ac4a10b3e6578351de307abad9e18a99182af56"}, -] - -[[package]] -name = "nvidia-nvjitlink-cu12" -version = "12.5.82" -description = "Nvidia JIT LTO Library" -optional = false -python-versions = ">=3" -files = [ - {file = "nvidia_nvjitlink_cu12-12.5.82-py3-none-manylinux2014_aarch64.whl", hash = "sha256:98103729cc5226e13ca319a10bbf9433bbbd44ef64fe72f45f067cacc14b8d27"}, - {file = "nvidia_nvjitlink_cu12-12.5.82-py3-none-manylinux2014_x86_64.whl", hash = "sha256:f9b37bc5c8cf7509665cb6ada5aaa0ce65618f2332b7d3e78e9790511f111212"}, - {file = "nvidia_nvjitlink_cu12-12.5.82-py3-none-win_amd64.whl", hash = "sha256:e782564d705ff0bf61ac3e1bf730166da66dd2fe9012f111ede5fc49b64ae697"}, -] - -[[package]] -name = "nvidia-nvtx-cu12" -version = "12.1.105" -description = "NVIDIA Tools Extension" -optional = false -python-versions = ">=3" -files = [ - {file = "nvidia_nvtx_cu12-12.1.105-py3-none-manylinux1_x86_64.whl", hash = "sha256:dc21cf308ca5691e7c04d962e213f8a4aa9bbfa23d95412f452254c2caeb09e5"}, - {file = "nvidia_nvtx_cu12-12.1.105-py3-none-win_amd64.whl", hash = "sha256:65f4d98982b31b60026e0e6de73fbdfc09d08a96f4656dd3665ca616a11e1e82"}, + {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"}, + {file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"}, + {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4"}, + {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f"}, + {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a"}, + {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2"}, + {file = "numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07"}, + {file = "numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5"}, + {file = "numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71"}, + {file = "numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef"}, + {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e"}, + {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5"}, + {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a"}, + {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a"}, + {file = "numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20"}, + {file = "numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2"}, + {file = "numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218"}, + {file = "numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b"}, + {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b"}, + {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed"}, + {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a"}, + {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0"}, + {file = "numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110"}, + {file = "numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818"}, + {file = "numpy-1.26.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c"}, + {file = "numpy-1.26.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be"}, + {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764"}, + {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3"}, + {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd"}, + {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c"}, + {file = "numpy-1.26.4-cp39-cp39-win32.whl", hash = "sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6"}, + {file = "numpy-1.26.4-cp39-cp39-win_amd64.whl", hash = "sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea"}, + {file = "numpy-1.26.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30"}, + {file = "numpy-1.26.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c"}, + {file = "numpy-1.26.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0"}, + {file = "numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010"}, ] [[package]] @@ -1596,103 +1444,6 @@ files = [ {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, ] -[[package]] -name = "pillow" -version = "10.4.0" -description = "Python Imaging Library (Fork)" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pillow-10.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e"}, - {file = "pillow-10.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:543f3dc61c18dafb755773efc89aae60d06b6596a63914107f75459cf984164d"}, - {file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7928ecbf1ece13956b95d9cbcfc77137652b02763ba384d9ab508099a2eca856"}, - {file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4d49b85c4348ea0b31ea63bc75a9f3857869174e2bf17e7aba02945cd218e6f"}, - {file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:6c762a5b0997f5659a5ef2266abc1d8851ad7749ad9a6a5506eb23d314e4f46b"}, - {file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a985e028fc183bf12a77a8bbf36318db4238a3ded7fa9df1b9a133f1cb79f8fc"}, - {file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:812f7342b0eee081eaec84d91423d1b4650bb9828eb53d8511bcef8ce5aecf1e"}, - {file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ac1452d2fbe4978c2eec89fb5a23b8387aba707ac72810d9490118817d9c0b46"}, - {file = "pillow-10.4.0-cp310-cp310-win32.whl", hash = "sha256:bcd5e41a859bf2e84fdc42f4edb7d9aba0a13d29a2abadccafad99de3feff984"}, - {file = "pillow-10.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:ecd85a8d3e79cd7158dec1c9e5808e821feea088e2f69a974db5edf84dc53141"}, - {file = "pillow-10.4.0-cp310-cp310-win_arm64.whl", hash = "sha256:ff337c552345e95702c5fde3158acb0625111017d0e5f24bf3acdb9cc16b90d1"}, - {file = "pillow-10.4.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0a9ec697746f268507404647e531e92889890a087e03681a3606d9b920fbee3c"}, - {file = "pillow-10.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe91cb65544a1321e631e696759491ae04a2ea11d36715eca01ce07284738be"}, - {file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dc6761a6efc781e6a1544206f22c80c3af4c8cf461206d46a1e6006e4429ff3"}, - {file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e84b6cc6a4a3d76c153a6b19270b3526a5a8ed6b09501d3af891daa2a9de7d6"}, - {file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:bbc527b519bd3aa9d7f429d152fea69f9ad37c95f0b02aebddff592688998abe"}, - {file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:76a911dfe51a36041f2e756b00f96ed84677cdeb75d25c767f296c1c1eda1319"}, - {file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59291fb29317122398786c2d44427bbd1a6d7ff54017075b22be9d21aa59bd8d"}, - {file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:416d3a5d0e8cfe4f27f574362435bc9bae57f679a7158e0096ad2beb427b8696"}, - {file = "pillow-10.4.0-cp311-cp311-win32.whl", hash = "sha256:7086cc1d5eebb91ad24ded9f58bec6c688e9f0ed7eb3dbbf1e4800280a896496"}, - {file = "pillow-10.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cbed61494057c0f83b83eb3a310f0bf774b09513307c434d4366ed64f4128a91"}, - {file = "pillow-10.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:f5f0c3e969c8f12dd2bb7e0b15d5c468b51e5017e01e2e867335c81903046a22"}, - {file = "pillow-10.4.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:673655af3eadf4df6b5457033f086e90299fdd7a47983a13827acf7459c15d94"}, - {file = "pillow-10.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:866b6942a92f56300012f5fbac71f2d610312ee65e22f1aa2609e491284e5597"}, - {file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29dbdc4207642ea6aad70fbde1a9338753d33fb23ed6956e706936706f52dd80"}, - {file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf2342ac639c4cf38799a44950bbc2dfcb685f052b9e262f446482afaf4bffca"}, - {file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f5b92f4d70791b4a67157321c4e8225d60b119c5cc9aee8ecf153aace4aad4ef"}, - {file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:86dcb5a1eb778d8b25659d5e4341269e8590ad6b4e8b44d9f4b07f8d136c414a"}, - {file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:780c072c2e11c9b2c7ca37f9a2ee8ba66f44367ac3e5c7832afcfe5104fd6d1b"}, - {file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37fb69d905be665f68f28a8bba3c6d3223c8efe1edf14cc4cfa06c241f8c81d9"}, - {file = "pillow-10.4.0-cp312-cp312-win32.whl", hash = "sha256:7dfecdbad5c301d7b5bde160150b4db4c659cee2b69589705b6f8a0c509d9f42"}, - {file = "pillow-10.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:1d846aea995ad352d4bdcc847535bd56e0fd88d36829d2c90be880ef1ee4668a"}, - {file = "pillow-10.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:e553cad5179a66ba15bb18b353a19020e73a7921296a7979c4a2b7f6a5cd57f9"}, - {file = "pillow-10.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8bc1a764ed8c957a2e9cacf97c8b2b053b70307cf2996aafd70e91a082e70df3"}, - {file = "pillow-10.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6209bb41dc692ddfee4942517c19ee81b86c864b626dbfca272ec0f7cff5d9fb"}, - {file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bee197b30783295d2eb680b311af15a20a8b24024a19c3a26431ff83eb8d1f70"}, - {file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ef61f5dd14c300786318482456481463b9d6b91ebe5ef12f405afbba77ed0be"}, - {file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:297e388da6e248c98bc4a02e018966af0c5f92dfacf5a5ca22fa01cb3179bca0"}, - {file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:e4db64794ccdf6cb83a59d73405f63adbe2a1887012e308828596100a0b2f6cc"}, - {file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd2880a07482090a3bcb01f4265f1936a903d70bc740bfcb1fd4e8a2ffe5cf5a"}, - {file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b35b21b819ac1dbd1233317adeecd63495f6babf21b7b2512d244ff6c6ce309"}, - {file = "pillow-10.4.0-cp313-cp313-win32.whl", hash = "sha256:551d3fd6e9dc15e4c1eb6fc4ba2b39c0c7933fa113b220057a34f4bb3268a060"}, - {file = "pillow-10.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:030abdbe43ee02e0de642aee345efa443740aa4d828bfe8e2eb11922ea6a21ea"}, - {file = "pillow-10.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:5b001114dd152cfd6b23befeb28d7aee43553e2402c9f159807bf55f33af8a8d"}, - {file = "pillow-10.4.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8d4d5063501b6dd4024b8ac2f04962d661222d120381272deea52e3fc52d3736"}, - {file = "pillow-10.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7c1ee6f42250df403c5f103cbd2768a28fe1a0ea1f0f03fe151c8741e1469c8b"}, - {file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15e02e9bb4c21e39876698abf233c8c579127986f8207200bc8a8f6bb27acf2"}, - {file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a8d4bade9952ea9a77d0c3e49cbd8b2890a399422258a77f357b9cc9be8d680"}, - {file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:43efea75eb06b95d1631cb784aa40156177bf9dd5b4b03ff38979e048258bc6b"}, - {file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:950be4d8ba92aca4b2bb0741285a46bfae3ca699ef913ec8416c1b78eadd64cd"}, - {file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d7480af14364494365e89d6fddc510a13e5a2c3584cb19ef65415ca57252fb84"}, - {file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:73664fe514b34c8f02452ffb73b7a92c6774e39a647087f83d67f010eb9a0cf0"}, - {file = "pillow-10.4.0-cp38-cp38-win32.whl", hash = "sha256:e88d5e6ad0d026fba7bdab8c3f225a69f063f116462c49892b0149e21b6c0a0e"}, - {file = "pillow-10.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:5161eef006d335e46895297f642341111945e2c1c899eb406882a6c61a4357ab"}, - {file = "pillow-10.4.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0ae24a547e8b711ccaaf99c9ae3cd975470e1a30caa80a6aaee9a2f19c05701d"}, - {file = "pillow-10.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:298478fe4f77a4408895605f3482b6cc6222c018b2ce565c2b6b9c354ac3229b"}, - {file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:134ace6dc392116566980ee7436477d844520a26a4b1bd4053f6f47d096997fd"}, - {file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:930044bb7679ab003b14023138b50181899da3f25de50e9dbee23b61b4de2126"}, - {file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:c76e5786951e72ed3686e122d14c5d7012f16c8303a674d18cdcd6d89557fc5b"}, - {file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b2724fdb354a868ddf9a880cb84d102da914e99119211ef7ecbdc613b8c96b3c"}, - {file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dbc6ae66518ab3c5847659e9988c3b60dc94ffb48ef9168656e0019a93dbf8a1"}, - {file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:06b2f7898047ae93fad74467ec3d28fe84f7831370e3c258afa533f81ef7f3df"}, - {file = "pillow-10.4.0-cp39-cp39-win32.whl", hash = "sha256:7970285ab628a3779aecc35823296a7869f889b8329c16ad5a71e4901a3dc4ef"}, - {file = "pillow-10.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:961a7293b2457b405967af9c77dcaa43cc1a8cd50d23c532e62d48ab6cdd56f5"}, - {file = "pillow-10.4.0-cp39-cp39-win_arm64.whl", hash = "sha256:32cda9e3d601a52baccb2856b8ea1fc213c90b340c542dcef77140dfa3278a9e"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5b4815f2e65b30f5fbae9dfffa8636d992d49705723fe86a3661806e069352d4"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8f0aef4ef59694b12cadee839e2ba6afeab89c0f39a3adc02ed51d109117b8da"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f4727572e2918acaa9077c919cbbeb73bd2b3ebcfe033b72f858fc9fbef0026"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff25afb18123cea58a591ea0244b92eb1e61a1fd497bf6d6384f09bc3262ec3e"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dc3e2db6ba09ffd7d02ae9141cfa0ae23393ee7687248d46a7507b75d610f4f5"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:02a2be69f9c9b8c1e97cf2713e789d4e398c751ecfd9967c18d0ce304efbf885"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0755ffd4a0c6f267cccbae2e9903d95477ca2f77c4fcf3a3a09570001856c8a5"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:a02364621fe369e06200d4a16558e056fe2805d3468350df3aef21e00d26214b"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:1b5dea9831a90e9d0721ec417a80d4cbd7022093ac38a568db2dd78363b00908"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b885f89040bb8c4a1573566bbb2f44f5c505ef6e74cec7ab9068c900047f04b"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87dd88ded2e6d74d31e1e0a99a726a6765cda32d00ba72dc37f0651f306daaa8"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:2db98790afc70118bd0255c2eeb465e9767ecf1f3c25f9a1abb8ffc8cfd1fe0a"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f7baece4ce06bade126fb84b8af1c33439a76d8a6fd818970215e0560ca28c27"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:cfdd747216947628af7b259d274771d84db2268ca062dd5faf373639d00113a3"}, - {file = "pillow-10.4.0.tar.gz", hash = "sha256:166c1cd4d24309b30d61f79f4a9114b7b2313d7450912277855ff5dfd7cd4a06"}, -] - -[package.extras] -docs = ["furo", "olefile", "sphinx (>=7.3)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"] -fpx = ["olefile"] -mic = ["olefile"] -tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] -typing = ["typing-extensions"] -xmp = ["defusedxml"] - [[package]] name = "platformdirs" version = "4.2.2" @@ -2008,6 +1759,24 @@ files = [ {file = "pyreadline3-3.4.1.tar.gz", hash = "sha256:6f3d1f7b8a31ba32b73917cefc1f28cc660562f39aea8646d30bd6eff21f7bae"}, ] +[[package]] +name = "pyright" +version = "1.1.372" +description = "Command line wrapper for pyright" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pyright-1.1.372-py3-none-any.whl", hash = "sha256:25b15fb8967740f0949fd35b963777187f0a0404c0bd753cc966ec139f3eaa0b"}, + {file = "pyright-1.1.372.tar.gz", hash = "sha256:a9f5e0daa955daaa17e3d1ef76d3623e75f8afd5e37b437d3ff84d5b38c15420"}, +] + +[package.dependencies] +nodeenv = ">=1.6.0" + +[package.extras] +all = ["twine (>=3.4.1)"] +dev = ["twine (>=3.4.1)"] + [[package]] name = "pytest" version = "8.2.2" @@ -2122,6 +1891,7 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -2129,8 +1899,16 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -2147,6 +1925,7 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -2154,6 +1933,7 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -2490,118 +2270,6 @@ tensorflow = ["safetensors[numpy]", "tensorflow (>=2.11.0)"] testing = ["h5py (>=3.7.0)", "huggingface-hub (>=0.12.1)", "hypothesis (>=6.70.2)", "pytest (>=7.2.0)", "pytest-benchmark (>=4.0.0)", "safetensors[numpy]", "setuptools-rust (>=1.5.2)"] torch = ["safetensors[numpy]", "torch (>=1.10)"] -[[package]] -name = "scikit-learn" -version = "1.5.1" -description = "A set of python modules for machine learning and data mining" -optional = false -python-versions = ">=3.9" -files = [ - {file = "scikit_learn-1.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:781586c414f8cc58e71da4f3d7af311e0505a683e112f2f62919e3019abd3745"}, - {file = "scikit_learn-1.5.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:f5b213bc29cc30a89a3130393b0e39c847a15d769d6e59539cd86b75d276b1a7"}, - {file = "scikit_learn-1.5.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ff4ba34c2abff5ec59c803ed1d97d61b036f659a17f55be102679e88f926fac"}, - {file = "scikit_learn-1.5.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:161808750c267b77b4a9603cf9c93579c7a74ba8486b1336034c2f1579546d21"}, - {file = "scikit_learn-1.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:10e49170691514a94bb2e03787aa921b82dbc507a4ea1f20fd95557862c98dc1"}, - {file = "scikit_learn-1.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:154297ee43c0b83af12464adeab378dee2d0a700ccd03979e2b821e7dd7cc1c2"}, - {file = "scikit_learn-1.5.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:b5e865e9bd59396220de49cb4a57b17016256637c61b4c5cc81aaf16bc123bbe"}, - {file = "scikit_learn-1.5.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:909144d50f367a513cee6090873ae582dba019cb3fca063b38054fa42704c3a4"}, - {file = "scikit_learn-1.5.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:689b6f74b2c880276e365fe84fe4f1befd6a774f016339c65655eaff12e10cbf"}, - {file = "scikit_learn-1.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:9a07f90846313a7639af6a019d849ff72baadfa4c74c778821ae0fad07b7275b"}, - {file = "scikit_learn-1.5.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5944ce1faada31c55fb2ba20a5346b88e36811aab504ccafb9f0339e9f780395"}, - {file = "scikit_learn-1.5.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:0828673c5b520e879f2af6a9e99eee0eefea69a2188be1ca68a6121b809055c1"}, - {file = "scikit_learn-1.5.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:508907e5f81390e16d754e8815f7497e52139162fd69c4fdbd2dfa5d6cc88915"}, - {file = "scikit_learn-1.5.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97625f217c5c0c5d0505fa2af28ae424bd37949bb2f16ace3ff5f2f81fb4498b"}, - {file = "scikit_learn-1.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:da3f404e9e284d2b0a157e1b56b6566a34eb2798205cba35a211df3296ab7a74"}, - {file = "scikit_learn-1.5.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:88e0672c7ac21eb149d409c74cc29f1d611d5158175846e7a9c2427bd12b3956"}, - {file = "scikit_learn-1.5.1-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:7b073a27797a283187a4ef4ee149959defc350b46cbf63a84d8514fe16b69855"}, - {file = "scikit_learn-1.5.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b59e3e62d2be870e5c74af4e793293753565c7383ae82943b83383fdcf5cc5c1"}, - {file = "scikit_learn-1.5.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bd8d3a19d4bd6dc5a7d4f358c8c3a60934dc058f363c34c0ac1e9e12a31421d"}, - {file = "scikit_learn-1.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:5f57428de0c900a98389c4a433d4a3cf89de979b3aa24d1c1d251802aa15e44d"}, - {file = "scikit_learn-1.5.1.tar.gz", hash = "sha256:0ea5d40c0e3951df445721927448755d3fe1d80833b0b7308ebff5d2a45e6414"}, -] - -[package.dependencies] -joblib = ">=1.2.0" -numpy = ">=1.19.5" -scipy = ">=1.6.0" -threadpoolctl = ">=3.1.0" - -[package.extras] -benchmark = ["matplotlib (>=3.3.4)", "memory_profiler (>=0.57.0)", "pandas (>=1.1.5)"] -build = ["cython (>=3.0.10)", "meson-python (>=0.16.0)", "numpy (>=1.19.5)", "scipy (>=1.6.0)"] -docs = ["Pillow (>=7.1.2)", "matplotlib (>=3.3.4)", "memory_profiler (>=0.57.0)", "numpydoc (>=1.2.0)", "pandas (>=1.1.5)", "plotly (>=5.14.0)", "polars (>=0.20.23)", "pooch (>=1.6.0)", "pydata-sphinx-theme (>=0.15.3)", "scikit-image (>=0.17.2)", "seaborn (>=0.9.0)", "sphinx (>=7.3.7)", "sphinx-copybutton (>=0.5.2)", "sphinx-design (>=0.5.0)", "sphinx-gallery (>=0.16.0)", "sphinx-prompt (>=1.4.0)", "sphinx-remove-toctrees (>=1.0.0.post1)", "sphinxcontrib-sass (>=0.3.4)", "sphinxext-opengraph (>=0.9.1)"] -examples = ["matplotlib (>=3.3.4)", "pandas (>=1.1.5)", "plotly (>=5.14.0)", "pooch (>=1.6.0)", "scikit-image (>=0.17.2)", "seaborn (>=0.9.0)"] -install = ["joblib (>=1.2.0)", "numpy (>=1.19.5)", "scipy (>=1.6.0)", "threadpoolctl (>=3.1.0)"] -maintenance = ["conda-lock (==2.5.6)"] -tests = ["black (>=24.3.0)", "matplotlib (>=3.3.4)", "mypy (>=1.9)", "numpydoc (>=1.2.0)", "pandas (>=1.1.5)", "polars (>=0.20.23)", "pooch (>=1.6.0)", "pyamg (>=4.0.0)", "pyarrow (>=12.0.0)", "pytest (>=7.1.2)", "pytest-cov (>=2.9.0)", "ruff (>=0.2.1)", "scikit-image (>=0.17.2)"] - -[[package]] -name = "scipy" -version = "1.14.0" -description = "Fundamental algorithms for scientific computing in Python" -optional = false -python-versions = ">=3.10" -files = [ - {file = "scipy-1.14.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7e911933d54ead4d557c02402710c2396529540b81dd554fc1ba270eb7308484"}, - {file = "scipy-1.14.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:687af0a35462402dd851726295c1a5ae5f987bd6e9026f52e9505994e2f84ef6"}, - {file = "scipy-1.14.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:07e179dc0205a50721022344fb85074f772eadbda1e1b3eecdc483f8033709b7"}, - {file = "scipy-1.14.0-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:6a9c9a9b226d9a21e0a208bdb024c3982932e43811b62d202aaf1bb59af264b1"}, - {file = "scipy-1.14.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:076c27284c768b84a45dcf2e914d4000aac537da74236a0d45d82c6fa4b7b3c0"}, - {file = "scipy-1.14.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42470ea0195336df319741e230626b6225a740fd9dce9642ca13e98f667047c0"}, - {file = "scipy-1.14.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:176c6f0d0470a32f1b2efaf40c3d37a24876cebf447498a4cefb947a79c21e9d"}, - {file = "scipy-1.14.0-cp310-cp310-win_amd64.whl", hash = "sha256:ad36af9626d27a4326c8e884917b7ec321d8a1841cd6dacc67d2a9e90c2f0359"}, - {file = "scipy-1.14.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6d056a8709ccda6cf36cdd2eac597d13bc03dba38360f418560a93050c76a16e"}, - {file = "scipy-1.14.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:f0a50da861a7ec4573b7c716b2ebdcdf142b66b756a0d392c236ae568b3a93fb"}, - {file = "scipy-1.14.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:94c164a9e2498e68308e6e148646e486d979f7fcdb8b4cf34b5441894bdb9caf"}, - {file = "scipy-1.14.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:a7d46c3e0aea5c064e734c3eac5cf9eb1f8c4ceee756262f2c7327c4c2691c86"}, - {file = "scipy-1.14.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9eee2989868e274aae26125345584254d97c56194c072ed96cb433f32f692ed8"}, - {file = "scipy-1.14.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e3154691b9f7ed73778d746da2df67a19d046a6c8087c8b385bc4cdb2cfca74"}, - {file = "scipy-1.14.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c40003d880f39c11c1edbae8144e3813904b10514cd3d3d00c277ae996488cdb"}, - {file = "scipy-1.14.0-cp311-cp311-win_amd64.whl", hash = "sha256:5b083c8940028bb7e0b4172acafda6df762da1927b9091f9611b0bcd8676f2bc"}, - {file = "scipy-1.14.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:bff2438ea1330e06e53c424893ec0072640dac00f29c6a43a575cbae4c99b2b9"}, - {file = "scipy-1.14.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:bbc0471b5f22c11c389075d091d3885693fd3f5e9a54ce051b46308bc787e5d4"}, - {file = "scipy-1.14.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:64b2ff514a98cf2bb734a9f90d32dc89dc6ad4a4a36a312cd0d6327170339eb0"}, - {file = "scipy-1.14.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:7d3da42fbbbb860211a811782504f38ae7aaec9de8764a9bef6b262de7a2b50f"}, - {file = "scipy-1.14.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d91db2c41dd6c20646af280355d41dfa1ec7eead235642178bd57635a3f82209"}, - {file = "scipy-1.14.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a01cc03bcdc777c9da3cfdcc74b5a75caffb48a6c39c8450a9a05f82c4250a14"}, - {file = "scipy-1.14.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:65df4da3c12a2bb9ad52b86b4dcf46813e869afb006e58be0f516bc370165159"}, - {file = "scipy-1.14.0-cp312-cp312-win_amd64.whl", hash = "sha256:4c4161597c75043f7154238ef419c29a64ac4a7c889d588ea77690ac4d0d9b20"}, - {file = "scipy-1.14.0.tar.gz", hash = "sha256:b5923f48cb840380f9854339176ef21763118a7300a88203ccd0bdd26e58527b"}, -] - -[package.dependencies] -numpy = ">=1.23.5,<2.3" - -[package.extras] -dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy (==1.10.0)", "pycodestyle", "pydevtool", "rich-click", "ruff (>=0.0.292)", "types-psutil", "typing_extensions"] -doc = ["jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.13.1)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0)", "sphinx-design (>=0.4.0)"] -test = ["Cython", "array-api-strict", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] - -[[package]] -name = "sentence-transformers" -version = "3.0.1" -description = "Multilingual text embeddings" -optional = false -python-versions = ">=3.8.0" -files = [ - {file = "sentence_transformers-3.0.1-py3-none-any.whl", hash = "sha256:01050cc4053c49b9f5b78f6980b5a72db3fd3a0abb9169b1792ac83875505ee6"}, - {file = "sentence_transformers-3.0.1.tar.gz", hash = "sha256:8a3d2c537cc4d1014ccc20ac92be3d6135420a3bc60ae29a3a8a9b4bb35fbff6"}, -] - -[package.dependencies] -huggingface-hub = ">=0.15.1" -numpy = "*" -Pillow = "*" -scikit-learn = "*" -scipy = "*" -torch = ">=1.11.0" -tqdm = "*" -transformers = ">=4.34.0,<5.0.0" - -[package.extras] -dev = ["accelerate (>=0.20.3)", "datasets", "pre-commit", "pytest", "ruff (>=0.3.0)"] -train = ["accelerate (>=0.20.3)", "datasets"] - [[package]] name = "six" version = "1.16.0" @@ -2701,17 +2369,6 @@ files = [ anyascii = "*" pyahocorasick = "*" -[[package]] -name = "threadpoolctl" -version = "3.5.0" -description = "threadpoolctl" -optional = false -python-versions = ">=3.8" -files = [ - {file = "threadpoolctl-3.5.0-py3-none-any.whl", hash = "sha256:56c1e26c150397e58c4926da8eeee87533b1e32bef131bd4bf6a2f45f3185467"}, - {file = "threadpoolctl-3.5.0.tar.gz", hash = "sha256:082433502dd922bf738de0d8bcc4fdcbf0979ff44c42bd40f5af8a282f6fa107"}, -] - [[package]] name = "tokenizers" version = "0.19.1" @@ -2842,31 +2499,21 @@ files = [ [[package]] name = "torch" -version = "2.3.1" +version = "2.3.1+cpu" description = "Tensors and Dynamic neural networks in Python with strong GPU acceleration" optional = false python-versions = ">=3.8.0" files = [ - {file = "torch-2.3.1-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:605a25b23944be5ab7c3467e843580e1d888b8066e5aaf17ff7bf9cc30001cc3"}, - {file = "torch-2.3.1-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:f2357eb0965583a0954d6f9ad005bba0091f956aef879822274b1bcdb11bd308"}, - {file = "torch-2.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:32b05fe0d1ada7f69c9f86c14ff69b0ef1957a5a54199bacba63d22d8fab720b"}, - {file = "torch-2.3.1-cp310-none-macosx_11_0_arm64.whl", hash = "sha256:7c09a94362778428484bcf995f6004b04952106aee0ef45ff0b4bab484f5498d"}, - {file = "torch-2.3.1-cp311-cp311-manylinux1_x86_64.whl", hash = "sha256:b2ec81b61bb094ea4a9dee1cd3f7b76a44555375719ad29f05c0ca8ef596ad39"}, - {file = "torch-2.3.1-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:490cc3d917d1fe0bd027057dfe9941dc1d6d8e3cae76140f5dd9a7e5bc7130ab"}, - {file = "torch-2.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:5802530783bd465fe66c2df99123c9a54be06da118fbd785a25ab0a88123758a"}, - {file = "torch-2.3.1-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:a7dd4ed388ad1f3d502bf09453d5fe596c7b121de7e0cfaca1e2017782e9bbac"}, - {file = "torch-2.3.1-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:a486c0b1976a118805fc7c9641d02df7afbb0c21e6b555d3bb985c9f9601b61a"}, - {file = "torch-2.3.1-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:224259821fe3e4c6f7edf1528e4fe4ac779c77addaa74215eb0b63a5c474d66c"}, - {file = "torch-2.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:e5fdccbf6f1334b2203a61a0e03821d5845f1421defe311dabeae2fc8fbeac2d"}, - {file = "torch-2.3.1-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:3c333dc2ebc189561514eda06e81df22bf8fb64e2384746b2cb9f04f96d1d4c8"}, - {file = "torch-2.3.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:07e9ba746832b8d069cacb45f312cadd8ad02b81ea527ec9766c0e7404bb3feb"}, - {file = "torch-2.3.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:462d1c07dbf6bb5d9d2f3316fee73a24f3d12cd8dacf681ad46ef6418f7f6626"}, - {file = "torch-2.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:ff60bf7ce3de1d43ad3f6969983f321a31f0a45df3690921720bcad6a8596cc4"}, - {file = "torch-2.3.1-cp38-none-macosx_11_0_arm64.whl", hash = "sha256:bee0bd33dc58aa8fc8a7527876e9b9a0e812ad08122054a5bff2ce5abf005b10"}, - {file = "torch-2.3.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:aaa872abde9a3d4f91580f6396d54888620f4a0b92e3976a6034759df4b961ad"}, - {file = "torch-2.3.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:3d7a7f7ef21a7520510553dc3938b0c57c116a7daee20736a9e25cbc0e832bdc"}, - {file = "torch-2.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:4777f6cefa0c2b5fa87223c213e7b6f417cf254a45e5829be4ccd1b2a4ee1011"}, - {file = "torch-2.3.1-cp39-none-macosx_11_0_arm64.whl", hash = "sha256:2bb5af780c55be68fe100feb0528d2edebace1d55cb2e351de735809ba7391eb"}, + {file = "torch-2.3.1+cpu-cp310-cp310-linux_x86_64.whl", hash = "sha256:d679e21d871982b9234444331a26350902cfd2d5ca44ce6f49896af8b3a3087d"}, + {file = "torch-2.3.1+cpu-cp310-cp310-win_amd64.whl", hash = "sha256:500bf790afc2fd374a15d06213242e517afccc50a46ea5955d321a9a68003335"}, + {file = "torch-2.3.1+cpu-cp311-cp311-linux_x86_64.whl", hash = "sha256:a272defe305dbd944aa28a91cc3db0f0149495b3ebec2e39723a7224fa05dc57"}, + {file = "torch-2.3.1+cpu-cp311-cp311-win_amd64.whl", hash = "sha256:d2965eb54d3c8818e2280a54bd53e8246a6bb34e4b10bd19c59f35b611dd9f05"}, + {file = "torch-2.3.1+cpu-cp312-cp312-linux_x86_64.whl", hash = "sha256:2141a6cb7021adf2f92a0fd372cfeac524ba460bd39ce3a641d30a561e41f69a"}, + {file = "torch-2.3.1+cpu-cp312-cp312-win_amd64.whl", hash = "sha256:6acdca2530462611095c44fd95af75ecd5b9646eac813452fe0adf31a9bc310a"}, + {file = "torch-2.3.1+cpu-cp38-cp38-linux_x86_64.whl", hash = "sha256:cab92d5101e6db686c5525e04d87cedbcf3a556073d71d07fbe7d1ce09630ffb"}, + {file = "torch-2.3.1+cpu-cp38-cp38-win_amd64.whl", hash = "sha256:dbc784569a367fd425158cf4ae82057dd3011185ba5fc68440432ba0562cb5b2"}, + {file = "torch-2.3.1+cpu-cp39-cp39-linux_x86_64.whl", hash = "sha256:a3cb8e61ba311cee1bb7463cbdcf3ebdfd071e2091e74c5785e3687eb02819f9"}, + {file = "torch-2.3.1+cpu-cp39-cp39-win_amd64.whl", hash = "sha256:df68668056e62c0332e03f43d9da5d4278b39df1ba58d30ec20d34242070955d"}, ] [package.dependencies] @@ -2875,17 +2522,6 @@ fsspec = "*" jinja2 = "*" mkl = {version = ">=2021.1.1,<=2021.4.0", markers = "platform_system == \"Windows\""} networkx = "*" -nvidia-cublas-cu12 = {version = "12.1.3.1", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} -nvidia-cuda-cupti-cu12 = {version = "12.1.105", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} -nvidia-cuda-nvrtc-cu12 = {version = "12.1.105", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} -nvidia-cuda-runtime-cu12 = {version = "12.1.105", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} -nvidia-cudnn-cu12 = {version = "8.9.2.26", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} -nvidia-cufft-cu12 = {version = "11.0.2.54", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} -nvidia-curand-cu12 = {version = "10.3.2.106", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} -nvidia-cusolver-cu12 = {version = "11.4.5.107", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} -nvidia-cusparse-cu12 = {version = "12.1.0.106", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} -nvidia-nccl-cu12 = {version = "2.20.5", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} -nvidia-nvtx-cu12 = {version = "12.1.105", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} sympy = "*" typing-extensions = ">=4.8.0" @@ -2893,6 +2529,11 @@ typing-extensions = ">=4.8.0" opt-einsum = ["opt-einsum (>=3.3)"] optree = ["optree (>=0.9.1)"] +[package.source] +type = "legacy" +url = "https://download.pytorch.org/whl/cpu" +reference = "pytorch-cpu" + [[package]] name = "tqdm" version = "4.66.4" @@ -2915,19 +2556,19 @@ telegram = ["requests"] [[package]] name = "transformers" -version = "4.41.2" +version = "4.42.4" description = "State-of-the-art Machine Learning for JAX, PyTorch and TensorFlow" optional = false python-versions = ">=3.8.0" files = [ - {file = "transformers-4.41.2-py3-none-any.whl", hash = "sha256:05555d20e43f808de1ef211ab64803cdb513170cef70d29a888b589caebefc67"}, - {file = "transformers-4.41.2.tar.gz", hash = "sha256:80a4db216533d573e9cc7388646c31ed9480918feb7c55eb211249cb23567f87"}, + {file = "transformers-4.42.4-py3-none-any.whl", hash = "sha256:6d59061392d0f1da312af29c962df9017ff3c0108c681a56d1bc981004d16d24"}, + {file = "transformers-4.42.4.tar.gz", hash = "sha256:f956e25e24df851f650cb2c158b6f4352dfae9d702f04c113ed24fc36ce7ae2d"}, ] [package.dependencies] filelock = "*" -huggingface-hub = ">=0.23.0,<1.0" -numpy = ">=1.17" +huggingface-hub = ">=0.23.2,<1.0" +numpy = ">=1.17,<2.0" packaging = ">=20.0" pyyaml = ">=5.1" regex = "!=2019.12.17" @@ -2939,14 +2580,15 @@ tqdm = ">=4.27" [package.extras] accelerate = ["accelerate (>=0.21.0)"] agents = ["Pillow (>=10.0.1,<=15.0)", "accelerate (>=0.21.0)", "datasets (!=2.5.0)", "diffusers", "opencv-python", "sentencepiece (>=0.1.91,!=0.1.92)", "torch"] -all = ["Pillow (>=10.0.1,<=15.0)", "accelerate (>=0.21.0)", "av (==9.2.0)", "codecarbon (==1.2.0)", "decord (==0.6.0)", "flax (>=0.4.1,<=0.7.0)", "jax (>=0.4.1,<=0.4.13)", "jaxlib (>=0.4.1,<=0.4.13)", "kenlm", "keras-nlp (>=0.3.1)", "librosa", "onnxconverter-common", "optax (>=0.0.8,<=0.1.4)", "optuna", "phonemizer", "protobuf", "pyctcdecode (>=0.4.0)", "ray[tune] (>=2.7.0)", "scipy (<1.13.0)", "sentencepiece (>=0.1.91,!=0.1.92)", "sigopt", "tensorflow (>2.9,<2.16)", "tensorflow-text (<2.16)", "tf2onnx", "timm", "tokenizers (>=0.19,<0.20)", "torch", "torchaudio", "torchvision"] +all = ["Pillow (>=10.0.1,<=15.0)", "accelerate (>=0.21.0)", "av (==9.2.0)", "codecarbon (==1.2.0)", "decord (==0.6.0)", "flax (>=0.4.1,<=0.7.0)", "jax (>=0.4.1,<=0.4.13)", "jaxlib (>=0.4.1,<=0.4.13)", "kenlm", "keras-nlp (>=0.3.1)", "librosa", "onnxconverter-common", "optax (>=0.0.8,<=0.1.4)", "optuna", "phonemizer", "protobuf", "pyctcdecode (>=0.4.0)", "ray[tune] (>=2.7.0)", "scipy (<1.13.0)", "sentencepiece (>=0.1.91,!=0.1.92)", "sigopt", "tensorflow (>2.9,<2.16)", "tensorflow-text (<2.16)", "tf2onnx", "timm (<=0.9.16)", "tokenizers (>=0.19,<0.20)", "torch", "torchaudio", "torchvision"] audio = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)"] +benchmark = ["optimum-benchmark (>=0.2.0)"] codecarbon = ["codecarbon (==1.2.0)"] deepspeed = ["accelerate (>=0.21.0)", "deepspeed (>=0.9.3)"] -deepspeed-testing = ["GitPython (<3.1.19)", "accelerate (>=0.21.0)", "beautifulsoup4", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "deepspeed (>=0.9.3)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "nltk", "optuna", "parameterized", "protobuf", "psutil", "pydantic", "pytest (>=7.2.0,<8.0.0)", "pytest-rich", "pytest-timeout", "pytest-xdist", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (==0.1.5)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "sentencepiece (>=0.1.91,!=0.1.92)", "tensorboard", "timeout-decorator"] -dev = ["GitPython (<3.1.19)", "Pillow (>=10.0.1,<=15.0)", "accelerate (>=0.21.0)", "av (==9.2.0)", "beautifulsoup4", "codecarbon (==1.2.0)", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "decord (==0.6.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "flax (>=0.4.1,<=0.7.0)", "fugashi (>=1.0)", "ipadic (>=1.0.0,<2.0)", "isort (>=5.5.4)", "jax (>=0.4.1,<=0.4.13)", "jaxlib (>=0.4.1,<=0.4.13)", "kenlm", "keras-nlp (>=0.3.1)", "librosa", "nltk", "onnxconverter-common", "optax (>=0.0.8,<=0.1.4)", "optuna", "parameterized", "phonemizer", "protobuf", "psutil", "pyctcdecode (>=0.4.0)", "pydantic", "pytest (>=7.2.0,<8.0.0)", "pytest-rich", "pytest-timeout", "pytest-xdist", "ray[tune] (>=2.7.0)", "rhoknp (>=1.1.0,<1.3.1)", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (==0.1.5)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "scikit-learn", "scipy (<1.13.0)", "sentencepiece (>=0.1.91,!=0.1.92)", "sigopt", "sudachidict-core (>=20220729)", "sudachipy (>=0.6.6)", "tensorboard", "tensorflow (>2.9,<2.16)", "tensorflow-text (<2.16)", "tf2onnx", "timeout-decorator", "timm", "tokenizers (>=0.19,<0.20)", "torch", "torchaudio", "torchvision", "unidic (>=1.0.2)", "unidic-lite (>=1.0.7)", "urllib3 (<2.0.0)"] -dev-tensorflow = ["GitPython (<3.1.19)", "Pillow (>=10.0.1,<=15.0)", "beautifulsoup4", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "isort (>=5.5.4)", "kenlm", "keras-nlp (>=0.3.1)", "librosa", "nltk", "onnxconverter-common", "onnxruntime (>=1.4.0)", "onnxruntime-tools (>=1.4.2)", "parameterized", "phonemizer", "protobuf", "psutil", "pyctcdecode (>=0.4.0)", "pydantic", "pytest (>=7.2.0,<8.0.0)", "pytest-rich", "pytest-timeout", "pytest-xdist", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (==0.1.5)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "scikit-learn", "sentencepiece (>=0.1.91,!=0.1.92)", "tensorboard", "tensorflow (>2.9,<2.16)", "tensorflow-text (<2.16)", "tf2onnx", "timeout-decorator", "tokenizers (>=0.19,<0.20)", "urllib3 (<2.0.0)"] -dev-torch = ["GitPython (<3.1.19)", "Pillow (>=10.0.1,<=15.0)", "accelerate (>=0.21.0)", "beautifulsoup4", "codecarbon (==1.2.0)", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "fugashi (>=1.0)", "ipadic (>=1.0.0,<2.0)", "isort (>=5.5.4)", "kenlm", "librosa", "nltk", "onnxruntime (>=1.4.0)", "onnxruntime-tools (>=1.4.2)", "optuna", "parameterized", "phonemizer", "protobuf", "psutil", "pyctcdecode (>=0.4.0)", "pydantic", "pytest (>=7.2.0,<8.0.0)", "pytest-rich", "pytest-timeout", "pytest-xdist", "ray[tune] (>=2.7.0)", "rhoknp (>=1.1.0,<1.3.1)", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (==0.1.5)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "scikit-learn", "sentencepiece (>=0.1.91,!=0.1.92)", "sigopt", "sudachidict-core (>=20220729)", "sudachipy (>=0.6.6)", "tensorboard", "timeout-decorator", "timm", "tokenizers (>=0.19,<0.20)", "torch", "torchaudio", "torchvision", "unidic (>=1.0.2)", "unidic-lite (>=1.0.7)", "urllib3 (<2.0.0)"] +deepspeed-testing = ["GitPython (<3.1.19)", "accelerate (>=0.21.0)", "beautifulsoup4", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "deepspeed (>=0.9.3)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "nltk", "optuna", "parameterized", "protobuf", "psutil", "pydantic", "pytest (>=7.2.0,<8.0.0)", "pytest-rich", "pytest-timeout", "pytest-xdist", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (==0.4.4)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "sentencepiece (>=0.1.91,!=0.1.92)", "tensorboard", "timeout-decorator"] +dev = ["GitPython (<3.1.19)", "Pillow (>=10.0.1,<=15.0)", "accelerate (>=0.21.0)", "av (==9.2.0)", "beautifulsoup4", "codecarbon (==1.2.0)", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "decord (==0.6.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "flax (>=0.4.1,<=0.7.0)", "fugashi (>=1.0)", "ipadic (>=1.0.0,<2.0)", "isort (>=5.5.4)", "jax (>=0.4.1,<=0.4.13)", "jaxlib (>=0.4.1,<=0.4.13)", "kenlm", "keras-nlp (>=0.3.1)", "librosa", "nltk", "onnxconverter-common", "optax (>=0.0.8,<=0.1.4)", "optuna", "parameterized", "phonemizer", "protobuf", "psutil", "pyctcdecode (>=0.4.0)", "pydantic", "pytest (>=7.2.0,<8.0.0)", "pytest-rich", "pytest-timeout", "pytest-xdist", "ray[tune] (>=2.7.0)", "rhoknp (>=1.1.0,<1.3.1)", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (==0.4.4)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "scikit-learn", "scipy (<1.13.0)", "sentencepiece (>=0.1.91,!=0.1.92)", "sigopt", "sudachidict-core (>=20220729)", "sudachipy (>=0.6.6)", "tensorboard", "tensorflow (>2.9,<2.16)", "tensorflow-text (<2.16)", "tf2onnx", "timeout-decorator", "timm (<=0.9.16)", "tokenizers (>=0.19,<0.20)", "torch", "torchaudio", "torchvision", "unidic (>=1.0.2)", "unidic-lite (>=1.0.7)", "urllib3 (<2.0.0)"] +dev-tensorflow = ["GitPython (<3.1.19)", "Pillow (>=10.0.1,<=15.0)", "beautifulsoup4", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "isort (>=5.5.4)", "kenlm", "keras-nlp (>=0.3.1)", "librosa", "nltk", "onnxconverter-common", "onnxruntime (>=1.4.0)", "onnxruntime-tools (>=1.4.2)", "parameterized", "phonemizer", "protobuf", "psutil", "pyctcdecode (>=0.4.0)", "pydantic", "pytest (>=7.2.0,<8.0.0)", "pytest-rich", "pytest-timeout", "pytest-xdist", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (==0.4.4)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "scikit-learn", "sentencepiece (>=0.1.91,!=0.1.92)", "tensorboard", "tensorflow (>2.9,<2.16)", "tensorflow-text (<2.16)", "tf2onnx", "timeout-decorator", "tokenizers (>=0.19,<0.20)", "urllib3 (<2.0.0)"] +dev-torch = ["GitPython (<3.1.19)", "Pillow (>=10.0.1,<=15.0)", "accelerate (>=0.21.0)", "beautifulsoup4", "codecarbon (==1.2.0)", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "fugashi (>=1.0)", "ipadic (>=1.0.0,<2.0)", "isort (>=5.5.4)", "kenlm", "librosa", "nltk", "onnxruntime (>=1.4.0)", "onnxruntime-tools (>=1.4.2)", "optuna", "parameterized", "phonemizer", "protobuf", "psutil", "pyctcdecode (>=0.4.0)", "pydantic", "pytest (>=7.2.0,<8.0.0)", "pytest-rich", "pytest-timeout", "pytest-xdist", "ray[tune] (>=2.7.0)", "rhoknp (>=1.1.0,<1.3.1)", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (==0.4.4)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "scikit-learn", "sentencepiece (>=0.1.91,!=0.1.92)", "sigopt", "sudachidict-core (>=20220729)", "sudachipy (>=0.6.6)", "tensorboard", "timeout-decorator", "timm (<=0.9.16)", "tokenizers (>=0.19,<0.20)", "torch", "torchaudio", "torchvision", "unidic (>=1.0.2)", "unidic-lite (>=1.0.7)", "urllib3 (<2.0.0)"] flax = ["flax (>=0.4.1,<=0.7.0)", "jax (>=0.4.1,<=0.4.13)", "jaxlib (>=0.4.1,<=0.4.13)", "optax (>=0.0.8,<=0.1.4)", "scipy (<1.13.0)"] flax-speech = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)"] ftfy = ["ftfy"] @@ -2957,25 +2599,26 @@ natten = ["natten (>=0.14.6,<0.15.0)"] onnx = ["onnxconverter-common", "onnxruntime (>=1.4.0)", "onnxruntime-tools (>=1.4.2)", "tf2onnx"] onnxruntime = ["onnxruntime (>=1.4.0)", "onnxruntime-tools (>=1.4.2)"] optuna = ["optuna"] -quality = ["GitPython (<3.1.19)", "datasets (!=2.5.0)", "isort (>=5.5.4)", "ruff (==0.1.5)", "urllib3 (<2.0.0)"] +quality = ["GitPython (<3.1.19)", "datasets (!=2.5.0)", "isort (>=5.5.4)", "ruff (==0.4.4)", "urllib3 (<2.0.0)"] ray = ["ray[tune] (>=2.7.0)"] retrieval = ["datasets (!=2.5.0)", "faiss-cpu"] +ruff = ["ruff (==0.4.4)"] sagemaker = ["sagemaker (>=2.31.0)"] sentencepiece = ["protobuf", "sentencepiece (>=0.1.91,!=0.1.92)"] serving = ["fastapi", "pydantic", "starlette", "uvicorn"] sigopt = ["sigopt"] sklearn = ["scikit-learn"] speech = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)", "torchaudio"] -testing = ["GitPython (<3.1.19)", "beautifulsoup4", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "nltk", "parameterized", "psutil", "pydantic", "pytest (>=7.2.0,<8.0.0)", "pytest-rich", "pytest-timeout", "pytest-xdist", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (==0.1.5)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "sentencepiece (>=0.1.91,!=0.1.92)", "tensorboard", "timeout-decorator"] +testing = ["GitPython (<3.1.19)", "beautifulsoup4", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "nltk", "parameterized", "psutil", "pydantic", "pytest (>=7.2.0,<8.0.0)", "pytest-rich", "pytest-timeout", "pytest-xdist", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (==0.4.4)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "sentencepiece (>=0.1.91,!=0.1.92)", "tensorboard", "timeout-decorator"] tf = ["keras-nlp (>=0.3.1)", "onnxconverter-common", "tensorflow (>2.9,<2.16)", "tensorflow-text (<2.16)", "tf2onnx"] -tf-cpu = ["keras (>2.9,<2.16)", "keras-nlp (>=0.3.1)", "onnxconverter-common", "tensorflow-cpu (>2.9,<2.16)", "tensorflow-probability (<2.16)", "tensorflow-text (<2.16)", "tf2onnx"] +tf-cpu = ["keras (>2.9,<2.16)", "keras-nlp (>=0.3.1)", "onnxconverter-common", "tensorflow-cpu (>2.9,<2.16)", "tensorflow-probability (<0.24)", "tensorflow-text (<2.16)", "tf2onnx"] tf-speech = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)"] -timm = ["timm"] +timm = ["timm (<=0.9.16)"] tokenizers = ["tokenizers (>=0.19,<0.20)"] torch = ["accelerate (>=0.21.0)", "torch"] torch-speech = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)", "torchaudio"] torch-vision = ["Pillow (>=10.0.1,<=15.0)", "torchvision"] -torchhub = ["filelock", "huggingface-hub (>=0.23.0,<1.0)", "importlib-metadata", "numpy (>=1.17)", "packaging (>=20.0)", "protobuf", "regex (!=2019.12.17)", "requests", "sentencepiece (>=0.1.91,!=0.1.92)", "tokenizers (>=0.19,<0.20)", "torch", "tqdm (>=4.27)"] +torchhub = ["filelock", "huggingface-hub (>=0.23.2,<1.0)", "importlib-metadata", "numpy (>=1.17,<2.0)", "packaging (>=20.0)", "protobuf", "regex (!=2019.12.17)", "requests", "sentencepiece (>=0.1.91,!=0.1.92)", "tokenizers (>=0.19,<0.20)", "torch", "tqdm (>=4.27)"] video = ["av (==9.2.0)", "decord (==0.6.0)"] vision = ["Pillow (>=10.0.1,<=15.0)"] @@ -3231,4 +2874,4 @@ test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", [metadata] lock-version = "2.0" python-versions = "~3.12" -content-hash = "6f3cea24ee781437f22206b6c78dc82869aacc270306d0950c3771078af09904" +content-hash = "2cc54b2bb53aef9e040db3e393d97f766eb5685c91c205472f65188a9787ac3b" diff --git a/pyproject.toml b/pyproject.toml index 72a6b40..cbbc15e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,8 +19,8 @@ python-dotenv = "1.0.1" pyyaml = "6.0.1" redis = { extras = ["hiredis"], version = "5.0.7" } redisvl = { extras = ["hiredis"], version = "0.2.3" } -sentence-transformers = "3.0.1" -numpy = "^2.0.1" +torch = { version = "^2.3.1+cpu", source = "pytorch-cpu" } +transformers = "^4.42.4" [tool.poetry.dev-dependencies] commitizen = "3.27.0" @@ -29,12 +29,18 @@ mkdocs = "1.6.0" mkdocs-material = "9.5.29" pre-commit = "3.7.1" pymdown-extensions = "10.8.1" +pyright = "^1.1.372" pytest = "8.2.2" pytest-asyncio = "0.23.7" pytest-mock = "3.14.0" pytest-sugar = "1.0.0" ruff = "0.5.2" +[[tool.poetry.source]] +name = "pytorch-cpu" +url = "https://download.pytorch.org/whl/cpu" +priority = "explicit" + [tool.pyright] typeCheckingMode = "basic" pythonVersion = "3.12" diff --git a/tests/integrations/test__redis.py b/tests/integrations/test__redis.py index d72c9dc..1c6f405 100644 --- a/tests/integrations/test__redis.py +++ b/tests/integrations/test__redis.py @@ -41,7 +41,7 @@ async def vectorized_message( models.VectorizedMessage A message with embedding vector """ - embedding = await vectorizer.embed(message.content) + embedding = await vectorizer.aencode(message.content) return models.VectorizedMessage(**message.model_dump(), embedding=embedding) From 9493bf5c2a2209ae94b05bd75253bfff752c4678 Mon Sep 17 00:00:00 2001 From: isaa_ctaylor Date: Mon, 22 Jul 2024 18:10:08 +0100 Subject: [PATCH 065/168] fix: fix sync command signature and internal logic (#38) --- courageous_comets/client.py | 13 ++++++------- tests/courageous_comets/test__client.py | 10 +++++----- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/courageous_comets/client.py b/courageous_comets/client.py index 9e3447c..db32d18 100644 --- a/courageous_comets/client.py +++ b/courageous_comets/client.py @@ -1,6 +1,5 @@ import logging import typing -from collections.abc import Collection from typing import override import discord @@ -105,7 +104,7 @@ async def setup_hook(self) -> None: @commands.is_owner() async def sync( ctx: commands.Context[commands.Bot], - guilds: Collection[discord.Object], + guilds: commands.Greedy[discord.Object], spec: typing.Literal["~", "*", "^"] | None = None, ) -> None: """ @@ -121,23 +120,23 @@ async def sync( ---------- ctx : commands.Context[commands.Bot] The context of the command. - guilds : Collection[discord.Object] + guilds : commands.Greedy[discord.Object] The guilds to sync to. spec : typing.Literal["~", "*", "^"] | None The scope to sync to. Defaults to `~`. """ async with ctx.typing(): if not guilds: - if spec == "~" and ctx.guild is not None: + if spec == "~": synced = await ctx.bot.tree.sync(guild=ctx.guild) elif spec == "*": synced = await ctx.bot.tree.sync() - elif spec == "^" and ctx.guild is not None: + elif spec == "^": ctx.bot.tree.clear_commands(guild=ctx.guild) await ctx.bot.tree.sync(guild=ctx.guild) synced = [] - elif ctx.guild is not None: - ctx.bot.tree.copy_global_to(guild=ctx.guild) + else: + ctx.bot.tree.copy_global_to(guild=ctx.guild) # type: ignore (@commands.guild_only() ensures ctx has guild attribute) synced = await ctx.bot.tree.sync(guild=ctx.guild) scope = "globally." if spec == "*" else "to the current guild." diff --git a/tests/courageous_comets/test__client.py b/tests/courageous_comets/test__client.py index 0f2f9a3..b223eb5 100644 --- a/tests/courageous_comets/test__client.py +++ b/tests/courageous_comets/test__client.py @@ -134,7 +134,7 @@ async def test__sync_syncs_to_current_guild(mock_context: MockType) -> None: - The bot.sync function is awaited with the expected parameters. - The ctx.send function is awaited with the expected message. """ - await sync(mock_context, [], "~") + await sync(mock_context, [], "~") # type: ignore mock_context.bot.tree.sync.assert_awaited_with(guild=mock_context.guild) mock_context.send.assert_awaited_with( @@ -151,7 +151,7 @@ async def test__sync_syncs_to_global_scope(mock_context: MockType) -> None: - The bot.sync function is awaited with the expected parameters. - The ctx.send function is awaited with the expected message """ - await sync(mock_context, [], "*") + await sync(mock_context, [], "*") # type: ignore mock_context.bot.tree.sync.assert_awaited_with() mock_context.send.assert_awaited_with( @@ -169,7 +169,7 @@ async def test__sync_removes_non_global_commands(mock_context: MockType) -> None - The bot.sync function is awaited with the expected parameters - The ctx.send function is awaited with the expected message """ - await sync(mock_context, [], "^") + await sync(mock_context, [], "^") # type: ignore mock_context.bot.tree.clear_commands.assert_called_with(guild=mock_context.guild) mock_context.bot.tree.sync.assert_awaited_with(guild=mock_context.guild) @@ -190,7 +190,7 @@ async def test__sync_syncs_to_given_guilds( """ guilds = [mocker.Mock(), mocker.Mock()] - await sync(mock_context, guilds) + await sync(mock_context, guilds) # type: ignore for guild in guilds: mock_context.bot.tree.sync.assert_any_await(guild=guild) @@ -216,7 +216,7 @@ async def test__sync_logs_exception_on_http_exception( logger_exception = mocker.spy(logger, "exception") - await sync(mock_context, guilds) + await sync(mock_context, guilds) # type: ignore for guild in guilds: logger_exception.assert_any_call("Failed to sync to guild %s", guild, exc_info=expected) From c488b62d19dc496a0c10adb7fa9d775281cd00a3 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Mon, 22 Jul 2024 19:48:22 +0200 Subject: [PATCH 066/168] feat: set up message content preprocessing (#40) --- courageous_comets/cogs/messages.py | 13 +- courageous_comets/preprocessing.py | 118 ++++++++++++++++++ courageous_comets/settings.py | 2 + poetry.lock | 13 +- pyproject.toml | 1 + .../courageous_comets/test__preprocessing.py | 100 +++++++++++++++ tests/integrations/test__messages.py | 2 +- 7 files changed, 245 insertions(+), 4 deletions(-) create mode 100644 courageous_comets/preprocessing.py create mode 100644 tests/courageous_comets/test__preprocessing.py diff --git a/courageous_comets/cogs/messages.py b/courageous_comets/cogs/messages.py index a9bdbdf..c783e2b 100644 --- a/courageous_comets/cogs/messages.py +++ b/courageous_comets/cogs/messages.py @@ -3,6 +3,7 @@ import discord from discord.ext import commands +from courageous_comets import preprocessing from courageous_comets.client import CourageousCometsBot from courageous_comets.models import VectorizedMessage from courageous_comets.redis import messages @@ -53,14 +54,22 @@ async def save_message(self, message: discord.Message) -> None: message.id, ) - embedding = await self.vectorizer.aencode(message.content) + text = preprocessing.process(message.clean_content) + + if not text: + return logger.debug( + "Ignoring message %s because it's empty after processing", + message.id, + ) + + embedding = await self.vectorizer.aencode(text) vectorized_message = VectorizedMessage( user_id=str(message.author.id), message_id=str(message.id), channel_id=str(message.channel.id), guild_id=str(message.guild.id), - content=message.content, + content=text, timestamp=message.created_at, embedding=embedding, ) diff --git a/courageous_comets/preprocessing.py b/courageous_comets/preprocessing.py new file mode 100644 index 0000000..b150cc1 --- /dev/null +++ b/courageous_comets/preprocessing.py @@ -0,0 +1,118 @@ +import re +import string +from collections.abc import Callable +from functools import partial + +import contractions +from unidecode import unidecode + +from courageous_comets import settings + +Processor = Callable[[str], str] + + +def drop_links(text: str) -> str: + """ + Remove links from the given text. + + Parameters + ---------- + text : str + The text to process. + + Returns + ------- + str + The text with links removed. + """ + return re.sub(r"http\S+", "", text) + + +def drop_punctuation(text: str) -> str: + """ + Remove punctuation from the given text. + + Parameters + ---------- + text : str + The text to process. + + Returns + ------- + str + The text without punctuation. + """ + return text.translate(str.maketrans("", "", string.punctuation)) + + +def drop_very_long_words(text: str, max_length: int) -> str: + """ + Remove very long words from the given text. + + Parameters + ---------- + text : str + The text to process. + max_length : int + The maximum length of a word to keep. + + Returns + ------- + str + The text with very long words removed. + """ + return re.sub(r"\S{%d,}" % max_length, "", text) + + +def truncate(text: str, max_length: int) -> str: + """ + Truncate the given text to the specified length. + + Parameters + ---------- + text : str + The text to truncate. + max_length : int + The maximum length of the text. + + Returns + ------- + str + The truncated text. + """ + return text[:max_length] + + +# Steps are executed in order +PROCESSORS: list[Processor] = [ + drop_links, + unidecode, + contractions.fix, # type: ignore + drop_punctuation, + partial(drop_very_long_words, max_length=settings.PREPROCESSING_MAX_WORD_LENGTH), + partial(truncate, max_length=settings.PREPROCESSING_MAX_WORD_LENGTH), +] + + +def process(text: str, processors: list[Processor] = PROCESSORS) -> str: + """ + Process the text using all available processors. + + Parameters + ---------- + text : str + The text to process. + processors : list[Processor], optional + The processors to use, by default uses a predefined set of processors. + + Returns + ------- + str + The processed text. + """ + result = text + + for processor in processors: + text = processor(result) + + return result diff --git a/courageous_comets/settings.py b/courageous_comets/settings.py index 7228874..753972d 100644 --- a/courageous_comets/settings.py +++ b/courageous_comets/settings.py @@ -154,6 +154,8 @@ def setup_logging() -> None: BOT_CONFIG_PATH = read_bot_config_path() NLTK_DATA_DIR = os.getenv("NLTK_DATA", "nltk_data") NLTK_DOWNLOAD_CONCURRENCY = read_int("NLTK_DOWNLOAD_CONCURRENCY", 3) + PREPROCESSING_MAX_WORD_LENGTH = read_int("MAX_WORD_LENGTH", 35) + PREPROCESSING_MESSAGE_TRUNCATE_LENGTH = read_int("TRUNCATE_LENGTH", 256) REDIS_HOST = os.getenv("REDIS_HOST", "localhost") REDIS_PORT = read_redis_port() REDIS_PASSWORD = os.getenv("REDIS_PASSWORD") diff --git a/poetry.lock b/poetry.lock index 1104237..5f4ad1e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2633,6 +2633,17 @@ files = [ {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, ] +[[package]] +name = "unidecode" +version = "1.3.8" +description = "ASCII transliterations of Unicode text" +optional = false +python-versions = ">=3.5" +files = [ + {file = "Unidecode-1.3.8-py3-none-any.whl", hash = "sha256:d130a61ce6696f8148a3bd8fe779c99adeb4b870584eeb9526584e9aa091fd39"}, + {file = "Unidecode-1.3.8.tar.gz", hash = "sha256:cfdb349d46ed3873ece4586b96aa75258726e2fa8ec21d6f00a591d98806c2f4"}, +] + [[package]] name = "urllib3" version = "2.2.2" @@ -2874,4 +2885,4 @@ test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", [metadata] lock-version = "2.0" python-versions = "~3.12" -content-hash = "2cc54b2bb53aef9e040db3e393d97f766eb5685c91c205472f65188a9787ac3b" +content-hash = "0b8a8a895ed65e8231b563269fc9a778de7d90b0f2d25ad9eb7bd59b3c1802a9" diff --git a/pyproject.toml b/pyproject.toml index cbbc15e..c924b4d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,6 +21,7 @@ redis = { extras = ["hiredis"], version = "5.0.7" } redisvl = { extras = ["hiredis"], version = "0.2.3" } torch = { version = "^2.3.1+cpu", source = "pytorch-cpu" } transformers = "^4.42.4" +unidecode = "^1.3.8" [tool.poetry.dev-dependencies] commitizen = "3.27.0" diff --git a/tests/courageous_comets/test__preprocessing.py b/tests/courageous_comets/test__preprocessing.py new file mode 100644 index 0000000..74f7396 --- /dev/null +++ b/tests/courageous_comets/test__preprocessing.py @@ -0,0 +1,100 @@ +import pytest + +from courageous_comets.preprocessing import drop_links, drop_punctuation, drop_very_long_words + + +@pytest.mark.parametrize( + ("text", "expected"), + [ + ("Hello, world!", "Hello, world!"), + ("Hello, http://world.com!", "Hello, "), + ("Hello, https://world.com! How are you?", "Hello, How are you?"), + ], +) +def test__drop_links(text: str, expected: str) -> None: + """ + Test whether `drop_links` removes links from the given text. + + Asserts + ------- + - Links are removed from the given text. + - Text without links is not modified. + """ + assert drop_links(text) == expected + + +@pytest.mark.parametrize( + ("text", "expected"), + [ + ( + "Hello, world!", + "Hello world", + ), + ( + "Testing... 1, 2, 3.", + "Testing 1 2 3", + ), + ( + "No punctuation here", + "No punctuation here", + ), + ( + "Special characters: @#&*()", + "Special characters ", + ), + ( + "Mixed punctuation! How's it going?", + "Mixed punctuation Hows it going", + ), + ( + "End with punctuation.", + "End with punctuation", + ), + ( + "Multiple spaces and punctuation!!!", + "Multiple spaces and punctuation", + ), + ( + "Punctuation-in-the-middle.", + "Punctuationinthemiddle", + ), + ( + "12345!@#$%", + "12345", + ), + ( + "Quotes 'single' and \"double\"", + "Quotes single and double", + ), + ], +) +def test__drop_punctuation(text: str, expected: str) -> None: + """ + Test whether `drop_punctuation` removes punctuation from the given text. + + Asserts + ------- + - Punctuation is removed from the given text. + - Text without punctuation is not modified. + """ + assert drop_punctuation(text) == expected + + +@pytest.mark.parametrize( + ("text", "expected"), + [ + ("Hello, world!", "Hello, world!"), + ("Hello, verylongword!", "Hello, "), + ("Hello, verylongword! How are you?", "Hello, How are you?"), + ], +) +def test__drop_very_long_word(text: str, expected: str) -> None: + """ + Test whether `drop_very_long_words` removes very long words from the given text. + + Asserts + ------- + - Very long words are removed from the given text. + - Text without very long words is not modified. + """ + assert drop_very_long_words(text, max_length=10) == expected diff --git a/tests/integrations/test__messages.py b/tests/integrations/test__messages.py index ace9ce2..d0ae573 100644 --- a/tests/integrations/test__messages.py +++ b/tests/integrations/test__messages.py @@ -28,7 +28,7 @@ async def test__messages_on_message__message_saved_to_redis( """ message = mocker.MagicMock(spec=discord.Message) - message.content = "The quick brown fox jumps over the lazy dog." + message.clean_content = "The quick brown fox jumps over the lazy dog." message.id = 1 message.author.id = 1 message.channel.id = 1 From 44762afd1320591633673c254f1e106a055a6d00 Mon Sep 17 00:00:00 2001 From: Uchechukwu Orji Date: Mon, 22 Jul 2024 19:20:02 +0100 Subject: [PATCH 067/168] feat: store sentiment analysis with message data (#39) --- courageous_comets/cogs/messages.py | 46 ++++--- courageous_comets/models.py | 24 ++-- courageous_comets/redis/messages.py | 10 +- courageous_comets/redis/schema.py | 5 + courageous_comets/sentiment.py | 87 +------------ courageous_comets/vectorizer.py | 8 +- tests/courageous_comets/test__sentiment.py | 140 +-------------------- tests/integrations/test__redis.py | 44 ++++--- 8 files changed, 87 insertions(+), 277 deletions(-) diff --git a/courageous_comets/cogs/messages.py b/courageous_comets/cogs/messages.py index c783e2b..e513bf9 100644 --- a/courageous_comets/cogs/messages.py +++ b/courageous_comets/cogs/messages.py @@ -5,8 +5,9 @@ from courageous_comets import preprocessing from courageous_comets.client import CourageousCometsBot -from courageous_comets.models import VectorizedMessage +from courageous_comets.models import MessageAnalysis from courageous_comets.redis import messages +from courageous_comets.sentiment import calculate_sentiment from courageous_comets.vectorizer import Vectorizer logger = logging.getLogger(__name__) @@ -47,12 +48,21 @@ async def save_message(self, message: discord.Message) -> None: "Ignoring message %s because the bot is not connected to Redis", message.id, ) - - if not message.guild: - return logger.debug( - "Ignoring message %s because it's not in a guild", - message.id, - ) + # Ignore empty messages + if not message.content: + logger.warning("Ignoring empty message %s", message.id) + return None + + # Extract the IDs from the message + guild_id = message.guild.id if message.guild else 0 + channel_id = message.channel.id if message.channel else 0 + user_id = message.author.id if message.author else 0 + message_id = message.id if message.id else 0 + + # Ignore messages without all required IDs + if not all((guild_id, channel_id, user_id, message_id)): + logger.warning("Ignoring message %s with missing IDs", message.id) + return None text = preprocessing.process(message.clean_content) @@ -63,19 +73,19 @@ async def save_message(self, message: discord.Message) -> None: ) embedding = await self.vectorizer.aencode(text) - - vectorized_message = VectorizedMessage( - user_id=str(message.author.id), - message_id=str(message.id), - channel_id=str(message.channel.id), - guild_id=str(message.guild.id), - content=text, - timestamp=message.created_at, - embedding=embedding, + key = await messages.save_message( + self.bot.redis, + MessageAnalysis( + user_id=str(message.author.id), + message_id=str(message.id), + channel_id=str(message.channel.id), + guild_id=str(message.guild.id), # pyright: ignore + timestamp=message.created_at, + embedding=embedding, + sentiment=calculate_sentiment(text), + ), ) - key = await messages.save_message(self.bot.redis, vectorized_message) - return logger.info( "Saved message %s to Redis with key %s", message.id, diff --git a/courageous_comets/models.py b/courageous_comets/models.py index 52016a5..b4258f9 100644 --- a/courageous_comets/models.py +++ b/courageous_comets/models.py @@ -33,8 +33,6 @@ class Message(BaseModel): The timestamp when the message was sent. user_id : str The ID of the user who sent the message. - content : str - The content of the message. """ message_id: str @@ -42,20 +40,18 @@ class Message(BaseModel): guild_id: str timestamp: UnixTimestamp user_id: str - content: str class VectorizedMessage(Message): - """Message with vector embedding of content. + """Message with embedding vector of content. Attributes ---------- - embedding : bytes - The embedding of the content. - embedding: bytes + embedding : list[float] + The embedding vector of the message content. """ - embedding: bytes + embedding: list[float] class SentimentResult(BaseModel): @@ -78,3 +74,15 @@ class SentimentResult(BaseModel): neu: float pos: float compound: float + + +class MessageAnalysis(VectorizedMessage): + """Vectorized message with sentiment analysis. + + Attributes + ---------- + sentiment : SentimentResult + The results of sentiment analysis. + """ + + sentiment: SentimentResult diff --git a/courageous_comets/redis/messages.py b/courageous_comets/redis/messages.py index 3e63e0f..ac771d1 100644 --- a/courageous_comets/redis/messages.py +++ b/courageous_comets/redis/messages.py @@ -47,7 +47,7 @@ async def update_message_tokens( async def save_message( redis: Redis, - message: models.VectorizedMessage, + message: models.MessageAnalysis, ) -> str: """Save a message on Redis. @@ -55,7 +55,7 @@ async def save_message( ---------- redis : Redis The Redis connection instance. - message : models.VectorizedMessage + message : models.MessageAnalysis The message to save Returns @@ -80,8 +80,7 @@ async def save_message( async def get_similar_messages( redis: Redis, - message: models.Message, - embedding: bytes, + message: models.VectorizedMessage, limit: int = 10, scope: StatisticScopeEnum = StatisticScopeEnum.GUILD, ) -> list[models.Message]: @@ -121,13 +120,12 @@ async def get_similar_messages( raise ValueError(error_message) query = VectorQuery( - vector=embedding, + vector=message.embedding, vector_field_name="embedding", return_fields=[ "message_id", "channel_id", "user_id", - "content", "guild_id", "timestamp", ], diff --git a/courageous_comets/redis/schema.py b/courageous_comets/redis/schema.py index e82f982..73e6e2c 100644 --- a/courageous_comets/redis/schema.py +++ b/courageous_comets/redis/schema.py @@ -4,6 +4,7 @@ "index": { "name": "message_idx", "prefix": settings.REDIS_KEYS_PREFIX, + "storage_type": "json", }, "fields": [ {"name": "content", "type": "text"}, @@ -22,5 +23,9 @@ "datatype": "float32", }, }, + {"name": "neu", "type": "numeric", "path": "$.sentiment.neu"}, + {"name": "neg", "type": "numeric", "path": "$.sentiment.neg"}, + {"name": "pos", "type": "numeric", "path": "$.sentiment.pos"}, + {"name": "compound", "type": "numeric", "path": "$.sentiment.compound"}, ], } diff --git a/courageous_comets/sentiment.py b/courageous_comets/sentiment.py index dc683b8..07e1edb 100644 --- a/courageous_comets/sentiment.py +++ b/courageous_comets/sentiment.py @@ -1,18 +1,15 @@ import logging -from discord import Message from nltk.sentiment import SentimentIntensityAnalyzer -from redis.asyncio import Redis from courageous_comets.models import SentimentResult -from courageous_comets.redis.keys import key_schema MAX_MESSAGE_LENGTH = 256 logger = logging.getLogger(__name__) -def calculate_sentiment(content: str, key: str) -> SentimentResult: +def calculate_sentiment(content: str) -> SentimentResult: """ Calculate the sentiment of a message. @@ -25,8 +22,6 @@ def calculate_sentiment(content: str, key: str) -> SentimentResult: ---------- content : str The message content to analyze. - key : str - The Redis key for the message. Used for logging. Returns ------- @@ -36,87 +31,9 @@ def calculate_sentiment(content: str, key: str) -> SentimentResult: truncated = content[:MAX_MESSAGE_LENGTH] if truncated != content: - logger.warning("Truncated message %s to %s characters", key, MAX_MESSAGE_LENGTH) + logger.warning("Truncated message to %s characters", MAX_MESSAGE_LENGTH) sia = SentimentIntensityAnalyzer() result = sia.polarity_scores(truncated) return SentimentResult.model_validate(result) - - -async def store_sentiment(message: Message, redis: Redis) -> None: - """ - Calculate the sentiment of a message and store the result in Redis. - - A message must have a guild, channel, author and message ID. If any of these are missing, - the message will be ignored. - - Empty messages will also be ignored. - - Parameters - ---------- - message : discord.Message - The message to process. - redis : redis.asyncio.Redis - The Redis connection instance. - """ - # Ignore empty messages - if not message.content: - logger.warning("Ignoring empty message %s", message.id) - return - - # Extract the IDs from the message - guild_id = message.guild.id if message.guild else 0 - channel_id = message.channel.id if message.channel else 0 - user_id = message.author.id if message.author else 0 - message_id = message.id if message.id else 0 - - # Ignore messages without all required IDs - if not all((guild_id, channel_id, user_id, message_id)): - logger.warning("Ignoring message %s with missing IDs", message.id) - return - - # Construct the Redis key - key = key_schema.sentiment_tokens( - guild_id=guild_id, - channel_id=channel_id, - user_id=user_id, - message_id=message_id, - ) - - # Calculate the sentiment - sentiment = calculate_sentiment(message.content, key) - - # Store the sentiment in Redis - await redis.hset( - key, - mapping=sentiment.model_dump(mode="json"), - ) # pyright: ignore[reportGeneralTypeIssues] - - logger.info("Stored sentiment for message %s", key) - - -async def get_sentiment(key: str, redis: Redis) -> SentimentResult: - """ - Retrieve the sentiment of a message from Redis. - - Parameters - ---------- - key : str - The Redis key to retrieve the sentiment from. - redis : redis.asyncio.Redis - The Redis connection instance. - - Returns - ------- - courageous_comets.models.SentimentResult - The sentiment of the message. - """ - neg, neu, pos, compound = await redis.hmget(key, "neg", "neu", "pos", "compound") # type: ignore - - return SentimentResult( - neg=float(neg or 0), - neu=float(neu or 0), - pos=float(pos or 0), - compound=float(compound or 0), - ) diff --git a/courageous_comets/vectorizer.py b/courageous_comets/vectorizer.py index 4610ba1..d8ca0b0 100644 --- a/courageous_comets/vectorizer.py +++ b/courageous_comets/vectorizer.py @@ -37,7 +37,7 @@ def __init__(self) -> None: cache_dir=settings.HF_HOME, ) - def encode(self, message: str) -> bytes: + def encode(self, message: str) -> list[float]: """ Create vector embedding of a message. @@ -57,7 +57,7 @@ def encode(self, message: str) -> bytes: Returns ------- - bytes + list[float] The vector embeddings of the message """ @@ -97,9 +97,9 @@ def mean_pooling(model_output: list[Tensor], attention_mask: Tensor) -> Tensor: torch_nn_functional.normalize(sentence_embeddings, p=2, dim=1) .numpy() .astype(np.float32) - .tobytes() + .tolist()[0] ) - async def aencode(self, message: str) -> bytes: + async def aencode(self, message: str) -> list[float]: """Create a vector embedding of message asynchronously.""" return await asyncio.to_thread(self.encode, message) diff --git a/tests/courageous_comets/test__sentiment.py b/tests/courageous_comets/test__sentiment.py index 488fc5a..6033086 100644 --- a/tests/courageous_comets/test__sentiment.py +++ b/tests/courageous_comets/test__sentiment.py @@ -1,17 +1,12 @@ -from unittest.mock import Mock - import pytest -from pytest_mock import MockerFixture, MockType +from pytest_mock import MockerFixture from redis.asyncio import Redis from courageous_comets.models import SentimentResult -from courageous_comets.redis.keys import key_schema from courageous_comets.sentiment import ( MAX_MESSAGE_LENGTH, calculate_sentiment, - get_sentiment, logger, - store_sentiment, ) @@ -40,7 +35,7 @@ def test__calculate_sentiment_analyzes_sentiment_of_given_text( pos=mocker.ANY, compound=mocker.ANY, ) - result = calculate_sentiment("I love this product!", "test") + result = calculate_sentiment("I love this product!") assert result == expected @@ -65,134 +60,5 @@ def test__calculate_sentiment_truncates_long_messages( - The function truncates messages longer than 256 characters. """ logger_warning = mocker.spy(logger, "warning") - calculate_sentiment(message, "test") + calculate_sentiment(message) assert logger_warning.called == expected - - -async def test__store_sentiment_calculates_and_stores_sentiment( - *, - mocker: MockerFixture, - redis: MockType, -) -> None: - """ - Test whether the store sentiment function calculates and stores the sentiment of a message. - - Asserts - ------- - - The sentiment is calculated for the message content. - - The sentiment is stored in the database. - """ - message = mocker.Mock( - content="I love this product!", - guild=Mock(id=1), - channel=Mock(id=1), - author=Mock(id=1), - id=1, - ) - - await store_sentiment(message, redis) - - redis.hset.assert_awaited_with( - key_schema.sentiment_tokens(1, 1, 1, 1), - mapping={ - "neg": mocker.ANY, - "neu": mocker.ANY, - "pos": mocker.ANY, - "compound": mocker.ANY, - }, - ) - - -async def test__store_sentiment_ignores_empty_messages( - *, - mocker: MockerFixture, - redis: MockType, -) -> None: - """ - Test whether the store sentiment function ignores empty messages. - - Asserts - ------- - - The database is not updated when the message is empty. - """ - message = mocker.Mock(content="") - await store_sentiment(message, redis) - redis.hset.assert_not_awaited() - - -@pytest.mark.parametrize( - "message", - [ - Mock(content="test", guild=None, channel=None, author=None, id=1), - Mock(content="test", guild=Mock(id=1), channel=None, author=None, id=1), - Mock(content="test", guild=Mock(id=1), channel=Mock(id=1), author=None, id=1), - ], -) -async def test__store_sentiment_ignores_messages_without_ids( - *, - redis: MockType, - message: MockType, -) -> None: - """ - Test whether the store sentiment function ignores messages without IDs. - - Asserts - ------- - - The database is not updated when the message is missing IDs. - """ - await store_sentiment(message, redis) - redis.hset.assert_not_awaited() - - -async def test__get_sentiment_retrieves_sentiment_from_redis( - *, - redis: MockType, -) -> None: - """ - Test whether the get sentiment function retrieves the sentiment of a message from Redis. - - Asserts - ------- - - The sentiment is retrieved from the database. - """ - key = key_schema.sentiment_tokens(1, 1, 1, 1) - redis.hmget.return_value = ["1.0", "1.0", "1.0", "1.0"] - - expected = SentimentResult( - neg=1.0, - neu=1.0, - pos=1.0, - compound=1.0, - ) - - result = await get_sentiment(key, redis) - - redis.hmget.assert_awaited_with(key, "neg", "neu", "pos", "compound") - assert result == expected - - -async def test__get_sentiment_handles_missing_sentiment( - *, - redis: MockType, -) -> None: - """ - Test whether the get sentiment function handles missing sentiment in Redis. - - Asserts - ------- - - The function returns default sentiment values when the sentiment is missing. - """ - key = key_schema.sentiment_tokens(1, 1, 1, 1) - redis.hmget.return_value = [None, None, None, None] - - expected = SentimentResult( - neg=0.0, - neu=0.0, - pos=0.0, - compound=0.0, - ) - - result = await get_sentiment(key, redis) - - redis.hmget.assert_awaited_with(key, "neg", "neu", "pos", "compound") - assert expected == result diff --git a/tests/integrations/test__redis.py b/tests/integrations/test__redis.py index 1c6f405..16f5d99 100644 --- a/tests/integrations/test__redis.py +++ b/tests/integrations/test__redis.py @@ -1,53 +1,55 @@ import datetime -import pytest import pytest_asyncio from redis.asyncio import Redis from courageous_comets import models from courageous_comets.redis.keys import key_schema from courageous_comets.redis.messages import get_similar_messages, save_message +from courageous_comets.sentiment import calculate_sentiment from courageous_comets.vectorizer import Vectorizer -@pytest.fixture(scope="session") -def message() -> models.Message: - """Fixture that sets up a message. +@pytest_asyncio.fixture(scope="session") +async def message(vectorizer: Vectorizer) -> models.MessageAnalysis: + """Fixture that sets up an analysis of a discord message. Returns ------- models.Message A Redis model of a Discord message """ - return models.Message( + content = "The quick brown fox jumps over the lazy dog." + embedding = await vectorizer.aencode(content) + return models.MessageAnalysis( message_id="1", channel_id="1", guild_id="1", timestamp=datetime.datetime.fromtimestamp(0, datetime.UTC), + embedding=embedding, user_id="1", - content="The quick brown fox jumps over the lazy dog.", + sentiment=calculate_sentiment(content), ) @pytest_asyncio.fixture(scope="session") async def vectorized_message( - message: models.Message, - vectorizer: Vectorizer, + message: models.MessageAnalysis, ) -> models.VectorizedMessage: - """Fixture that creates an embedding vector of contents of message. + """Fixture that creates a models.VectorizedMessage. Returns ------- models.VectorizedMessage A message with embedding vector """ - embedding = await vectorizer.aencode(message.content) - return models.VectorizedMessage(**message.model_dump(), embedding=embedding) + # NOTE: This works because BaseModel ignores extra fields + return models.VectorizedMessage(**message.model_dump()) async def test__save_message( redis: Redis, - vectorized_message: models.VectorizedMessage, + message: models.MessageAnalysis, ) -> None: """ Tests whether the save_mesage function stores the message on Redis. @@ -56,16 +58,16 @@ async def test__save_message( ------- - The returned key is the same as the one constructed by the key_schema """ - key = await save_message(redis, vectorized_message) + key = await save_message(redis, message) assert key == key_schema.guild_messages( - guild_id=vectorized_message.guild_id, - message_id=vectorized_message.message_id, + guild_id=message.guild_id, + message_id=message.message_id, ) async def test__get_similar_message( redis: Redis, - message: models.Message, + message: models.MessageAnalysis, vectorized_message: models.VectorizedMessage, ) -> None: """ @@ -75,7 +77,11 @@ async def test__get_similar_message( ------- - The returned message is the same message whose embedding vector was used """ - await save_message(redis, vectorized_message) - messages = await get_similar_messages(redis, message, vectorized_message.embedding) + await save_message(redis, message) + messages = await get_similar_messages(redis, vectorized_message) assert len(messages) == 1 - assert messages[0].model_dump() == message.model_dump() + redis_message = messages[0] + assert redis_message.channel_id == message.channel_id + assert redis_message.guild_id == message.guild_id + assert redis_message.message_id == message.message_id + assert message.timestamp == message.timestamp From 4e2e1963c67b5f9b56eb8e80cad94d4e66c7b57e Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Mon, 22 Jul 2024 18:40:08 +0000 Subject: [PATCH 068/168] =?UTF-8?q?bump:=20version=200.2.0=20=E2=86=92=200?= =?UTF-8?q?.3.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/CHANGELOG.md | 23 +++++++++++++++++++++++ pyproject.toml | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 247bf6f..c41a30a 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,3 +1,26 @@ +## v0.3.0 (2024-07-22) + +### Feat + +- store sentiment analysis with message data (#39) +- set up message content preprocessing (#40) +- replace sentence_transformers with transformers library (#36) +- add messages cog (#32) +- return messages based on similarity score (#30) + +### Fix + +- fix sync command signature and internal logic (#38) +- save messages with message_id on redis (#33) +- ensure consistent log output (#29) +- suppress warnings from libraries + +### Refactor + +- remove hfvectorizer (#35) +- include save_message in Messages cog (#34) +- remove throws clause from docstring + ## v0.2.0 (2024-07-21) ### Feat diff --git a/pyproject.toml b/pyproject.toml index c924b4d..f9920f5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "courageous-comets" -version = "0.2.0" +version = "0.3.0" authors = ["Courageous Comets"] description = "" readme = "docs/README.md" From b2a0f1f39496a81cb12ed9526caf46471115aa1a Mon Sep 17 00:00:00 2001 From: Uchechukwu Orji Date: Tue, 23 Jul 2024 05:21:06 +0100 Subject: [PATCH 069/168] feat: revert back to hash data type for storing messages (#41) --- courageous_comets/cogs/messages.py | 45 +++---- courageous_comets/models.py | 21 +--- courageous_comets/redis/messages.py | 9 +- courageous_comets/redis/schema.py | 5 - courageous_comets/sentiment.py | 87 ++++++++++++- courageous_comets/vectorizer.py | 8 +- tests/courageous_comets/test__sentiment.py | 140 ++++++++++++++++++++- tests/integrations/test__redis.py | 44 +++---- 8 files changed, 272 insertions(+), 87 deletions(-) diff --git a/courageous_comets/cogs/messages.py b/courageous_comets/cogs/messages.py index e513bf9..837e57b 100644 --- a/courageous_comets/cogs/messages.py +++ b/courageous_comets/cogs/messages.py @@ -5,9 +5,8 @@ from courageous_comets import preprocessing from courageous_comets.client import CourageousCometsBot -from courageous_comets.models import MessageAnalysis +from courageous_comets.models import VectorizedMessage from courageous_comets.redis import messages -from courageous_comets.sentiment import calculate_sentiment from courageous_comets.vectorizer import Vectorizer logger = logging.getLogger(__name__) @@ -48,21 +47,12 @@ async def save_message(self, message: discord.Message) -> None: "Ignoring message %s because the bot is not connected to Redis", message.id, ) - # Ignore empty messages - if not message.content: - logger.warning("Ignoring empty message %s", message.id) - return None - - # Extract the IDs from the message - guild_id = message.guild.id if message.guild else 0 - channel_id = message.channel.id if message.channel else 0 - user_id = message.author.id if message.author else 0 - message_id = message.id if message.id else 0 - - # Ignore messages without all required IDs - if not all((guild_id, channel_id, user_id, message_id)): - logger.warning("Ignoring message %s with missing IDs", message.id) - return None + + if not message.guild: + return logger.debug( + "Ignoring message %s because it's not in a guild", + message.id, + ) text = preprocessing.process(message.clean_content) @@ -73,19 +63,18 @@ async def save_message(self, message: discord.Message) -> None: ) embedding = await self.vectorizer.aencode(text) - key = await messages.save_message( - self.bot.redis, - MessageAnalysis( - user_id=str(message.author.id), - message_id=str(message.id), - channel_id=str(message.channel.id), - guild_id=str(message.guild.id), # pyright: ignore - timestamp=message.created_at, - embedding=embedding, - sentiment=calculate_sentiment(text), - ), + + vectorized_message = VectorizedMessage( + user_id=str(message.author.id), + message_id=str(message.id), + channel_id=str(message.channel.id), + guild_id=str(message.guild.id), + timestamp=message.created_at, + embedding=embedding, ) + key = await messages.save_message(self.bot.redis, vectorized_message) + return logger.info( "Saved message %s to Redis with key %s", message.id, diff --git a/courageous_comets/models.py b/courageous_comets/models.py index b4258f9..1c24287 100644 --- a/courageous_comets/models.py +++ b/courageous_comets/models.py @@ -43,15 +43,16 @@ class Message(BaseModel): class VectorizedMessage(Message): - """Message with embedding vector of content. + """Message with vector embedding of content. Attributes ---------- - embedding : list[float] - The embedding vector of the message content. + embedding : bytes + The embedding of the content. + embedding: bytes """ - embedding: list[float] + embedding: bytes class SentimentResult(BaseModel): @@ -74,15 +75,3 @@ class SentimentResult(BaseModel): neu: float pos: float compound: float - - -class MessageAnalysis(VectorizedMessage): - """Vectorized message with sentiment analysis. - - Attributes - ---------- - sentiment : SentimentResult - The results of sentiment analysis. - """ - - sentiment: SentimentResult diff --git a/courageous_comets/redis/messages.py b/courageous_comets/redis/messages.py index ac771d1..c0615a9 100644 --- a/courageous_comets/redis/messages.py +++ b/courageous_comets/redis/messages.py @@ -47,7 +47,7 @@ async def update_message_tokens( async def save_message( redis: Redis, - message: models.MessageAnalysis, + message: models.VectorizedMessage, ) -> str: """Save a message on Redis. @@ -55,7 +55,7 @@ async def save_message( ---------- redis : Redis The Redis connection instance. - message : models.MessageAnalysis + message : models.VectorizedMessage The message to save Returns @@ -80,7 +80,8 @@ async def save_message( async def get_similar_messages( redis: Redis, - message: models.VectorizedMessage, + message: models.Message, + embedding: bytes, limit: int = 10, scope: StatisticScopeEnum = StatisticScopeEnum.GUILD, ) -> list[models.Message]: @@ -120,7 +121,7 @@ async def get_similar_messages( raise ValueError(error_message) query = VectorQuery( - vector=message.embedding, + vector=embedding, vector_field_name="embedding", return_fields=[ "message_id", diff --git a/courageous_comets/redis/schema.py b/courageous_comets/redis/schema.py index 73e6e2c..e82f982 100644 --- a/courageous_comets/redis/schema.py +++ b/courageous_comets/redis/schema.py @@ -4,7 +4,6 @@ "index": { "name": "message_idx", "prefix": settings.REDIS_KEYS_PREFIX, - "storage_type": "json", }, "fields": [ {"name": "content", "type": "text"}, @@ -23,9 +22,5 @@ "datatype": "float32", }, }, - {"name": "neu", "type": "numeric", "path": "$.sentiment.neu"}, - {"name": "neg", "type": "numeric", "path": "$.sentiment.neg"}, - {"name": "pos", "type": "numeric", "path": "$.sentiment.pos"}, - {"name": "compound", "type": "numeric", "path": "$.sentiment.compound"}, ], } diff --git a/courageous_comets/sentiment.py b/courageous_comets/sentiment.py index 07e1edb..dc683b8 100644 --- a/courageous_comets/sentiment.py +++ b/courageous_comets/sentiment.py @@ -1,15 +1,18 @@ import logging +from discord import Message from nltk.sentiment import SentimentIntensityAnalyzer +from redis.asyncio import Redis from courageous_comets.models import SentimentResult +from courageous_comets.redis.keys import key_schema MAX_MESSAGE_LENGTH = 256 logger = logging.getLogger(__name__) -def calculate_sentiment(content: str) -> SentimentResult: +def calculate_sentiment(content: str, key: str) -> SentimentResult: """ Calculate the sentiment of a message. @@ -22,6 +25,8 @@ def calculate_sentiment(content: str) -> SentimentResult: ---------- content : str The message content to analyze. + key : str + The Redis key for the message. Used for logging. Returns ------- @@ -31,9 +36,87 @@ def calculate_sentiment(content: str) -> SentimentResult: truncated = content[:MAX_MESSAGE_LENGTH] if truncated != content: - logger.warning("Truncated message to %s characters", MAX_MESSAGE_LENGTH) + logger.warning("Truncated message %s to %s characters", key, MAX_MESSAGE_LENGTH) sia = SentimentIntensityAnalyzer() result = sia.polarity_scores(truncated) return SentimentResult.model_validate(result) + + +async def store_sentiment(message: Message, redis: Redis) -> None: + """ + Calculate the sentiment of a message and store the result in Redis. + + A message must have a guild, channel, author and message ID. If any of these are missing, + the message will be ignored. + + Empty messages will also be ignored. + + Parameters + ---------- + message : discord.Message + The message to process. + redis : redis.asyncio.Redis + The Redis connection instance. + """ + # Ignore empty messages + if not message.content: + logger.warning("Ignoring empty message %s", message.id) + return + + # Extract the IDs from the message + guild_id = message.guild.id if message.guild else 0 + channel_id = message.channel.id if message.channel else 0 + user_id = message.author.id if message.author else 0 + message_id = message.id if message.id else 0 + + # Ignore messages without all required IDs + if not all((guild_id, channel_id, user_id, message_id)): + logger.warning("Ignoring message %s with missing IDs", message.id) + return + + # Construct the Redis key + key = key_schema.sentiment_tokens( + guild_id=guild_id, + channel_id=channel_id, + user_id=user_id, + message_id=message_id, + ) + + # Calculate the sentiment + sentiment = calculate_sentiment(message.content, key) + + # Store the sentiment in Redis + await redis.hset( + key, + mapping=sentiment.model_dump(mode="json"), + ) # pyright: ignore[reportGeneralTypeIssues] + + logger.info("Stored sentiment for message %s", key) + + +async def get_sentiment(key: str, redis: Redis) -> SentimentResult: + """ + Retrieve the sentiment of a message from Redis. + + Parameters + ---------- + key : str + The Redis key to retrieve the sentiment from. + redis : redis.asyncio.Redis + The Redis connection instance. + + Returns + ------- + courageous_comets.models.SentimentResult + The sentiment of the message. + """ + neg, neu, pos, compound = await redis.hmget(key, "neg", "neu", "pos", "compound") # type: ignore + + return SentimentResult( + neg=float(neg or 0), + neu=float(neu or 0), + pos=float(pos or 0), + compound=float(compound or 0), + ) diff --git a/courageous_comets/vectorizer.py b/courageous_comets/vectorizer.py index d8ca0b0..4610ba1 100644 --- a/courageous_comets/vectorizer.py +++ b/courageous_comets/vectorizer.py @@ -37,7 +37,7 @@ def __init__(self) -> None: cache_dir=settings.HF_HOME, ) - def encode(self, message: str) -> list[float]: + def encode(self, message: str) -> bytes: """ Create vector embedding of a message. @@ -57,7 +57,7 @@ def encode(self, message: str) -> list[float]: Returns ------- - list[float] + bytes The vector embeddings of the message """ @@ -97,9 +97,9 @@ def mean_pooling(model_output: list[Tensor], attention_mask: Tensor) -> Tensor: torch_nn_functional.normalize(sentence_embeddings, p=2, dim=1) .numpy() .astype(np.float32) - .tolist()[0] + .tobytes() ) - async def aencode(self, message: str) -> list[float]: + async def aencode(self, message: str) -> bytes: """Create a vector embedding of message asynchronously.""" return await asyncio.to_thread(self.encode, message) diff --git a/tests/courageous_comets/test__sentiment.py b/tests/courageous_comets/test__sentiment.py index 6033086..488fc5a 100644 --- a/tests/courageous_comets/test__sentiment.py +++ b/tests/courageous_comets/test__sentiment.py @@ -1,12 +1,17 @@ +from unittest.mock import Mock + import pytest -from pytest_mock import MockerFixture +from pytest_mock import MockerFixture, MockType from redis.asyncio import Redis from courageous_comets.models import SentimentResult +from courageous_comets.redis.keys import key_schema from courageous_comets.sentiment import ( MAX_MESSAGE_LENGTH, calculate_sentiment, + get_sentiment, logger, + store_sentiment, ) @@ -35,7 +40,7 @@ def test__calculate_sentiment_analyzes_sentiment_of_given_text( pos=mocker.ANY, compound=mocker.ANY, ) - result = calculate_sentiment("I love this product!") + result = calculate_sentiment("I love this product!", "test") assert result == expected @@ -60,5 +65,134 @@ def test__calculate_sentiment_truncates_long_messages( - The function truncates messages longer than 256 characters. """ logger_warning = mocker.spy(logger, "warning") - calculate_sentiment(message) + calculate_sentiment(message, "test") assert logger_warning.called == expected + + +async def test__store_sentiment_calculates_and_stores_sentiment( + *, + mocker: MockerFixture, + redis: MockType, +) -> None: + """ + Test whether the store sentiment function calculates and stores the sentiment of a message. + + Asserts + ------- + - The sentiment is calculated for the message content. + - The sentiment is stored in the database. + """ + message = mocker.Mock( + content="I love this product!", + guild=Mock(id=1), + channel=Mock(id=1), + author=Mock(id=1), + id=1, + ) + + await store_sentiment(message, redis) + + redis.hset.assert_awaited_with( + key_schema.sentiment_tokens(1, 1, 1, 1), + mapping={ + "neg": mocker.ANY, + "neu": mocker.ANY, + "pos": mocker.ANY, + "compound": mocker.ANY, + }, + ) + + +async def test__store_sentiment_ignores_empty_messages( + *, + mocker: MockerFixture, + redis: MockType, +) -> None: + """ + Test whether the store sentiment function ignores empty messages. + + Asserts + ------- + - The database is not updated when the message is empty. + """ + message = mocker.Mock(content="") + await store_sentiment(message, redis) + redis.hset.assert_not_awaited() + + +@pytest.mark.parametrize( + "message", + [ + Mock(content="test", guild=None, channel=None, author=None, id=1), + Mock(content="test", guild=Mock(id=1), channel=None, author=None, id=1), + Mock(content="test", guild=Mock(id=1), channel=Mock(id=1), author=None, id=1), + ], +) +async def test__store_sentiment_ignores_messages_without_ids( + *, + redis: MockType, + message: MockType, +) -> None: + """ + Test whether the store sentiment function ignores messages without IDs. + + Asserts + ------- + - The database is not updated when the message is missing IDs. + """ + await store_sentiment(message, redis) + redis.hset.assert_not_awaited() + + +async def test__get_sentiment_retrieves_sentiment_from_redis( + *, + redis: MockType, +) -> None: + """ + Test whether the get sentiment function retrieves the sentiment of a message from Redis. + + Asserts + ------- + - The sentiment is retrieved from the database. + """ + key = key_schema.sentiment_tokens(1, 1, 1, 1) + redis.hmget.return_value = ["1.0", "1.0", "1.0", "1.0"] + + expected = SentimentResult( + neg=1.0, + neu=1.0, + pos=1.0, + compound=1.0, + ) + + result = await get_sentiment(key, redis) + + redis.hmget.assert_awaited_with(key, "neg", "neu", "pos", "compound") + assert result == expected + + +async def test__get_sentiment_handles_missing_sentiment( + *, + redis: MockType, +) -> None: + """ + Test whether the get sentiment function handles missing sentiment in Redis. + + Asserts + ------- + - The function returns default sentiment values when the sentiment is missing. + """ + key = key_schema.sentiment_tokens(1, 1, 1, 1) + redis.hmget.return_value = [None, None, None, None] + + expected = SentimentResult( + neg=0.0, + neu=0.0, + pos=0.0, + compound=0.0, + ) + + result = await get_sentiment(key, redis) + + redis.hmget.assert_awaited_with(key, "neg", "neu", "pos", "compound") + assert expected == result diff --git a/tests/integrations/test__redis.py b/tests/integrations/test__redis.py index 16f5d99..f9df7b1 100644 --- a/tests/integrations/test__redis.py +++ b/tests/integrations/test__redis.py @@ -1,55 +1,53 @@ import datetime +import pytest import pytest_asyncio from redis.asyncio import Redis from courageous_comets import models from courageous_comets.redis.keys import key_schema from courageous_comets.redis.messages import get_similar_messages, save_message -from courageous_comets.sentiment import calculate_sentiment from courageous_comets.vectorizer import Vectorizer -@pytest_asyncio.fixture(scope="session") -async def message(vectorizer: Vectorizer) -> models.MessageAnalysis: - """Fixture that sets up an analysis of a discord message. +@pytest.fixture(scope="session") +def message() -> models.Message: + """Fixture that sets up a message. Returns ------- models.Message A Redis model of a Discord message """ - content = "The quick brown fox jumps over the lazy dog." - embedding = await vectorizer.aencode(content) - return models.MessageAnalysis( + return models.Message( message_id="1", channel_id="1", guild_id="1", timestamp=datetime.datetime.fromtimestamp(0, datetime.UTC), - embedding=embedding, user_id="1", - sentiment=calculate_sentiment(content), ) @pytest_asyncio.fixture(scope="session") async def vectorized_message( - message: models.MessageAnalysis, + message: models.Message, + vectorizer: Vectorizer, ) -> models.VectorizedMessage: - """Fixture that creates a models.VectorizedMessage. + """Fixture that creates an embedding vector of contents of message. Returns ------- models.VectorizedMessage A message with embedding vector """ - # NOTE: This works because BaseModel ignores extra fields - return models.VectorizedMessage(**message.model_dump()) + content = "The quick brown fox jumps over the lazy dog." + embedding = await vectorizer.aencode(content) + return models.VectorizedMessage(**message.model_dump(), embedding=embedding) async def test__save_message( redis: Redis, - message: models.MessageAnalysis, + vectorized_message: models.VectorizedMessage, ) -> None: """ Tests whether the save_mesage function stores the message on Redis. @@ -58,16 +56,16 @@ async def test__save_message( ------- - The returned key is the same as the one constructed by the key_schema """ - key = await save_message(redis, message) + key = await save_message(redis, vectorized_message) assert key == key_schema.guild_messages( - guild_id=message.guild_id, - message_id=message.message_id, + guild_id=vectorized_message.guild_id, + message_id=vectorized_message.message_id, ) async def test__get_similar_message( redis: Redis, - message: models.MessageAnalysis, + message: models.Message, vectorized_message: models.VectorizedMessage, ) -> None: """ @@ -77,11 +75,7 @@ async def test__get_similar_message( ------- - The returned message is the same message whose embedding vector was used """ - await save_message(redis, message) - messages = await get_similar_messages(redis, vectorized_message) + await save_message(redis, vectorized_message) + messages = await get_similar_messages(redis, message, vectorized_message.embedding) assert len(messages) == 1 - redis_message = messages[0] - assert redis_message.channel_id == message.channel_id - assert redis_message.guild_id == message.guild_id - assert redis_message.message_id == message.message_id - assert message.timestamp == message.timestamp + assert messages[0].model_dump() == message.model_dump() From 33bfc548a1272e832731e0c59efbfd46e1eae251 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Tue, 23 Jul 2024 04:33:22 +0000 Subject: [PATCH 070/168] fix: preprocessing drops extra whitespace --- courageous_comets/preprocessing.py | 18 +++++++++++ .../courageous_comets/test__preprocessing.py | 31 ++++++++++++++++++- 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/courageous_comets/preprocessing.py b/courageous_comets/preprocessing.py index b150cc1..6d3c6b8 100644 --- a/courageous_comets/preprocessing.py +++ b/courageous_comets/preprocessing.py @@ -11,6 +11,23 @@ Processor = Callable[[str], str] +def drop_extra_whitespace(text: str) -> str: + """ + Remove extra whitespace from the given text. + + Parameters + ---------- + text : str + The text to process. + + Returns + ------- + str + The text with extra whitespace removed. + """ + return re.sub(r"\s+", " ", text.strip()) + + def drop_links(text: str) -> str: """ Remove links from the given text. @@ -90,6 +107,7 @@ def truncate(text: str, max_length: int) -> str: contractions.fix, # type: ignore drop_punctuation, partial(drop_very_long_words, max_length=settings.PREPROCESSING_MAX_WORD_LENGTH), + drop_extra_whitespace, partial(truncate, max_length=settings.PREPROCESSING_MAX_WORD_LENGTH), ] diff --git a/tests/courageous_comets/test__preprocessing.py b/tests/courageous_comets/test__preprocessing.py index 74f7396..4c71ab1 100644 --- a/tests/courageous_comets/test__preprocessing.py +++ b/tests/courageous_comets/test__preprocessing.py @@ -1,6 +1,35 @@ import pytest -from courageous_comets.preprocessing import drop_links, drop_punctuation, drop_very_long_words +from courageous_comets.preprocessing import ( + drop_extra_whitespace, + drop_links, + drop_punctuation, + drop_very_long_words, +) + + +@pytest.mark.parametrize( + ("text", "expected"), + [ + ("Hello, world!", "Hello, world!"), + ("Hello, world!", "Hello, world!"), + ("Hello, world!", "Hello, world!"), + ("Hello, world! ", "Hello, world!"), + (" Hello, world! ", "Hello, world!"), + (" Hello, world! ", "Hello, world!"), + (" Hello, world! ", "Hello, world!"), + ], +) +def test__drop_extra_whitespace(text: str, expected: str) -> None: + """ + Test whether `drop_extra_whitespace` removes extra whitespace from the given text. + + Asserts + ------- + - Extra whitespace is removed from the given text. + - Text without extra whitespace is not modified. + """ + assert drop_extra_whitespace(text) == expected @pytest.mark.parametrize( From 2a2124a519ccd92bf5ad7cd0290ce8b50c470146 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Tue, 23 Jul 2024 04:36:55 +0000 Subject: [PATCH 071/168] fix: trucate uses correct length --- courageous_comets/preprocessing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/courageous_comets/preprocessing.py b/courageous_comets/preprocessing.py index 6d3c6b8..e4fa8f8 100644 --- a/courageous_comets/preprocessing.py +++ b/courageous_comets/preprocessing.py @@ -108,7 +108,7 @@ def truncate(text: str, max_length: int) -> str: drop_punctuation, partial(drop_very_long_words, max_length=settings.PREPROCESSING_MAX_WORD_LENGTH), drop_extra_whitespace, - partial(truncate, max_length=settings.PREPROCESSING_MAX_WORD_LENGTH), + partial(truncate, max_length=settings.PREPROCESSING_MESSAGE_TRUNCATE_LENGTH), ] From be087b967baa8fda878d746be377d2377578160a Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Tue, 23 Jul 2024 04:42:44 +0000 Subject: [PATCH 072/168] docs: describe preprocessing options --- docs/admin-guide/configuration.md | 34 ++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/docs/admin-guide/configuration.md b/docs/admin-guide/configuration.md index 0395f44..8b29285 100644 --- a/docs/admin-guide/configuration.md +++ b/docs/admin-guide/configuration.md @@ -2,18 +2,20 @@ The following environment variables are available to configure the application: -| Variable | Description | Required | Default | -| ------------------------------------------------------------------------- | ------------------------------------------------------------------------ | -------- | ------------------ | -| [`DISCORD_TOKEN`](#discord_token) | The Discord bot token. | Yes | - | -| [`BOT_CONFIG_PATH`](#bot_config_path) | The path to the bot's configuration file. | No | `application.yaml` | -| [`HF_DOWNLOAD_CONCURRENCY`](#hf_download_concurrency) | The maximum number of concurrent downloads when installing transformers. | No | `3` | -| [`HF_HOME`](#hf_home) | The directory containing Huggingface Transformers data files. | No | `hf_data` | -| [`LOG_LEVEL`](#log_level) | The minimum log level. | No | `INFO` | -| [`NLTK_DATA`](#nltk_data) | The directory containing NLTK data files. | No | `nltk_data` | -| [`NLTK_DOWNLOAD_CONCURRENCY`](#nltk_download_concurrency) | The maximum number of concurrent downloads when installing NLTK data. | No | `3` | -| [`REDIS_HOST`](#redis_host) | The Redis host. | No | `localhost` | -| [`REDIS_PORT`](#redis_port) | The Redis port. | No | `6379` | -| [`REDIS_PASSWORD`](#redis_password) | The Redis password. | No | - | +| Variable | Description | Required | Default | +| --------------------------------------------------------------------------------- | ------------------------------------------------------------------------ | -------- | ------------------ | +| [`DISCORD_TOKEN`](#discord_token) | The Discord bot token. | Yes | - | +| [`BOT_CONFIG_PATH`](#bot_config_path) | The path to the bot's configuration file. | No | `application.yaml` | +| [`HF_DOWNLOAD_CONCURRENCY`](#hf_download_concurrency) | The maximum number of concurrent downloads when installing transformers. | No | `3` | +| [`HF_HOME`](#hf_home) | The directory containing Huggingface Transformers data files. | No | `hf_data` | +| [`LOG_LEVEL`](#log_level) | The minimum log level. | No | `INFO` | +| [`NLTK_DATA`](#nltk_data) | The directory containing NLTK data files. | No | `nltk_data` | +| [`NLTK_DOWNLOAD_CONCURRENCY`](#nltk_download_concurrency) | The maximum number of concurrent downloads when installing NLTK data. | No | `3` | +| [`PREPROCESSING_MAX_WORD_LENGTH`](#preprocessing_max_word_length) | The maximum word length. Longer words are dropped. | No | `35` | +| [`PREPROCESSING_MESSAGE_TRUNCATE_LENGTH`](#preprocessing_message_truncate_length) | The maximum message length. Longer messages are truncated. | No | `256` | +| [`REDIS_HOST`](#redis_host) | The Redis host. | No | `localhost` | +| [`REDIS_PORT`](#redis_port) | The Redis port. | No | `6379` | +| [`REDIS_PASSWORD`](#redis_password) | The Redis password. | No | - | ## Required Settings @@ -79,6 +81,14 @@ application is launched. In the Docker image, this directory is located at `/app The application automatically downloads NLTK data files on startup. This setting controls the number of concurrent downloads. By default, this is set to `3`. +### `PREPROCESSING_MAX_WORD_LENGTH` + +The maximum word length. Words longer than this value are dropped. By default, this is set to `35`. + +### `PREPROCESSING_MESSAGE_TRUNCATE_LENGTH` + +The maximum message length. Messages longer than this value are truncated. By default, this is set to `256`. + ### `REDIS_HOST` The hostname of the Redis server. Defaults to `localhost`. From 8c3ae33f63fd0ae292f4213ee897207a6e584297 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Tue, 23 Jul 2024 06:16:52 +0000 Subject: [PATCH 073/168] fix: open app config in read mode --- courageous_comets/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/courageous_comets/client.py b/courageous_comets/client.py index db32d18..bd70e7a 100644 --- a/courageous_comets/client.py +++ b/courageous_comets/client.py @@ -18,7 +18,7 @@ intents.members = True intents.message_content = True -with settings.BOT_CONFIG_PATH.open() as config_file: +with settings.BOT_CONFIG_PATH.open("r") as config_file: CONFIG = yaml.safe_load(config_file) From 2f003a325b86131ad56b17740d9649aa9c27a730 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Tue, 23 Jul 2024 06:17:14 +0000 Subject: [PATCH 074/168] =?UTF-8?q?bump:=20version=200.3.0=20=E2=86=92=200?= =?UTF-8?q?.4.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/CHANGELOG.md | 12 +++++ docs/README.md | 2 +- docs/admin-guide/deployment.md | 87 +++++++++++++++++++++++++++++----- mkdocs.yaml | 3 ++ pyproject.toml | 2 +- 5 files changed, 93 insertions(+), 13 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index c41a30a..f3f9316 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,3 +1,15 @@ +## v0.4.0 (2024-07-23) + +### Feat + +- revert back to hash data type for storing messages (#41) + +### Fix + +- open app config in read mode +- trucate uses correct length +- preprocessing drops extra whitespace + ## v0.3.0 (2024-07-22) ### Feat diff --git a/docs/README.md b/docs/README.md index 3c24267..ad949f2 100644 --- a/docs/README.md +++ b/docs/README.md @@ -9,7 +9,7 @@ Code Jam 2024. Click the button below to install the bot in your Discord server: - + [Add to Discord :fontawesome-brands-discord:](https://discord.com/oauth2/authorize?client_id=1262672493978714174){ .md-button .md-button--primary } ## Contents diff --git a/docs/admin-guide/deployment.md b/docs/admin-guide/deployment.md index 32e1f33..6337f24 100644 --- a/docs/admin-guide/deployment.md +++ b/docs/admin-guide/deployment.md @@ -2,27 +2,92 @@ This section provides instructions on how to deploy the application in a production environment. +## Checklist + +Use the checklist below to keep track of your progress while deploying the application: + +- [ ] Set up Docker and Docker Compose on your system. +- [ ] Get the Docker Compose file. +- [ ] Set up a `.env` file. +- [ ] Configure your `DISCORD_TOKEN` in the `.env` file. +- [ ] Select the image versions (optional). +- [ ] Configure additional options (optional). +- [ ] Start the application using Docker Compose. + +## Get the Docker Compose File + !!! NOTE "Prerequisites" - The application is distributed as a [Docker](https://www.docker.com/) image. Please ensure that you have Docker - installed on your system. + The application is distributed as a Docker image. Please ensure that you have [Docker](https://www.docker.com/) + and [Docker Compose](https://docs.docker.com/compose/) installed on your system before proceeding. -Open your terminal and run the following command to deploy the latest version of the application: +The application can be deployed using Docker Compose. You can use the `docker-compose.yaml` file provided in the +GitHub repository to start the application. -```bash -docker run -e DISCORD_TOKEN="" ghcr.io/thijsfranck/courageous-comets:latest + +[Get the Docker Compose :fontawesome-brands-docker:](https://github.com/thijsfranck/courageous-comets/blob/main/docker-compose.yaml){ .md-button .md-button--primary } + +Download the file and save it in any directory on your system. + +## Services + +The Docker Compose file defines the following services: + +- [**courageous-comets**]((https://github.com/thijsfranck/courageous-comets/pkgs/container/courageous-comets)): + The Courageous Comets Discord application. +- [**redis-stack**](https://hub.docker.com/r/redis/redis-stack-server): The database used by the application. + +## Configuration + +Before starting the application, you need to configure the application settings. Create a `.env` file in the same +directory as the `docker-compose.yaml` file. + +The sections below will guide you through setting up a minimal configuration to start the application. + +??? QUESTION "What other configuration options are available?" + Refer to the [configuration](configuration.md) section for a complete list of the available options. + +### Discord Token + +The application requires a valid Discord bot token to connect to Discord. Add the following line to the `.env` +file: + +```dotenv +DISCORD_TOKEN= ``` Replace `` with your Discord bot token. The application will start and connect to Discord using the provided token. -You can now interact with the application in any Discord server where it has been installed. - ??? QUESTION "Where do I find my Discord bot token?" See the configuration section for instructions on [how to obtain a Discord bot token](./configuration.md#discord_token). -??? QUESTION "What other options are available?" - The example above is a minimal configuration for to start the application. Refer to the [configuration](configuration.md) - section for a list of the available options. +### Image Versions + +By default, the application uses the latest version of each Docker image. To specify a particular version, you +can add the following variables to the `.env` file: + +```dotenv +COURAGEOUS_COMETS_VERSION=latest +REDIS_STACK_VERSION=latest +``` ??? QUESTION "Where can I find previous versions of the image?" - Previous versions of the image are available on the [GitHub Container Registry](https://github.com/thijsfranck/courageous-comets/pkgs/container/courageous-comets). + Previous versions of the Courageous Comets image are available on the [GitHub Container Registry](https://github.com/thijsfranck/courageous-comets/pkgs/container/courageous-comets). + +## Start the Application + +Once you have set up the configuration, you can start the application using Docker Compose. Open a terminal and +navigate to the directory where you saved the `docker-compose.yaml` file. Run the following command: + +```bash +docker-compose up -d +``` + +Docker Compose will start the application in the background. You can check the logs to verify that the application +has started successfully: + +```bash +docker-compose logs -f +``` + +You can now interact with the application in any Discord server where it has been installed. diff --git a/mkdocs.yaml b/mkdocs.yaml index 199bb41..0d02552 100644 --- a/mkdocs.yaml +++ b/mkdocs.yaml @@ -60,6 +60,9 @@ markdown_extensions: emoji_generator: !!python/name:material.extensions.emoji.to_svg - pymdownx.tabbed: alternate_style: true + - pymdownx.tasklist: + clickable_checkbox: true + custom_checkbox: true - toc: permalink: true diff --git a/pyproject.toml b/pyproject.toml index f9920f5..1c1dffe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "courageous-comets" -version = "0.3.0" +version = "0.4.0" authors = ["Courageous Comets"] description = "" readme = "docs/README.md" From cd2b416408d3d6ac5930d2db90ac22f1d9d14b63 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Tue, 23 Jul 2024 07:21:24 +0000 Subject: [PATCH 075/168] feat: add docker compose and fix production dockerfile --- Dockerfile | 11 ++++++----- docker-compose.yaml | 30 ++++++++++++++++++++++++++++++ docs/admin-guide/deployment.md | 2 ++ 3 files changed, 38 insertions(+), 5 deletions(-) create mode 100644 docker-compose.yaml diff --git a/Dockerfile b/Dockerfile index dc10708..22c3ad5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,18 +15,19 @@ ENV LOG_LEVEL=INFO ENV NLTK_DATA=/app/nltk_data ENV HF_HOME=/app/hf_data -# Add a non-root user -RUN adduser --system courageous-comets +# Add a non-root user and group +RUN addgroup --system courageous-comets && \ + adduser --system --ingroup courageous-comets courageous-comets # Set the working directory WORKDIR /app # Assign the working directory to the non-root user and set the permissions -RUN chown -R courageous-comets /app && \ - chmod -R 0600 /app +RUN chown -R courageous-comets:courageous-comets /app && \ + chmod -R 0770 /app # Copy the app config and the wheel file to the working directory and set the permissions -COPY --chown=courageous-comets --chmod=0400 application.yaml dist/*.whl ./ +COPY --chown=courageous-comets:courageous-comets --chmod=0440 application.yaml dist/*.whl ./ # Install the wheel file and clean up to reduce image size RUN pip install --no-cache-dir *.whl && \ diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..0dda020 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,30 @@ +services: + courageous-comets: + image: ghcr.io/thijsfranck/courageous-comets:${COURAGEOUS_COMETS_VERSION:-latest} + environment: + REDIS_HOST: redis-stack + REDIS_PORT: 6379 + env_file: + - .env + restart: always + depends_on: + - redis-stack + networks: + - comets-network + + redis-stack: + image: redis/redis-stack-server:${REDIS_STACK_VERSION:-latest} + volumes: + - redis-stack-data:/data + ports: + - "6379:6379" + restart: always + networks: + - comets-network + +volumes: + redis-stack-data: + + +networks: + comets-network: diff --git a/docs/admin-guide/deployment.md b/docs/admin-guide/deployment.md index 6337f24..44ebeb4 100644 --- a/docs/admin-guide/deployment.md +++ b/docs/admin-guide/deployment.md @@ -71,6 +71,8 @@ COURAGEOUS_COMETS_VERSION=latest REDIS_STACK_VERSION=latest ``` +Replace `latest` with the tag corresponding to the version you want to use. + ??? QUESTION "Where can I find previous versions of the image?" Previous versions of the Courageous Comets image are available on the [GitHub Container Registry](https://github.com/thijsfranck/courageous-comets/pkgs/container/courageous-comets). From 4b0587c32b2ccd1c358406bd28bbb927647ce139 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Tue, 23 Jul 2024 07:21:38 +0000 Subject: [PATCH 076/168] =?UTF-8?q?bump:=20version=200.4.0=20=E2=86=92=200?= =?UTF-8?q?.5.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/CHANGELOG.md | 6 ++++++ pyproject.toml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index f3f9316..bd5d839 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,3 +1,9 @@ +## v0.5.0 (2024-07-23) + +### Feat + +- add docker compose and fix production dockerfile + ## v0.4.0 (2024-07-23) ### Feat diff --git a/pyproject.toml b/pyproject.toml index 1c1dffe..544a65f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "courageous-comets" -version = "0.4.0" +version = "0.5.0" authors = ["Courageous Comets"] description = "" readme = "docs/README.md" From 3dd7ca2f0decaf870276f7a60d4a2fcdca10e6b7 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Tue, 23 Jul 2024 07:35:12 +0000 Subject: [PATCH 077/168] docs: fix broken link --- docs/admin-guide/deployment.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/admin-guide/deployment.md b/docs/admin-guide/deployment.md index 44ebeb4..1ee9ae6 100644 --- a/docs/admin-guide/deployment.md +++ b/docs/admin-guide/deployment.md @@ -32,7 +32,7 @@ Download the file and save it in any directory on your system. The Docker Compose file defines the following services: -- [**courageous-comets**]((https://github.com/thijsfranck/courageous-comets/pkgs/container/courageous-comets)): +- [**courageous-comets**](https://github.com/thijsfranck/courageous-comets/pkgs/container/courageous-comets): The Courageous Comets Discord application. - [**redis-stack**](https://hub.docker.com/r/redis/redis-stack-server): The database used by the application. From b7fdddaefd9693ff60a5a4a5d1b6fd523b7acf95 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Tue, 23 Jul 2024 07:52:12 +0000 Subject: [PATCH 078/168] docs: improve phrasing around discord token setup --- docs/admin-guide/deployment.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/admin-guide/deployment.md b/docs/admin-guide/deployment.md index 1ee9ae6..c5ad2db 100644 --- a/docs/admin-guide/deployment.md +++ b/docs/admin-guide/deployment.md @@ -55,8 +55,8 @@ file: DISCORD_TOKEN= ``` -Replace `` with your Discord bot token. The application will start and connect to Discord using the -provided token. +Replace `` with your Discord bot token. When the application starts, it will use this token to authenticate +with Discord. ??? QUESTION "Where do I find my Discord bot token?" See the configuration section for instructions on [how to obtain a Discord bot token](./configuration.md#discord_token). From 936fa950ae377d21e360392bdb6d91b49309df70 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Tue, 23 Jul 2024 07:52:46 +0000 Subject: [PATCH 079/168] docs: simplify discord token setup description --- docs/admin-guide/deployment.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/admin-guide/deployment.md b/docs/admin-guide/deployment.md index c5ad2db..954c043 100644 --- a/docs/admin-guide/deployment.md +++ b/docs/admin-guide/deployment.md @@ -55,8 +55,7 @@ file: DISCORD_TOKEN= ``` -Replace `` with your Discord bot token. When the application starts, it will use this token to authenticate -with Discord. +Replace `` with your Discord bot token. ??? QUESTION "Where do I find my Discord bot token?" See the configuration section for instructions on [how to obtain a Discord bot token](./configuration.md#discord_token). From 866f1aa673af2c0325b814dc71a7fd98c6cbcb57 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Tue, 23 Jul 2024 08:00:09 +0000 Subject: [PATCH 080/168] docs: extend application.yaml docs --- docs/admin-guide/configuration.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/admin-guide/configuration.md b/docs/admin-guide/configuration.md index 8b29285..f28f7df 100644 --- a/docs/admin-guide/configuration.md +++ b/docs/admin-guide/configuration.md @@ -44,6 +44,14 @@ This specifies the location of the bot's configuration file, which is a YAML fil cogs: - - +# List of NLTK datasets to download on startup. +nltk: + - + - +# List of Huggingface Transformers models to download on startup. +transformers: + - + - ``` By default, the application searches for a file named `application.yaml` in the directory from which it is launched. From 62627afc998d6c476fb7dd4a1914f2db9bbeeab8 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Tue, 23 Jul 2024 08:28:04 +0000 Subject: [PATCH 081/168] docs: simplify deployment checklist --- docs/admin-guide/deployment.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/admin-guide/deployment.md b/docs/admin-guide/deployment.md index 954c043..9228d8f 100644 --- a/docs/admin-guide/deployment.md +++ b/docs/admin-guide/deployment.md @@ -4,8 +4,6 @@ This section provides instructions on how to deploy the application in a product ## Checklist -Use the checklist below to keep track of your progress while deploying the application: - - [ ] Set up Docker and Docker Compose on your system. - [ ] Get the Docker Compose file. - [ ] Set up a `.env` file. From 0a5041761cf8c28dec8ee5362140a334dbc68c09 Mon Sep 17 00:00:00 2001 From: Uchechukwu Orji Date: Tue, 23 Jul 2024 09:41:55 +0100 Subject: [PATCH 082/168] feat: store sentiment results along with message (#42) --- courageous_comets/cogs/messages.py | 4 +- courageous_comets/models.py | 10 +- courageous_comets/redis/keys.py | 15 --- courageous_comets/redis/messages.py | 126 ++++++++++++++----- courageous_comets/redis/schema.py | 7 +- courageous_comets/sentiment.py | 87 +------------ courageous_comets/settings.py | 2 + tests/conftest.py | 20 ++- tests/courageous_comets/test__sentiment.py | 140 +-------------------- tests/integrations/test__redis.py | 122 ++++++++++++++++-- 10 files changed, 248 insertions(+), 285 deletions(-) diff --git a/courageous_comets/cogs/messages.py b/courageous_comets/cogs/messages.py index 837e57b..ff34346 100644 --- a/courageous_comets/cogs/messages.py +++ b/courageous_comets/cogs/messages.py @@ -7,6 +7,7 @@ from courageous_comets.client import CourageousCometsBot from courageous_comets.models import VectorizedMessage from courageous_comets.redis import messages +from courageous_comets.sentiment import calculate_sentiment from courageous_comets.vectorizer import Vectorizer logger = logging.getLogger(__name__) @@ -63,6 +64,7 @@ async def save_message(self, message: discord.Message) -> None: ) embedding = await self.vectorizer.aencode(text) + sentiment = calculate_sentiment(text) vectorized_message = VectorizedMessage( user_id=str(message.author.id), @@ -73,7 +75,7 @@ async def save_message(self, message: discord.Message) -> None: embedding=embedding, ) - key = await messages.save_message(self.bot.redis, vectorized_message) + key = await messages.save_message(self.bot.redis, vectorized_message, sentiment) return logger.info( "Saved message %s to Redis with key %s", diff --git a/courageous_comets/models.py b/courageous_comets/models.py index 1c24287..a08d591 100644 --- a/courageous_comets/models.py +++ b/courageous_comets/models.py @@ -2,7 +2,7 @@ from typing import Annotated import pydantic -from pydantic import PlainSerializer +from pydantic import Field, PlainSerializer UnixTimestamp = Annotated[ datetime.datetime, @@ -71,7 +71,7 @@ class SentimentResult(BaseModel): The compound sentiment score. """ - neg: float - neu: float - pos: float - compound: float + neg: float = Field(..., serialization_alias="sentiment_neg") + neu: float = Field(..., serialization_alias="sentiment_neu") + pos: float = Field(..., serialization_alias="sentiment_pos") + compound: float = Field(..., serialization_alias="sentiment_compound") diff --git a/courageous_comets/redis/keys.py b/courageous_comets/redis/keys.py index 85b2fd7..1d741dd 100644 --- a/courageous_comets/redis/keys.py +++ b/courageous_comets/redis/keys.py @@ -57,20 +57,5 @@ def guild_message_tokens(self, guild_id: int) -> str: """ return f"messages:tokens:{guild_id}" - @prefix_key - def sentiment_tokens( - self, - guild_id: int, - channel_id: int, - user_id: int, - message_id: int, - ) -> str: - """ - Key to sentiment tokens for a message. - - Redis type: hash - """ - return f"sentiment:{guild_id}:{channel_id}:{user_id}:{message_id}" - key_schema = KeySchema() diff --git a/courageous_comets/redis/messages.py b/courageous_comets/redis/messages.py index c0615a9..e5358dc 100644 --- a/courageous_comets/redis/messages.py +++ b/courageous_comets/redis/messages.py @@ -1,14 +1,46 @@ from redis.asyncio import Redis from redisvl.index import AsyncSearchIndex -from redisvl.query import VectorQuery -from redisvl.query.filter import Tag +from redisvl.query import FilterQuery, VectorQuery +from redisvl.query.filter import FilterExpression, Num, Tag -from courageous_comets import models +from courageous_comets import models, settings from courageous_comets.enums import StatisticScopeEnum from courageous_comets.redis import schema from courageous_comets.redis.keys import key_schema +def get_search_scope( + scope: StatisticScopeEnum, + message: models.Message, +) -> FilterExpression: + """Determine the scope of a search. + + Parameters + ---------- + scope : courageous_comets.enums.StatisticScopeEnum + The scope of a search. + message : courageous_comets.models.Message + The comparison message. + + Returns + ------- + redisvl.query.FilterExpression + The redis filter expression for the specified scope + + """ + match scope: + case StatisticScopeEnum.GUILD: + filter_expression = Tag("guild_id") == message.guild_id + case StatisticScopeEnum.CHANNEL: + filter_expression = Tag("channel_id") == message.channel_id + case StatisticScopeEnum.USER: + filter_expression = Tag("user_id") == message.user_id + case _: + error_message = f"Unhandled scope: {scope!r}" + raise ValueError(error_message) + return filter_expression + + async def update_message_tokens( redis: Redis, guild_id: int, @@ -19,7 +51,7 @@ async def update_message_tokens( Parameters ---------- - redis : Redis + redis : redis.Redis The Redis connection instance. guild_id : int The ID of the guild. @@ -48,15 +80,18 @@ async def update_message_tokens( async def save_message( redis: Redis, message: models.VectorizedMessage, + sentiment: models.SentimentResult, ) -> str: """Save a message on Redis. Parameters ---------- - redis : Redis + redis : redis.Redis The Redis connection instance. - message : models.VectorizedMessage + message : courageous_comets.models.VectorizedMessage The message to save + sentiment: courageous_comets.models.SentimentResult + The sentiment analayis result of the message Returns ------- @@ -67,7 +102,7 @@ async def save_message( index.set_client(redis) return ( await index.load( - [message.model_dump()], + [{**message.model_dump(), **sentiment.model_dump(by_alias=True)}], keys=[ key_schema.guild_messages( guild_id=message.guild_id, @@ -78,11 +113,11 @@ async def save_message( )[0] -async def get_similar_messages( +async def get_messages_by_semantics_similarity( redis: Redis, message: models.Message, embedding: bytes, - limit: int = 10, + limit: int = settings.QUERY_LIMIT, scope: StatisticScopeEnum = StatisticScopeEnum.GUILD, ) -> list[models.Message]: """ @@ -90,36 +125,26 @@ async def get_similar_messages( Parameters ---------- - redis : Redis + redis : redis.Redis The Redis connection instance. - message : models.Message + message : courageous_comets.models.Message The comparison message. embedding: bytes The vector embedding of the message. - limit : - The number of similar messages to fetch. - scope : enums.StatisticScopeEnum - The scope to limit the search. + limit : int + The number of similar messages to fetch (default: settings.PAGE_SIZE). + scope : courageous_comets.enums.StatisticScopeEnum + The scope to limit the search (default: enums.StatisticScopeEnum.GUILD). Returns ------- - list[models.Message] - The messages that are similar semantically. + list[courageous_comets.models.Message] + The messages that are semantically similar """ index = AsyncSearchIndex.from_dict(schema.MESSAGE_SCHEMA) index.set_client(redis) # Determine the scope to filter the search - match scope: - case StatisticScopeEnum.GUILD: - filter_expression = Tag("guild_id") == message.guild_id - case StatisticScopeEnum.CHANNEL: - filter_expression = Tag("channel_id") == message.channel_id - case StatisticScopeEnum.USER: - filter_expression = Tag("user_id") == message.user_id - case _: - error_message = f"Unhandled scope: {scope!r}" - raise ValueError(error_message) - + search_scope = get_search_scope(scope, message) query = VectorQuery( vector=embedding, vector_field_name="embedding", @@ -130,6 +155,51 @@ async def get_similar_messages( "guild_id", "timestamp", ], + filter_expression=search_scope, + num_results=limit, + ) + results = await index.query(query) + return [models.Message.model_validate(result) for result in results] + + +async def get_messages_by_sentiment_similarity( # noqa: PLR0913 + redis: Redis, + message: models.Message, + sentiment: models.SentimentResult, + radius: float, + limit: int = settings.QUERY_LIMIT, + scope: StatisticScopeEnum = StatisticScopeEnum.GUILD, +) -> list[models.Message]: + """ + Get the messages with similar sentiment analysis. + + Parameters + ---------- + redis : redis.Redis + The Redis connection instance. + sentiment : courageous_comets.models.SentimentResult + The sentiment analayis result of a message. + radius: float + The distance threshold of the search. + limit : int + The number of similar messages to fetch (default: settings.PAGE_SIZE). + scope : courageous_comets.enums.StatisticScopeEnum + The scope to limit the search (default: enums.StatisticScopeEnum.GUILD). + + Returns + ------- + list[courageous_comets.models.Message] + The messages that are sentimentally similar. + """ + index = AsyncSearchIndex.from_dict(schema.MESSAGE_SCHEMA) + index.set_client(redis) + # Determine the scope to filter the search + search_scope = get_search_scope(scope, message) + low = Num("sentiment_compound") >= sentiment.compound # pyright: ignore + high = Num("sentiment_compound") <= sentiment.compound + radius # pyright: ignore + filter_expression = search_scope & low & high + query = FilterQuery( + return_fields=["message_id", "user_id", "channel_id", "guild_id", "timestamp"], filter_expression=filter_expression, num_results=limit, ) diff --git a/courageous_comets/redis/schema.py b/courageous_comets/redis/schema.py index e82f982..53854a7 100644 --- a/courageous_comets/redis/schema.py +++ b/courageous_comets/redis/schema.py @@ -3,15 +3,18 @@ MESSAGE_SCHEMA = { "index": { "name": "message_idx", - "prefix": settings.REDIS_KEYS_PREFIX, + "prefix": f"{settings.REDIS_KEYS_PREFIX}:messages", }, "fields": [ - {"name": "content", "type": "text"}, {"name": "user_id", "type": "tag"}, {"name": "message_id", "type": "tag"}, {"name": "channel_id", "type": "tag"}, {"name": "guild_id", "type": "tag"}, {"name": "timestamp", "type": "numeric", "attrs": {"sortable": True}}, + {"name": "sentiment_neg", "type": "numeric", "attrs": {"sortable": True}}, + {"name": "sentiment_neu", "type": "numeric", "attrs": {"sortable": True}}, + {"name": "sentiment_pos", "type": "numeric", "attrs": {"sortable": True}}, + {"name": "sentiment_compound", "type": "numeric", "attrs": {"sortable": True}}, { "name": "embedding", "type": "vector", diff --git a/courageous_comets/sentiment.py b/courageous_comets/sentiment.py index dc683b8..07e1edb 100644 --- a/courageous_comets/sentiment.py +++ b/courageous_comets/sentiment.py @@ -1,18 +1,15 @@ import logging -from discord import Message from nltk.sentiment import SentimentIntensityAnalyzer -from redis.asyncio import Redis from courageous_comets.models import SentimentResult -from courageous_comets.redis.keys import key_schema MAX_MESSAGE_LENGTH = 256 logger = logging.getLogger(__name__) -def calculate_sentiment(content: str, key: str) -> SentimentResult: +def calculate_sentiment(content: str) -> SentimentResult: """ Calculate the sentiment of a message. @@ -25,8 +22,6 @@ def calculate_sentiment(content: str, key: str) -> SentimentResult: ---------- content : str The message content to analyze. - key : str - The Redis key for the message. Used for logging. Returns ------- @@ -36,87 +31,9 @@ def calculate_sentiment(content: str, key: str) -> SentimentResult: truncated = content[:MAX_MESSAGE_LENGTH] if truncated != content: - logger.warning("Truncated message %s to %s characters", key, MAX_MESSAGE_LENGTH) + logger.warning("Truncated message to %s characters", MAX_MESSAGE_LENGTH) sia = SentimentIntensityAnalyzer() result = sia.polarity_scores(truncated) return SentimentResult.model_validate(result) - - -async def store_sentiment(message: Message, redis: Redis) -> None: - """ - Calculate the sentiment of a message and store the result in Redis. - - A message must have a guild, channel, author and message ID. If any of these are missing, - the message will be ignored. - - Empty messages will also be ignored. - - Parameters - ---------- - message : discord.Message - The message to process. - redis : redis.asyncio.Redis - The Redis connection instance. - """ - # Ignore empty messages - if not message.content: - logger.warning("Ignoring empty message %s", message.id) - return - - # Extract the IDs from the message - guild_id = message.guild.id if message.guild else 0 - channel_id = message.channel.id if message.channel else 0 - user_id = message.author.id if message.author else 0 - message_id = message.id if message.id else 0 - - # Ignore messages without all required IDs - if not all((guild_id, channel_id, user_id, message_id)): - logger.warning("Ignoring message %s with missing IDs", message.id) - return - - # Construct the Redis key - key = key_schema.sentiment_tokens( - guild_id=guild_id, - channel_id=channel_id, - user_id=user_id, - message_id=message_id, - ) - - # Calculate the sentiment - sentiment = calculate_sentiment(message.content, key) - - # Store the sentiment in Redis - await redis.hset( - key, - mapping=sentiment.model_dump(mode="json"), - ) # pyright: ignore[reportGeneralTypeIssues] - - logger.info("Stored sentiment for message %s", key) - - -async def get_sentiment(key: str, redis: Redis) -> SentimentResult: - """ - Retrieve the sentiment of a message from Redis. - - Parameters - ---------- - key : str - The Redis key to retrieve the sentiment from. - redis : redis.asyncio.Redis - The Redis connection instance. - - Returns - ------- - courageous_comets.models.SentimentResult - The sentiment of the message. - """ - neg, neu, pos, compound = await redis.hmget(key, "neg", "neu", "pos", "compound") # type: ignore - - return SentimentResult( - neg=float(neg or 0), - neu=float(neu or 0), - pos=float(pos or 0), - compound=float(compound or 0), - ) diff --git a/courageous_comets/settings.py b/courageous_comets/settings.py index 753972d..de32863 100644 --- a/courageous_comets/settings.py +++ b/courageous_comets/settings.py @@ -160,6 +160,8 @@ def setup_logging() -> None: REDIS_PORT = read_redis_port() REDIS_PASSWORD = os.getenv("REDIS_PASSWORD") REDIS_KEYS_PREFIX = os.getenv("REDIS_KEYS_PREFIX", "courageous_comets") + # Maximum number of items to return from a query + QUERY_LIMIT = read_int("QUERY_LIMIT", 10) # Huggingface environment variable for caching downloaded models. # https://huggingface.co/docs/huggingface_hub/v0.24.0/package_reference/environment_variables#hf_home HF_HOME = os.getenv( diff --git a/tests/conftest.py b/tests/conftest.py index dbcbdca..d2a50d5 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -5,6 +5,7 @@ from courageous_comets import settings from courageous_comets.nltk import init_nltk +from courageous_comets.redis.schema import MESSAGE_SCHEMA from courageous_comets.transformers import init_transformers from courageous_comets.vectorizer import Vectorizer @@ -30,10 +31,23 @@ async def _load_transformers(application_config: dict) -> None: await init_transformers(transformers) -@pytest.fixture(scope="session", autouse=True) -def _patch_redis_keys_prefix() -> None: - """Set the REDIS_KEYS_PREFIX for testing.""" +@pytest.fixture(autouse=True) +def _patch_redis_keys_prefix(monkeypatch: pytest.MonkeyPatch) -> None: + """Set the REDIS_KEYS_PREFIX for testing. + + The patch applies the following steps: + + - Append _test to the courageous_comets.settings.REDIS_KEYS_PREFIX + - Update the names of Redis indexes using courageous_comets.settings.REDIS_KEYS_PREFIX + + """ settings.REDIS_KEYS_PREFIX = settings.REDIS_KEYS_PREFIX + "_test" + # MonkeyPatch the name of the Redis index for testing + monkeypatch.setitem( + MESSAGE_SCHEMA["index"], + "prefix", + f"{settings.REDIS_KEYS_PREFIX}:messages", + ) @pytest.fixture(scope="session") diff --git a/tests/courageous_comets/test__sentiment.py b/tests/courageous_comets/test__sentiment.py index 488fc5a..6033086 100644 --- a/tests/courageous_comets/test__sentiment.py +++ b/tests/courageous_comets/test__sentiment.py @@ -1,17 +1,12 @@ -from unittest.mock import Mock - import pytest -from pytest_mock import MockerFixture, MockType +from pytest_mock import MockerFixture from redis.asyncio import Redis from courageous_comets.models import SentimentResult -from courageous_comets.redis.keys import key_schema from courageous_comets.sentiment import ( MAX_MESSAGE_LENGTH, calculate_sentiment, - get_sentiment, logger, - store_sentiment, ) @@ -40,7 +35,7 @@ def test__calculate_sentiment_analyzes_sentiment_of_given_text( pos=mocker.ANY, compound=mocker.ANY, ) - result = calculate_sentiment("I love this product!", "test") + result = calculate_sentiment("I love this product!") assert result == expected @@ -65,134 +60,5 @@ def test__calculate_sentiment_truncates_long_messages( - The function truncates messages longer than 256 characters. """ logger_warning = mocker.spy(logger, "warning") - calculate_sentiment(message, "test") + calculate_sentiment(message) assert logger_warning.called == expected - - -async def test__store_sentiment_calculates_and_stores_sentiment( - *, - mocker: MockerFixture, - redis: MockType, -) -> None: - """ - Test whether the store sentiment function calculates and stores the sentiment of a message. - - Asserts - ------- - - The sentiment is calculated for the message content. - - The sentiment is stored in the database. - """ - message = mocker.Mock( - content="I love this product!", - guild=Mock(id=1), - channel=Mock(id=1), - author=Mock(id=1), - id=1, - ) - - await store_sentiment(message, redis) - - redis.hset.assert_awaited_with( - key_schema.sentiment_tokens(1, 1, 1, 1), - mapping={ - "neg": mocker.ANY, - "neu": mocker.ANY, - "pos": mocker.ANY, - "compound": mocker.ANY, - }, - ) - - -async def test__store_sentiment_ignores_empty_messages( - *, - mocker: MockerFixture, - redis: MockType, -) -> None: - """ - Test whether the store sentiment function ignores empty messages. - - Asserts - ------- - - The database is not updated when the message is empty. - """ - message = mocker.Mock(content="") - await store_sentiment(message, redis) - redis.hset.assert_not_awaited() - - -@pytest.mark.parametrize( - "message", - [ - Mock(content="test", guild=None, channel=None, author=None, id=1), - Mock(content="test", guild=Mock(id=1), channel=None, author=None, id=1), - Mock(content="test", guild=Mock(id=1), channel=Mock(id=1), author=None, id=1), - ], -) -async def test__store_sentiment_ignores_messages_without_ids( - *, - redis: MockType, - message: MockType, -) -> None: - """ - Test whether the store sentiment function ignores messages without IDs. - - Asserts - ------- - - The database is not updated when the message is missing IDs. - """ - await store_sentiment(message, redis) - redis.hset.assert_not_awaited() - - -async def test__get_sentiment_retrieves_sentiment_from_redis( - *, - redis: MockType, -) -> None: - """ - Test whether the get sentiment function retrieves the sentiment of a message from Redis. - - Asserts - ------- - - The sentiment is retrieved from the database. - """ - key = key_schema.sentiment_tokens(1, 1, 1, 1) - redis.hmget.return_value = ["1.0", "1.0", "1.0", "1.0"] - - expected = SentimentResult( - neg=1.0, - neu=1.0, - pos=1.0, - compound=1.0, - ) - - result = await get_sentiment(key, redis) - - redis.hmget.assert_awaited_with(key, "neg", "neu", "pos", "compound") - assert result == expected - - -async def test__get_sentiment_handles_missing_sentiment( - *, - redis: MockType, -) -> None: - """ - Test whether the get sentiment function handles missing sentiment in Redis. - - Asserts - ------- - - The function returns default sentiment values when the sentiment is missing. - """ - key = key_schema.sentiment_tokens(1, 1, 1, 1) - redis.hmget.return_value = [None, None, None, None] - - expected = SentimentResult( - neg=0.0, - neu=0.0, - pos=0.0, - compound=0.0, - ) - - result = await get_sentiment(key, redis) - - redis.hmget.assert_awaited_with(key, "neg", "neu", "pos", "compound") - assert expected == result diff --git a/tests/integrations/test__redis.py b/tests/integrations/test__redis.py index f9df7b1..b302f1e 100644 --- a/tests/integrations/test__redis.py +++ b/tests/integrations/test__redis.py @@ -6,7 +6,12 @@ from courageous_comets import models from courageous_comets.redis.keys import key_schema -from courageous_comets.redis.messages import get_similar_messages, save_message +from courageous_comets.redis.messages import ( + get_messages_by_semantics_similarity, + get_messages_by_sentiment_similarity, + save_message, +) +from courageous_comets.sentiment import calculate_sentiment from courageous_comets.vectorizer import Vectorizer @@ -16,7 +21,7 @@ def message() -> models.Message: Returns ------- - models.Message + courageous_comets.models.Message A Redis model of a Discord message """ return models.Message( @@ -28,54 +33,153 @@ def message() -> models.Message: ) +@pytest.fixture(scope="session") +def content() -> str: + """Fixture that sets up the content of message. + + Returns + ------- + str + The message content. + """ + return "The quick brown fox jumps over the lazy dog." + + @pytest_asyncio.fixture(scope="session") async def vectorized_message( message: models.Message, + content: str, vectorizer: Vectorizer, ) -> models.VectorizedMessage: """Fixture that creates an embedding vector of contents of message. + Parameters + ---------- + message: courageous_comets.models.Message + The (base) model of the Discord message. + content: + The contents of the message. + vectorizer: courageous_comets.vectorizer.Vectorizer + The model that creates the embedding vector of content + Returns ------- - models.VectorizedMessage - A message with embedding vector + courageous_comets.models.VectorizedMessage + A message with embedding vector of contents. """ - content = "The quick brown fox jumps over the lazy dog." embedding = await vectorizer.aencode(content) return models.VectorizedMessage(**message.model_dump(), embedding=embedding) +@pytest.fixture(scope="session") +def sentiment(content: str) -> models.SentimentResult: + """Fixture that calculates the sentiment analysis of content. + + Parameters + ---------- + content: + The text to run the sentiment analysis on. + + Returns + ------- + models.SentimentResult + The result of the sentiment analysis. + """ + return calculate_sentiment(content) + + async def test__save_message( redis: Redis, vectorized_message: models.VectorizedMessage, + sentiment: models.SentimentResult, ) -> None: """ Tests whether the save_mesage function stores the message on Redis. + Parameters + ---------- + redis: redis.Redis + The Redis connection instance. + vectorized_message: courageous_comets.models.VectorizedMessage + The Discord message with an embedding vector of its content. + sentiment : courageous_comets.models.SentimentResult + The sentiment analayis result of a message. + Asserts ------- - The returned key is the same as the one constructed by the key_schema """ - key = await save_message(redis, vectorized_message) + key = await save_message(redis, vectorized_message, sentiment) assert key == key_schema.guild_messages( guild_id=vectorized_message.guild_id, message_id=vectorized_message.message_id, ) -async def test__get_similar_message( +async def test__get_messages_by_semantics_similarity( redis: Redis, message: models.Message, vectorized_message: models.VectorizedMessage, + sentiment: models.SentimentResult, ) -> None: """ Tests that the same message is returned by the get_similar_messages function. + Parameters + ---------- + redis: redis.Redis + The Redis connection instance. + message: courageous_comets.models.Message + The (base) model of the Discord message. + vectorized_message: courageous_comets.models.VectorizedMessage + The Discord message with an embedding vector of its content. + sentiment : courageous_comets.models.SentimentResult + The sentiment analayis result of a message. + Asserts ------- - The returned message is the same message whose embedding vector was used """ - await save_message(redis, vectorized_message) - messages = await get_similar_messages(redis, message, vectorized_message.embedding) + await save_message(redis, vectorized_message, sentiment) + messages = await get_messages_by_semantics_similarity( + redis, + message, + vectorized_message.embedding, + ) + assert len(messages) == 1 + assert messages[0].model_dump() == message.model_dump() + + +async def test__get_messages_by_sentiment_similarity( + redis: Redis, + message: models.Message, + vectorized_message: models.VectorizedMessage, + sentiment: models.SentimentResult, +) -> None: + """ + Tests that the same message is returned by the get_messages_by_sentiment_similarity function. + + Parameters + ---------- + redis: redis.Redis + The Redis connection instance. + message: courageous_comets.models.Message + The (base) model of the Discord message. + vectorized_message: courageous_comets.models.VectorizedMessage + The Discord message with an embedding vector of its content. + sentiment : courageous_comets.models.SentimentResult + The sentiment analayis result of a message. + + Asserts + ------- + - The returned message is the same message whose sentiment analysis result was used + """ + await save_message(redis, vectorized_message, sentiment) + messages = await get_messages_by_sentiment_similarity( + redis, + message, + sentiment, + radius=0.1, + ) assert len(messages) == 1 assert messages[0].model_dump() == message.model_dump() From cde7628873236347dfcb1f2b486c10c486335ae9 Mon Sep 17 00:00:00 2001 From: Uchechukwu Orji Date: Wed, 24 Jul 2024 05:39:59 +0100 Subject: [PATCH 083/168] feat: limit queries to most recent messsages (#43) --- courageous_comets/models.py | 6 +- courageous_comets/redis/keys.py | 2 +- courageous_comets/redis/messages.py | 104 ++++++++++++++++++++++------ tests/integrations/test__redis.py | 37 +++++++++- 4 files changed, 122 insertions(+), 27 deletions(-) diff --git a/courageous_comets/models.py b/courageous_comets/models.py index a08d591..900b08b 100644 --- a/courageous_comets/models.py +++ b/courageous_comets/models.py @@ -14,7 +14,11 @@ class BaseModel(pydantic.BaseModel): """Base for model definitions.""" # Redis tags need to be strings, thus, coerce integers passed during creation to string - model_config = pydantic.ConfigDict(extra="ignore", coerce_numbers_to_str=True) + model_config = pydantic.ConfigDict( + extra="ignore", + coerce_numbers_to_str=True, + from_attributes=True, + ) class Message(BaseModel): diff --git a/courageous_comets/redis/keys.py b/courageous_comets/redis/keys.py index 1d741dd..6fc6ae6 100644 --- a/courageous_comets/redis/keys.py +++ b/courageous_comets/redis/keys.py @@ -42,7 +42,7 @@ class KeySchema: """ @prefix_key - def guild_messages(self, *, guild_id: int | str, message_id: int | str) -> str: + def guild_messages(self, *, guild_id: int, message_id: int) -> str: """Key to messages for a Discord guild. Redis type: hash diff --git a/courageous_comets/redis/messages.py b/courageous_comets/redis/messages.py index e5358dc..6c7bf18 100644 --- a/courageous_comets/redis/messages.py +++ b/courageous_comets/redis/messages.py @@ -2,12 +2,50 @@ from redisvl.index import AsyncSearchIndex from redisvl.query import FilterQuery, VectorQuery from redisvl.query.filter import FilterExpression, Num, Tag +from redisvl.query.query import BaseQuery from courageous_comets import models, settings from courageous_comets.enums import StatisticScopeEnum from courageous_comets.redis import schema from courageous_comets.redis.keys import key_schema +# List of courageous_comets.models.Message return fields used acrosss queries +# that return a list of courageous_comets.models.Message +RETURN_FIELDS = ["message_id", "user_id", "channel_id", "guild_id", "timestamp"] + + +async def _get_messages_from_query( + redis: Redis, + query: BaseQuery, +) -> list[models.Message]: + """Get a list of messages from Redis query. + + Assumes the fields returned in the query correspond to the attributes + of the courageous_comets.models.Message. + + Parameters + ---------- + redis: redis.Redis + The Redis connection instance. + query: redisvl.query.query.BaseQuery + The query to run on Redis. + + Returns + ------- + courageous_comets.models.Message + The list of messages from the query. + """ + index = AsyncSearchIndex.from_dict(schema.MESSAGE_SCHEMA) + index.set_client(redis) + results = await index.search( + query.query.sort_by("timestamp", asc=False), + query.params, + ) + if results.total == 0: + return [] + + return [models.Message.model_validate(doc) for doc in results.docs] + def get_search_scope( scope: StatisticScopeEnum, @@ -105,14 +143,47 @@ async def save_message( [{**message.model_dump(), **sentiment.model_dump(by_alias=True)}], keys=[ key_schema.guild_messages( - guild_id=message.guild_id, - message_id=message.message_id, + guild_id=int(message.guild_id), + message_id=int(message.message_id), ), ], ) )[0] +async def get_recent_messages( + redis: Redis, + message: models.Message, + scope: StatisticScopeEnum = StatisticScopeEnum.GUILD, + limit: int = settings.QUERY_LIMIT, +) -> list[models.Message]: + """ + Get the most recent `limit` messages. + + Parameters + ---------- + redis : redis.Redis + The Redis connection instance. + message: courageous_comets.models.Message + The discord message. + limit : int + The number of messages to fetch (default: settings.PAGE_SIZE). + scope : courageous_comets.enums.StatisticScopeEnum + The scope to limit the search (default: enums.StatisticScopeEnum.GUILD). + + Returns + ------- + list[courageous_comets.models.Message] + The list of recent messages. + """ + query = FilterQuery( + return_fields=RETURN_FIELDS, + filter_expression=get_search_scope(scope, message), + num_results=limit, + ) + return await _get_messages_from_query(redis, query) + + async def get_messages_by_semantics_similarity( redis: Redis, message: models.Message, @@ -141,25 +212,14 @@ async def get_messages_by_semantics_similarity( list[courageous_comets.models.Message] The messages that are semantically similar """ - index = AsyncSearchIndex.from_dict(schema.MESSAGE_SCHEMA) - index.set_client(redis) - # Determine the scope to filter the search - search_scope = get_search_scope(scope, message) query = VectorQuery( vector=embedding, vector_field_name="embedding", - return_fields=[ - "message_id", - "channel_id", - "user_id", - "guild_id", - "timestamp", - ], - filter_expression=search_scope, + return_fields=RETURN_FIELDS, + filter_expression=get_search_scope(scope, message), num_results=limit, ) - results = await index.query(query) - return [models.Message.model_validate(result) for result in results] + return await _get_messages_from_query(redis, query) async def get_messages_by_sentiment_similarity( # noqa: PLR0913 @@ -177,8 +237,10 @@ async def get_messages_by_sentiment_similarity( # noqa: PLR0913 ---------- redis : redis.Redis The Redis connection instance. + message: courageous_comets.models.Message + The discord messaage. sentiment : courageous_comets.models.SentimentResult - The sentiment analayis result of a message. + The sentiment analayis result of message. radius: float The distance threshold of the search. limit : int @@ -191,17 +253,13 @@ async def get_messages_by_sentiment_similarity( # noqa: PLR0913 list[courageous_comets.models.Message] The messages that are sentimentally similar. """ - index = AsyncSearchIndex.from_dict(schema.MESSAGE_SCHEMA) - index.set_client(redis) - # Determine the scope to filter the search search_scope = get_search_scope(scope, message) low = Num("sentiment_compound") >= sentiment.compound # pyright: ignore high = Num("sentiment_compound") <= sentiment.compound + radius # pyright: ignore filter_expression = search_scope & low & high query = FilterQuery( - return_fields=["message_id", "user_id", "channel_id", "guild_id", "timestamp"], + return_fields=RETURN_FIELDS, filter_expression=filter_expression, num_results=limit, ) - results = await index.query(query) - return [models.Message.model_validate(result) for result in results] + return await _get_messages_from_query(redis, query) diff --git a/tests/integrations/test__redis.py b/tests/integrations/test__redis.py index b302f1e..47d1544 100644 --- a/tests/integrations/test__redis.py +++ b/tests/integrations/test__redis.py @@ -9,6 +9,7 @@ from courageous_comets.redis.messages import ( get_messages_by_semantics_similarity, get_messages_by_sentiment_similarity, + get_recent_messages, save_message, ) from courageous_comets.sentiment import calculate_sentiment @@ -111,8 +112,8 @@ async def test__save_message( """ key = await save_message(redis, vectorized_message, sentiment) assert key == key_schema.guild_messages( - guild_id=vectorized_message.guild_id, - message_id=vectorized_message.message_id, + guild_id=int(vectorized_message.guild_id), + message_id=int(vectorized_message.message_id), ) @@ -183,3 +184,35 @@ async def test__get_messages_by_sentiment_similarity( ) assert len(messages) == 1 assert messages[0].model_dump() == message.model_dump() + + +async def test__get_recent_messages( + redis: Redis, + message: models.Message, + vectorized_message: models.VectorizedMessage, + sentiment: models.SentimentResult, +) -> None: + """ + Tests that the most recently saved message is returned by get_recent_messages . + + Parameters + ---------- + redis: redis.Redis + The Redis connection instance. + message: courageous_comets.models.Message + The (base) model of the Discord message. + vectorized_message: courageous_comets.models.VectorizedMessage + The Discord message with an embedding vector of its content. + sentiment: courageous_comets.models.SentimentResult + The sentiment analayis result of the message + + Asserts + ------- + - The most recently saved message is returned. + """ + # Save the default message to the database. + await save_message(redis, vectorized_message, sentiment) + # Update its timestamp with the provided message_timestamp + messages = await get_recent_messages(redis, message) + assert len(messages) == 1 + assert messages[0].model_dump() == message.model_dump() From 3168e95c2d5f17637cd326476f95eab54b551119 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Wed, 24 Jul 2024 08:28:15 +0200 Subject: [PATCH 084/168] refactor: simplify function signatures and make it easier to build scope filters (#44) --- courageous_comets/enums.py | 10 +-- courageous_comets/redis/messages.py | 116 ++++++++++++++-------------- tests/integrations/test__redis.py | 8 +- 3 files changed, 69 insertions(+), 65 deletions(-) diff --git a/courageous_comets/enums.py b/courageous_comets/enums.py index 56005a9..aed7003 100644 --- a/courageous_comets/enums.py +++ b/courageous_comets/enums.py @@ -1,9 +1,9 @@ -import enum +from enum import StrEnum -class StatisticScopeEnum(enum.Enum): +class StatisticScope(StrEnum): """Scope of statistics results to fetch.""" - GUILD = enum.auto() - CHANNEL = enum.auto() - USER = enum.auto() + GUILD = "guild_id" + CHANNEL = "channel_id" + USER = "user_id" diff --git a/courageous_comets/redis/messages.py b/courageous_comets/redis/messages.py index 6c7bf18..2be4a0d 100644 --- a/courageous_comets/redis/messages.py +++ b/courageous_comets/redis/messages.py @@ -5,7 +5,7 @@ from redisvl.query.query import BaseQuery from courageous_comets import models, settings -from courageous_comets.enums import StatisticScopeEnum +from courageous_comets.enums import StatisticScope from courageous_comets.redis import schema from courageous_comets.redis.keys import key_schema @@ -37,46 +37,38 @@ async def _get_messages_from_query( """ index = AsyncSearchIndex.from_dict(schema.MESSAGE_SCHEMA) index.set_client(redis) + results = await index.search( query.query.sort_by("timestamp", asc=False), query.params, ) + if results.total == 0: return [] return [models.Message.model_validate(doc) for doc in results.docs] -def get_search_scope( - scope: StatisticScopeEnum, - message: models.Message, +def build_search_scope( + id_: str, + scope: StatisticScope, ) -> FilterExpression: - """Determine the scope of a search. + """ + Build a filter expression based on the given id and specified scope. Parameters ---------- - scope : courageous_comets.enums.StatisticScopeEnum - The scope of a search. - message : courageous_comets.models.Message - The comparison message. + id_ : str + The ID to search for. + scope : courageous_comets.enums.StatisticScope + The scope to limit the search. Returns ------- redisvl.query.FilterExpression - The redis filter expression for the specified scope - + The redis filter expression for the specified scope. """ - match scope: - case StatisticScopeEnum.GUILD: - filter_expression = Tag("guild_id") == message.guild_id - case StatisticScopeEnum.CHANNEL: - filter_expression = Tag("channel_id") == message.channel_id - case StatisticScopeEnum.USER: - filter_expression = Tag("user_id") == message.user_id - case _: - error_message = f"Unhandled scope: {scope!r}" - raise ValueError(error_message) - return filter_expression + return Tag(scope) == id_ async def update_message_tokens( @@ -138,23 +130,24 @@ async def save_message( """ index = AsyncSearchIndex.from_dict(schema.MESSAGE_SCHEMA) index.set_client(redis) - return ( - await index.load( - [{**message.model_dump(), **sentiment.model_dump(by_alias=True)}], - keys=[ - key_schema.guild_messages( - guild_id=int(message.guild_id), - message_id=int(message.message_id), - ), - ], - ) - )[0] + + # Merge the message and sentiment data into a single record + data = {**message.model_dump(), **sentiment.model_dump(by_alias=True)} + + key = key_schema.guild_messages( + guild_id=int(message.guild_id), + message_id=int(message.message_id), + ) + + results = await index.load(data=[data], keys=[key]) + + return results[0] async def get_recent_messages( redis: Redis, - message: models.Message, - scope: StatisticScopeEnum = StatisticScopeEnum.GUILD, + id_: str, + scope: StatisticScope = StatisticScope.GUILD, limit: int = settings.QUERY_LIMIT, ) -> list[models.Message]: """ @@ -164,32 +157,35 @@ async def get_recent_messages( ---------- redis : redis.Redis The Redis connection instance. - message: courageous_comets.models.Message - The discord message. + id_ : str + The ID of the entity to search for. It should correspond to the given scope. + scope : courageous_comets.enums.StatisticScope + The scope to limit the search (default: enums.StatisticScopeEnum.GUILD). limit : int The number of messages to fetch (default: settings.PAGE_SIZE). - scope : courageous_comets.enums.StatisticScopeEnum - The scope to limit the search (default: enums.StatisticScopeEnum.GUILD). Returns ------- list[courageous_comets.models.Message] The list of recent messages. """ + search_scope = build_search_scope(id_, scope) + query = FilterQuery( return_fields=RETURN_FIELDS, - filter_expression=get_search_scope(scope, message), + filter_expression=search_scope, num_results=limit, ) + return await _get_messages_from_query(redis, query) async def get_messages_by_semantics_similarity( redis: Redis, - message: models.Message, + id_: str, embedding: bytes, + scope: StatisticScope = StatisticScope.GUILD, limit: int = settings.QUERY_LIMIT, - scope: StatisticScopeEnum = StatisticScopeEnum.GUILD, ) -> list[models.Message]: """ Get the messages with similar semantics to the provided message. @@ -198,37 +194,40 @@ async def get_messages_by_semantics_similarity( ---------- redis : redis.Redis The Redis connection instance. - message : courageous_comets.models.Message - The comparison message. + id_ : str + The ID of the entity to search for. It should correspond to the given scope. embedding: bytes The vector embedding of the message. + scope : courageous_comets.enums.StatisticScope + The scope to limit the search (default: enums.StatisticScopeEnum.GUILD). limit : int The number of similar messages to fetch (default: settings.PAGE_SIZE). - scope : courageous_comets.enums.StatisticScopeEnum - The scope to limit the search (default: enums.StatisticScopeEnum.GUILD). Returns ------- list[courageous_comets.models.Message] The messages that are semantically similar """ + search_scope = build_search_scope(id_, scope) + query = VectorQuery( vector=embedding, vector_field_name="embedding", return_fields=RETURN_FIELDS, - filter_expression=get_search_scope(scope, message), + filter_expression=search_scope, num_results=limit, ) + return await _get_messages_from_query(redis, query) async def get_messages_by_sentiment_similarity( # noqa: PLR0913 redis: Redis, - message: models.Message, - sentiment: models.SentimentResult, + id_: str, + sentiment: float, radius: float, + scope: StatisticScope = StatisticScope.GUILD, limit: int = settings.QUERY_LIMIT, - scope: StatisticScopeEnum = StatisticScopeEnum.GUILD, ) -> list[models.Message]: """ Get the messages with similar sentiment analysis. @@ -237,9 +236,9 @@ async def get_messages_by_sentiment_similarity( # noqa: PLR0913 ---------- redis : redis.Redis The Redis connection instance. - message: courageous_comets.models.Message - The discord messaage. - sentiment : courageous_comets.models.SentimentResult + id_ : str + The ID of the entity to search for. It should correspond to the given scope. + sentiment : float The sentiment analayis result of message. radius: float The distance threshold of the search. @@ -253,13 +252,18 @@ async def get_messages_by_sentiment_similarity( # noqa: PLR0913 list[courageous_comets.models.Message] The messages that are sentimentally similar. """ - search_scope = get_search_scope(scope, message) - low = Num("sentiment_compound") >= sentiment.compound # pyright: ignore - high = Num("sentiment_compound") <= sentiment.compound + radius # pyright: ignore + search_scope = build_search_scope(id_, scope) + + # Define lower and upper bounds for the sentiment compound score + low = Num("sentiment_compound") >= sentiment - radius # type: ignore + high = Num("sentiment_compound") <= sentiment + radius # type: ignore + filter_expression = search_scope & low & high + query = FilterQuery( return_fields=RETURN_FIELDS, filter_expression=filter_expression, num_results=limit, ) + return await _get_messages_from_query(redis, query) diff --git a/tests/integrations/test__redis.py b/tests/integrations/test__redis.py index 47d1544..791278e 100644 --- a/tests/integrations/test__redis.py +++ b/tests/integrations/test__redis.py @@ -144,7 +144,7 @@ async def test__get_messages_by_semantics_similarity( await save_message(redis, vectorized_message, sentiment) messages = await get_messages_by_semantics_similarity( redis, - message, + message.guild_id, vectorized_message.embedding, ) assert len(messages) == 1 @@ -178,8 +178,8 @@ async def test__get_messages_by_sentiment_similarity( await save_message(redis, vectorized_message, sentiment) messages = await get_messages_by_sentiment_similarity( redis, - message, - sentiment, + message.guild_id, + sentiment.compound, radius=0.1, ) assert len(messages) == 1 @@ -213,6 +213,6 @@ async def test__get_recent_messages( # Save the default message to the database. await save_message(redis, vectorized_message, sentiment) # Update its timestamp with the provided message_timestamp - messages = await get_recent_messages(redis, message) + messages = await get_recent_messages(redis, message.guild_id) assert len(messages) == 1 assert messages[0].model_dump() == message.model_dump() From d7caa8098e12fba63d64173e484e760df0499e21 Mon Sep 17 00:00:00 2001 From: Uchechukwu Orji Date: Wed, 24 Jul 2024 11:30:19 +0100 Subject: [PATCH 085/168] feat: store message tokens (#45) --- courageous_comets/cogs/messages.py | 27 +++-- courageous_comets/models.py | 32 +++--- courageous_comets/redis/messages.py | 72 ++++--------- tests/integrations/test__redis.py | 151 ++++++++++++---------------- 4 files changed, 118 insertions(+), 164 deletions(-) diff --git a/courageous_comets/cogs/messages.py b/courageous_comets/cogs/messages.py index ff34346..804fe5a 100644 --- a/courageous_comets/cogs/messages.py +++ b/courageous_comets/cogs/messages.py @@ -5,10 +5,11 @@ from courageous_comets import preprocessing from courageous_comets.client import CourageousCometsBot -from courageous_comets.models import VectorizedMessage +from courageous_comets.models import MessageAnalysis from courageous_comets.redis import messages from courageous_comets.sentiment import calculate_sentiment from courageous_comets.vectorizer import Vectorizer +from courageous_comets.words import tokenize_sentence, word_frequency logger = logging.getLogger(__name__) @@ -65,18 +66,22 @@ async def save_message(self, message: discord.Message) -> None: embedding = await self.vectorizer.aencode(text) sentiment = calculate_sentiment(text) - - vectorized_message = VectorizedMessage( - user_id=str(message.author.id), - message_id=str(message.id), - channel_id=str(message.channel.id), - guild_id=str(message.guild.id), - timestamp=message.created_at, - embedding=embedding, + tokens = tokenize_sentence(text) + + key = await messages.save_message( + self.bot.redis, + MessageAnalysis( + user_id=str(message.author.id), + message_id=str(message.id), + channel_id=str(message.channel.id), + guild_id=str(message.guild.id), + timestamp=message.created_at, + embedding=embedding, + sentiment=sentiment, + tokens=word_frequency(tokens), + ), ) - key = await messages.save_message(self.bot.redis, vectorized_message, sentiment) - return logger.info( "Saved message %s to Redis with key %s", message.id, diff --git a/courageous_comets/models.py b/courageous_comets/models.py index 900b08b..0782ade 100644 --- a/courageous_comets/models.py +++ b/courageous_comets/models.py @@ -46,19 +46,6 @@ class Message(BaseModel): user_id: str -class VectorizedMessage(Message): - """Message with vector embedding of content. - - Attributes - ---------- - embedding : bytes - The embedding of the content. - embedding: bytes - """ - - embedding: bytes - - class SentimentResult(BaseModel): """ Result of sentiment analysis. @@ -79,3 +66,22 @@ class SentimentResult(BaseModel): neu: float = Field(..., serialization_alias="sentiment_neu") pos: float = Field(..., serialization_alias="sentiment_pos") compound: float = Field(..., serialization_alias="sentiment_compound") + + +class MessageAnalysis(Message): + """ + Analysis of a discord message. + + Attributes + ---------- + sentiment: courageous_comets.models.SentimentResult + The result of sentiment analysis on the message. + tokens: dict[str, int] + Mapping of token to number of times it appears in message. + embedding : bytes + The embedding vector of the content. + """ + + sentiment: SentimentResult + tokens: dict[str, int] + embedding: bytes diff --git a/courageous_comets/redis/messages.py b/courageous_comets/redis/messages.py index 2be4a0d..1c39d07 100644 --- a/courageous_comets/redis/messages.py +++ b/courageous_comets/redis/messages.py @@ -1,3 +1,5 @@ +import json + from redis.asyncio import Redis from redisvl.index import AsyncSearchIndex from redisvl.query import FilterQuery, VectorQuery @@ -71,46 +73,9 @@ def build_search_scope( return Tag(scope) == id_ -async def update_message_tokens( - redis: Redis, - guild_id: int, - words: dict[str, int], -) -> dict[str, int]: - """ - Update the word frequencies for a guild's messages in the database. - - Parameters - ---------- - redis : redis.Redis - The Redis connection instance. - guild_id : int - The ID of the guild. - words : dict[str, int] - The words and their frequencies to update. - - Returns - ------- - dict[str, int] - The updated word frequencies. - """ - async with redis.pipeline() as pipe: - # Queue the increment operations for each word - for word, frequency in words.items(): - pipe.hincrby(key_schema.guild_message_tokens(guild_id), word, frequency) - - # Execute the pipeline - new_frequencies: list[int] = await pipe.execute() - - # Given dictionary keys are iterated in insertion order, execution of the - # instructions are evaluated in the same order, thus, the return values are - # in the same order. - return dict(zip(words.keys(), new_frequencies, strict=False)) - - async def save_message( redis: Redis, - message: models.VectorizedMessage, - sentiment: models.SentimentResult, + message: models.MessageAnalysis, ) -> str: """Save a message on Redis. @@ -118,30 +83,33 @@ async def save_message( ---------- redis : redis.Redis The Redis connection instance. - message : courageous_comets.models.VectorizedMessage - The message to save - sentiment: courageous_comets.models.SentimentResult - The sentiment analayis result of the message + message : courageous_comets.models.MessageAnalysis + The message to save. Returns ------- str The key to the data on Redis. """ - index = AsyncSearchIndex.from_dict(schema.MESSAGE_SCHEMA) - index.set_client(redis) - - # Merge the message and sentiment data into a single record - data = {**message.model_dump(), **sentiment.model_dump(by_alias=True)} - + payload = { + "message_id": message.message_id, + "channel_id": message.channel_id, + "guild_id": message.guild_id, + "timestamp": message.timestamp.timestamp(), + "user_id": message.user_id, + "sentiment_neg": message.sentiment.neg, + "sentiment_neu": message.sentiment.neu, + "sentiment_pos": message.sentiment.pos, + "sentiment_compound": message.sentiment.compound, + "embedding": message.embedding, + "tokens": json.dumps(message.tokens), + } key = key_schema.guild_messages( guild_id=int(message.guild_id), message_id=int(message.message_id), ) - - results = await index.load(data=[data], keys=[key]) - - return results[0] + await redis.hset(key, mapping=payload) # type: ignore + return key async def get_recent_messages( diff --git a/tests/integrations/test__redis.py b/tests/integrations/test__redis.py index 791278e..cbb8094 100644 --- a/tests/integrations/test__redis.py +++ b/tests/integrations/test__redis.py @@ -14,24 +14,7 @@ ) from courageous_comets.sentiment import calculate_sentiment from courageous_comets.vectorizer import Vectorizer - - -@pytest.fixture(scope="session") -def message() -> models.Message: - """Fixture that sets up a message. - - Returns - ------- - courageous_comets.models.Message - A Redis model of a Discord message - """ - return models.Message( - message_id="1", - channel_id="1", - guild_id="1", - timestamp=datetime.datetime.fromtimestamp(0, datetime.UTC), - user_id="1", - ) +from courageous_comets.words import tokenize_sentence, word_frequency @pytest.fixture(scope="session") @@ -46,53 +29,62 @@ def content() -> str: return "The quick brown fox jumps over the lazy dog." -@pytest_asyncio.fixture(scope="session") -async def vectorized_message( - message: models.Message, - content: str, - vectorizer: Vectorizer, -) -> models.VectorizedMessage: - """Fixture that creates an embedding vector of contents of message. +@pytest.fixture(scope="session") +def sentiment(content: str) -> models.SentimentResult: + """Fixture that calculates the sentiment analysis of content. Parameters ---------- - message: courageous_comets.models.Message - The (base) model of the Discord message. - content: - The contents of the message. - vectorizer: courageous_comets.vectorizer.Vectorizer - The model that creates the embedding vector of content + content: str + The text to run the sentiment analysis on. Returns ------- - courageous_comets.models.VectorizedMessage - A message with embedding vector of contents. + models.SentimentResult + The result of the sentiment analysis. """ - embedding = await vectorizer.aencode(content) - return models.VectorizedMessage(**message.model_dump(), embedding=embedding) + return calculate_sentiment(content) -@pytest.fixture(scope="session") -def sentiment(content: str) -> models.SentimentResult: - """Fixture that calculates the sentiment analysis of content. +@pytest_asyncio.fixture(scope="session") +async def message( + content: str, + vectorizer: Vectorizer, + sentiment: models.SentimentResult, +) -> models.MessageAnalysis: + """Fixture that creates a message to be stored on Redis. Parameters ---------- - content: - The text to run the sentiment analysis on. + content: str + The contents of the message. + vectorizer: courageous_comets.vectorizer.Vectorizer + The model that creates the embedding vector of content. + sentiment: models.SentimentResult + The result of the sentiment analysis of content. Returns ------- - models.SentimentResult - The result of the sentiment analysis. + courageous_comets.models.MessageAnalysis + An analysis of the message contents. """ - return calculate_sentiment(content) + embedding = await vectorizer.aencode(content) + tokens = tokenize_sentence(content) + return models.MessageAnalysis( + message_id="1", + channel_id="1", + guild_id="1", + timestamp=datetime.datetime.fromtimestamp(0, datetime.UTC), + user_id="1", + embedding=embedding, + sentiment=sentiment, + tokens=word_frequency(tokens), + ) async def test__save_message( redis: Redis, - vectorized_message: models.VectorizedMessage, - sentiment: models.SentimentResult, + message: models.MessageAnalysis, ) -> None: """ Tests whether the save_mesage function stores the message on Redis. @@ -101,81 +93,70 @@ async def test__save_message( ---------- redis: redis.Redis The Redis connection instance. - vectorized_message: courageous_comets.models.VectorizedMessage + message: courageous_comets.models.MessageAnalysis The Discord message with an embedding vector of its content. - sentiment : courageous_comets.models.SentimentResult - The sentiment analayis result of a message. Asserts ------- - The returned key is the same as the one constructed by the key_schema """ - key = await save_message(redis, vectorized_message, sentiment) + key = await save_message(redis, message) assert key == key_schema.guild_messages( - guild_id=int(vectorized_message.guild_id), - message_id=int(vectorized_message.message_id), + guild_id=int(message.guild_id), + message_id=int(message.message_id), ) async def test__get_messages_by_semantics_similarity( redis: Redis, - message: models.Message, - vectorized_message: models.VectorizedMessage, - sentiment: models.SentimentResult, + message: models.MessageAnalysis, ) -> None: """ - Tests that the same message is returned by the get_similar_messages function. + Tests that the same message is returned from a semantics similarity search. Parameters ---------- redis: redis.Redis The Redis connection instance. - message: courageous_comets.models.Message - The (base) model of the Discord message. - vectorized_message: courageous_comets.models.VectorizedMessage - The Discord message with an embedding vector of its content. - sentiment : courageous_comets.models.SentimentResult - The sentiment analayis result of a message. + message: courageous_comets.models.MessageAnalysis + The message to save. Asserts ------- - - The returned message is the same message whose embedding vector was used + - The same message is returned using a semantics similarity search. """ - await save_message(redis, vectorized_message, sentiment) + await save_message(redis, message) messages = await get_messages_by_semantics_similarity( redis, message.guild_id, - vectorized_message.embedding, + message.embedding, ) assert len(messages) == 1 - assert messages[0].model_dump() == message.model_dump() + assert messages[0].message_id == message.message_id async def test__get_messages_by_sentiment_similarity( redis: Redis, - message: models.Message, - vectorized_message: models.VectorizedMessage, + message: models.MessageAnalysis, sentiment: models.SentimentResult, ) -> None: """ - Tests that the same message is returned by the get_messages_by_sentiment_similarity function. + Tests that the same message is returned from a sentiment similarity search. Parameters ---------- redis: redis.Redis The Redis connection instance. - message: courageous_comets.models.Message - The (base) model of the Discord message. - vectorized_message: courageous_comets.models.VectorizedMessage - The Discord message with an embedding vector of its content. + message: courageous_comets.models.MessageAnalysis + The message to save. sentiment : courageous_comets.models.SentimentResult - The sentiment analayis result of a message. + The sentiment analayis result of message. Asserts ------- - - The returned message is the same message whose sentiment analysis result was used + - The same message is returned using a sentiment similarity search. """ - await save_message(redis, vectorized_message, sentiment) + await save_message(redis, message) messages = await get_messages_by_sentiment_similarity( redis, message.guild_id, @@ -183,36 +164,30 @@ async def test__get_messages_by_sentiment_similarity( radius=0.1, ) assert len(messages) == 1 - assert messages[0].model_dump() == message.model_dump() + assert messages[0].message_id == message.message_id async def test__get_recent_messages( redis: Redis, - message: models.Message, - vectorized_message: models.VectorizedMessage, - sentiment: models.SentimentResult, + message: models.MessageAnalysis, ) -> None: """ - Tests that the most recently saved message is returned by get_recent_messages . + Tests that the most recently saved message is returned from the list of messages. Parameters ---------- redis: redis.Redis The Redis connection instance. - message: courageous_comets.models.Message - The (base) model of the Discord message. - vectorized_message: courageous_comets.models.VectorizedMessage - The Discord message with an embedding vector of its content. - sentiment: courageous_comets.models.SentimentResult - The sentiment analayis result of the message + message: courageous_comets.models.MessageAnalysis + The message to save. Asserts ------- - The most recently saved message is returned. """ # Save the default message to the database. - await save_message(redis, vectorized_message, sentiment) + await save_message(redis, message) # Update its timestamp with the provided message_timestamp messages = await get_recent_messages(redis, message.guild_id) assert len(messages) == 1 - assert messages[0].model_dump() == message.model_dump() + assert messages[0].message_id == message.message_id From 90432ed81112b281de51bb460373ed706c759ce0 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Wed, 24 Jul 2024 14:52:33 +0000 Subject: [PATCH 086/168] docs: add architecture and design page --- docs/contributor-guide/architecture-design.md | 69 +++++++++++++++++++ mkdocs.yaml | 7 +- 2 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 docs/contributor-guide/architecture-design.md diff --git a/docs/contributor-guide/architecture-design.md b/docs/contributor-guide/architecture-design.md new file mode 100644 index 0000000..0a9bfc8 --- /dev/null +++ b/docs/contributor-guide/architecture-design.md @@ -0,0 +1,69 @@ +# Architecture & Design + +This page describes the architecture and design of the Courageous Comets application. + +## Components + +Below is a high-level view of the application components. Click on the links in the diagram to jump to the +corresponding details section. + +```mermaid +graph LR + subgraph Discord + User["User"] + end + + subgraph Courageous Comets + subgraph Bot[Bot] + subgraph Controllers[Controllers] + MessagesCog["Messages"] + end + subgraph ApplicationLogic[Application Logic] + Preprocessing{{"Preprocessing"}} + WordCount["Word Count"] + Sentiment["Sentiment Analysis"] + Vectorization["Vectorization"] + end + end + subgraph Storage[Storage] + Redis[("Redis")] + end + end + + User -->|"Messages"| MessagesCog + MessagesCog -->|"Text"| Preprocessing + Preprocessing --->|"Text"| WordCount + Preprocessing --->|"Text"| Sentiment + Preprocessing --->|"Text"| Vectorization + WordCount -->|"Cache"| Redis + Sentiment -->|"Cache"| Redis + Vectorization -->|"Cache"| Redis +``` + +## Bot + +The bot is the main component of the Courageous Comets application. It is responsible for processing messages +and performing analysis on them. + +The bot is built using the [discord.py](https://discordpy.readthedocs.io/en/stable/) library. It is designed to +be modular and extensible, with the core features implemented on separate layers. + +### Controllers + +Controllers are responsible for handling user input and invoking the appropriate application logic. Each controller +is a separate cog in the bot, allowing for easy extension and maintenance. + +### Application Logic + +The application logic is responsible for processing messages and performing analysis on them. The logic is divided +into several components: + +- **Preprocessing**: Cleans and normalizes the input text. +- **Word Count**: Counts the number of words in the input text. +- **Sentiment Analysis**: Analyzes the sentiment of the input text. +- **Vectorization**: Generates a vector representation to support similarity search. + +## Storage + +The bot uses Redis as a caching layer to store the results of the analysis. Redis is a fast and efficient key-value +store that supports the data structures needed to enable the application logic. diff --git a/mkdocs.yaml b/mkdocs.yaml index 0d02552..56fa85c 100644 --- a/mkdocs.yaml +++ b/mkdocs.yaml @@ -44,6 +44,7 @@ nav: - Changelog: CHANGELOG.md - Contributors: - contributor-guide/index.md + - Architecture & Design: contributor-guide/architecture-design.md - Development Environment: contributor-guide/development-environment.md - Version Control: contributor-guide/version-control.md - Documentation: contributor-guide/documentation.md @@ -54,7 +55,11 @@ markdown_extensions: - attr_list - pymdownx.details - pymdownx.snippets - - pymdownx.superfences + - pymdownx.superfences: + custom_fences: + - name: mermaid + class: mermaid + format: !!python/name:pymdownx.superfences.fence_code_format - pymdownx.emoji: emoji_index: !!python/name:material.extensions.emoji.twemoji emoji_generator: !!python/name:material.extensions.emoji.to_svg From c581c71670940878abfd9774664eb1c7caf95249 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Wed, 24 Jul 2024 14:52:46 +0000 Subject: [PATCH 087/168] =?UTF-8?q?bump:=20version=200.5.0=20=E2=86=92=200?= =?UTF-8?q?.6.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/CHANGELOG.md | 12 ++++++++++++ pyproject.toml | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index bd5d839..95fe011 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,3 +1,15 @@ +## v0.6.0 (2024-07-24) + +### Feat + +- store message tokens (#45) +- limit queries to most recent messsages (#43) +- store sentiment results along with message (#42) + +### Refactor + +- simplify function signatures and make it easier to build scope filters (#44) + ## v0.5.0 (2024-07-23) ### Feat diff --git a/pyproject.toml b/pyproject.toml index 544a65f..0c66618 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "courageous-comets" -version = "0.5.0" +version = "0.6.0" authors = ["Courageous Comets"] description = "" readme = "docs/README.md" From 55e412d49d93425f0366ed44da1961017fa71a07 Mon Sep 17 00:00:00 2001 From: Thijs Franck <37906715+thijs-franck@users.noreply.github.com> Date: Wed, 24 Jul 2024 18:14:55 +0200 Subject: [PATCH 088/168] feat: add development mode (#46) Co-authored-by: thijsfranck --- .devcontainer/devcontainer.json | 1 + application.yaml | 1 + courageous_comets/client.py | 4 ++++ courageous_comets/settings.py | 6 ++++++ docs/admin-guide/configuration.md | 10 ++++++++++ 5 files changed, 22 insertions(+) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index d1e1aa8..6b3966b 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,6 +1,7 @@ { "containerEnv": { "BOT_CONFIG_PATH": "${containerWorkspaceFolder}/application.yaml", + "ENVIRONMENT": "development", "HF_HOME": "${containerWorkspaceFolder}/hf_data", "NLTK_DATA": "${containerWorkspaceFolder}/nltk_data", "POETRY_VIRTUALENVS_CREATE": "false", diff --git a/application.yaml b/application.yaml index cfe10cc..b8eff24 100644 --- a/application.yaml +++ b/application.yaml @@ -1,6 +1,7 @@ cogs: - courageous_comets.cogs.messages - courageous_comets.cogs.ping +dev-cogs: - jishaku nltk: - punkt diff --git a/courageous_comets/client.py b/courageous_comets/client.py index bd70e7a..05d6bd8 100644 --- a/courageous_comets/client.py +++ b/courageous_comets/client.py @@ -93,6 +93,10 @@ async def setup_hook(self) -> None: cogs = CONFIG.get("cogs", []) await self.load_cogs(cogs) + if settings.IS_DEV: + dev_cogs = CONFIG.get("dev-cogs", []) + await self.load_cogs(dev_cogs) + logger.info("Initialization complete 🚀") diff --git a/courageous_comets/settings.py b/courageous_comets/settings.py index de32863..fab2b4e 100644 --- a/courageous_comets/settings.py +++ b/courageous_comets/settings.py @@ -148,6 +148,12 @@ def setup_logging() -> None: setup_logging() +# Determine whether the application is running in a development environment +IS_DEV = os.getenv("ENVIRONMENT", "production") == "development" + +if IS_DEV: + logging.warning("🚨 Application is running in development mode! 🚨") + # Load configuration values from the environment try: DISCORD_TOKEN = read_discord_token() diff --git a/docs/admin-guide/configuration.md b/docs/admin-guide/configuration.md index f28f7df..f17b319 100644 --- a/docs/admin-guide/configuration.md +++ b/docs/admin-guide/configuration.md @@ -6,6 +6,7 @@ The following environment variables are available to configure the application: | --------------------------------------------------------------------------------- | ------------------------------------------------------------------------ | -------- | ------------------ | | [`DISCORD_TOKEN`](#discord_token) | The Discord bot token. | Yes | - | | [`BOT_CONFIG_PATH`](#bot_config_path) | The path to the bot's configuration file. | No | `application.yaml` | +| [`ENVIRONMENT`](#environment) | The environment in which the application is running. | No | `production` | | [`HF_DOWNLOAD_CONCURRENCY`](#hf_download_concurrency) | The maximum number of concurrent downloads when installing transformers. | No | `3` | | [`HF_HOME`](#hf_home) | The directory containing Huggingface Transformers data files. | No | `hf_data` | | [`LOG_LEVEL`](#log_level) | The minimum log level. | No | `INFO` | @@ -44,6 +45,10 @@ This specifies the location of the bot's configuration file, which is a YAML fil cogs: - - +# List of cogs to load in development mode only, identified by their package name. +dev-cogs: + - + - # List of NLTK datasets to download on startup. nltk: - @@ -57,6 +62,11 @@ transformers: By default, the application searches for a file named `application.yaml` in the directory from which it is launched. In the Docker image, this file is located at `/app/application.yaml`. +### `ENVIRONMENT` + +The environment in which the application is running. Set this to `development` to enable development features +such as loading development cogs. By default, this is set to `production`. + ### `HF_DOWNLOAD_CONCURRENCY` The maximum number of concurrent downloads when installing Huggingface Transformers models. By default, this From f92f4c95097826bb47c36ae606f1ae85150fd147 Mon Sep 17 00:00:00 2001 From: Uchechukwu Orji Date: Wed, 24 Jul 2024 18:06:25 +0100 Subject: [PATCH 089/168] feat: allow redis search filtering across multiple ids (#47) Co-authored-by: thijsfranck --- courageous_comets/redis/messages.py | 81 +++++++++++++++++--------- tests/courageous_comets/test__scope.py | 23 ++++++++ tests/integrations/test__redis.py | 10 ++-- 3 files changed, 83 insertions(+), 31 deletions(-) create mode 100644 tests/courageous_comets/test__scope.py diff --git a/courageous_comets/redis/messages.py b/courageous_comets/redis/messages.py index 1c39d07..df6d46c 100644 --- a/courageous_comets/redis/messages.py +++ b/courageous_comets/redis/messages.py @@ -52,7 +52,8 @@ async def _get_messages_from_query( def build_search_scope( - id_: str, + guild_id: str, + ids: list[str] | None, scope: StatisticScope, ) -> FilterExpression: """ @@ -60,17 +61,30 @@ def build_search_scope( Parameters ---------- - id_ : str - The ID to search for. + guild_id: str + The ID of the guild to make the search. + ids : list[str] | None + The additional IDs to limit the search to. scope : courageous_comets.enums.StatisticScope - The scope to limit the search. + The scope of the additional IDs. + + Notes + ----- + If the scope of the search is courageous_comets.enums.StatisticScope.GUILD, + the `ids` are ignored. Returns ------- redisvl.query.FilterExpression The redis filter expression for the specified scope. """ - return Tag(scope) == id_ + search_scope = Tag(StatisticScope.GUILD) == guild_id + + # Ignore the other IDs as this would imply searching across multiple scopes. + if scope == StatisticScope.GUILD or not ids: + return search_scope + + return search_scope & (Tag(scope) == ids) async def save_message( @@ -114,8 +128,10 @@ async def save_message( async def get_recent_messages( redis: Redis, - id_: str, - scope: StatisticScope = StatisticScope.GUILD, + *, + guild_id: str, + ids: list[str] | None = None, + scope: StatisticScope = StatisticScope.CHANNEL, limit: int = settings.QUERY_LIMIT, ) -> list[models.Message]: """ @@ -125,19 +141,22 @@ async def get_recent_messages( ---------- redis : redis.Redis The Redis connection instance. - id_ : str - The ID of the entity to search for. It should correspond to the given scope. + guild_id: str + The ID of the guild to make the search. + ids : list[str] + Optional list of IDs to search for. scope : courageous_comets.enums.StatisticScope - The scope to limit the search (default: enums.StatisticScopeEnum.GUILD). + The scope of additional IDs (default: courageous_comets.enums.StatisticScope.CHANNEL). + Ignored it is equal to courageous_comets.enums.StatisticScope.GUILD, limit : int - The number of messages to fetch (default: settings.PAGE_SIZE). + The number of messages to fetch (default: courageous_comets.settings.QUERY_LIMIT). Returns ------- list[courageous_comets.models.Message] The list of recent messages. """ - search_scope = build_search_scope(id_, scope) + search_scope = build_search_scope(guild_id, ids, scope) query = FilterQuery( return_fields=RETURN_FIELDS, @@ -148,11 +167,13 @@ async def get_recent_messages( return await _get_messages_from_query(redis, query) -async def get_messages_by_semantics_similarity( +async def get_messages_by_semantics_similarity( # noqa: PLR0913 redis: Redis, - id_: str, + *, + guild_id: str, embedding: bytes, - scope: StatisticScope = StatisticScope.GUILD, + ids: list[str] | None = None, + scope: StatisticScope = StatisticScope.CHANNEL, limit: int = settings.QUERY_LIMIT, ) -> list[models.Message]: """ @@ -162,21 +183,24 @@ async def get_messages_by_semantics_similarity( ---------- redis : redis.Redis The Redis connection instance. - id_ : str - The ID of the entity to search for. It should correspond to the given scope. + guild_id: str + The ID of the guild to make the search. embedding: bytes The vector embedding of the message. + ids : list[str] | None + Optional list of IDs to search for. scope : courageous_comets.enums.StatisticScope - The scope to limit the search (default: enums.StatisticScopeEnum.GUILD). + The scope of additional IDs (default: courageous_comets.enums.StatisticScopeEnum.CHANNEL). + Ignored if it is set to courageous_comets.enums.StatisticScope.GUILD. limit : int - The number of similar messages to fetch (default: settings.PAGE_SIZE). + The number of similar messages to fetch (default: courageous_comets.settings.QUERY_LIMIT). Returns ------- list[courageous_comets.models.Message] The messages that are semantically similar """ - search_scope = build_search_scope(id_, scope) + search_scope = build_search_scope(guild_id, ids, scope) query = VectorQuery( vector=embedding, @@ -191,9 +215,11 @@ async def get_messages_by_semantics_similarity( async def get_messages_by_sentiment_similarity( # noqa: PLR0913 redis: Redis, - id_: str, + *, + guild_id: str, sentiment: float, radius: float, + ids: list[str] | None = None, scope: StatisticScope = StatisticScope.GUILD, limit: int = settings.QUERY_LIMIT, ) -> list[models.Message]: @@ -204,23 +230,26 @@ async def get_messages_by_sentiment_similarity( # noqa: PLR0913 ---------- redis : redis.Redis The Redis connection instance. - id_ : str - The ID of the entity to search for. It should correspond to the given scope. + guild_id: str + The ID of the guild to make the search. sentiment : float The sentiment analayis result of message. radius: float The distance threshold of the search. + ids : list[str] | None + Optional list of IDs to search for. + scope : courageous_comets.enums.StatisticScope + The scope of additional IDs (default: courageous_comets.enums.StatisticScopeEnum.CHANNEL). + Ignored if it is set to courageous_comets.enums.StatisticScope.GUILD. limit : int The number of similar messages to fetch (default: settings.PAGE_SIZE). - scope : courageous_comets.enums.StatisticScopeEnum - The scope to limit the search (default: enums.StatisticScopeEnum.GUILD). Returns ------- list[courageous_comets.models.Message] The messages that are sentimentally similar. """ - search_scope = build_search_scope(id_, scope) + search_scope = build_search_scope(guild_id, ids, scope) # Define lower and upper bounds for the sentiment compound score low = Num("sentiment_compound") >= sentiment - radius # type: ignore diff --git a/tests/courageous_comets/test__scope.py b/tests/courageous_comets/test__scope.py new file mode 100644 index 0000000..bf43417 --- /dev/null +++ b/tests/courageous_comets/test__scope.py @@ -0,0 +1,23 @@ +import pytest + +from courageous_comets.enums import StatisticScope +from courageous_comets.redis.messages import build_search_scope + + +@pytest.mark.parametrize( + ("guild_id", "ids", "scope", "expected"), + [ + ("1", ["2", "3"], StatisticScope.GUILD, "@guild_id:{1}"), + ("1", None, StatisticScope.GUILD, "@guild_id:{1}"), + ("1", ["2", "3"], StatisticScope.CHANNEL, "(@guild_id:{1} @channel_id:{2|3})"), + ("1", ["2", "3"], StatisticScope.USER, "(@guild_id:{1} @user_id:{2|3})"), + ], +) +def test__build_search_scope( + guild_id: str, + ids: list[str] | None, + scope: StatisticScope, + expected: str, +) -> None: + """Tests whether the correct search scope is generated.""" + assert str(build_search_scope(guild_id, ids, scope)) == expected diff --git a/tests/integrations/test__redis.py b/tests/integrations/test__redis.py index cbb8094..c3d90e2 100644 --- a/tests/integrations/test__redis.py +++ b/tests/integrations/test__redis.py @@ -128,8 +128,8 @@ async def test__get_messages_by_semantics_similarity( await save_message(redis, message) messages = await get_messages_by_semantics_similarity( redis, - message.guild_id, - message.embedding, + guild_id=message.guild_id, + embedding=message.embedding, ) assert len(messages) == 1 assert messages[0].message_id == message.message_id @@ -159,8 +159,8 @@ async def test__get_messages_by_sentiment_similarity( await save_message(redis, message) messages = await get_messages_by_sentiment_similarity( redis, - message.guild_id, - sentiment.compound, + guild_id=message.guild_id, + sentiment=sentiment.compound, radius=0.1, ) assert len(messages) == 1 @@ -188,6 +188,6 @@ async def test__get_recent_messages( # Save the default message to the database. await save_message(redis, message) # Update its timestamp with the provided message_timestamp - messages = await get_recent_messages(redis, message.guild_id) + messages = await get_recent_messages(redis, guild_id=message.guild_id) assert len(messages) == 1 assert messages[0].message_id == message.message_id From aad9546f23dc1877ba04317d564a5ad976e4a1b0 Mon Sep 17 00:00:00 2001 From: Uchechukwu Orji Date: Wed, 24 Jul 2024 21:25:33 +0100 Subject: [PATCH 090/168] feat: calculate count of tokens across messages (#48) --- courageous_comets/redis/messages.py | 58 ++++++++++++++++++++++++++--- 1 file changed, 53 insertions(+), 5 deletions(-) diff --git a/courageous_comets/redis/messages.py b/courageous_comets/redis/messages.py index df6d46c..e7dc6d0 100644 --- a/courageous_comets/redis/messages.py +++ b/courageous_comets/redis/messages.py @@ -1,4 +1,5 @@ import json +from collections import Counter from redis.asyncio import Redis from redisvl.index import AsyncSearchIndex @@ -45,10 +46,7 @@ async def _get_messages_from_query( query.params, ) - if results.total == 0: - return [] - - return [models.Message.model_validate(doc) for doc in results.docs] + return [models.Message.model_validate(doc) for doc in results.docs if results.total] def build_search_scope( @@ -220,7 +218,7 @@ async def get_messages_by_sentiment_similarity( # noqa: PLR0913 sentiment: float, radius: float, ids: list[str] | None = None, - scope: StatisticScope = StatisticScope.GUILD, + scope: StatisticScope = StatisticScope.CHANNEL, limit: int = settings.QUERY_LIMIT, ) -> list[models.Message]: """ @@ -264,3 +262,53 @@ async def get_messages_by_sentiment_similarity( # noqa: PLR0913 ) return await _get_messages_from_query(redis, query) + + +async def get_tokens_count( + redis: Redis, + *, + guild_id: str, + ids: list[str] | None = None, + scope: StatisticScope = StatisticScope.CHANNEL, + limit: int = settings.QUERY_LIMIT, +) -> Counter: + """ + Get the count of tokens across messages. + + Parameters + ---------- + redis: redis.Redis + The Redis connection instance. + guild_id: str + The ID of the guild to make the search + ids: list[strr] + Optional list of IDs to search for. + scope : courageous_comets.enums.StatisticScope + The scope of additional IDs (default: courageous_comets.enums.StatisticScope.CHANNEL). + Ignored it is equal to courageous_comets.enums.StatisticScope.GUILD, + limit : int + The number of messages to fetch (default: courageous_comets.settings.QUERY_LIMIT). + + Returns + ------- + collections.Counter + Mapping of each token to its count. + """ + search_scope = build_search_scope(guild_id, ids, scope) + query = FilterQuery( + return_fields=["tokens"], + filter_expression=search_scope, + num_results=limit, + ) + index = AsyncSearchIndex.from_dict(schema.MESSAGE_SCHEMA) + index.set_client(redis) + + results = await index.search( + query.query.sort_by("timestamp", asc=False), + query.params, + ) + counter = Counter() + tokens: list[dict[str, int]] = [json.loads(doc.tokens) for doc in results.docs if results.total] + for token in tokens: + counter.update(token) + return counter From 0a858f34bbca65e4012cc4fed1e2aa39945d769e Mon Sep 17 00:00:00 2001 From: Uchechukwu Orji Date: Thu, 25 Jul 2024 18:44:46 +0100 Subject: [PATCH 091/168] feat: get message rate by duration (#49) Co-authored-by: thijsfranck --- courageous_comets/enums.py | 10 +++- courageous_comets/redis/messages.py | 77 +++++++++++++++++++++++++++-- poetry.lock | 27 +++++----- pyproject.toml | 7 +++ tests/integrations/conftest.py | 63 ++++++++++++++++++++++- tests/integrations/test__redis.py | 28 ++++++----- 6 files changed, 184 insertions(+), 28 deletions(-) diff --git a/courageous_comets/enums.py b/courageous_comets/enums.py index aed7003..a1e70d2 100644 --- a/courageous_comets/enums.py +++ b/courageous_comets/enums.py @@ -1,4 +1,4 @@ -from enum import StrEnum +from enum import IntEnum, StrEnum class StatisticScope(StrEnum): @@ -7,3 +7,11 @@ class StatisticScope(StrEnum): GUILD = "guild_id" CHANNEL = "channel_id" USER = "user_id" + + +class Duration(IntEnum): + """Number of seconds in time durations.""" + + MINUTE = 60 + HOUR = 60 * 60 + DAY = 60 * 60 * 24 diff --git a/courageous_comets/redis/messages.py b/courageous_comets/redis/messages.py index e7dc6d0..0ba6a30 100644 --- a/courageous_comets/redis/messages.py +++ b/courageous_comets/redis/messages.py @@ -1,14 +1,17 @@ +import itertools import json from collections import Counter +import redis.commands.search.aggregation as aggregations from redis.asyncio import Redis +from redis.commands.search import AsyncSearch, reducers from redisvl.index import AsyncSearchIndex from redisvl.query import FilterQuery, VectorQuery from redisvl.query.filter import FilterExpression, Num, Tag from redisvl.query.query import BaseQuery from courageous_comets import models, settings -from courageous_comets.enums import StatisticScope +from courageous_comets.enums import Duration, StatisticScope from courageous_comets.redis import schema from courageous_comets.redis.keys import key_schema @@ -17,6 +20,12 @@ RETURN_FIELDS = ["message_id", "user_id", "channel_id", "guild_id", "timestamp"] +def _get_raw_index(redis: Redis) -> AsyncSearch: + """Get the raw messages index on Redis.""" + index = AsyncSearchIndex.from_dict(schema.MESSAGE_SCHEMA) + return redis.ft(index.schema.index.name) + + async def _get_messages_from_query( redis: Redis, query: BaseQuery, @@ -295,20 +304,82 @@ async def get_tokens_count( Mapping of each token to its count. """ search_scope = build_search_scope(guild_id, ids, scope) + index = AsyncSearchIndex.from_dict(schema.MESSAGE_SCHEMA) + index.set_client(redis) + query = FilterQuery( return_fields=["tokens"], filter_expression=search_scope, num_results=limit, ) - index = AsyncSearchIndex.from_dict(schema.MESSAGE_SCHEMA) - index.set_client(redis) results = await index.search( query.query.sort_by("timestamp", asc=False), query.params, ) + counter = Counter() tokens: list[dict[str, int]] = [json.loads(doc.tokens) for doc in results.docs if results.total] + for token in tokens: counter.update(token) + return counter + + +async def get_message_rate( # noqa: PLR0913 + redis: Redis, + *, + guild_id: str, + ids: list[str] | None = None, + scope: StatisticScope = StatisticScope.CHANNEL, + limit: int = settings.QUERY_LIMIT, + duration: Duration = Duration.HOUR, +) -> list[dict[str, str]]: + """ + Get the rate of messages over an interval. + + Parameters + ---------- + redis : Redis + The Redis connection instance. + guild_id : str + The ID of the guild to make the search. + ids : list[str], optional + Optional list of IDs to search for. Defaults to None. + scope : StatisticScope, optional + The scope of additional IDs (default: StatisticScope.CHANNEL). + Ignored if it is equal to StatisticScope.GUILD. + duration : Duration, optional + The duration over which to make the aggregation (default: Duration.HOUR). + limit : int, optional + The number of messages to aggregate over (default: settings.QUERY_LIMIT). + + Returns + ------- + list[dict[str, str]] + A list of dictionaries mapping each timestamp to its count of distinct messages. + """ + search_scope = build_search_scope(guild_id, ids, scope) + index = _get_raw_index(redis) + + # Define a reducer to count distinct message IDs and alias the result as "num_messages" + reducer = reducers.count_distinct("@message_id").alias("num_messages") + + # Build the aggregation query + query = ( + aggregations.AggregateRequest(f"{search_scope!s} @timestamp:[0 inf]") + .limit(0, limit) + # Create a new property `timestamp` rounded to the start of the interval + .apply(timestamp=f"minute(@timestamp) - ((minute(@timestamp) % {duration.value}))") + # Group results by interval using the new `timestamp` property + .group_by(["@timestamp"], reducer) + # Sort results by the number of messages in each interval + .sort_by(aggregations.Asc("@num_messages")) # type: ignore + ) + + # Execute the aggregation query on the index + results = await index.aggregate(query) # type: ignore + + # Deserialize all rows as dictionaries. Each row is a flat list of key-value pairs. + return [dict(itertools.batched(row, 2)) for row in results.rows] diff --git a/poetry.lock b/poetry.lock index 5f4ad1e..23df5e6 100644 --- a/poetry.lock +++ b/poetry.lock @@ -510,6 +510,20 @@ files = [ {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, ] +[[package]] +name = "faker" +version = "26.0.0" +description = "Faker is a Python package that generates fake data for you." +optional = false +python-versions = ">=3.8" +files = [ + {file = "Faker-26.0.0-py3-none-any.whl", hash = "sha256:886ee28219be96949cd21ecc96c4c742ee1680e77f687b095202c8def1a08f06"}, + {file = "Faker-26.0.0.tar.gz", hash = "sha256:0f60978314973de02c00474c2ae899785a42b2cf4f41b7987e93c132a2b8a4a9"}, +] + +[package.dependencies] +python-dateutil = ">=2.4" + [[package]] name = "filelock" version = "3.15.4" @@ -1891,7 +1905,6 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, - {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -1899,16 +1912,8 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, - {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, - {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, - {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, - {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -1925,7 +1930,6 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, - {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -1933,7 +1937,6 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, - {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -2885,4 +2888,4 @@ test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", [metadata] lock-version = "2.0" python-versions = "~3.12" -content-hash = "0b8a8a895ed65e8231b563269fc9a778de7d90b0f2d25ad9eb7bd59b3c1802a9" +content-hash = "7a348c7ffe5d3be3a7a8c038f9d36075bb4ee7ce8dc13c67af721f1152399e3d" diff --git a/pyproject.toml b/pyproject.toml index 0c66618..b744081 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,6 +42,10 @@ name = "pytorch-cpu" url = "https://download.pytorch.org/whl/cpu" priority = "explicit" + +[tool.poetry.group.dev.dependencies] +faker = "^26.0.0" + [tool.pyright] typeCheckingMode = "basic" pythonVersion = "3.12" @@ -56,6 +60,9 @@ filterwarnings = [ "ignore:`resume_download` is deprecated and will be removed *", "ignore:datetime.datetime.utcnow", ] +markers = [ + "num_messages(num=10): create num messages.", +] [tool.ruff] line-length = 100 diff --git a/tests/integrations/conftest.py b/tests/integrations/conftest.py index 9871600..d452dbe 100644 --- a/tests/integrations/conftest.py +++ b/tests/integrations/conftest.py @@ -2,10 +2,15 @@ from typing import cast import pytest +import pytest_asyncio +from faker import Faker from redis.asyncio import Redis -from courageous_comets import settings +from courageous_comets import models, settings from courageous_comets.client import CourageousCometsBot +from courageous_comets.sentiment import calculate_sentiment +from courageous_comets.vectorizer import Vectorizer +from courageous_comets.words import tokenize_sentence, word_frequency @pytest.fixture() @@ -31,3 +36,59 @@ async def redis(bot: CourageousCometsBot) -> AsyncGenerator[Redis, None]: await instance.delete(*keys_to_delete) await instance.aclose() + + +@pytest_asyncio.fixture +async def messages( + vectorizer: Vectorizer, + faker: Faker, + request: pytest.FixtureRequest, +) -> list[models.MessageAnalysis]: + """ + Generate random messages for testing. + + The number of messages to generate can be specified using the `num_messages` mark. + + Example + ------- + ```python + @pytest.mark.num_messages(100) + async def test__something(messages: list[models.MessageAnalysis]): + assert len(messages) == 100 + ``` + """ + # Retrieve the number of messages to generate from the test function + # If the mark is not present, or the number of messages is not specified, generate 10 messages + mark = request.node.get_closest_marker("num_messages") + num_messages = int(mark.args[0]) if mark and len(mark.args) > 0 else 10 + + # Message ID range + min_id = 1 + max_id = 1_0000_000 + + messages: list[models.MessageAnalysis] = [] + + for _ in range(num_messages): + # Generate a random message of 10 words + sentence = faker.sentence(nb_words=10) + + # Process the message + embedding = await vectorizer.aencode(sentence) + sentiment = calculate_sentiment(sentence) + tokens = tokenize_sentence(sentence) + + # Create a message object and append it to the list + messages.append( + models.MessageAnalysis( + guild_id="1", + channel_id=str(faker.random_int(min=min_id, max=5)), # 5 channels + message_id=str(faker.random_int(min=min_id, max=max_id)), + user_id=str(faker.random_int(min=min_id, max=5)), # 5 users + timestamp=faker.date_time(), + embedding=embedding, + sentiment=sentiment, + tokens=word_frequency(tokens), + ), + ) + + return messages diff --git a/tests/integrations/test__redis.py b/tests/integrations/test__redis.py index c3d90e2..470480d 100644 --- a/tests/integrations/test__redis.py +++ b/tests/integrations/test__redis.py @@ -167,27 +167,33 @@ async def test__get_messages_by_sentiment_similarity( assert messages[0].message_id == message.message_id +@pytest.mark.parametrize(("limit", "expect"), [(10, 10), (100, 100)]) +@pytest.mark.num_messages(100) async def test__get_recent_messages( redis: Redis, - message: models.MessageAnalysis, + messages: list[models.MessageAnalysis], + limit: int, + expect: int, ) -> None: - """ - Tests that the most recently saved message is returned from the list of messages. + """Tests that the expected number of messages are returned from Redis. Parameters ---------- redis: redis.Redis The Redis connection instance. - message: courageous_comets.models.MessageAnalysis - The message to save. + messages list[courageous_comets.models.MessageAnalysis] + The messages to save Asserts ------- - - The most recently saved message is returned. + - The number of mesages returned does not exceed specified limit. """ - # Save the default message to the database. - await save_message(redis, message) + # Save the messages to the database. + for message in messages: + await save_message(redis, message) + # All messages have the same guild_id + guild_id = messages[0].guild_id + # Update its timestamp with the provided message_timestamp - messages = await get_recent_messages(redis, guild_id=message.guild_id) - assert len(messages) == 1 - assert messages[0].message_id == message.message_id + db_messages = await get_recent_messages(redis, guild_id=guild_id, limit=limit) + assert len(db_messages) == expect From d714d6795dd76961f1dc7e0685cfa4bd5fd38bae Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Thu, 25 Jul 2024 17:53:02 +0000 Subject: [PATCH 092/168] fix: add __version__ and log version on startup --- courageous_comets/__init__.py | 3 +++ courageous_comets/__main__.py | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/courageous_comets/__init__.py b/courageous_comets/__init__.py index eec4b71..add97f7 100644 --- a/courageous_comets/__init__.py +++ b/courageous_comets/__init__.py @@ -1,3 +1,6 @@ +import importlib.metadata + from .client import bot __all__ = ["bot"] +__version__ = importlib.metadata.version("courageous_comets") diff --git a/courageous_comets/__main__.py b/courageous_comets/__main__.py index 83cb864..e93af28 100644 --- a/courageous_comets/__main__.py +++ b/courageous_comets/__main__.py @@ -3,7 +3,7 @@ import discord -from courageous_comets import bot, exceptions, settings +from courageous_comets import __version__, bot, exceptions, settings async def main() -> None: @@ -15,7 +15,7 @@ async def main() -> None: # Override logging configuration by dependencies settings.setup_logging() - logging.info("Starting the Courageous Comets application ☄️") + logging.info("Starting the Courageous Comets application (v%s) ☄️", __version__) try: await bot.start(settings.DISCORD_TOKEN) except discord.LoginFailure: From 7c5ce5f2edec229a75b98344d37b186bb7bf8fef Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Thu, 25 Jul 2024 18:12:01 +0000 Subject: [PATCH 093/168] fix: handle PackageNotFoundError --- courageous_comets/__init__.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/courageous_comets/__init__.py b/courageous_comets/__init__.py index add97f7..9edbba3 100644 --- a/courageous_comets/__init__.py +++ b/courageous_comets/__init__.py @@ -1,6 +1,12 @@ import importlib.metadata +import logging from .client import bot __all__ = ["bot"] -__version__ = importlib.metadata.version("courageous_comets") + +try: + __version__ = importlib.metadata.version("courageous_comets") +except importlib.metadata.PackageNotFoundError: + logging.warning("Could not determine the package version.") + __version__ = "unknown" From cd04d60d9eba252dc4ee78052ac8cf871d5c0bec Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Thu, 25 Jul 2024 21:22:02 +0200 Subject: [PATCH 094/168] Add user interactions (#51) Co-authored-by: isaa-ctaylor --- application.yaml | 2 + courageous_comets/cogs/keywords.py | 103 ++++++++++++++++++++++++++++ courageous_comets/cogs/sentiment.py | 63 +++++++++++++++++ courageous_comets/utils.py | 12 ++++ pyproject.toml | 3 + 5 files changed, 183 insertions(+) create mode 100644 courageous_comets/cogs/keywords.py create mode 100644 courageous_comets/cogs/sentiment.py create mode 100644 courageous_comets/utils.py diff --git a/application.yaml b/application.yaml index b8eff24..53c0420 100644 --- a/application.yaml +++ b/application.yaml @@ -1,4 +1,6 @@ cogs: + - courageous_comets.cogs.sentiment + - courageous_comets.cogs.keywords - courageous_comets.cogs.messages - courageous_comets.cogs.ping dev-cogs: diff --git a/courageous_comets/cogs/keywords.py b/courageous_comets/cogs/keywords.py new file mode 100644 index 0000000..6b4a2fb --- /dev/null +++ b/courageous_comets/cogs/keywords.py @@ -0,0 +1,103 @@ +import discord +from discord import app_commands +from discord.ext import commands + +from courageous_comets.client import CourageousCometsBot +from courageous_comets.models import Message +from courageous_comets.utils import contextmenu +from courageous_comets.vectorizer import Vectorizer + +# from courageous_comets.redis.messages import get_recent_messages # noqa: ERA001 + + +class MessagesNotFound(app_commands.AppCommandError): + """No messages were found.""" + + +class Keywords(commands.Cog): + """A boilerplate cog.""" + + def __init__(self, bot: CourageousCometsBot) -> None: + self.bot = bot + + self.vectorizer = Vectorizer() + + for attribute in dir(self): + obj = getattr(self, attribute, None) + if obj and getattr(obj, "is_contextmenu", False): + menu = app_commands.ContextMenu(name=obj.name, callback=obj) + self.bot.tree.add_command(menu) + + async def _get_recent_messages( + self, + user: discord.Member, + *, + limit: int = 10, + channels: list[discord.TextChannel] | None = None, + ) -> list[Message]: # type: ignore + """ + Return `limit` most recent messages, only from `channels`, if provided. + + Parameters + ---------- + user: discord.Member + The user to get the most recent messages for. + limit: int + The number of messages to retrieve. Defaults to 10. + channels: list[discord.TextChannel] + The channels to query. + """ + + @contextmenu(name="Show recent messages") + async def _show_recent_messages_context_menu( + self, + interaction: discord.Interaction, + member: discord.Member, + ) -> None: + """Get the most recent messages from a user.""" + await self._show_recent_messages(interaction, member) + + @app_commands.command(name="recent") + async def _show_recent_messages_command( + self, + interaction: discord.Interaction, + member: discord.Member, + ) -> None: + """Get the most recent messages from a user.""" + await self._show_recent_messages(interaction, member) + + async def _show_recent_messages( + self, + interaction: discord.Interaction, + member: discord.Member, + ) -> None: + author = interaction.user + + visible_channels = [ + channel + for channel in interaction.guild.text_channels # type: ignore + if channel.permissions_for(author).view_channel # type: ignore + ] + + messages = await self._get_recent_messages(member, channels=visible_channels) + + resolved_messages = [] + + for message in messages: + channel: discord.TextChannel = self.bot.get_channel(int(message.channel_id)) # type: ignore + if channel: + resolved_message = await channel.fetch_message(int(message.message_id)) + if resolved_message: + resolved_messages.append(resolved_message) + continue + resolved_messages.append(None) + + if not [m for m in resolved_messages if m]: + raise MessagesNotFound + + # TODO(isaa-ctaylor): Display messages + + +async def setup(bot: CourageousCometsBot) -> None: + """Load the cog.""" + await bot.add_cog(Keywords(bot)) diff --git a/courageous_comets/cogs/sentiment.py b/courageous_comets/cogs/sentiment.py new file mode 100644 index 0000000..c02dfd5 --- /dev/null +++ b/courageous_comets/cogs/sentiment.py @@ -0,0 +1,63 @@ +import discord +from discord import app_commands +from discord.ext import commands + +from courageous_comets.client import CourageousCometsBot +from courageous_comets.enums import StatisticScope +from courageous_comets.redis.messages import get_messages_by_semantics_similarity +from courageous_comets.utils import contextmenu +from courageous_comets.vectorizer import Vectorizer + + +class MessagesNotFound(app_commands.AppCommandError): + """No messages were found.""" + + +class Sentiment(commands.Cog): + """Sentiment related commands.""" + + def __init__(self, bot: CourageousCometsBot) -> None: + self.bot = bot + + self.vectorizer = Vectorizer() + + for attribute in dir(self): + obj = getattr(self, attribute, None) + if obj and getattr(obj, "is_contextmenu", False): + menu = app_commands.ContextMenu(name=obj.name, callback=obj) + self.bot.tree.add_command(menu) + + @contextmenu(name="Show Sentiment") + async def _get_message_similarities( + self, + interaction: discord.Interaction, + message: discord.Message, + ) -> None: + """Get similar messages based on semantics.""" + if self.bot.redis is None: + return await interaction.response.send_message( + "This feature is currently unavailable. Please try again later.", + ephemeral=True, + ) + + await interaction.response.defer() + + embedding = await self.vectorizer.aencode(message.content) + + messages = await get_messages_by_semantics_similarity( + self.bot.redis, + message.guild.id, # type: ignore + embedding, + StatisticScope.GUILD, + ) + + if not messages: + raise MessagesNotFound + + # TODO(isaa-ctaylor): Display messages + return None + + +async def setup(bot: CourageousCometsBot) -> None: + """Load the cog.""" + await bot.add_cog(Sentiment(bot)) diff --git a/courageous_comets/utils.py b/courageous_comets/utils.py new file mode 100644 index 0000000..9d3bef4 --- /dev/null +++ b/courageous_comets/utils.py @@ -0,0 +1,12 @@ +import typing + + +def contextmenu(name: str) -> typing.Callable: + """Mark a function as a context menu.""" + + def wrap(func: typing.Callable) -> typing.Callable: + func.is_contextmenu = True + func.name = name + return func + + return wrap diff --git a/pyproject.toml b/pyproject.toml index b744081..1e4ea66 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -85,6 +85,9 @@ ignore = [ "D107", # We do not need cryptographically secure random functions. "S311", + # Just let us use TODOs minimally!! + "TD003", + "FIX002", ] select = ["ALL"] From 4aac780ba38e34074d036cbbb1301fbb156ced5c Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Thu, 25 Jul 2024 19:35:30 +0000 Subject: [PATCH 095/168] feat: add drop_code_blocks processor --- courageous_comets/preprocessing.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/courageous_comets/preprocessing.py b/courageous_comets/preprocessing.py index e4fa8f8..80f6f95 100644 --- a/courageous_comets/preprocessing.py +++ b/courageous_comets/preprocessing.py @@ -11,6 +11,23 @@ Processor = Callable[[str], str] +def drop_code_blocks(text: str) -> str: + """ + Remove code blocks from the given text. + + Parameters + ---------- + text : str + The text to process. + + Returns + ------- + str + The text with code blocks removed. + """ + return re.sub(r"```.*?```", "", text, flags=re.DOTALL) + + def drop_extra_whitespace(text: str) -> str: """ Remove extra whitespace from the given text. @@ -102,6 +119,7 @@ def truncate(text: str, max_length: int) -> str: # Steps are executed in order PROCESSORS: list[Processor] = [ + drop_code_blocks, drop_links, unidecode, contractions.fix, # type: ignore From cdf1244625f854b92d03a3ff5c7cf231e4f7bf35 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Thu, 25 Jul 2024 22:09:56 +0200 Subject: [PATCH 096/168] feat: add cog that returns information about the app (#50) Co-authored-by: isaa-ctaylor --- application.yaml | 3 +- courageous_comets/__init__.py | 5 ++-- courageous_comets/__main__.py | 2 +- courageous_comets/client.py | 14 +++++++++ courageous_comets/cogs/about.py | 52 +++++++++++++++++++++++++++++++++ 5 files changed, 72 insertions(+), 4 deletions(-) create mode 100644 courageous_comets/cogs/about.py diff --git a/application.yaml b/application.yaml index 53c0420..bfdb77c 100644 --- a/application.yaml +++ b/application.yaml @@ -1,8 +1,9 @@ cogs: - - courageous_comets.cogs.sentiment + - courageous_comets.cogs.about - courageous_comets.cogs.keywords - courageous_comets.cogs.messages - courageous_comets.cogs.ping + - courageous_comets.cogs.sentiment dev-cogs: - jishaku nltk: diff --git a/courageous_comets/__init__.py b/courageous_comets/__init__.py index 9edbba3..3e6d1ef 100644 --- a/courageous_comets/__init__.py +++ b/courageous_comets/__init__.py @@ -5,8 +5,9 @@ __all__ = ["bot"] +# Package version may not be available in CI. try: - __version__ = importlib.metadata.version("courageous_comets") + __version__ = f"v{importlib.metadata.version("courageous_comets")}" except importlib.metadata.PackageNotFoundError: logging.warning("Could not determine the package version.") - __version__ = "unknown" + __version__ = "latest" diff --git a/courageous_comets/__main__.py b/courageous_comets/__main__.py index e93af28..7259910 100644 --- a/courageous_comets/__main__.py +++ b/courageous_comets/__main__.py @@ -15,7 +15,7 @@ async def main() -> None: # Override logging configuration by dependencies settings.setup_logging() - logging.info("Starting the Courageous Comets application (v%s) ☄️", __version__) + logging.info("Starting the Courageous Comets application (%s) ☄️", __version__) try: await bot.start(settings.DISCORD_TOKEN) except discord.LoginFailure: diff --git a/courageous_comets/client.py b/courageous_comets/client.py index 05d6bd8..19aa825 100644 --- a/courageous_comets/client.py +++ b/courageous_comets/client.py @@ -12,8 +12,21 @@ from courageous_comets.nltk import init_nltk from courageous_comets.redis import init_redis +DESCRIPTION = """ +Thank you for using Courageous Comets! ☄️ + +This is a Discord app that provides various statistical analyses on messages. + +- **Sentiment Analysis**: Analyze the sentiment of a message. +- **Word Frequency**: Analyze the frequency of words in a message. +- **Similarity Analysis**: Analyze the similarity between two messages. + +Click the link in the header to visit the documentation! +""" + logger = logging.getLogger(__name__) + intents = Intents.default() intents.members = True intents.message_content = True @@ -38,6 +51,7 @@ def __init__(self) -> None: super().__init__( command_prefix=commands.when_mentioned, intents=intents, + description=DESCRIPTION, ) @override diff --git a/courageous_comets/cogs/about.py b/courageous_comets/cogs/about.py new file mode 100644 index 0000000..d123df3 --- /dev/null +++ b/courageous_comets/cogs/about.py @@ -0,0 +1,52 @@ +import logging + +import discord +from discord import Embed, app_commands +from discord.ext import commands + +from courageous_comets import __version__ + +logger = logging.getLogger(__name__) + + +class About(commands.Cog): + """A cog that provides information about the app upon request.""" + + def __init__(self, bot: commands.Bot) -> None: + self.bot = bot + + @app_commands.command( + name="about", + description="Get information about the app.", + ) + async def about_command(self, interaction: discord.Interaction) -> None: + """ + Respond to the `/about` command with information about the app. + + Parameters + ---------- + interaction : discord.Interaction + The interaction that triggered the command + """ + logger.info( + "User %s requested the about message using the /about command.", + interaction.user.id, + ) + try: + await interaction.response.send_message( + embed=Embed( + title=f"Courageous Comets ({__version__})", + description=self.bot.description, + color=discord.Color.blurple(), + url=f"https://thijsfranck.github.io/courageous-comets/{__version__}/", + timestamp=discord.utils.utcnow(), + ), + ephemeral=True, + ) + except discord.HTTPException as e: + logger.exception("Could not deliver the about message.", exc_info=e) + + +async def setup(bot: commands.Bot) -> None: + """Load the cog.""" + await bot.add_cog(About(bot)) From 8b1e3f4e8f8d84b20134cb650fb97debb6101cb9 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Fri, 26 Jul 2024 04:02:02 +0000 Subject: [PATCH 097/168] fix: remove reference to link from general bot description and add it to the about message --- courageous_comets/client.py | 2 -- courageous_comets/cogs/about.py | 13 ++++++++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/courageous_comets/client.py b/courageous_comets/client.py index 19aa825..bc43368 100644 --- a/courageous_comets/client.py +++ b/courageous_comets/client.py @@ -20,8 +20,6 @@ - **Sentiment Analysis**: Analyze the sentiment of a message. - **Word Frequency**: Analyze the frequency of words in a message. - **Similarity Analysis**: Analyze the similarity between two messages. - -Click the link in the header to visit the documentation! """ logger = logging.getLogger(__name__) diff --git a/courageous_comets/cogs/about.py b/courageous_comets/cogs/about.py index d123df3..73cd008 100644 --- a/courageous_comets/cogs/about.py +++ b/courageous_comets/cogs/about.py @@ -8,6 +8,12 @@ logger = logging.getLogger(__name__) +DESCRIPTION = """ +%s + +Click the link in the header to visit the documentation! +""" + class About(commands.Cog): """A cog that provides information about the app upon request.""" @@ -36,7 +42,7 @@ async def about_command(self, interaction: discord.Interaction) -> None: await interaction.response.send_message( embed=Embed( title=f"Courageous Comets ({__version__})", - description=self.bot.description, + description=self.description, color=discord.Color.blurple(), url=f"https://thijsfranck.github.io/courageous-comets/{__version__}/", timestamp=discord.utils.utcnow(), @@ -46,6 +52,11 @@ async def about_command(self, interaction: discord.Interaction) -> None: except discord.HTTPException as e: logger.exception("Could not deliver the about message.", exc_info=e) + @property + def description(self) -> str: + """Return the body of the about message.""" + return DESCRIPTION % self.bot.description + async def setup(bot: commands.Bot) -> None: """Load the cog.""" From 9e529fc7c0de245320e6cd8b4a9766be8c7a3bb3 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Fri, 26 Jul 2024 14:39:02 +0200 Subject: [PATCH 098/168] feat: add sentiment chart (#52) Co-authored-by: Uchechukwu Orji --- .gitignore | 3 + courageous_comets/cogs/sentiment.py | 191 ++++++++++++- courageous_comets/models.py | 26 +- courageous_comets/redis/messages.py | 39 ++- poetry.lock | 418 +++++++++++++++++++++++++++- pyproject.toml | 1 + tests/integrations/test__redis.py | 2 +- 7 files changed, 662 insertions(+), 18 deletions(-) diff --git a/.gitignore b/.gitignore index 9768057..6bf316f 100644 --- a/.gitignore +++ b/.gitignore @@ -184,3 +184,6 @@ nltk_data # Huggingface hf_data + +# Artifacts +artifacts/ diff --git a/courageous_comets/cogs/sentiment.py b/courageous_comets/cogs/sentiment.py index c02dfd5..fb02599 100644 --- a/courageous_comets/cogs/sentiment.py +++ b/courageous_comets/cogs/sentiment.py @@ -1,18 +1,107 @@ +import logging +from pathlib import Path + import discord +import matplotlib.pyplot as plt from discord import app_commands from discord.ext import commands +from courageous_comets import models from courageous_comets.client import CourageousCometsBot from courageous_comets.enums import StatisticScope -from courageous_comets.redis.messages import get_messages_by_semantics_similarity +from courageous_comets.redis.keys import key_schema +from courageous_comets.redis.messages import ( + get_message_sentiment, + get_messages_by_sentiment_similarity, +) +from courageous_comets.sentiment import calculate_sentiment from courageous_comets.utils import contextmenu from courageous_comets.vectorizer import Vectorizer +logger = logging.getLogger(__name__) + class MessagesNotFound(app_commands.AppCommandError): """No messages were found.""" +SENTIMENT: dict[range, str] = { + range(-100, -50): "very negative 😡", + range(-50, -10): "negative 🙁", + range(-10, 10): "neutral 🙂", + range(10, 50): "positive 😁", + range(50, 100): "very positive 😍", +} + + +SENTIMENT_DESCRIPTION_TEMPLATE = """ +Overall the sentiment of the message is **{sentiment}**. + +Here's a breakdown of the scores: + +- Negative: {neg} +- Neutral: {neu} +- Positive: {pos} + +The compound score is {compound}. +""" + + +def plot_sentiment_analysis( + message_id: str | int, + analysis_result: models.SentimentResult, +) -> Path: + """ + Plot the sentiment analysis of a message. + + Creates a bar chart of the sentiment analysis of a message and saves it to a file. + If the file already exists, it will be returned instead of being recreated. + + Parameters + ---------- + message_id : str | int + The id of the message. + analysis_result : SentimentResult + The result of sentiment analysis on a message. + + Returns + ------- + Path + The path to the saved image. + """ + chart_dir = Path("artifacts/charts/sentiment").resolve() + chart_dir.mkdir(parents=True, exist_ok=True) + chart_path = chart_dir / f"{message_id}.png" + + if chart_path.exists(): + return chart_path + + _, ax = plt.subplots() + ax.bar( + [ + "Negative", + "Neutral", + "Positive", + ], + [ + analysis_result.neg, + analysis_result.neu, + analysis_result.pos, + ], + color=[ + "red", + "blue", + "green", + ], + ) + ax.set_ylabel("Sentiment Score") + ax.set_title("Sentiment Analysis") + + plt.savefig(chart_path) + + return chart_path + + class Sentiment(commands.Cog): """Sentiment related commands.""" @@ -27,8 +116,92 @@ def __init__(self, bot: CourageousCometsBot) -> None: menu = app_commands.ContextMenu(name=obj.name, callback=obj) self.bot.tree.add_command(menu) - @contextmenu(name="Show Sentiment") - async def _get_message_similarities( + @contextmenu(name="Show message sentiment") + async def show_message_sentiment( + self, + interaction: discord.Interaction, + message: discord.Message, + ) -> None: + """ + Allow users to view the sentiment analysis of a message using a context menu. + + Generates an embed with the sentiment analysis of a message and sends it to the user. + + The embed contains a text description of the sentiment analysis and a bar chart. + + Parameters + ---------- + interaction : discord.Interaction + The interaction that triggered the command. + message : discord.Message + The message to analyze. + """ + logger.info( + "User %s requested sentiment analysis results for message %s.", + interaction.user.id, + message.id, + ) + + if self.bot.redis is None: + return await interaction.response.send_message( + "This feature is currently unavailable. Please try again later.", + ephemeral=True, + ) + + if message.guild is None: + return await interaction.response.send_message( + "This feature is only available in guilds.", + ephemeral=True, + ) + + key = key_schema.guild_messages( + guild_id=message.guild.id, + message_id=message.id, + ) + + analysis_result = await get_message_sentiment(key, redis=self.bot.redis) + + if analysis_result is None: + return await interaction.response.send_message( + "No sentiment analysis found for this message.", + ephemeral=True, + ) + + color = discord.Color.green() if analysis_result.compound >= 0 else discord.Color.red() + + sentiment = next( + ( + value + for key, value in SENTIMENT.items() + if int(analysis_result.compound * 100) in key + ), + "unknown", + ) + + template_vars = { + **analysis_result.model_dump(), + "sentiment": sentiment, + } + + view = discord.Embed( + title="Message Sentiment", + description=SENTIMENT_DESCRIPTION_TEMPLATE.format(**template_vars), + color=color, + timestamp=discord.utils.utcnow(), + ) + + chart = plot_sentiment_analysis(message.id, analysis_result) + chart_file = discord.File(chart, filename="sentiment_analysis.png") + view.set_image(url="attachment://sentiment_analysis.png") + + return await interaction.response.send_message( + embed=view, + file=chart_file, + ephemeral=True, + ) + + @contextmenu(name="Show similar sentiment") + async def get_similar_messages( self, interaction: discord.Interaction, message: discord.Message, @@ -42,13 +215,13 @@ async def _get_message_similarities( await interaction.response.defer() - embedding = await self.vectorizer.aencode(message.content) - - messages = await get_messages_by_semantics_similarity( + sentiment = calculate_sentiment(message.content) + messages = await get_messages_by_sentiment_similarity( self.bot.redis, - message.guild.id, # type: ignore - embedding, - StatisticScope.GUILD, + guild_id=str(message.guild.id), # type: ignore + sentiment=sentiment.compound, + scope=StatisticScope.GUILD, + radius=0.1, ) if not messages: diff --git a/courageous_comets/models.py b/courageous_comets/models.py index 0782ade..458f086 100644 --- a/courageous_comets/models.py +++ b/courageous_comets/models.py @@ -2,7 +2,7 @@ from typing import Annotated import pydantic -from pydantic import Field, PlainSerializer +from pydantic import AliasChoices, Field, PlainSerializer UnixTimestamp = Annotated[ datetime.datetime, @@ -62,10 +62,26 @@ class SentimentResult(BaseModel): The compound sentiment score. """ - neg: float = Field(..., serialization_alias="sentiment_neg") - neu: float = Field(..., serialization_alias="sentiment_neu") - pos: float = Field(..., serialization_alias="sentiment_pos") - compound: float = Field(..., serialization_alias="sentiment_compound") + neg: float = Field( + ..., + serialization_alias="sentiment_neg", + validation_alias=AliasChoices("neg", "sentiment_neg"), + ) + neu: float = Field( + ..., + serialization_alias="sentiment_neu", + validation_alias=AliasChoices("neu", "sentiment_neu"), + ) + pos: float = Field( + ..., + serialization_alias="sentiment_pos", + validation_alias=AliasChoices("pos", "sentiment_pos"), + ) + compound: float = Field( + ..., + serialization_alias="sentiment_compound", + validation_alias=AliasChoices("compound", "sentiment_compound"), + ) class MessageAnalysis(Message): diff --git a/courageous_comets/redis/messages.py b/courageous_comets/redis/messages.py index 0ba6a30..1845f09 100644 --- a/courageous_comets/redis/messages.py +++ b/courageous_comets/redis/messages.py @@ -133,6 +133,36 @@ async def save_message( return key +async def get_message_sentiment( + key: str, + *, + redis: Redis, +) -> models.SentimentResult | None: + """ + Get the sentiment of message from the database given its key. + + Parameters + ---------- + key: str + The key of the message to fetch. + redis : redis.Redis + The Redis connection instance. + + Returns + ------- + courageous_comets.models.SentimentResult | None + The sentiment analysis result if found, else None. + """ + fields = ["sentiment_neg", "sentiment_neu", "sentiment_pos", "sentiment_compound"] + data = await redis.hmget(key, fields) # type: ignore + + if not data: + return None + return models.SentimentResult.model_validate( + dict(zip(fields, map(float, data), strict=True)), + ) + + async def get_recent_messages( redis: Redis, *, @@ -369,9 +399,14 @@ async def get_message_rate( # noqa: PLR0913 # Build the aggregation query query = ( aggregations.AggregateRequest(f"{search_scope!s} @timestamp:[0 inf]") - .limit(0, limit) + .limit( + 0, + limit, + ) # Create a new property `timestamp` rounded to the start of the interval - .apply(timestamp=f"minute(@timestamp) - ((minute(@timestamp) % {duration.value}))") + .apply( + timestamp=f"minute(@timestamp) - ((minute(@timestamp) % {duration.value}))", + ) # Group results by interval using the new `timestamp` property .group_by(["@timestamp"], reducer) # Sort results by the number of messages in each interval diff --git a/poetry.lock b/poetry.lock index 23df5e6..f30d7ec 100644 --- a/poetry.lock +++ b/poetry.lock @@ -455,6 +455,69 @@ questionary = ">=2.0,<3.0" termcolor = ">=1.1,<3" tomlkit = ">=0.5.3,<1.0.0" +[[package]] +name = "contourpy" +version = "1.2.1" +description = "Python library for calculating contours of 2D quadrilateral grids" +optional = false +python-versions = ">=3.9" +files = [ + {file = "contourpy-1.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bd7c23df857d488f418439686d3b10ae2fbf9bc256cd045b37a8c16575ea1040"}, + {file = "contourpy-1.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5b9eb0ca724a241683c9685a484da9d35c872fd42756574a7cfbf58af26677fd"}, + {file = "contourpy-1.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4c75507d0a55378240f781599c30e7776674dbaf883a46d1c90f37e563453480"}, + {file = "contourpy-1.2.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:11959f0ce4a6f7b76ec578576a0b61a28bdc0696194b6347ba3f1c53827178b9"}, + {file = "contourpy-1.2.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eb3315a8a236ee19b6df481fc5f997436e8ade24a9f03dfdc6bd490fea20c6da"}, + {file = "contourpy-1.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39f3ecaf76cd98e802f094e0d4fbc6dc9c45a8d0c4d185f0f6c2234e14e5f75b"}, + {file = "contourpy-1.2.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:94b34f32646ca0414237168d68a9157cb3889f06b096612afdd296003fdd32fd"}, + {file = "contourpy-1.2.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:457499c79fa84593f22454bbd27670227874cd2ff5d6c84e60575c8b50a69619"}, + {file = "contourpy-1.2.1-cp310-cp310-win32.whl", hash = "sha256:ac58bdee53cbeba2ecad824fa8159493f0bf3b8ea4e93feb06c9a465d6c87da8"}, + {file = "contourpy-1.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:9cffe0f850e89d7c0012a1fb8730f75edd4320a0a731ed0c183904fe6ecfc3a9"}, + {file = "contourpy-1.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6022cecf8f44e36af10bd9118ca71f371078b4c168b6e0fab43d4a889985dbb5"}, + {file = "contourpy-1.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ef5adb9a3b1d0c645ff694f9bca7702ec2c70f4d734f9922ea34de02294fdf72"}, + {file = "contourpy-1.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6150ffa5c767bc6332df27157d95442c379b7dce3a38dff89c0f39b63275696f"}, + {file = "contourpy-1.2.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c863140fafc615c14a4bf4efd0f4425c02230eb8ef02784c9a156461e62c965"}, + {file = "contourpy-1.2.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:00e5388f71c1a0610e6fe56b5c44ab7ba14165cdd6d695429c5cd94021e390b2"}, + {file = "contourpy-1.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4492d82b3bc7fbb7e3610747b159869468079fe149ec5c4d771fa1f614a14df"}, + {file = "contourpy-1.2.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:49e70d111fee47284d9dd867c9bb9a7058a3c617274900780c43e38d90fe1205"}, + {file = "contourpy-1.2.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b59c0ffceff8d4d3996a45f2bb6f4c207f94684a96bf3d9728dbb77428dd8cb8"}, + {file = "contourpy-1.2.1-cp311-cp311-win32.whl", hash = "sha256:7b4182299f251060996af5249c286bae9361fa8c6a9cda5efc29fe8bfd6062ec"}, + {file = "contourpy-1.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2855c8b0b55958265e8b5888d6a615ba02883b225f2227461aa9127c578a4922"}, + {file = "contourpy-1.2.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:62828cada4a2b850dbef89c81f5a33741898b305db244904de418cc957ff05dc"}, + {file = "contourpy-1.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:309be79c0a354afff9ff7da4aaed7c3257e77edf6c1b448a779329431ee79d7e"}, + {file = "contourpy-1.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e785e0f2ef0d567099b9ff92cbfb958d71c2d5b9259981cd9bee81bd194c9a4"}, + {file = "contourpy-1.2.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1cac0a8f71a041aa587410424ad46dfa6a11f6149ceb219ce7dd48f6b02b87a7"}, + {file = "contourpy-1.2.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:af3f4485884750dddd9c25cb7e3915d83c2db92488b38ccb77dd594eac84c4a0"}, + {file = "contourpy-1.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ce6889abac9a42afd07a562c2d6d4b2b7134f83f18571d859b25624a331c90b"}, + {file = "contourpy-1.2.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a1eea9aecf761c661d096d39ed9026574de8adb2ae1c5bd7b33558af884fb2ce"}, + {file = "contourpy-1.2.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:187fa1d4c6acc06adb0fae5544c59898ad781409e61a926ac7e84b8f276dcef4"}, + {file = "contourpy-1.2.1-cp312-cp312-win32.whl", hash = "sha256:c2528d60e398c7c4c799d56f907664673a807635b857df18f7ae64d3e6ce2d9f"}, + {file = "contourpy-1.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:1a07fc092a4088ee952ddae19a2b2a85757b923217b7eed584fdf25f53a6e7ce"}, + {file = "contourpy-1.2.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bb6834cbd983b19f06908b45bfc2dad6ac9479ae04abe923a275b5f48f1a186b"}, + {file = "contourpy-1.2.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1d59e739ab0e3520e62a26c60707cc3ab0365d2f8fecea74bfe4de72dc56388f"}, + {file = "contourpy-1.2.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd3db01f59fdcbce5b22afad19e390260d6d0222f35a1023d9adc5690a889364"}, + {file = "contourpy-1.2.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a12a813949e5066148712a0626895c26b2578874e4cc63160bb007e6df3436fe"}, + {file = "contourpy-1.2.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe0ccca550bb8e5abc22f530ec0466136379c01321fd94f30a22231e8a48d985"}, + {file = "contourpy-1.2.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1d59258c3c67c865435d8fbeb35f8c59b8bef3d6f46c1f29f6123556af28445"}, + {file = "contourpy-1.2.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f32c38afb74bd98ce26de7cc74a67b40afb7b05aae7b42924ea990d51e4dac02"}, + {file = "contourpy-1.2.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d31a63bc6e6d87f77d71e1abbd7387ab817a66733734883d1fc0021ed9bfa083"}, + {file = "contourpy-1.2.1-cp39-cp39-win32.whl", hash = "sha256:ddcb8581510311e13421b1f544403c16e901c4e8f09083c881fab2be80ee31ba"}, + {file = "contourpy-1.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:10a37ae557aabf2509c79715cd20b62e4c7c28b8cd62dd7d99e5ed3ce28c3fd9"}, + {file = "contourpy-1.2.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a31f94983fecbac95e58388210427d68cd30fe8a36927980fab9c20062645609"}, + {file = "contourpy-1.2.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef2b055471c0eb466033760a521efb9d8a32b99ab907fc8358481a1dd29e3bd3"}, + {file = "contourpy-1.2.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:b33d2bc4f69caedcd0a275329eb2198f560b325605810895627be5d4b876bf7f"}, + {file = "contourpy-1.2.1.tar.gz", hash = "sha256:4d8908b3bee1c889e547867ca4cdc54e5ab6be6d3e078556814a22457f49423c"}, +] + +[package.dependencies] +numpy = ">=1.20" + +[package.extras] +bokeh = ["bokeh", "selenium"] +docs = ["furo", "sphinx (>=7.2)", "sphinx-copybutton"] +mypy = ["contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.8.0)", "types-Pillow"] +test = ["Pillow", "contourpy[test-no-images]", "matplotlib"] +test-no-images = ["pytest", "pytest-cov", "pytest-xdist", "wurlitzer"] + [[package]] name = "contractions" version = "0.1.73" @@ -468,6 +531,21 @@ files = [ [package.dependencies] textsearch = ">=0.0.21" +[[package]] +name = "cycler" +version = "0.12.1" +description = "Composable style cycles" +optional = false +python-versions = ">=3.8" +files = [ + {file = "cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30"}, + {file = "cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c"}, +] + +[package.extras] +docs = ["ipython", "matplotlib", "numpydoc", "sphinx"] +tests = ["pytest", "pytest-cov", "pytest-xdist"] + [[package]] name = "decli" version = "0.6.2" @@ -540,6 +618,71 @@ docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1 testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-asyncio (>=0.21)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)", "virtualenv (>=20.26.2)"] typing = ["typing-extensions (>=4.8)"] +[[package]] +name = "fonttools" +version = "4.53.1" +description = "Tools to manipulate font files" +optional = false +python-versions = ">=3.8" +files = [ + {file = "fonttools-4.53.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0679a30b59d74b6242909945429dbddb08496935b82f91ea9bf6ad240ec23397"}, + {file = "fonttools-4.53.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e8bf06b94694251861ba7fdeea15c8ec0967f84c3d4143ae9daf42bbc7717fe3"}, + {file = "fonttools-4.53.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b96cd370a61f4d083c9c0053bf634279b094308d52fdc2dd9a22d8372fdd590d"}, + {file = "fonttools-4.53.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1c7c5aa18dd3b17995898b4a9b5929d69ef6ae2af5b96d585ff4005033d82f0"}, + {file = "fonttools-4.53.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e013aae589c1c12505da64a7d8d023e584987e51e62006e1bb30d72f26522c41"}, + {file = "fonttools-4.53.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9efd176f874cb6402e607e4cc9b4a9cd584d82fc34a4b0c811970b32ba62501f"}, + {file = "fonttools-4.53.1-cp310-cp310-win32.whl", hash = "sha256:c8696544c964500aa9439efb6761947393b70b17ef4e82d73277413f291260a4"}, + {file = "fonttools-4.53.1-cp310-cp310-win_amd64.whl", hash = "sha256:8959a59de5af6d2bec27489e98ef25a397cfa1774b375d5787509c06659b3671"}, + {file = "fonttools-4.53.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:da33440b1413bad53a8674393c5d29ce64d8c1a15ef8a77c642ffd900d07bfe1"}, + {file = "fonttools-4.53.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5ff7e5e9bad94e3a70c5cd2fa27f20b9bb9385e10cddab567b85ce5d306ea923"}, + {file = "fonttools-4.53.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6e7170d675d12eac12ad1a981d90f118c06cf680b42a2d74c6c931e54b50719"}, + {file = "fonttools-4.53.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bee32ea8765e859670c4447b0817514ca79054463b6b79784b08a8df3a4d78e3"}, + {file = "fonttools-4.53.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6e08f572625a1ee682115223eabebc4c6a2035a6917eac6f60350aba297ccadb"}, + {file = "fonttools-4.53.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b21952c092ffd827504de7e66b62aba26fdb5f9d1e435c52477e6486e9d128b2"}, + {file = "fonttools-4.53.1-cp311-cp311-win32.whl", hash = "sha256:9dfdae43b7996af46ff9da520998a32b105c7f098aeea06b2226b30e74fbba88"}, + {file = "fonttools-4.53.1-cp311-cp311-win_amd64.whl", hash = "sha256:d4d0096cb1ac7a77b3b41cd78c9b6bc4a400550e21dc7a92f2b5ab53ed74eb02"}, + {file = "fonttools-4.53.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d92d3c2a1b39631a6131c2fa25b5406855f97969b068e7e08413325bc0afba58"}, + {file = "fonttools-4.53.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3b3c8ebafbee8d9002bd8f1195d09ed2bd9ff134ddec37ee8f6a6375e6a4f0e8"}, + {file = "fonttools-4.53.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32f029c095ad66c425b0ee85553d0dc326d45d7059dbc227330fc29b43e8ba60"}, + {file = "fonttools-4.53.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10f5e6c3510b79ea27bb1ebfcc67048cde9ec67afa87c7dd7efa5c700491ac7f"}, + {file = "fonttools-4.53.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f677ce218976496a587ab17140da141557beb91d2a5c1a14212c994093f2eae2"}, + {file = "fonttools-4.53.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9e6ceba2a01b448e36754983d376064730690401da1dd104ddb543519470a15f"}, + {file = "fonttools-4.53.1-cp312-cp312-win32.whl", hash = "sha256:791b31ebbc05197d7aa096bbc7bd76d591f05905d2fd908bf103af4488e60670"}, + {file = "fonttools-4.53.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ed170b5e17da0264b9f6fae86073be3db15fa1bd74061c8331022bca6d09bab"}, + {file = "fonttools-4.53.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c818c058404eb2bba05e728d38049438afd649e3c409796723dfc17cd3f08749"}, + {file = "fonttools-4.53.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:651390c3b26b0c7d1f4407cad281ee7a5a85a31a110cbac5269de72a51551ba2"}, + {file = "fonttools-4.53.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e54f1bba2f655924c1138bbc7fa91abd61f45c68bd65ab5ed985942712864bbb"}, + {file = "fonttools-4.53.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9cd19cf4fe0595ebdd1d4915882b9440c3a6d30b008f3cc7587c1da7b95be5f"}, + {file = "fonttools-4.53.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:2af40ae9cdcb204fc1d8f26b190aa16534fcd4f0df756268df674a270eab575d"}, + {file = "fonttools-4.53.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:35250099b0cfb32d799fb5d6c651220a642fe2e3c7d2560490e6f1d3f9ae9169"}, + {file = "fonttools-4.53.1-cp38-cp38-win32.whl", hash = "sha256:f08df60fbd8d289152079a65da4e66a447efc1d5d5a4d3f299cdd39e3b2e4a7d"}, + {file = "fonttools-4.53.1-cp38-cp38-win_amd64.whl", hash = "sha256:7b6b35e52ddc8fb0db562133894e6ef5b4e54e1283dff606fda3eed938c36fc8"}, + {file = "fonttools-4.53.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:75a157d8d26c06e64ace9df037ee93a4938a4606a38cb7ffaf6635e60e253b7a"}, + {file = "fonttools-4.53.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4824c198f714ab5559c5be10fd1adf876712aa7989882a4ec887bf1ef3e00e31"}, + {file = "fonttools-4.53.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:becc5d7cb89c7b7afa8321b6bb3dbee0eec2b57855c90b3e9bf5fb816671fa7c"}, + {file = "fonttools-4.53.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84ec3fb43befb54be490147b4a922b5314e16372a643004f182babee9f9c3407"}, + {file = "fonttools-4.53.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:73379d3ffdeecb376640cd8ed03e9d2d0e568c9d1a4e9b16504a834ebadc2dfb"}, + {file = "fonttools-4.53.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:02569e9a810f9d11f4ae82c391ebc6fb5730d95a0657d24d754ed7763fb2d122"}, + {file = "fonttools-4.53.1-cp39-cp39-win32.whl", hash = "sha256:aae7bd54187e8bf7fd69f8ab87b2885253d3575163ad4d669a262fe97f0136cb"}, + {file = "fonttools-4.53.1-cp39-cp39-win_amd64.whl", hash = "sha256:e5b708073ea3d684235648786f5f6153a48dc8762cdfe5563c57e80787c29fbb"}, + {file = "fonttools-4.53.1-py3-none-any.whl", hash = "sha256:f1f8758a2ad110bd6432203a344269f445a2907dc24ef6bccfd0ac4e14e0d71d"}, + {file = "fonttools-4.53.1.tar.gz", hash = "sha256:e128778a8e9bc11159ce5447f76766cefbd876f44bd79aff030287254e4752c4"}, +] + +[package.extras] +all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "pycairo", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.1.0)", "xattr", "zopfli (>=0.1.4)"] +graphite = ["lz4 (>=1.7.4.2)"] +interpolatable = ["munkres", "pycairo", "scipy"] +lxml = ["lxml (>=4.0)"] +pathops = ["skia-pathops (>=0.5.0)"] +plot = ["matplotlib"] +repacker = ["uharfbuzz (>=0.23.0)"] +symfont = ["sympy"] +type1 = ["xattr"] +ufo = ["fs (>=2.2.0,<3)"] +unicode = ["unicodedata2 (>=15.1.0)"] +woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] + [[package]] name = "frozenlist" version = "1.4.1" @@ -987,6 +1130,119 @@ files = [ {file = "joblib-1.4.2.tar.gz", hash = "sha256:2382c5816b2636fbd20a09e0f4e9dad4736765fdfb7dca582943b9c1366b3f0e"}, ] +[[package]] +name = "kiwisolver" +version = "1.4.5" +description = "A fast implementation of the Cassowary constraint solver" +optional = false +python-versions = ">=3.7" +files = [ + {file = "kiwisolver-1.4.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:05703cf211d585109fcd72207a31bb170a0f22144d68298dc5e61b3c946518af"}, + {file = "kiwisolver-1.4.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:146d14bebb7f1dc4d5fbf74f8a6cb15ac42baadee8912eb84ac0b3b2a3dc6ac3"}, + {file = "kiwisolver-1.4.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6ef7afcd2d281494c0a9101d5c571970708ad911d028137cd558f02b851c08b4"}, + {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9eaa8b117dc8337728e834b9c6e2611f10c79e38f65157c4c38e9400286f5cb1"}, + {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ec20916e7b4cbfb1f12380e46486ec4bcbaa91a9c448b97023fde0d5bbf9e4ff"}, + {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39b42c68602539407884cf70d6a480a469b93b81b7701378ba5e2328660c847a"}, + {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa12042de0171fad672b6c59df69106d20d5596e4f87b5e8f76df757a7c399aa"}, + {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a40773c71d7ccdd3798f6489aaac9eee213d566850a9533f8d26332d626b82c"}, + {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:19df6e621f6d8b4b9c4d45f40a66839294ff2bb235e64d2178f7522d9170ac5b"}, + {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:83d78376d0d4fd884e2c114d0621624b73d2aba4e2788182d286309ebdeed770"}, + {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e391b1f0a8a5a10ab3b9bb6afcfd74f2175f24f8975fb87ecae700d1503cdee0"}, + {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:852542f9481f4a62dbb5dd99e8ab7aedfeb8fb6342349a181d4036877410f525"}, + {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59edc41b24031bc25108e210c0def6f6c2191210492a972d585a06ff246bb79b"}, + {file = "kiwisolver-1.4.5-cp310-cp310-win32.whl", hash = "sha256:a6aa6315319a052b4ee378aa171959c898a6183f15c1e541821c5c59beaa0238"}, + {file = "kiwisolver-1.4.5-cp310-cp310-win_amd64.whl", hash = "sha256:d0ef46024e6a3d79c01ff13801cb19d0cad7fd859b15037aec74315540acc276"}, + {file = "kiwisolver-1.4.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:11863aa14a51fd6ec28688d76f1735f8f69ab1fabf388851a595d0721af042f5"}, + {file = "kiwisolver-1.4.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8ab3919a9997ab7ef2fbbed0cc99bb28d3c13e6d4b1ad36e97e482558a91be90"}, + {file = "kiwisolver-1.4.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fcc700eadbbccbf6bc1bcb9dbe0786b4b1cb91ca0dcda336eef5c2beed37b797"}, + {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dfdd7c0b105af050eb3d64997809dc21da247cf44e63dc73ff0fd20b96be55a9"}, + {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76c6a5964640638cdeaa0c359382e5703e9293030fe730018ca06bc2010c4437"}, + {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bbea0db94288e29afcc4c28afbf3a7ccaf2d7e027489c449cf7e8f83c6346eb9"}, + {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ceec1a6bc6cab1d6ff5d06592a91a692f90ec7505d6463a88a52cc0eb58545da"}, + {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:040c1aebeda72197ef477a906782b5ab0d387642e93bda547336b8957c61022e"}, + {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f91de7223d4c7b793867797bacd1ee53bfe7359bd70d27b7b58a04efbb9436c8"}, + {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:faae4860798c31530dd184046a900e652c95513796ef51a12bc086710c2eec4d"}, + {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:b0157420efcb803e71d1b28e2c287518b8808b7cf1ab8af36718fd0a2c453eb0"}, + {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:06f54715b7737c2fecdbf140d1afb11a33d59508a47bf11bb38ecf21dc9ab79f"}, + {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fdb7adb641a0d13bdcd4ef48e062363d8a9ad4a182ac7647ec88f695e719ae9f"}, + {file = "kiwisolver-1.4.5-cp311-cp311-win32.whl", hash = "sha256:bb86433b1cfe686da83ce32a9d3a8dd308e85c76b60896d58f082136f10bffac"}, + {file = "kiwisolver-1.4.5-cp311-cp311-win_amd64.whl", hash = "sha256:6c08e1312a9cf1074d17b17728d3dfce2a5125b2d791527f33ffbe805200a355"}, + {file = "kiwisolver-1.4.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:32d5cf40c4f7c7b3ca500f8985eb3fb3a7dfc023215e876f207956b5ea26632a"}, + {file = "kiwisolver-1.4.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f846c260f483d1fd217fe5ed7c173fb109efa6b1fc8381c8b7552c5781756192"}, + {file = "kiwisolver-1.4.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5ff5cf3571589b6d13bfbfd6bcd7a3f659e42f96b5fd1c4830c4cf21d4f5ef45"}, + {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7269d9e5f1084a653d575c7ec012ff57f0c042258bf5db0954bf551c158466e7"}, + {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da802a19d6e15dffe4b0c24b38b3af68e6c1a68e6e1d8f30148c83864f3881db"}, + {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3aba7311af82e335dd1e36ffff68aaca609ca6290c2cb6d821a39aa075d8e3ff"}, + {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:763773d53f07244148ccac5b084da5adb90bfaee39c197554f01b286cf869228"}, + {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2270953c0d8cdab5d422bee7d2007f043473f9d2999631c86a223c9db56cbd16"}, + {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d099e745a512f7e3bbe7249ca835f4d357c586d78d79ae8f1dcd4d8adeb9bda9"}, + {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:74db36e14a7d1ce0986fa104f7d5637aea5c82ca6326ed0ec5694280942d1162"}, + {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e5bab140c309cb3a6ce373a9e71eb7e4873c70c2dda01df6820474f9889d6d4"}, + {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0f114aa76dc1b8f636d077979c0ac22e7cd8f3493abbab152f20eb8d3cda71f3"}, + {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:88a2df29d4724b9237fc0c6eaf2a1adae0cdc0b3e9f4d8e7dc54b16812d2d81a"}, + {file = "kiwisolver-1.4.5-cp312-cp312-win32.whl", hash = "sha256:72d40b33e834371fd330fb1472ca19d9b8327acb79a5821d4008391db8e29f20"}, + {file = "kiwisolver-1.4.5-cp312-cp312-win_amd64.whl", hash = "sha256:2c5674c4e74d939b9d91dda0fae10597ac7521768fec9e399c70a1f27e2ea2d9"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3a2b053a0ab7a3960c98725cfb0bf5b48ba82f64ec95fe06f1d06c99b552e130"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cd32d6c13807e5c66a7cbb79f90b553642f296ae4518a60d8d76243b0ad2898"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59ec7b7c7e1a61061850d53aaf8e93db63dce0c936db1fda2658b70e4a1be709"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da4cfb373035def307905d05041c1d06d8936452fe89d464743ae7fb8371078b"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2400873bccc260b6ae184b2b8a4fec0e4082d30648eadb7c3d9a13405d861e89"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1b04139c4236a0f3aff534479b58f6f849a8b351e1314826c2d230849ed48985"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:4e66e81a5779b65ac21764c295087de82235597a2293d18d943f8e9e32746265"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:7931d8f1f67c4be9ba1dd9c451fb0eeca1a25b89e4d3f89e828fe12a519b782a"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:b3f7e75f3015df442238cca659f8baa5f42ce2a8582727981cbfa15fee0ee205"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:bbf1d63eef84b2e8c89011b7f2235b1e0bf7dacc11cac9431fc6468e99ac77fb"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:4c380469bd3f970ef677bf2bcba2b6b0b4d5c75e7a020fb863ef75084efad66f"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-win32.whl", hash = "sha256:9408acf3270c4b6baad483865191e3e582b638b1654a007c62e3efe96f09a9a3"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-win_amd64.whl", hash = "sha256:5b94529f9b2591b7af5f3e0e730a4e0a41ea174af35a4fd067775f9bdfeee01a"}, + {file = "kiwisolver-1.4.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:11c7de8f692fc99816e8ac50d1d1aef4f75126eefc33ac79aac02c099fd3db71"}, + {file = "kiwisolver-1.4.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:53abb58632235cd154176ced1ae8f0d29a6657aa1aa9decf50b899b755bc2b93"}, + {file = "kiwisolver-1.4.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:88b9f257ca61b838b6f8094a62418421f87ac2a1069f7e896c36a7d86b5d4c29"}, + {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3195782b26fc03aa9c6913d5bad5aeb864bdc372924c093b0f1cebad603dd712"}, + {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc579bf0f502e54926519451b920e875f433aceb4624a3646b3252b5caa9e0b6"}, + {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a580c91d686376f0f7c295357595c5a026e6cbc3d77b7c36e290201e7c11ecb"}, + {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cfe6ab8da05c01ba6fbea630377b5da2cd9bcbc6338510116b01c1bc939a2c18"}, + {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d2e5a98f0ec99beb3c10e13b387f8db39106d53993f498b295f0c914328b1333"}, + {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a51a263952b1429e429ff236d2f5a21c5125437861baeed77f5e1cc2d2c7c6da"}, + {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3edd2fa14e68c9be82c5b16689e8d63d89fe927e56debd6e1dbce7a26a17f81b"}, + {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:74d1b44c6cfc897df648cc9fdaa09bc3e7679926e6f96df05775d4fb3946571c"}, + {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:76d9289ed3f7501012e05abb8358bbb129149dbd173f1f57a1bf1c22d19ab7cc"}, + {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:92dea1ffe3714fa8eb6a314d2b3c773208d865a0e0d35e713ec54eea08a66250"}, + {file = "kiwisolver-1.4.5-cp38-cp38-win32.whl", hash = "sha256:5c90ae8c8d32e472be041e76f9d2f2dbff4d0b0be8bd4041770eddb18cf49a4e"}, + {file = "kiwisolver-1.4.5-cp38-cp38-win_amd64.whl", hash = "sha256:c7940c1dc63eb37a67721b10d703247552416f719c4188c54e04334321351ced"}, + {file = "kiwisolver-1.4.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9407b6a5f0d675e8a827ad8742e1d6b49d9c1a1da5d952a67d50ef5f4170b18d"}, + {file = "kiwisolver-1.4.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15568384086b6df3c65353820a4473575dbad192e35010f622c6ce3eebd57af9"}, + {file = "kiwisolver-1.4.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0dc9db8e79f0036e8173c466d21ef18e1befc02de8bf8aa8dc0813a6dc8a7046"}, + {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:cdc8a402aaee9a798b50d8b827d7ecf75edc5fb35ea0f91f213ff927c15f4ff0"}, + {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6c3bd3cde54cafb87d74d8db50b909705c62b17c2099b8f2e25b461882e544ff"}, + {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:955e8513d07a283056b1396e9a57ceddbd272d9252c14f154d450d227606eb54"}, + {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:346f5343b9e3f00b8db8ba359350eb124b98c99efd0b408728ac6ebf38173958"}, + {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b9098e0049e88c6a24ff64545cdfc50807818ba6c1b739cae221bbbcbc58aad3"}, + {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:00bd361b903dc4bbf4eb165f24d1acbee754fce22ded24c3d56eec268658a5cf"}, + {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7b8b454bac16428b22560d0a1cf0a09875339cab69df61d7805bf48919415901"}, + {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:f1d072c2eb0ad60d4c183f3fb44ac6f73fb7a8f16a2694a91f988275cbf352f9"}, + {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:31a82d498054cac9f6d0b53d02bb85811185bcb477d4b60144f915f3b3126342"}, + {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6512cb89e334e4700febbffaaa52761b65b4f5a3cf33f960213d5656cea36a77"}, + {file = "kiwisolver-1.4.5-cp39-cp39-win32.whl", hash = "sha256:9db8ea4c388fdb0f780fe91346fd438657ea602d58348753d9fb265ce1bca67f"}, + {file = "kiwisolver-1.4.5-cp39-cp39-win_amd64.whl", hash = "sha256:59415f46a37f7f2efeec758353dd2eae1b07640d8ca0f0c42548ec4125492635"}, + {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5c7b3b3a728dc6faf3fc372ef24f21d1e3cee2ac3e9596691d746e5a536de920"}, + {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:620ced262a86244e2be10a676b646f29c34537d0d9cc8eb26c08f53d98013390"}, + {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:378a214a1e3bbf5ac4a8708304318b4f890da88c9e6a07699c4ae7174c09a68d"}, + {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaf7be1207676ac608a50cd08f102f6742dbfc70e8d60c4db1c6897f62f71523"}, + {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:ba55dce0a9b8ff59495ddd050a0225d58bd0983d09f87cfe2b6aec4f2c1234e4"}, + {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:fd32ea360bcbb92d28933fc05ed09bffcb1704ba3fc7942e81db0fd4f81a7892"}, + {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5e7139af55d1688f8b960ee9ad5adafc4ac17c1c473fe07133ac092310d76544"}, + {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dced8146011d2bc2e883f9bd68618b8247387f4bbec46d7392b3c3b032640126"}, + {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9bf3325c47b11b2e51bca0824ea217c7cd84491d8ac4eefd1e409705ef092bd"}, + {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5794cf59533bc3f1b1c821f7206a3617999db9fbefc345360aafe2e067514929"}, + {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e368f200bbc2e4f905b8e71eb38b3c04333bddaa6a2464a6355487b02bb7fb09"}, + {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5d706eba36b4c4d5bc6c6377bb6568098765e990cfc21ee16d13963fab7b3e7"}, + {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85267bd1aa8880a9c88a8cb71e18d3d64d2751a790e6ca6c27b8ccc724bcd5ad"}, + {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:210ef2c3a1f03272649aff1ef992df2e724748918c4bc2d5a90352849eb40bea"}, + {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:11d011a7574eb3b82bcc9c1a1d35c1d7075677fdd15de527d91b46bd35e935ee"}, + {file = "kiwisolver-1.4.5.tar.gz", hash = "sha256:e57e563a57fb22a142da34f38acc2fc1a5c864bc29ca1517a88abc963e60d6ec"}, +] + [[package]] name = "markdown" version = "3.6" @@ -1071,6 +1327,58 @@ files = [ {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, ] +[[package]] +name = "matplotlib" +version = "3.9.1" +description = "Python plotting package" +optional = false +python-versions = ">=3.9" +files = [ + {file = "matplotlib-3.9.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:7ccd6270066feb9a9d8e0705aa027f1ff39f354c72a87efe8fa07632f30fc6bb"}, + {file = "matplotlib-3.9.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:591d3a88903a30a6d23b040c1e44d1afdd0d778758d07110eb7596f811f31842"}, + {file = "matplotlib-3.9.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd2a59ff4b83d33bca3b5ec58203cc65985367812cb8c257f3e101632be86d92"}, + {file = "matplotlib-3.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0fc001516ffcf1a221beb51198b194d9230199d6842c540108e4ce109ac05cc0"}, + {file = "matplotlib-3.9.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:83c6a792f1465d174c86d06f3ae85a8fe36e6f5964633ae8106312ec0921fdf5"}, + {file = "matplotlib-3.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:421851f4f57350bcf0811edd754a708d2275533e84f52f6760b740766c6747a7"}, + {file = "matplotlib-3.9.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:b3fce58971b465e01b5c538f9d44915640c20ec5ff31346e963c9e1cd66fa812"}, + {file = "matplotlib-3.9.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a973c53ad0668c53e0ed76b27d2eeeae8799836fd0d0caaa4ecc66bf4e6676c0"}, + {file = "matplotlib-3.9.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82cd5acf8f3ef43f7532c2f230249720f5dc5dd40ecafaf1c60ac8200d46d7eb"}, + {file = "matplotlib-3.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab38a4f3772523179b2f772103d8030215b318fef6360cb40558f585bf3d017f"}, + {file = "matplotlib-3.9.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2315837485ca6188a4b632c5199900e28d33b481eb083663f6a44cfc8987ded3"}, + {file = "matplotlib-3.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:a0c977c5c382f6696caf0bd277ef4f936da7e2aa202ff66cad5f0ac1428ee15b"}, + {file = "matplotlib-3.9.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:565d572efea2b94f264dd86ef27919515aa6d629252a169b42ce5f570db7f37b"}, + {file = "matplotlib-3.9.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6d397fd8ccc64af2ec0af1f0efc3bacd745ebfb9d507f3f552e8adb689ed730a"}, + {file = "matplotlib-3.9.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26040c8f5121cd1ad712abffcd4b5222a8aec3a0fe40bc8542c94331deb8780d"}, + {file = "matplotlib-3.9.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d12cb1837cffaac087ad6b44399d5e22b78c729de3cdae4629e252067b705e2b"}, + {file = "matplotlib-3.9.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0e835c6988edc3d2d08794f73c323cc62483e13df0194719ecb0723b564e0b5c"}, + {file = "matplotlib-3.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:44a21d922f78ce40435cb35b43dd7d573cf2a30138d5c4b709d19f00e3907fd7"}, + {file = "matplotlib-3.9.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:0c584210c755ae921283d21d01f03a49ef46d1afa184134dd0f95b0202ee6f03"}, + {file = "matplotlib-3.9.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:11fed08f34fa682c2b792942f8902e7aefeed400da71f9e5816bea40a7ce28fe"}, + {file = "matplotlib-3.9.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0000354e32efcfd86bda75729716b92f5c2edd5b947200be9881f0a671565c33"}, + {file = "matplotlib-3.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4db17fea0ae3aceb8e9ac69c7e3051bae0b3d083bfec932240f9bf5d0197a049"}, + {file = "matplotlib-3.9.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:208cbce658b72bf6a8e675058fbbf59f67814057ae78165d8a2f87c45b48d0ff"}, + {file = "matplotlib-3.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:dc23f48ab630474264276be156d0d7710ac6c5a09648ccdf49fef9200d8cbe80"}, + {file = "matplotlib-3.9.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3fda72d4d472e2ccd1be0e9ccb6bf0d2eaf635e7f8f51d737ed7e465ac020cb3"}, + {file = "matplotlib-3.9.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:84b3ba8429935a444f1fdc80ed930babbe06725bcf09fbeb5c8757a2cd74af04"}, + {file = "matplotlib-3.9.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b918770bf3e07845408716e5bbda17eadfc3fcbd9307dc67f37d6cf834bb3d98"}, + {file = "matplotlib-3.9.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:f1f2e5d29e9435c97ad4c36fb6668e89aee13d48c75893e25cef064675038ac9"}, + {file = "matplotlib-3.9.1.tar.gz", hash = "sha256:de06b19b8db95dd33d0dc17c926c7c9ebed9f572074b6fac4f65068a6814d010"}, +] + +[package.dependencies] +contourpy = ">=1.0.1" +cycler = ">=0.10" +fonttools = ">=4.22.0" +kiwisolver = ">=1.3.1" +numpy = ">=1.23" +packaging = ">=20.0" +pillow = ">=8" +pyparsing = ">=2.3.1" +python-dateutil = ">=2.7" + +[package.extras] +dev = ["meson-python (>=0.13.1)", "numpy (>=1.25)", "pybind11 (>=2.6)", "setuptools (>=64)", "setuptools_scm (>=7)"] + [[package]] name = "mergedeep" version = "1.3.4" @@ -1458,6 +1766,103 @@ files = [ {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, ] +[[package]] +name = "pillow" +version = "10.4.0" +description = "Python Imaging Library (Fork)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pillow-10.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e"}, + {file = "pillow-10.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:543f3dc61c18dafb755773efc89aae60d06b6596a63914107f75459cf984164d"}, + {file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7928ecbf1ece13956b95d9cbcfc77137652b02763ba384d9ab508099a2eca856"}, + {file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4d49b85c4348ea0b31ea63bc75a9f3857869174e2bf17e7aba02945cd218e6f"}, + {file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:6c762a5b0997f5659a5ef2266abc1d8851ad7749ad9a6a5506eb23d314e4f46b"}, + {file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a985e028fc183bf12a77a8bbf36318db4238a3ded7fa9df1b9a133f1cb79f8fc"}, + {file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:812f7342b0eee081eaec84d91423d1b4650bb9828eb53d8511bcef8ce5aecf1e"}, + {file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ac1452d2fbe4978c2eec89fb5a23b8387aba707ac72810d9490118817d9c0b46"}, + {file = "pillow-10.4.0-cp310-cp310-win32.whl", hash = "sha256:bcd5e41a859bf2e84fdc42f4edb7d9aba0a13d29a2abadccafad99de3feff984"}, + {file = "pillow-10.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:ecd85a8d3e79cd7158dec1c9e5808e821feea088e2f69a974db5edf84dc53141"}, + {file = "pillow-10.4.0-cp310-cp310-win_arm64.whl", hash = "sha256:ff337c552345e95702c5fde3158acb0625111017d0e5f24bf3acdb9cc16b90d1"}, + {file = "pillow-10.4.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0a9ec697746f268507404647e531e92889890a087e03681a3606d9b920fbee3c"}, + {file = "pillow-10.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe91cb65544a1321e631e696759491ae04a2ea11d36715eca01ce07284738be"}, + {file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dc6761a6efc781e6a1544206f22c80c3af4c8cf461206d46a1e6006e4429ff3"}, + {file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e84b6cc6a4a3d76c153a6b19270b3526a5a8ed6b09501d3af891daa2a9de7d6"}, + {file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:bbc527b519bd3aa9d7f429d152fea69f9ad37c95f0b02aebddff592688998abe"}, + {file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:76a911dfe51a36041f2e756b00f96ed84677cdeb75d25c767f296c1c1eda1319"}, + {file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59291fb29317122398786c2d44427bbd1a6d7ff54017075b22be9d21aa59bd8d"}, + {file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:416d3a5d0e8cfe4f27f574362435bc9bae57f679a7158e0096ad2beb427b8696"}, + {file = "pillow-10.4.0-cp311-cp311-win32.whl", hash = "sha256:7086cc1d5eebb91ad24ded9f58bec6c688e9f0ed7eb3dbbf1e4800280a896496"}, + {file = "pillow-10.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cbed61494057c0f83b83eb3a310f0bf774b09513307c434d4366ed64f4128a91"}, + {file = "pillow-10.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:f5f0c3e969c8f12dd2bb7e0b15d5c468b51e5017e01e2e867335c81903046a22"}, + {file = "pillow-10.4.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:673655af3eadf4df6b5457033f086e90299fdd7a47983a13827acf7459c15d94"}, + {file = "pillow-10.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:866b6942a92f56300012f5fbac71f2d610312ee65e22f1aa2609e491284e5597"}, + {file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29dbdc4207642ea6aad70fbde1a9338753d33fb23ed6956e706936706f52dd80"}, + {file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf2342ac639c4cf38799a44950bbc2dfcb685f052b9e262f446482afaf4bffca"}, + {file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f5b92f4d70791b4a67157321c4e8225d60b119c5cc9aee8ecf153aace4aad4ef"}, + {file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:86dcb5a1eb778d8b25659d5e4341269e8590ad6b4e8b44d9f4b07f8d136c414a"}, + {file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:780c072c2e11c9b2c7ca37f9a2ee8ba66f44367ac3e5c7832afcfe5104fd6d1b"}, + {file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37fb69d905be665f68f28a8bba3c6d3223c8efe1edf14cc4cfa06c241f8c81d9"}, + {file = "pillow-10.4.0-cp312-cp312-win32.whl", hash = "sha256:7dfecdbad5c301d7b5bde160150b4db4c659cee2b69589705b6f8a0c509d9f42"}, + {file = "pillow-10.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:1d846aea995ad352d4bdcc847535bd56e0fd88d36829d2c90be880ef1ee4668a"}, + {file = "pillow-10.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:e553cad5179a66ba15bb18b353a19020e73a7921296a7979c4a2b7f6a5cd57f9"}, + {file = "pillow-10.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8bc1a764ed8c957a2e9cacf97c8b2b053b70307cf2996aafd70e91a082e70df3"}, + {file = "pillow-10.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6209bb41dc692ddfee4942517c19ee81b86c864b626dbfca272ec0f7cff5d9fb"}, + {file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bee197b30783295d2eb680b311af15a20a8b24024a19c3a26431ff83eb8d1f70"}, + {file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ef61f5dd14c300786318482456481463b9d6b91ebe5ef12f405afbba77ed0be"}, + {file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:297e388da6e248c98bc4a02e018966af0c5f92dfacf5a5ca22fa01cb3179bca0"}, + {file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:e4db64794ccdf6cb83a59d73405f63adbe2a1887012e308828596100a0b2f6cc"}, + {file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd2880a07482090a3bcb01f4265f1936a903d70bc740bfcb1fd4e8a2ffe5cf5a"}, + {file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b35b21b819ac1dbd1233317adeecd63495f6babf21b7b2512d244ff6c6ce309"}, + {file = "pillow-10.4.0-cp313-cp313-win32.whl", hash = "sha256:551d3fd6e9dc15e4c1eb6fc4ba2b39c0c7933fa113b220057a34f4bb3268a060"}, + {file = "pillow-10.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:030abdbe43ee02e0de642aee345efa443740aa4d828bfe8e2eb11922ea6a21ea"}, + {file = "pillow-10.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:5b001114dd152cfd6b23befeb28d7aee43553e2402c9f159807bf55f33af8a8d"}, + {file = "pillow-10.4.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8d4d5063501b6dd4024b8ac2f04962d661222d120381272deea52e3fc52d3736"}, + {file = "pillow-10.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7c1ee6f42250df403c5f103cbd2768a28fe1a0ea1f0f03fe151c8741e1469c8b"}, + {file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15e02e9bb4c21e39876698abf233c8c579127986f8207200bc8a8f6bb27acf2"}, + {file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a8d4bade9952ea9a77d0c3e49cbd8b2890a399422258a77f357b9cc9be8d680"}, + {file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:43efea75eb06b95d1631cb784aa40156177bf9dd5b4b03ff38979e048258bc6b"}, + {file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:950be4d8ba92aca4b2bb0741285a46bfae3ca699ef913ec8416c1b78eadd64cd"}, + {file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d7480af14364494365e89d6fddc510a13e5a2c3584cb19ef65415ca57252fb84"}, + {file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:73664fe514b34c8f02452ffb73b7a92c6774e39a647087f83d67f010eb9a0cf0"}, + {file = "pillow-10.4.0-cp38-cp38-win32.whl", hash = "sha256:e88d5e6ad0d026fba7bdab8c3f225a69f063f116462c49892b0149e21b6c0a0e"}, + {file = "pillow-10.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:5161eef006d335e46895297f642341111945e2c1c899eb406882a6c61a4357ab"}, + {file = "pillow-10.4.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0ae24a547e8b711ccaaf99c9ae3cd975470e1a30caa80a6aaee9a2f19c05701d"}, + {file = "pillow-10.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:298478fe4f77a4408895605f3482b6cc6222c018b2ce565c2b6b9c354ac3229b"}, + {file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:134ace6dc392116566980ee7436477d844520a26a4b1bd4053f6f47d096997fd"}, + {file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:930044bb7679ab003b14023138b50181899da3f25de50e9dbee23b61b4de2126"}, + {file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:c76e5786951e72ed3686e122d14c5d7012f16c8303a674d18cdcd6d89557fc5b"}, + {file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b2724fdb354a868ddf9a880cb84d102da914e99119211ef7ecbdc613b8c96b3c"}, + {file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dbc6ae66518ab3c5847659e9988c3b60dc94ffb48ef9168656e0019a93dbf8a1"}, + {file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:06b2f7898047ae93fad74467ec3d28fe84f7831370e3c258afa533f81ef7f3df"}, + {file = "pillow-10.4.0-cp39-cp39-win32.whl", hash = "sha256:7970285ab628a3779aecc35823296a7869f889b8329c16ad5a71e4901a3dc4ef"}, + {file = "pillow-10.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:961a7293b2457b405967af9c77dcaa43cc1a8cd50d23c532e62d48ab6cdd56f5"}, + {file = "pillow-10.4.0-cp39-cp39-win_arm64.whl", hash = "sha256:32cda9e3d601a52baccb2856b8ea1fc213c90b340c542dcef77140dfa3278a9e"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5b4815f2e65b30f5fbae9dfffa8636d992d49705723fe86a3661806e069352d4"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8f0aef4ef59694b12cadee839e2ba6afeab89c0f39a3adc02ed51d109117b8da"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f4727572e2918acaa9077c919cbbeb73bd2b3ebcfe033b72f858fc9fbef0026"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff25afb18123cea58a591ea0244b92eb1e61a1fd497bf6d6384f09bc3262ec3e"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dc3e2db6ba09ffd7d02ae9141cfa0ae23393ee7687248d46a7507b75d610f4f5"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:02a2be69f9c9b8c1e97cf2713e789d4e398c751ecfd9967c18d0ce304efbf885"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0755ffd4a0c6f267cccbae2e9903d95477ca2f77c4fcf3a3a09570001856c8a5"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:a02364621fe369e06200d4a16558e056fe2805d3468350df3aef21e00d26214b"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:1b5dea9831a90e9d0721ec417a80d4cbd7022093ac38a568db2dd78363b00908"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b885f89040bb8c4a1573566bbb2f44f5c505ef6e74cec7ab9068c900047f04b"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87dd88ded2e6d74d31e1e0a99a726a6765cda32d00ba72dc37f0651f306daaa8"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:2db98790afc70118bd0255c2eeb465e9767ecf1f3c25f9a1abb8ffc8cfd1fe0a"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f7baece4ce06bade126fb84b8af1c33439a76d8a6fd818970215e0560ca28c27"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:cfdd747216947628af7b259d274771d84db2268ca062dd5faf373639d00113a3"}, + {file = "pillow-10.4.0.tar.gz", hash = "sha256:166c1cd4d24309b30d61f79f4a9114b7b2313d7450912277855ff5dfd7cd4a06"}, +] + +[package.extras] +docs = ["furo", "olefile", "sphinx (>=7.3)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"] +fpx = ["olefile"] +mic = ["olefile"] +tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] +typing = ["typing-extensions"] +xmp = ["defusedxml"] + [[package]] name = "platformdirs" version = "4.2.2" @@ -1905,6 +2310,7 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -1912,8 +2318,16 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -1930,6 +2344,7 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -1937,6 +2352,7 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -2888,4 +3304,4 @@ test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", [metadata] lock-version = "2.0" python-versions = "~3.12" -content-hash = "7a348c7ffe5d3be3a7a8c038f9d36075bb4ee7ce8dc13c67af721f1152399e3d" +content-hash = "d92ea9ce73ffb4aec8b716e045ddbd37a7dddb368877a80020aacabad4abd2d3" diff --git a/pyproject.toml b/pyproject.toml index 1e4ea66..0e7ed85 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,6 +22,7 @@ redisvl = { extras = ["hiredis"], version = "0.2.3" } torch = { version = "^2.3.1+cpu", source = "pytorch-cpu" } transformers = "^4.42.4" unidecode = "^1.3.8" +matplotlib = "^3.9.1" [tool.poetry.dev-dependencies] commitizen = "3.27.0" diff --git a/tests/integrations/test__redis.py b/tests/integrations/test__redis.py index 470480d..c8fb37e 100644 --- a/tests/integrations/test__redis.py +++ b/tests/integrations/test__redis.py @@ -167,7 +167,7 @@ async def test__get_messages_by_sentiment_similarity( assert messages[0].message_id == message.message_id -@pytest.mark.parametrize(("limit", "expect"), [(10, 10), (100, 100)]) +@pytest.mark.parametrize(("limit", "expect"), [(10, 10), (150, 100)]) @pytest.mark.num_messages(100) async def test__get_recent_messages( redis: Redis, From 0238f657e4bfe96239702786c3434fccc586948e Mon Sep 17 00:00:00 2001 From: Uchechukwu Orji Date: Fri, 26 Jul 2024 16:00:06 +0100 Subject: [PATCH 099/168] feat: set up frequency cog (#53) --- application.yaml | 1 + courageous_comets/cogs/frequency.py | 53 +++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 courageous_comets/cogs/frequency.py diff --git a/application.yaml b/application.yaml index bfdb77c..730c634 100644 --- a/application.yaml +++ b/application.yaml @@ -4,6 +4,7 @@ cogs: - courageous_comets.cogs.messages - courageous_comets.cogs.ping - courageous_comets.cogs.sentiment + - courageous_comets.cogs.frequency dev-cogs: - jishaku nltk: diff --git a/courageous_comets/cogs/frequency.py b/courageous_comets/cogs/frequency.py new file mode 100644 index 0000000..f6172aa --- /dev/null +++ b/courageous_comets/cogs/frequency.py @@ -0,0 +1,53 @@ +import discord +from discord import app_commands +from discord.ext import commands + +from courageous_comets.client import CourageousCometsBot + + +class Frequency(commands.Cog): + """Frequency related commands.""" + + def __init__(self, bot: CourageousCometsBot) -> None: + self.bot = bot + + @app_commands.command( + name="frequency", + description="Get frequency of messages over a duration.", + ) + async def frequency_command( + self, + interaction: discord.Interaction, + ) -> None: + """ + Allow users to view the frequency of messages. + + Generates an embed of a graph of number of messages over duration. + + Parameters + ---------- + interaction: discord.Interaction + The interaction that triggered the command. + duration: courageous_comets.enums.Duration + The duration to calculate the number of messages. + """ + if self.bot.redis is None: + return await interaction.response.send_message( + "This feature is currently unavailable. Please try again later.", + ephemeral=True, + ) + + if interaction.guild is None: + return await interaction.response.send_message( + "This feature is only available in guilds.", + ephemeral=True, + ) + return await interaction.response.send_message( + "This feature is currently unavailable. Please try again later.", + ephemeral=True, + ) + + +async def setup(bot: CourageousCometsBot) -> None: + """Load the cog.""" + await bot.add_cog(Frequency(bot)) From 8533d736a177a58be20863b5d8a9e50411443550 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Fri, 26 Jul 2024 20:08:04 +0200 Subject: [PATCH 100/168] feat: add user sentiment interaction (#54) Co-authored-by: Uchechukwu Orji --- .pre-commit-config.yaml | 2 +- courageous_comets/cogs/sentiment.py | 116 +++++-- courageous_comets/redis/messages.py | 54 +++- courageous_comets/sentiment.py | 14 +- poetry.lock | 347 +++++++++------------ pyproject.toml | 6 +- tests/courageous_comets/test__sentiment.py | 27 -- tests/integrations/conftest.py | 2 +- 8 files changed, 296 insertions(+), 272 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7985d64..0a50b0f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -45,7 +45,7 @@ repos: name: Check for Markdown linting errors - repo: https://github.com/RobertCraigie/pyright-python - rev: v1.1.372 + rev: v1.1.373 hooks: - id: pyright name: pyright (system) diff --git a/courageous_comets/cogs/sentiment.py b/courageous_comets/cogs/sentiment.py index fb02599..874c881 100644 --- a/courageous_comets/cogs/sentiment.py +++ b/courageous_comets/cogs/sentiment.py @@ -11,10 +11,9 @@ from courageous_comets.enums import StatisticScope from courageous_comets.redis.keys import key_schema from courageous_comets.redis.messages import ( + get_average_sentiment, get_message_sentiment, - get_messages_by_sentiment_similarity, ) -from courageous_comets.sentiment import calculate_sentiment from courageous_comets.utils import contextmenu from courageous_comets.vectorizer import Vectorizer @@ -34,7 +33,7 @@ class MessagesNotFound(app_commands.AppCommandError): } -SENTIMENT_DESCRIPTION_TEMPLATE = """ +MESSAGE_SENTIMENT_TEMPLATE = """ Overall the sentiment of the message is **{sentiment}**. Here's a breakdown of the scores: @@ -46,6 +45,12 @@ class MessagesNotFound(app_commands.AppCommandError): The compound score is {compound}. """ +USER_SENTIMENT_TEMPLATE = """ +Overall the sentiment of {user} is **{sentiment}**. + +Their average compound score is {compound}. +""" + def plot_sentiment_analysis( message_id: str | int, @@ -116,6 +121,79 @@ def __init__(self, bot: CourageousCometsBot) -> None: menu = app_commands.ContextMenu(name=obj.name, callback=obj) self.bot.tree.add_command(menu) + @contextmenu(name="Show user sentiment") + async def show_user_sentiment( + self, + interaction: discord.Interaction, + user: discord.User | discord.Member, + ) -> None: + """ + Allow users to view the sentiment analysis of a user using a context menu. + + Generates an embed with the sentiment analysis of a user and sends it to the user. + + The embed contains a line chart of the sentiment of a user over time. + + Parameters + ---------- + interaction : discord.Interaction + The interaction that triggered the command. + user : discord.User | discord.Member + The user to analyze. + """ + logger.info( + "User %s requested sentiment analysis results for user %s.", + interaction.user.id, + user.id, + ) + + if self.bot.redis is None: + return await interaction.response.send_message( + "This feature is currently unavailable. Please try again later.", + ephemeral=True, + ) + + if interaction.guild is None: + return await interaction.response.send_message( + "This feature is only available in guilds.", + ephemeral=True, + ) + + user_sentiment = await get_average_sentiment( + redis=self.bot.redis, + guild_id=str(interaction.guild.id), + ids=[str(user.id)], + scope=StatisticScope.USER, + ) + + if not user_sentiment: + raise MessagesNotFound + + average_sentiment = user_sentiment[0]["avg_sentiment"] + + sentiment = next( + (value for key, value in SENTIMENT.items() if int(average_sentiment * 100) in key), + "unknown", + ) + + if not user_sentiment: + raise MessagesNotFound + + view = discord.Embed( + title="User Sentiment", + description=USER_SENTIMENT_TEMPLATE.format( + sentiment=sentiment, + user=user.mention, + compound=average_sentiment, + ), + timestamp=discord.utils.utcnow(), + ) + + return await interaction.response.send_message( + embed=view, + ephemeral=True, + ) + @contextmenu(name="Show message sentiment") async def show_message_sentiment( self, @@ -185,7 +263,7 @@ async def show_message_sentiment( view = discord.Embed( title="Message Sentiment", - description=SENTIMENT_DESCRIPTION_TEMPLATE.format(**template_vars), + description=MESSAGE_SENTIMENT_TEMPLATE.format(**template_vars), color=color, timestamp=discord.utils.utcnow(), ) @@ -200,36 +278,6 @@ async def show_message_sentiment( ephemeral=True, ) - @contextmenu(name="Show similar sentiment") - async def get_similar_messages( - self, - interaction: discord.Interaction, - message: discord.Message, - ) -> None: - """Get similar messages based on semantics.""" - if self.bot.redis is None: - return await interaction.response.send_message( - "This feature is currently unavailable. Please try again later.", - ephemeral=True, - ) - - await interaction.response.defer() - - sentiment = calculate_sentiment(message.content) - messages = await get_messages_by_sentiment_similarity( - self.bot.redis, - guild_id=str(message.guild.id), # type: ignore - sentiment=sentiment.compound, - scope=StatisticScope.GUILD, - radius=0.1, - ) - - if not messages: - raise MessagesNotFound - - # TODO(isaa-ctaylor): Display messages - return None - async def setup(bot: CourageousCometsBot) -> None: """Load the cog.""" diff --git a/courageous_comets/redis/messages.py b/courageous_comets/redis/messages.py index 1845f09..5c2fef3 100644 --- a/courageous_comets/redis/messages.py +++ b/courageous_comets/redis/messages.py @@ -159,7 +159,13 @@ async def get_message_sentiment( if not data: return None return models.SentimentResult.model_validate( - dict(zip(fields, map(float, data), strict=True)), + dict( + zip( + fields, + (float(value) if value is not None else 0 for value in data), + strict=True, + ), + ), ) @@ -418,3 +424,49 @@ async def get_message_rate( # noqa: PLR0913 # Deserialize all rows as dictionaries. Each row is a flat list of key-value pairs. return [dict(itertools.batched(row, 2)) for row in results.rows] + + +async def get_average_sentiment( + redis: Redis, + *, + guild_id: str, + ids: list[str] | None = None, + scope: StatisticScope = StatisticScope.CHANNEL, + limit: int = settings.QUERY_LIMIT, +) -> list[dict[str, float]]: + """ + Get the average sentiment of messages for the given ids and scope. + + Parameters + ---------- + redis : Redis + The Redis connection instance. + guild_id : str + The ID of the guild to make the search. + ids : list[str], optional + Optional list of IDs to search for. Defaults to None. + scope : StatisticScope, optional + The scope of additional IDs (default: StatisticScope.CHANNEL). + Ignored if it is equal to StatisticScope.GUILD. + limit : int, optional + The number of messages to aggregate over (default: settings.QUERY_LIMIT). + """ + search_scope = build_search_scope(guild_id, ids, scope) + index = _get_raw_index(redis) + + # Define a reducer to calculate the average sentiment compound score and alias the result as + # "avg_sentiment" + reducer = reducers.avg("@sentiment_compound").alias("avg_sentiment") + + # Build the aggregation query + query = ( + aggregations.AggregateRequest(f"{search_scope!s}") + .limit(0, limit) + .group_by([f"@{scope}"], reducer) + ) + + # Execute the aggregation query on the index + results = await index.aggregate(query) # type: ignore + + # Deserialize all rows as dictionaries. Each row is a flat list of key-value pairs. + return [{key: float(value) for row in results.rows for key, value in itertools.batched(row, 2)}] diff --git a/courageous_comets/sentiment.py b/courageous_comets/sentiment.py index 07e1edb..4d425e5 100644 --- a/courageous_comets/sentiment.py +++ b/courageous_comets/sentiment.py @@ -4,8 +4,6 @@ from courageous_comets.models import SentimentResult -MAX_MESSAGE_LENGTH = 256 - logger = logging.getLogger(__name__) @@ -13,10 +11,7 @@ def calculate_sentiment(content: str) -> SentimentResult: """ Calculate the sentiment of a message. - Uses the NLTK sentiment intensity analyzer to calculate the sentiment of a message. - - Messages can be up to 256 characters long. If a message is longer than 256 characters, - it will be truncated. + Uses the NLTK sentiment intensity analyzer. Parameters ---------- @@ -28,12 +23,7 @@ def calculate_sentiment(content: str) -> SentimentResult: courageous_comets.models.SentimentResult The sentiment of the message. """ - truncated = content[:MAX_MESSAGE_LENGTH] - - if truncated != content: - logger.warning("Truncated message to %s characters", MAX_MESSAGE_LENGTH) - sia = SentimentIntensityAnalyzer() - result = sia.polarity_scores(truncated) + result = sia.polarity_scores(content) return SentimentResult.model_validate(result) diff --git a/poetry.lock b/poetry.lock index f30d7ec..776a595 100644 --- a/poetry.lock +++ b/poetry.lock @@ -930,13 +930,13 @@ files = [ [[package]] name = "huggingface-hub" -version = "0.24.0" +version = "0.24.2" description = "Client library to download and publish models, datasets and other repos on the huggingface.co hub" optional = false python-versions = ">=3.8.0" files = [ - {file = "huggingface_hub-0.24.0-py3-none-any.whl", hash = "sha256:7ad92edefb93d8145c061f6df8d99df2ff85f8379ba5fac8a95aca0642afa5d7"}, - {file = "huggingface_hub-0.24.0.tar.gz", hash = "sha256:6c7092736b577d89d57b3cdfea026f1b0dc2234ae783fa0d59caf1bf7d52dfa7"}, + {file = "huggingface_hub-0.24.2-py3-none-any.whl", hash = "sha256:abdf3244d3a274c4b1fbc5c4a1ef700032b3f60ba93cc63e4f036fd082aa2805"}, + {file = "huggingface_hub-0.24.2.tar.gz", hash = "sha256:92be892405d2f6a7a8479016f9a5662354f202b2c6c1ff499609621aed1fae10"}, ] [package.dependencies] @@ -1063,20 +1063,6 @@ files = [ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] -[[package]] -name = "intel-openmp" -version = "2021.4.0" -description = "Intel OpenMP* Runtime Library" -optional = false -python-versions = "*" -files = [ - {file = "intel_openmp-2021.4.0-py2.py3-none-macosx_10_15_x86_64.macosx_11_0_x86_64.whl", hash = "sha256:41c01e266a7fdb631a7609191709322da2bbf24b252ba763f125dd651bcc7675"}, - {file = "intel_openmp-2021.4.0-py2.py3-none-manylinux1_i686.whl", hash = "sha256:3b921236a38384e2016f0f3d65af6732cf2c12918087128a9163225451e776f2"}, - {file = "intel_openmp-2021.4.0-py2.py3-none-manylinux1_x86_64.whl", hash = "sha256:e2240ab8d01472fed04f3544a878cda5da16c26232b7ea1b59132dbfb48b186e"}, - {file = "intel_openmp-2021.4.0-py2.py3-none-win32.whl", hash = "sha256:6e863d8fd3d7e8ef389d52cf97a50fe2afe1a19247e8c0d168ce021546f96fc9"}, - {file = "intel_openmp-2021.4.0-py2.py3-none-win_amd64.whl", hash = "sha256:eef4c8bcc8acefd7f5cd3b9384dbf73d59e2c99fc56545712ded913f43c4a94f"}, -] - [[package]] name = "jinja2" version = "3.1.4" @@ -1501,24 +1487,6 @@ files = [ {file = "mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443"}, ] -[[package]] -name = "mkl" -version = "2021.4.0" -description = "Intel® oneAPI Math Kernel Library" -optional = false -python-versions = "*" -files = [ - {file = "mkl-2021.4.0-py2.py3-none-macosx_10_15_x86_64.macosx_11_0_x86_64.whl", hash = "sha256:67460f5cd7e30e405b54d70d1ed3ca78118370b65f7327d495e9c8847705e2fb"}, - {file = "mkl-2021.4.0-py2.py3-none-manylinux1_i686.whl", hash = "sha256:636d07d90e68ccc9630c654d47ce9fdeb036bb46e2b193b3a9ac8cfea683cce5"}, - {file = "mkl-2021.4.0-py2.py3-none-manylinux1_x86_64.whl", hash = "sha256:398dbf2b0d12acaf54117a5210e8f191827f373d362d796091d161f610c1ebfb"}, - {file = "mkl-2021.4.0-py2.py3-none-win32.whl", hash = "sha256:439c640b269a5668134e3dcbcea4350459c4a8bc46469669b2d67e07e3d330e8"}, - {file = "mkl-2021.4.0-py2.py3-none-win_amd64.whl", hash = "sha256:ceef3cafce4c009dd25f65d7ad0d833a0fbadc3d8903991ec92351fe5de1e718"}, -] - -[package.dependencies] -intel-openmp = "==2021.*" -tbb = "==2021.*" - [[package]] name = "mpmath" version = "1.3.0" @@ -1691,47 +1659,56 @@ files = [ [[package]] name = "numpy" -version = "1.26.4" +version = "2.0.1" description = "Fundamental package for array computing in Python" optional = false python-versions = ">=3.9" files = [ - {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"}, - {file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"}, - {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4"}, - {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f"}, - {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a"}, - {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2"}, - {file = "numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07"}, - {file = "numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5"}, - {file = "numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71"}, - {file = "numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef"}, - {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e"}, - {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5"}, - {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a"}, - {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a"}, - {file = "numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20"}, - {file = "numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2"}, - {file = "numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218"}, - {file = "numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b"}, - {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b"}, - {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed"}, - {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a"}, - {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0"}, - {file = "numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110"}, - {file = "numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818"}, - {file = "numpy-1.26.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c"}, - {file = "numpy-1.26.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be"}, - {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764"}, - {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3"}, - {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd"}, - {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c"}, - {file = "numpy-1.26.4-cp39-cp39-win32.whl", hash = "sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6"}, - {file = "numpy-1.26.4-cp39-cp39-win_amd64.whl", hash = "sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea"}, - {file = "numpy-1.26.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30"}, - {file = "numpy-1.26.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c"}, - {file = "numpy-1.26.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0"}, - {file = "numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010"}, + {file = "numpy-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0fbb536eac80e27a2793ffd787895242b7f18ef792563d742c2d673bfcb75134"}, + {file = "numpy-2.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:69ff563d43c69b1baba77af455dd0a839df8d25e8590e79c90fcbe1499ebde42"}, + {file = "numpy-2.0.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:1b902ce0e0a5bb7704556a217c4f63a7974f8f43e090aff03fcf262e0b135e02"}, + {file = "numpy-2.0.1-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:f1659887361a7151f89e79b276ed8dff3d75877df906328f14d8bb40bb4f5101"}, + {file = "numpy-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4658c398d65d1b25e1760de3157011a80375da861709abd7cef3bad65d6543f9"}, + {file = "numpy-2.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4127d4303b9ac9f94ca0441138acead39928938660ca58329fe156f84b9f3015"}, + {file = "numpy-2.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e5eeca8067ad04bc8a2a8731183d51d7cbaac66d86085d5f4766ee6bf19c7f87"}, + {file = "numpy-2.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9adbd9bb520c866e1bfd7e10e1880a1f7749f1f6e5017686a5fbb9b72cf69f82"}, + {file = "numpy-2.0.1-cp310-cp310-win32.whl", hash = "sha256:7b9853803278db3bdcc6cd5beca37815b133e9e77ff3d4733c247414e78eb8d1"}, + {file = "numpy-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:81b0893a39bc5b865b8bf89e9ad7807e16717f19868e9d234bdaf9b1f1393868"}, + {file = "numpy-2.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:75b4e316c5902d8163ef9d423b1c3f2f6252226d1aa5cd8a0a03a7d01ffc6268"}, + {file = "numpy-2.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6e4eeb6eb2fced786e32e6d8df9e755ce5be920d17f7ce00bc38fcde8ccdbf9e"}, + {file = "numpy-2.0.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:a1e01dcaab205fbece13c1410253a9eea1b1c9b61d237b6fa59bcc46e8e89343"}, + {file = "numpy-2.0.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:a8fc2de81ad835d999113ddf87d1ea2b0f4704cbd947c948d2f5513deafe5a7b"}, + {file = "numpy-2.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a3d94942c331dd4e0e1147f7a8699a4aa47dffc11bf8a1523c12af8b2e91bbe"}, + {file = "numpy-2.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15eb4eca47d36ec3f78cde0a3a2ee24cf05ca7396ef808dda2c0ddad7c2bde67"}, + {file = "numpy-2.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b83e16a5511d1b1f8a88cbabb1a6f6a499f82c062a4251892d9ad5d609863fb7"}, + {file = "numpy-2.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f87fec1f9bc1efd23f4227becff04bd0e979e23ca50cc92ec88b38489db3b55"}, + {file = "numpy-2.0.1-cp311-cp311-win32.whl", hash = "sha256:36d3a9405fd7c511804dc56fc32974fa5533bdeb3cd1604d6b8ff1d292b819c4"}, + {file = "numpy-2.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:08458fbf403bff5e2b45f08eda195d4b0c9b35682311da5a5a0a0925b11b9bd8"}, + {file = "numpy-2.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6bf4e6f4a2a2e26655717a1983ef6324f2664d7011f6ef7482e8c0b3d51e82ac"}, + {file = "numpy-2.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6fddc5fe258d3328cd8e3d7d3e02234c5d70e01ebe377a6ab92adb14039cb4"}, + {file = "numpy-2.0.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:5daab361be6ddeb299a918a7c0864fa8618af66019138263247af405018b04e1"}, + {file = "numpy-2.0.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:ea2326a4dca88e4a274ba3a4405eb6c6467d3ffbd8c7d38632502eaae3820587"}, + {file = "numpy-2.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:529af13c5f4b7a932fb0e1911d3a75da204eff023ee5e0e79c1751564221a5c8"}, + {file = "numpy-2.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6790654cb13eab303d8402354fabd47472b24635700f631f041bd0b65e37298a"}, + {file = "numpy-2.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cbab9fc9c391700e3e1287666dfd82d8666d10e69a6c4a09ab97574c0b7ee0a7"}, + {file = "numpy-2.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:99d0d92a5e3613c33a5f01db206a33f8fdf3d71f2912b0de1739894668b7a93b"}, + {file = "numpy-2.0.1-cp312-cp312-win32.whl", hash = "sha256:173a00b9995f73b79eb0191129f2455f1e34c203f559dd118636858cc452a1bf"}, + {file = "numpy-2.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:bb2124fdc6e62baae159ebcfa368708867eb56806804d005860b6007388df171"}, + {file = "numpy-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bfc085b28d62ff4009364e7ca34b80a9a080cbd97c2c0630bb5f7f770dae9414"}, + {file = "numpy-2.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8fae4ebbf95a179c1156fab0b142b74e4ba4204c87bde8d3d8b6f9c34c5825ef"}, + {file = "numpy-2.0.1-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:72dc22e9ec8f6eaa206deb1b1355eb2e253899d7347f5e2fae5f0af613741d06"}, + {file = "numpy-2.0.1-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:ec87f5f8aca726117a1c9b7083e7656a9d0d606eec7299cc067bb83d26f16e0c"}, + {file = "numpy-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f682ea61a88479d9498bf2091fdcd722b090724b08b31d63e022adc063bad59"}, + {file = "numpy-2.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8efc84f01c1cd7e34b3fb310183e72fcdf55293ee736d679b6d35b35d80bba26"}, + {file = "numpy-2.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3fdabe3e2a52bc4eff8dc7a5044342f8bd9f11ef0934fcd3289a788c0eb10018"}, + {file = "numpy-2.0.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:24a0e1befbfa14615b49ba9659d3d8818a0f4d8a1c5822af8696706fbda7310c"}, + {file = "numpy-2.0.1-cp39-cp39-win32.whl", hash = "sha256:f9cf5ea551aec449206954b075db819f52adc1638d46a6738253a712d553c7b4"}, + {file = "numpy-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:e9e81fa9017eaa416c056e5d9e71be93d05e2c3c2ab308d23307a8bc4443c368"}, + {file = "numpy-2.0.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:61728fba1e464f789b11deb78a57805c70b2ed02343560456190d0501ba37b0f"}, + {file = "numpy-2.0.1-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:12f5d865d60fb9734e60a60f1d5afa6d962d8d4467c120a1c0cda6eb2964437d"}, + {file = "numpy-2.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eacf3291e263d5a67d8c1a581a8ebbcfd6447204ef58828caf69a5e3e8c75990"}, + {file = "numpy-2.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2c3a346ae20cfd80b6cfd3e60dc179963ef2ea58da5ec074fd3d9e7a1e7ba97f"}, + {file = "numpy-2.0.1.tar.gz", hash = "sha256:485b87235796410c3519a699cfe1faab097e509e90ebb05dcd098db2ae87e7b3"}, ] [[package]] @@ -2180,13 +2157,13 @@ files = [ [[package]] name = "pyright" -version = "1.1.372" +version = "1.1.373" description = "Command line wrapper for pyright" optional = false python-versions = ">=3.7" files = [ - {file = "pyright-1.1.372-py3-none-any.whl", hash = "sha256:25b15fb8967740f0949fd35b963777187f0a0404c0bd753cc966ec139f3eaa0b"}, - {file = "pyright-1.1.372.tar.gz", hash = "sha256:a9f5e0daa955daaa17e3d1ef76d3623e75f8afd5e37b437d3ff84d5b38c15420"}, + {file = "pyright-1.1.373-py3-none-any.whl", hash = "sha256:b805413227f2c209f27b14b55da27fe5e9fb84129c9f1eb27708a5d12f6f000e"}, + {file = "pyright-1.1.373.tar.gz", hash = "sha256:f41bcfc8b9d1802b09921a394d6ae1ce19694957b628bc657629688daf8a83ff"}, ] [package.dependencies] @@ -2433,90 +2410,90 @@ sentence-transformers = ["sentence-transformers (>=2.2.2)"] [[package]] name = "regex" -version = "2024.5.15" +version = "2024.7.24" description = "Alternative regular expression module, to replace re." optional = false python-versions = ">=3.8" files = [ - {file = "regex-2024.5.15-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a81e3cfbae20378d75185171587cbf756015ccb14840702944f014e0d93ea09f"}, - {file = "regex-2024.5.15-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7b59138b219ffa8979013be7bc85bb60c6f7b7575df3d56dc1e403a438c7a3f6"}, - {file = "regex-2024.5.15-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a0bd000c6e266927cb7a1bc39d55be95c4b4f65c5be53e659537537e019232b1"}, - {file = "regex-2024.5.15-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5eaa7ddaf517aa095fa8da0b5015c44d03da83f5bd49c87961e3c997daed0de7"}, - {file = "regex-2024.5.15-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba68168daedb2c0bab7fd7e00ced5ba90aebf91024dea3c88ad5063c2a562cca"}, - {file = "regex-2024.5.15-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6e8d717bca3a6e2064fc3a08df5cbe366369f4b052dcd21b7416e6d71620dca1"}, - {file = "regex-2024.5.15-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1337b7dbef9b2f71121cdbf1e97e40de33ff114801263b275aafd75303bd62b5"}, - {file = "regex-2024.5.15-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f9ebd0a36102fcad2f03696e8af4ae682793a5d30b46c647eaf280d6cfb32796"}, - {file = "regex-2024.5.15-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9efa1a32ad3a3ea112224897cdaeb6aa00381627f567179c0314f7b65d354c62"}, - {file = "regex-2024.5.15-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:1595f2d10dff3d805e054ebdc41c124753631b6a471b976963c7b28543cf13b0"}, - {file = "regex-2024.5.15-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b802512f3e1f480f41ab5f2cfc0e2f761f08a1f41092d6718868082fc0d27143"}, - {file = "regex-2024.5.15-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:a0981022dccabca811e8171f913de05720590c915b033b7e601f35ce4ea7019f"}, - {file = "regex-2024.5.15-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:19068a6a79cf99a19ccefa44610491e9ca02c2be3305c7760d3831d38a467a6f"}, - {file = "regex-2024.5.15-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1b5269484f6126eee5e687785e83c6b60aad7663dafe842b34691157e5083e53"}, - {file = "regex-2024.5.15-cp310-cp310-win32.whl", hash = "sha256:ada150c5adfa8fbcbf321c30c751dc67d2f12f15bd183ffe4ec7cde351d945b3"}, - {file = "regex-2024.5.15-cp310-cp310-win_amd64.whl", hash = "sha256:ac394ff680fc46b97487941f5e6ae49a9f30ea41c6c6804832063f14b2a5a145"}, - {file = "regex-2024.5.15-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f5b1dff3ad008dccf18e652283f5e5339d70bf8ba7c98bf848ac33db10f7bc7a"}, - {file = "regex-2024.5.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c6a2b494a76983df8e3d3feea9b9ffdd558b247e60b92f877f93a1ff43d26656"}, - {file = "regex-2024.5.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a32b96f15c8ab2e7d27655969a23895eb799de3665fa94349f3b2fbfd547236f"}, - {file = "regex-2024.5.15-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:10002e86e6068d9e1c91eae8295ef690f02f913c57db120b58fdd35a6bb1af35"}, - {file = "regex-2024.5.15-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ec54d5afa89c19c6dd8541a133be51ee1017a38b412b1321ccb8d6ddbeb4cf7d"}, - {file = "regex-2024.5.15-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:10e4ce0dca9ae7a66e6089bb29355d4432caed736acae36fef0fdd7879f0b0cb"}, - {file = "regex-2024.5.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e507ff1e74373c4d3038195fdd2af30d297b4f0950eeda6f515ae3d84a1770f"}, - {file = "regex-2024.5.15-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d1f059a4d795e646e1c37665b9d06062c62d0e8cc3c511fe01315973a6542e40"}, - {file = "regex-2024.5.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0721931ad5fe0dda45d07f9820b90b2148ccdd8e45bb9e9b42a146cb4f695649"}, - {file = "regex-2024.5.15-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:833616ddc75ad595dee848ad984d067f2f31be645d603e4d158bba656bbf516c"}, - {file = "regex-2024.5.15-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:287eb7f54fc81546346207c533ad3c2c51a8d61075127d7f6d79aaf96cdee890"}, - {file = "regex-2024.5.15-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:19dfb1c504781a136a80ecd1fff9f16dddf5bb43cec6871778c8a907a085bb3d"}, - {file = "regex-2024.5.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:119af6e56dce35e8dfb5222573b50c89e5508d94d55713c75126b753f834de68"}, - {file = "regex-2024.5.15-cp311-cp311-win32.whl", hash = "sha256:1c1c174d6ec38d6c8a7504087358ce9213d4332f6293a94fbf5249992ba54efa"}, - {file = "regex-2024.5.15-cp311-cp311-win_amd64.whl", hash = "sha256:9e717956dcfd656f5055cc70996ee2cc82ac5149517fc8e1b60261b907740201"}, - {file = "regex-2024.5.15-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:632b01153e5248c134007209b5c6348a544ce96c46005d8456de1d552455b014"}, - {file = "regex-2024.5.15-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e64198f6b856d48192bf921421fdd8ad8eb35e179086e99e99f711957ffedd6e"}, - {file = "regex-2024.5.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68811ab14087b2f6e0fc0c2bae9ad689ea3584cad6917fc57be6a48bbd012c49"}, - {file = "regex-2024.5.15-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8ec0c2fea1e886a19c3bee0cd19d862b3aa75dcdfb42ebe8ed30708df64687a"}, - {file = "regex-2024.5.15-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d0c0c0003c10f54a591d220997dd27d953cd9ccc1a7294b40a4be5312be8797b"}, - {file = "regex-2024.5.15-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2431b9e263af1953c55abbd3e2efca67ca80a3de8a0437cb58e2421f8184717a"}, - {file = "regex-2024.5.15-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a605586358893b483976cffc1723fb0f83e526e8f14c6e6614e75919d9862cf"}, - {file = "regex-2024.5.15-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:391d7f7f1e409d192dba8bcd42d3e4cf9e598f3979cdaed6ab11288da88cb9f2"}, - {file = "regex-2024.5.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9ff11639a8d98969c863d4617595eb5425fd12f7c5ef6621a4b74b71ed8726d5"}, - {file = "regex-2024.5.15-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4eee78a04e6c67e8391edd4dad3279828dd66ac4b79570ec998e2155d2e59fd5"}, - {file = "regex-2024.5.15-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8fe45aa3f4aa57faabbc9cb46a93363edd6197cbc43523daea044e9ff2fea83e"}, - {file = "regex-2024.5.15-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:d0a3d8d6acf0c78a1fff0e210d224b821081330b8524e3e2bc5a68ef6ab5803d"}, - {file = "regex-2024.5.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c486b4106066d502495b3025a0a7251bf37ea9540433940a23419461ab9f2a80"}, - {file = "regex-2024.5.15-cp312-cp312-win32.whl", hash = "sha256:c49e15eac7c149f3670b3e27f1f28a2c1ddeccd3a2812cba953e01be2ab9b5fe"}, - {file = "regex-2024.5.15-cp312-cp312-win_amd64.whl", hash = "sha256:673b5a6da4557b975c6c90198588181029c60793835ce02f497ea817ff647cb2"}, - {file = "regex-2024.5.15-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:87e2a9c29e672fc65523fb47a90d429b70ef72b901b4e4b1bd42387caf0d6835"}, - {file = "regex-2024.5.15-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c3bea0ba8b73b71b37ac833a7f3fd53825924165da6a924aec78c13032f20850"}, - {file = "regex-2024.5.15-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bfc4f82cabe54f1e7f206fd3d30fda143f84a63fe7d64a81558d6e5f2e5aaba9"}, - {file = "regex-2024.5.15-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5bb9425fe881d578aeca0b2b4b3d314ec88738706f66f219c194d67179337cb"}, - {file = "regex-2024.5.15-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:64c65783e96e563103d641760664125e91bd85d8e49566ee560ded4da0d3e704"}, - {file = "regex-2024.5.15-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cf2430df4148b08fb4324b848672514b1385ae3807651f3567871f130a728cc3"}, - {file = "regex-2024.5.15-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5397de3219a8b08ae9540c48f602996aa6b0b65d5a61683e233af8605c42b0f2"}, - {file = "regex-2024.5.15-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:455705d34b4154a80ead722f4f185b04c4237e8e8e33f265cd0798d0e44825fa"}, - {file = "regex-2024.5.15-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b2b6f1b3bb6f640c1a92be3bbfbcb18657b125b99ecf141fb3310b5282c7d4ed"}, - {file = "regex-2024.5.15-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:3ad070b823ca5890cab606c940522d05d3d22395d432f4aaaf9d5b1653e47ced"}, - {file = "regex-2024.5.15-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:5b5467acbfc153847d5adb21e21e29847bcb5870e65c94c9206d20eb4e99a384"}, - {file = "regex-2024.5.15-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:e6662686aeb633ad65be2a42b4cb00178b3fbf7b91878f9446075c404ada552f"}, - {file = "regex-2024.5.15-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:2b4c884767504c0e2401babe8b5b7aea9148680d2e157fa28f01529d1f7fcf67"}, - {file = "regex-2024.5.15-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:3cd7874d57f13bf70078f1ff02b8b0aa48d5b9ed25fc48547516c6aba36f5741"}, - {file = "regex-2024.5.15-cp38-cp38-win32.whl", hash = "sha256:e4682f5ba31f475d58884045c1a97a860a007d44938c4c0895f41d64481edbc9"}, - {file = "regex-2024.5.15-cp38-cp38-win_amd64.whl", hash = "sha256:d99ceffa25ac45d150e30bd9ed14ec6039f2aad0ffa6bb87a5936f5782fc1569"}, - {file = "regex-2024.5.15-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:13cdaf31bed30a1e1c2453ef6015aa0983e1366fad2667657dbcac7b02f67133"}, - {file = "regex-2024.5.15-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cac27dcaa821ca271855a32188aa61d12decb6fe45ffe3e722401fe61e323cd1"}, - {file = "regex-2024.5.15-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7dbe2467273b875ea2de38ded4eba86cbcbc9a1a6d0aa11dcf7bd2e67859c435"}, - {file = "regex-2024.5.15-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64f18a9a3513a99c4bef0e3efd4c4a5b11228b48aa80743be822b71e132ae4f5"}, - {file = "regex-2024.5.15-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d347a741ea871c2e278fde6c48f85136c96b8659b632fb57a7d1ce1872547600"}, - {file = "regex-2024.5.15-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1878b8301ed011704aea4c806a3cadbd76f84dece1ec09cc9e4dc934cfa5d4da"}, - {file = "regex-2024.5.15-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4babf07ad476aaf7830d77000874d7611704a7fcf68c9c2ad151f5d94ae4bfc4"}, - {file = "regex-2024.5.15-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:35cb514e137cb3488bce23352af3e12fb0dbedd1ee6e60da053c69fb1b29cc6c"}, - {file = "regex-2024.5.15-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cdd09d47c0b2efee9378679f8510ee6955d329424c659ab3c5e3a6edea696294"}, - {file = "regex-2024.5.15-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:72d7a99cd6b8f958e85fc6ca5b37c4303294954eac1376535b03c2a43eb72629"}, - {file = "regex-2024.5.15-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:a094801d379ab20c2135529948cb84d417a2169b9bdceda2a36f5f10977ebc16"}, - {file = "regex-2024.5.15-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:c0c18345010870e58238790a6779a1219b4d97bd2e77e1140e8ee5d14df071aa"}, - {file = "regex-2024.5.15-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:16093f563098448ff6b1fa68170e4acbef94e6b6a4e25e10eae8598bb1694b5d"}, - {file = "regex-2024.5.15-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e38a7d4e8f633a33b4c7350fbd8bad3b70bf81439ac67ac38916c4a86b465456"}, - {file = "regex-2024.5.15-cp39-cp39-win32.whl", hash = "sha256:71a455a3c584a88f654b64feccc1e25876066c4f5ef26cd6dd711308aa538694"}, - {file = "regex-2024.5.15-cp39-cp39-win_amd64.whl", hash = "sha256:cab12877a9bdafde5500206d1020a584355a97884dfd388af3699e9137bf7388"}, - {file = "regex-2024.5.15.tar.gz", hash = "sha256:d3ee02d9e5f482cc8309134a91eeaacbdd2261ba111b0fef3748eeb4913e6a2c"}, + {file = "regex-2024.7.24-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:228b0d3f567fafa0633aee87f08b9276c7062da9616931382993c03808bb68ce"}, + {file = "regex-2024.7.24-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3426de3b91d1bc73249042742f45c2148803c111d1175b283270177fdf669024"}, + {file = "regex-2024.7.24-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f273674b445bcb6e4409bf8d1be67bc4b58e8b46fd0d560055d515b8830063cd"}, + {file = "regex-2024.7.24-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23acc72f0f4e1a9e6e9843d6328177ae3074b4182167e34119ec7233dfeccf53"}, + {file = "regex-2024.7.24-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65fd3d2e228cae024c411c5ccdffae4c315271eee4a8b839291f84f796b34eca"}, + {file = "regex-2024.7.24-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c414cbda77dbf13c3bc88b073a1a9f375c7b0cb5e115e15d4b73ec3a2fbc6f59"}, + {file = "regex-2024.7.24-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf7a89eef64b5455835f5ed30254ec19bf41f7541cd94f266ab7cbd463f00c41"}, + {file = "regex-2024.7.24-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19c65b00d42804e3fbea9708f0937d157e53429a39b7c61253ff15670ff62cb5"}, + {file = "regex-2024.7.24-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7a5486ca56c8869070a966321d5ab416ff0f83f30e0e2da1ab48815c8d165d46"}, + {file = "regex-2024.7.24-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6f51f9556785e5a203713f5efd9c085b4a45aecd2a42573e2b5041881b588d1f"}, + {file = "regex-2024.7.24-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:a4997716674d36a82eab3e86f8fa77080a5d8d96a389a61ea1d0e3a94a582cf7"}, + {file = "regex-2024.7.24-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:c0abb5e4e8ce71a61d9446040c1e86d4e6d23f9097275c5bd49ed978755ff0fe"}, + {file = "regex-2024.7.24-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:18300a1d78cf1290fa583cd8b7cde26ecb73e9f5916690cf9d42de569c89b1ce"}, + {file = "regex-2024.7.24-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:416c0e4f56308f34cdb18c3f59849479dde5b19febdcd6e6fa4d04b6c31c9faa"}, + {file = "regex-2024.7.24-cp310-cp310-win32.whl", hash = "sha256:fb168b5924bef397b5ba13aabd8cf5df7d3d93f10218d7b925e360d436863f66"}, + {file = "regex-2024.7.24-cp310-cp310-win_amd64.whl", hash = "sha256:6b9fc7e9cc983e75e2518496ba1afc524227c163e43d706688a6bb9eca41617e"}, + {file = "regex-2024.7.24-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:382281306e3adaaa7b8b9ebbb3ffb43358a7bbf585fa93821300a418bb975281"}, + {file = "regex-2024.7.24-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4fdd1384619f406ad9037fe6b6eaa3de2749e2e12084abc80169e8e075377d3b"}, + {file = "regex-2024.7.24-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3d974d24edb231446f708c455fd08f94c41c1ff4f04bcf06e5f36df5ef50b95a"}, + {file = "regex-2024.7.24-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a2ec4419a3fe6cf8a4795752596dfe0adb4aea40d3683a132bae9c30b81e8d73"}, + {file = "regex-2024.7.24-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb563dd3aea54c797adf513eeec819c4213d7dbfc311874eb4fd28d10f2ff0f2"}, + {file = "regex-2024.7.24-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:45104baae8b9f67569f0f1dca5e1f1ed77a54ae1cd8b0b07aba89272710db61e"}, + {file = "regex-2024.7.24-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:994448ee01864501912abf2bad9203bffc34158e80fe8bfb5b031f4f8e16da51"}, + {file = "regex-2024.7.24-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3fac296f99283ac232d8125be932c5cd7644084a30748fda013028c815ba3364"}, + {file = "regex-2024.7.24-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7e37e809b9303ec3a179085415cb5f418ecf65ec98cdfe34f6a078b46ef823ee"}, + {file = "regex-2024.7.24-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:01b689e887f612610c869421241e075c02f2e3d1ae93a037cb14f88ab6a8934c"}, + {file = "regex-2024.7.24-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f6442f0f0ff81775eaa5b05af8a0ffa1dda36e9cf6ec1e0d3d245e8564b684ce"}, + {file = "regex-2024.7.24-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:871e3ab2838fbcb4e0865a6e01233975df3a15e6fce93b6f99d75cacbd9862d1"}, + {file = "regex-2024.7.24-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c918b7a1e26b4ab40409820ddccc5d49871a82329640f5005f73572d5eaa9b5e"}, + {file = "regex-2024.7.24-cp311-cp311-win32.whl", hash = "sha256:2dfbb8baf8ba2c2b9aa2807f44ed272f0913eeeba002478c4577b8d29cde215c"}, + {file = "regex-2024.7.24-cp311-cp311-win_amd64.whl", hash = "sha256:538d30cd96ed7d1416d3956f94d54e426a8daf7c14527f6e0d6d425fcb4cca52"}, + {file = "regex-2024.7.24-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:fe4ebef608553aff8deb845c7f4f1d0740ff76fa672c011cc0bacb2a00fbde86"}, + {file = "regex-2024.7.24-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:74007a5b25b7a678459f06559504f1eec2f0f17bca218c9d56f6a0a12bfffdad"}, + {file = "regex-2024.7.24-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7df9ea48641da022c2a3c9c641650cd09f0cd15e8908bf931ad538f5ca7919c9"}, + {file = "regex-2024.7.24-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a1141a1dcc32904c47f6846b040275c6e5de0bf73f17d7a409035d55b76f289"}, + {file = "regex-2024.7.24-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80c811cfcb5c331237d9bad3bea2c391114588cf4131707e84d9493064d267f9"}, + {file = "regex-2024.7.24-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7214477bf9bd195894cf24005b1e7b496f46833337b5dedb7b2a6e33f66d962c"}, + {file = "regex-2024.7.24-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d55588cba7553f0b6ec33130bc3e114b355570b45785cebdc9daed8c637dd440"}, + {file = "regex-2024.7.24-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:558a57cfc32adcf19d3f791f62b5ff564922942e389e3cfdb538a23d65a6b610"}, + {file = "regex-2024.7.24-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a512eed9dfd4117110b1881ba9a59b31433caed0c4101b361f768e7bcbaf93c5"}, + {file = "regex-2024.7.24-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:86b17ba823ea76256b1885652e3a141a99a5c4422f4a869189db328321b73799"}, + {file = "regex-2024.7.24-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5eefee9bfe23f6df09ffb6dfb23809f4d74a78acef004aa904dc7c88b9944b05"}, + {file = "regex-2024.7.24-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:731fcd76bbdbf225e2eb85b7c38da9633ad3073822f5ab32379381e8c3c12e94"}, + {file = "regex-2024.7.24-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:eaef80eac3b4cfbdd6de53c6e108b4c534c21ae055d1dbea2de6b3b8ff3def38"}, + {file = "regex-2024.7.24-cp312-cp312-win32.whl", hash = "sha256:185e029368d6f89f36e526764cf12bf8d6f0e3a2a7737da625a76f594bdfcbfc"}, + {file = "regex-2024.7.24-cp312-cp312-win_amd64.whl", hash = "sha256:2f1baff13cc2521bea83ab2528e7a80cbe0ebb2c6f0bfad15be7da3aed443908"}, + {file = "regex-2024.7.24-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:66b4c0731a5c81921e938dcf1a88e978264e26e6ac4ec96a4d21ae0354581ae0"}, + {file = "regex-2024.7.24-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:88ecc3afd7e776967fa16c80f974cb79399ee8dc6c96423321d6f7d4b881c92b"}, + {file = "regex-2024.7.24-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:64bd50cf16bcc54b274e20235bf8edbb64184a30e1e53873ff8d444e7ac656b2"}, + {file = "regex-2024.7.24-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb462f0e346fcf41a901a126b50f8781e9a474d3927930f3490f38a6e73b6950"}, + {file = "regex-2024.7.24-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a82465ebbc9b1c5c50738536fdfa7cab639a261a99b469c9d4c7dcbb2b3f1e57"}, + {file = "regex-2024.7.24-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:68a8f8c046c6466ac61a36b65bb2395c74451df2ffb8458492ef49900efed293"}, + {file = "regex-2024.7.24-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac8e84fff5d27420f3c1e879ce9929108e873667ec87e0c8eeb413a5311adfe"}, + {file = "regex-2024.7.24-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba2537ef2163db9e6ccdbeb6f6424282ae4dea43177402152c67ef869cf3978b"}, + {file = "regex-2024.7.24-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:43affe33137fcd679bdae93fb25924979517e011f9dea99163f80b82eadc7e53"}, + {file = "regex-2024.7.24-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:c9bb87fdf2ab2370f21e4d5636e5317775e5d51ff32ebff2cf389f71b9b13750"}, + {file = "regex-2024.7.24-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:945352286a541406f99b2655c973852da7911b3f4264e010218bbc1cc73168f2"}, + {file = "regex-2024.7.24-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:8bc593dcce679206b60a538c302d03c29b18e3d862609317cb560e18b66d10cf"}, + {file = "regex-2024.7.24-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:3f3b6ca8eae6d6c75a6cff525c8530c60e909a71a15e1b731723233331de4169"}, + {file = "regex-2024.7.24-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c51edc3541e11fbe83f0c4d9412ef6c79f664a3745fab261457e84465ec9d5a8"}, + {file = "regex-2024.7.24-cp38-cp38-win32.whl", hash = "sha256:d0a07763776188b4db4c9c7fb1b8c494049f84659bb387b71c73bbc07f189e96"}, + {file = "regex-2024.7.24-cp38-cp38-win_amd64.whl", hash = "sha256:8fd5afd101dcf86a270d254364e0e8dddedebe6bd1ab9d5f732f274fa00499a5"}, + {file = "regex-2024.7.24-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0ffe3f9d430cd37d8fa5632ff6fb36d5b24818c5c986893063b4e5bdb84cdf24"}, + {file = "regex-2024.7.24-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:25419b70ba00a16abc90ee5fce061228206173231f004437730b67ac77323f0d"}, + {file = "regex-2024.7.24-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:33e2614a7ce627f0cdf2ad104797d1f68342d967de3695678c0cb84f530709f8"}, + {file = "regex-2024.7.24-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d33a0021893ede5969876052796165bab6006559ab845fd7b515a30abdd990dc"}, + {file = "regex-2024.7.24-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:04ce29e2c5fedf296b1a1b0acc1724ba93a36fb14031f3abfb7abda2806c1535"}, + {file = "regex-2024.7.24-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b16582783f44fbca6fcf46f61347340c787d7530d88b4d590a397a47583f31dd"}, + {file = "regex-2024.7.24-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:836d3cc225b3e8a943d0b02633fb2f28a66e281290302a79df0e1eaa984ff7c1"}, + {file = "regex-2024.7.24-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:438d9f0f4bc64e8dea78274caa5af971ceff0f8771e1a2333620969936ba10be"}, + {file = "regex-2024.7.24-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:973335b1624859cb0e52f96062a28aa18f3a5fc77a96e4a3d6d76e29811a0e6e"}, + {file = "regex-2024.7.24-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c5e69fd3eb0b409432b537fe3c6f44ac089c458ab6b78dcec14478422879ec5f"}, + {file = "regex-2024.7.24-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:fbf8c2f00904eaf63ff37718eb13acf8e178cb940520e47b2f05027f5bb34ce3"}, + {file = "regex-2024.7.24-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ae2757ace61bc4061b69af19e4689fa4416e1a04840f33b441034202b5cd02d4"}, + {file = "regex-2024.7.24-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:44fc61b99035fd9b3b9453f1713234e5a7c92a04f3577252b45feefe1b327759"}, + {file = "regex-2024.7.24-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:84c312cdf839e8b579f504afcd7b65f35d60b6285d892b19adea16355e8343c9"}, + {file = "regex-2024.7.24-cp39-cp39-win32.whl", hash = "sha256:ca5b2028c2f7af4e13fb9fc29b28d0ce767c38c7facdf64f6c2cd040413055f1"}, + {file = "regex-2024.7.24-cp39-cp39-win_amd64.whl", hash = "sha256:7c479f5ae937ec9985ecaf42e2e10631551d909f203e31308c12d703922742f9"}, + {file = "regex-2024.7.24.tar.gz", hash = "sha256:9cfd009eed1a46b27c14039ad5bbc5e71b6367c5b2e6d5f5da0ea91600817506"}, ] [[package]] @@ -2731,19 +2708,6 @@ files = [ [package.extras] widechars = ["wcwidth"] -[[package]] -name = "tbb" -version = "2021.13.0" -description = "Intel® oneAPI Threading Building Blocks (oneTBB)" -optional = false -python-versions = "*" -files = [ - {file = "tbb-2021.13.0-py2.py3-none-manylinux1_i686.whl", hash = "sha256:a2567725329639519d46d92a2634cf61e76601dac2f777a05686fea546c4fe4f"}, - {file = "tbb-2021.13.0-py2.py3-none-manylinux1_x86_64.whl", hash = "sha256:aaf667e92849adb012b8874d6393282afc318aca4407fc62f912ee30a22da46a"}, - {file = "tbb-2021.13.0-py3-none-win32.whl", hash = "sha256:6669d26703e9943f6164c6407bd4a237a45007e79b8d3832fe6999576eaaa9ef"}, - {file = "tbb-2021.13.0-py3-none-win_amd64.whl", hash = "sha256:3528a53e4bbe64b07a6112b4c5a00ff3c61924ee46c9c68e004a1ac7ad1f09c3"}, -] - [[package]] name = "tenacity" version = "8.5.0" @@ -2918,35 +2882,34 @@ files = [ [[package]] name = "torch" -version = "2.3.1+cpu" +version = "2.4.0+cpu" description = "Tensors and Dynamic neural networks in Python with strong GPU acceleration" optional = false python-versions = ">=3.8.0" files = [ - {file = "torch-2.3.1+cpu-cp310-cp310-linux_x86_64.whl", hash = "sha256:d679e21d871982b9234444331a26350902cfd2d5ca44ce6f49896af8b3a3087d"}, - {file = "torch-2.3.1+cpu-cp310-cp310-win_amd64.whl", hash = "sha256:500bf790afc2fd374a15d06213242e517afccc50a46ea5955d321a9a68003335"}, - {file = "torch-2.3.1+cpu-cp311-cp311-linux_x86_64.whl", hash = "sha256:a272defe305dbd944aa28a91cc3db0f0149495b3ebec2e39723a7224fa05dc57"}, - {file = "torch-2.3.1+cpu-cp311-cp311-win_amd64.whl", hash = "sha256:d2965eb54d3c8818e2280a54bd53e8246a6bb34e4b10bd19c59f35b611dd9f05"}, - {file = "torch-2.3.1+cpu-cp312-cp312-linux_x86_64.whl", hash = "sha256:2141a6cb7021adf2f92a0fd372cfeac524ba460bd39ce3a641d30a561e41f69a"}, - {file = "torch-2.3.1+cpu-cp312-cp312-win_amd64.whl", hash = "sha256:6acdca2530462611095c44fd95af75ecd5b9646eac813452fe0adf31a9bc310a"}, - {file = "torch-2.3.1+cpu-cp38-cp38-linux_x86_64.whl", hash = "sha256:cab92d5101e6db686c5525e04d87cedbcf3a556073d71d07fbe7d1ce09630ffb"}, - {file = "torch-2.3.1+cpu-cp38-cp38-win_amd64.whl", hash = "sha256:dbc784569a367fd425158cf4ae82057dd3011185ba5fc68440432ba0562cb5b2"}, - {file = "torch-2.3.1+cpu-cp39-cp39-linux_x86_64.whl", hash = "sha256:a3cb8e61ba311cee1bb7463cbdcf3ebdfd071e2091e74c5785e3687eb02819f9"}, - {file = "torch-2.3.1+cpu-cp39-cp39-win_amd64.whl", hash = "sha256:df68668056e62c0332e03f43d9da5d4278b39df1ba58d30ec20d34242070955d"}, + {file = "torch-2.4.0+cpu-cp310-cp310-linux_x86_64.whl", hash = "sha256:0e59377b27823dda6d26528febb7ca06fc5b77816eaa58b4420cc8785e33d4ce"}, + {file = "torch-2.4.0+cpu-cp310-cp310-win_amd64.whl", hash = "sha256:53c3f75fa4ef0726e494ebef003b17d8a61c3c9fa4630b465610b462bf06c3de"}, + {file = "torch-2.4.0+cpu-cp311-cp311-linux_x86_64.whl", hash = "sha256:14a7a8b595347dddca594f9e448b93ce68ce4f871acbd32cf04bda7c03664c0c"}, + {file = "torch-2.4.0+cpu-cp311-cp311-win_amd64.whl", hash = "sha256:3b3cb9a6c17b5a4cea42bb37a243bfbad7659cef6d9b4ee29cb793bdf20f482c"}, + {file = "torch-2.4.0+cpu-cp312-cp312-linux_x86_64.whl", hash = "sha256:78dbf5f2789933a7ea2dabeead4daa44679b1e0d8eb35ddb7071c8ab7b181eb3"}, + {file = "torch-2.4.0+cpu-cp312-cp312-win_amd64.whl", hash = "sha256:f59c53a1c3247efb3700f9f78bdd289712177037a85b5519b9ecdef7c77c1fee"}, + {file = "torch-2.4.0+cpu-cp38-cp38-linux_x86_64.whl", hash = "sha256:08753c3d776ae49dc9ddbae02e26720a513a4dc7997e41d95392bca71623a0cd"}, + {file = "torch-2.4.0+cpu-cp38-cp38-win_amd64.whl", hash = "sha256:9f376f5a14eb04a44974c3a9dfd857a68090acb435b98e62bbf523baeefac85e"}, + {file = "torch-2.4.0+cpu-cp39-cp39-linux_x86_64.whl", hash = "sha256:040abaee8affa1bb0f3ca14ca693ba81d0d90d88df5b8a839af96933a7fa2d29"}, + {file = "torch-2.4.0+cpu-cp39-cp39-win_amd64.whl", hash = "sha256:441fbf517c46fee6782a4289ffe49f701d0a52e3533ab5397ce395da165d921d"}, ] [package.dependencies] filelock = "*" fsspec = "*" jinja2 = "*" -mkl = {version = ">=2021.1.1,<=2021.4.0", markers = "platform_system == \"Windows\""} networkx = "*" sympy = "*" typing-extensions = ">=4.8.0" [package.extras] opt-einsum = ["opt-einsum (>=3.3)"] -optree = ["optree (>=0.9.1)"] +optree = ["optree (>=0.11.0)"] [package.source] type = "legacy" @@ -2975,19 +2938,19 @@ telegram = ["requests"] [[package]] name = "transformers" -version = "4.42.4" +version = "4.43.2" description = "State-of-the-art Machine Learning for JAX, PyTorch and TensorFlow" optional = false python-versions = ">=3.8.0" files = [ - {file = "transformers-4.42.4-py3-none-any.whl", hash = "sha256:6d59061392d0f1da312af29c962df9017ff3c0108c681a56d1bc981004d16d24"}, - {file = "transformers-4.42.4.tar.gz", hash = "sha256:f956e25e24df851f650cb2c158b6f4352dfae9d702f04c113ed24fc36ce7ae2d"}, + {file = "transformers-4.43.2-py3-none-any.whl", hash = "sha256:283c8b47cf38640c5c0caea60be0dfa948669fa48e9739b03717cbf5e8b20f11"}, + {file = "transformers-4.43.2.tar.gz", hash = "sha256:99dbbdeef9d451cdbc1c5316dce3af3dd5bb56b6cda5d0c564253e8fa7ccaf02"}, ] [package.dependencies] filelock = "*" huggingface-hub = ">=0.23.2,<1.0" -numpy = ">=1.17,<2.0" +numpy = ">=1.17" packaging = ">=20.0" pyyaml = ">=5.1" regex = "!=2019.12.17" @@ -2999,14 +2962,14 @@ tqdm = ">=4.27" [package.extras] accelerate = ["accelerate (>=0.21.0)"] agents = ["Pillow (>=10.0.1,<=15.0)", "accelerate (>=0.21.0)", "datasets (!=2.5.0)", "diffusers", "opencv-python", "sentencepiece (>=0.1.91,!=0.1.92)", "torch"] -all = ["Pillow (>=10.0.1,<=15.0)", "accelerate (>=0.21.0)", "av (==9.2.0)", "codecarbon (==1.2.0)", "decord (==0.6.0)", "flax (>=0.4.1,<=0.7.0)", "jax (>=0.4.1,<=0.4.13)", "jaxlib (>=0.4.1,<=0.4.13)", "kenlm", "keras-nlp (>=0.3.1)", "librosa", "onnxconverter-common", "optax (>=0.0.8,<=0.1.4)", "optuna", "phonemizer", "protobuf", "pyctcdecode (>=0.4.0)", "ray[tune] (>=2.7.0)", "scipy (<1.13.0)", "sentencepiece (>=0.1.91,!=0.1.92)", "sigopt", "tensorflow (>2.9,<2.16)", "tensorflow-text (<2.16)", "tf2onnx", "timm (<=0.9.16)", "tokenizers (>=0.19,<0.20)", "torch", "torchaudio", "torchvision"] +all = ["Pillow (>=10.0.1,<=15.0)", "accelerate (>=0.21.0)", "av (==9.2.0)", "codecarbon (==1.2.0)", "decord (==0.6.0)", "flax (>=0.4.1,<=0.7.0)", "jax (>=0.4.1,<=0.4.13)", "jaxlib (>=0.4.1,<=0.4.13)", "kenlm", "keras-nlp (>=0.3.1,<0.14.0)", "librosa", "onnxconverter-common", "optax (>=0.0.8,<=0.1.4)", "optuna", "phonemizer", "protobuf", "pyctcdecode (>=0.4.0)", "ray[tune] (>=2.7.0)", "scipy (<1.13.0)", "sentencepiece (>=0.1.91,!=0.1.92)", "sigopt", "tensorflow (>2.9,<2.16)", "tensorflow-text (<2.16)", "tf2onnx", "timm (<=0.9.16)", "tokenizers (>=0.19,<0.20)", "torch", "torchaudio", "torchvision"] audio = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)"] benchmark = ["optimum-benchmark (>=0.2.0)"] codecarbon = ["codecarbon (==1.2.0)"] deepspeed = ["accelerate (>=0.21.0)", "deepspeed (>=0.9.3)"] deepspeed-testing = ["GitPython (<3.1.19)", "accelerate (>=0.21.0)", "beautifulsoup4", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "deepspeed (>=0.9.3)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "nltk", "optuna", "parameterized", "protobuf", "psutil", "pydantic", "pytest (>=7.2.0,<8.0.0)", "pytest-rich", "pytest-timeout", "pytest-xdist", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (==0.4.4)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "sentencepiece (>=0.1.91,!=0.1.92)", "tensorboard", "timeout-decorator"] -dev = ["GitPython (<3.1.19)", "Pillow (>=10.0.1,<=15.0)", "accelerate (>=0.21.0)", "av (==9.2.0)", "beautifulsoup4", "codecarbon (==1.2.0)", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "decord (==0.6.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "flax (>=0.4.1,<=0.7.0)", "fugashi (>=1.0)", "ipadic (>=1.0.0,<2.0)", "isort (>=5.5.4)", "jax (>=0.4.1,<=0.4.13)", "jaxlib (>=0.4.1,<=0.4.13)", "kenlm", "keras-nlp (>=0.3.1)", "librosa", "nltk", "onnxconverter-common", "optax (>=0.0.8,<=0.1.4)", "optuna", "parameterized", "phonemizer", "protobuf", "psutil", "pyctcdecode (>=0.4.0)", "pydantic", "pytest (>=7.2.0,<8.0.0)", "pytest-rich", "pytest-timeout", "pytest-xdist", "ray[tune] (>=2.7.0)", "rhoknp (>=1.1.0,<1.3.1)", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (==0.4.4)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "scikit-learn", "scipy (<1.13.0)", "sentencepiece (>=0.1.91,!=0.1.92)", "sigopt", "sudachidict-core (>=20220729)", "sudachipy (>=0.6.6)", "tensorboard", "tensorflow (>2.9,<2.16)", "tensorflow-text (<2.16)", "tf2onnx", "timeout-decorator", "timm (<=0.9.16)", "tokenizers (>=0.19,<0.20)", "torch", "torchaudio", "torchvision", "unidic (>=1.0.2)", "unidic-lite (>=1.0.7)", "urllib3 (<2.0.0)"] -dev-tensorflow = ["GitPython (<3.1.19)", "Pillow (>=10.0.1,<=15.0)", "beautifulsoup4", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "isort (>=5.5.4)", "kenlm", "keras-nlp (>=0.3.1)", "librosa", "nltk", "onnxconverter-common", "onnxruntime (>=1.4.0)", "onnxruntime-tools (>=1.4.2)", "parameterized", "phonemizer", "protobuf", "psutil", "pyctcdecode (>=0.4.0)", "pydantic", "pytest (>=7.2.0,<8.0.0)", "pytest-rich", "pytest-timeout", "pytest-xdist", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (==0.4.4)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "scikit-learn", "sentencepiece (>=0.1.91,!=0.1.92)", "tensorboard", "tensorflow (>2.9,<2.16)", "tensorflow-text (<2.16)", "tf2onnx", "timeout-decorator", "tokenizers (>=0.19,<0.20)", "urllib3 (<2.0.0)"] +dev = ["GitPython (<3.1.19)", "Pillow (>=10.0.1,<=15.0)", "accelerate (>=0.21.0)", "av (==9.2.0)", "beautifulsoup4", "codecarbon (==1.2.0)", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "decord (==0.6.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "flax (>=0.4.1,<=0.7.0)", "fugashi (>=1.0)", "ipadic (>=1.0.0,<2.0)", "isort (>=5.5.4)", "jax (>=0.4.1,<=0.4.13)", "jaxlib (>=0.4.1,<=0.4.13)", "kenlm", "keras-nlp (>=0.3.1,<0.14.0)", "librosa", "nltk", "onnxconverter-common", "optax (>=0.0.8,<=0.1.4)", "optuna", "parameterized", "phonemizer", "protobuf", "psutil", "pyctcdecode (>=0.4.0)", "pydantic", "pytest (>=7.2.0,<8.0.0)", "pytest-rich", "pytest-timeout", "pytest-xdist", "ray[tune] (>=2.7.0)", "rhoknp (>=1.1.0,<1.3.1)", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (==0.4.4)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "scikit-learn", "scipy (<1.13.0)", "sentencepiece (>=0.1.91,!=0.1.92)", "sigopt", "sudachidict-core (>=20220729)", "sudachipy (>=0.6.6)", "tensorboard", "tensorflow (>2.9,<2.16)", "tensorflow-text (<2.16)", "tf2onnx", "timeout-decorator", "timm (<=0.9.16)", "tokenizers (>=0.19,<0.20)", "torch", "torchaudio", "torchvision", "unidic (>=1.0.2)", "unidic-lite (>=1.0.7)", "urllib3 (<2.0.0)"] +dev-tensorflow = ["GitPython (<3.1.19)", "Pillow (>=10.0.1,<=15.0)", "beautifulsoup4", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "isort (>=5.5.4)", "kenlm", "keras-nlp (>=0.3.1,<0.14.0)", "librosa", "nltk", "onnxconverter-common", "onnxruntime (>=1.4.0)", "onnxruntime-tools (>=1.4.2)", "parameterized", "phonemizer", "protobuf", "psutil", "pyctcdecode (>=0.4.0)", "pydantic", "pytest (>=7.2.0,<8.0.0)", "pytest-rich", "pytest-timeout", "pytest-xdist", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (==0.4.4)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "scikit-learn", "sentencepiece (>=0.1.91,!=0.1.92)", "tensorboard", "tensorflow (>2.9,<2.16)", "tensorflow-text (<2.16)", "tf2onnx", "timeout-decorator", "tokenizers (>=0.19,<0.20)", "urllib3 (<2.0.0)"] dev-torch = ["GitPython (<3.1.19)", "Pillow (>=10.0.1,<=15.0)", "accelerate (>=0.21.0)", "beautifulsoup4", "codecarbon (==1.2.0)", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "fugashi (>=1.0)", "ipadic (>=1.0.0,<2.0)", "isort (>=5.5.4)", "kenlm", "librosa", "nltk", "onnxruntime (>=1.4.0)", "onnxruntime-tools (>=1.4.2)", "optuna", "parameterized", "phonemizer", "protobuf", "psutil", "pyctcdecode (>=0.4.0)", "pydantic", "pytest (>=7.2.0,<8.0.0)", "pytest-rich", "pytest-timeout", "pytest-xdist", "ray[tune] (>=2.7.0)", "rhoknp (>=1.1.0,<1.3.1)", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (==0.4.4)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "scikit-learn", "sentencepiece (>=0.1.91,!=0.1.92)", "sigopt", "sudachidict-core (>=20220729)", "sudachipy (>=0.6.6)", "tensorboard", "timeout-decorator", "timm (<=0.9.16)", "tokenizers (>=0.19,<0.20)", "torch", "torchaudio", "torchvision", "unidic (>=1.0.2)", "unidic-lite (>=1.0.7)", "urllib3 (<2.0.0)"] flax = ["flax (>=0.4.1,<=0.7.0)", "jax (>=0.4.1,<=0.4.13)", "jaxlib (>=0.4.1,<=0.4.13)", "optax (>=0.0.8,<=0.1.4)", "scipy (<1.13.0)"] flax-speech = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)"] @@ -3029,15 +2992,15 @@ sigopt = ["sigopt"] sklearn = ["scikit-learn"] speech = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)", "torchaudio"] testing = ["GitPython (<3.1.19)", "beautifulsoup4", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "nltk", "parameterized", "psutil", "pydantic", "pytest (>=7.2.0,<8.0.0)", "pytest-rich", "pytest-timeout", "pytest-xdist", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (==0.4.4)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "sentencepiece (>=0.1.91,!=0.1.92)", "tensorboard", "timeout-decorator"] -tf = ["keras-nlp (>=0.3.1)", "onnxconverter-common", "tensorflow (>2.9,<2.16)", "tensorflow-text (<2.16)", "tf2onnx"] -tf-cpu = ["keras (>2.9,<2.16)", "keras-nlp (>=0.3.1)", "onnxconverter-common", "tensorflow-cpu (>2.9,<2.16)", "tensorflow-probability (<0.24)", "tensorflow-text (<2.16)", "tf2onnx"] +tf = ["keras-nlp (>=0.3.1,<0.14.0)", "onnxconverter-common", "tensorflow (>2.9,<2.16)", "tensorflow-text (<2.16)", "tf2onnx"] +tf-cpu = ["keras (>2.9,<2.16)", "keras-nlp (>=0.3.1,<0.14.0)", "onnxconverter-common", "tensorflow-cpu (>2.9,<2.16)", "tensorflow-probability (<0.24)", "tensorflow-text (<2.16)", "tf2onnx"] tf-speech = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)"] timm = ["timm (<=0.9.16)"] tokenizers = ["tokenizers (>=0.19,<0.20)"] torch = ["accelerate (>=0.21.0)", "torch"] torch-speech = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)", "torchaudio"] torch-vision = ["Pillow (>=10.0.1,<=15.0)", "torchvision"] -torchhub = ["filelock", "huggingface-hub (>=0.23.2,<1.0)", "importlib-metadata", "numpy (>=1.17,<2.0)", "packaging (>=20.0)", "protobuf", "regex (!=2019.12.17)", "requests", "sentencepiece (>=0.1.91,!=0.1.92)", "tokenizers (>=0.19,<0.20)", "torch", "tqdm (>=4.27)"] +torchhub = ["filelock", "huggingface-hub (>=0.23.2,<1.0)", "importlib-metadata", "numpy (>=1.17)", "packaging (>=20.0)", "protobuf", "regex (!=2019.12.17)", "requests", "sentencepiece (>=0.1.91,!=0.1.92)", "tokenizers (>=0.19,<0.20)", "torch", "tqdm (>=4.27)"] video = ["av (==9.2.0)", "decord (==0.6.0)"] vision = ["Pillow (>=10.0.1,<=15.0)"] @@ -3304,4 +3267,4 @@ test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", [metadata] lock-version = "2.0" python-versions = "~3.12" -content-hash = "d92ea9ce73ffb4aec8b716e045ddbd37a7dddb368877a80020aacabad4abd2d3" +content-hash = "549a12ff83021147e38240b6934b36e6d9b0dbf66c669dc4f2dea46d37ff81cf" diff --git a/pyproject.toml b/pyproject.toml index 0e7ed85..2a62f9a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,7 +31,7 @@ mkdocs = "1.6.0" mkdocs-material = "9.5.29" pre-commit = "3.7.1" pymdown-extensions = "10.8.1" -pyright = "^1.1.372" +pyright = "^1.1.373" pytest = "8.2.2" pytest-asyncio = "0.23.7" pytest-mock = "3.14.0" @@ -61,9 +61,7 @@ filterwarnings = [ "ignore:`resume_download` is deprecated and will be removed *", "ignore:datetime.datetime.utcnow", ] -markers = [ - "num_messages(num=10): create num messages.", -] +markers = ["num_messages(num=10): create num messages."] [tool.ruff] line-length = 100 diff --git a/tests/courageous_comets/test__sentiment.py b/tests/courageous_comets/test__sentiment.py index 6033086..cf3e82e 100644 --- a/tests/courageous_comets/test__sentiment.py +++ b/tests/courageous_comets/test__sentiment.py @@ -4,9 +4,7 @@ from courageous_comets.models import SentimentResult from courageous_comets.sentiment import ( - MAX_MESSAGE_LENGTH, calculate_sentiment, - logger, ) @@ -37,28 +35,3 @@ def test__calculate_sentiment_analyzes_sentiment_of_given_text( ) result = calculate_sentiment("I love this product!") assert result == expected - - -@pytest.mark.parametrize( - ("message", "expected"), - [ - ("a" * MAX_MESSAGE_LENGTH, False), - ("a" * (MAX_MESSAGE_LENGTH + 1), True), - ], -) -def test__calculate_sentiment_truncates_long_messages( - *, - mocker: MockerFixture, - message: str, - expected: bool, -) -> None: - """ - Test whether the sentiment calculation truncates long messages. - - Asserts - ------- - - The function truncates messages longer than 256 characters. - """ - logger_warning = mocker.spy(logger, "warning") - calculate_sentiment(message) - assert logger_warning.called == expected diff --git a/tests/integrations/conftest.py b/tests/integrations/conftest.py index d452dbe..4edf44a 100644 --- a/tests/integrations/conftest.py +++ b/tests/integrations/conftest.py @@ -59,7 +59,7 @@ async def test__something(messages: list[models.MessageAnalysis]): """ # Retrieve the number of messages to generate from the test function # If the mark is not present, or the number of messages is not specified, generate 10 messages - mark = request.node.get_closest_marker("num_messages") + mark: pytest.Mark | None = request.node.get_closest_marker("num_messages") num_messages = int(mark.args[0]) if mark and len(mark.args) > 0 else 10 # Message ID range From 748f0c79070b5998ad74244b10af6e146cbbb154 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Fri, 26 Jul 2024 20:15:03 +0200 Subject: [PATCH 101/168] docs: describe data model and add product pitch on homepage (#55) Co-authored-by: Uchechukwu Orji Co-authored-by: Thijs Franck <37906715+thijs-franck@users.noreply.github.com> --- .markdownlint.json | 1 + docs/README.md | 41 +++- docs/contributor-guide/architecture-design.md | 212 +++++++++++++++++- docs/contributor-guide/index.md | 1 + 4 files changed, 248 insertions(+), 7 deletions(-) diff --git a/.markdownlint.json b/.markdownlint.json index 6f661bd..86e4dcc 100644 --- a/.markdownlint.json +++ b/.markdownlint.json @@ -9,6 +9,7 @@ }, "MD033": { "allowed_elements": [ + "img", "source", "video" ] diff --git a/docs/README.md b/docs/README.md index ad949f2..31337ab 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,9 +1,34 @@ # Courageous Comets ☄️ -Thank you for your interest in our project! +Navigating a new Discord server can be overwhelming, but it doesn't have to be. Introducing **Courageous Comets**, +the Discord bot that helps you feel at home in any server! -This is the documentation for the Courageous Comets Discord bot. It was built as part of the Python Discord Summer -Code Jam 2024. +## Features + +Our bot is designed to help you: + +### Discover and Connect Effortlessly 🚀 + +**Channel and Message Search**: Easily find channels and messages that match your interests. Whether you're into +gaming, coding, or just chatting, Courageous Comets helps you quickly locate the content you care about. + +**Similar Message Finder**: Found a message you like? Use the bot to find related messages across different channels. +Stay informed and engaged without endless scrolling. + +### Foster Positive Communities 🤗 + +**Sentiment Analysis**: Identify the most welcoming and positive communities on Discord. Our bot uses advanced +sentiment analysis to highlight the friendliest spaces and people. + +**Community Member Insights**: Curious about someone’s activity and attitude? Ask the bot for a summary and get +a quick overview of their contributions and demeanor. + +### Moderate with Ease 🛡️ + +**Toxic Behavior Detection**: Moderators can use Courageous Comets to spot toxic behavior and spam to maintain +a positive environment. + +**Reward Positive Contributions**: Recognize and reward community members who make positive or relevant contributions. ## How to Install @@ -20,6 +45,16 @@ This documentation is divided into the following sections: - [Contributor Guide](./contributor-guide/index.md): How to set up a development environment and contribute to the project. +## Authors + +Thank you for your interest in our project! It was built as part of the [Python Discord Summer Code Jam 2024](https://www.pythondiscord.com/events/code-jams/11/). + +The following team members contributed to the project: + +[elfkuzco](https://github.com/elfkuzco) +[isaa-ctaylor](https://github.com/isaa-ctaylor) +[thijsfranck](https://github.com/thijsfranck) + ## License This project is published under the [MIT license](https://github.com/thijsfranck/courageous-comets/blob/main/LICENSE). diff --git a/docs/contributor-guide/architecture-design.md b/docs/contributor-guide/architecture-design.md index 0a9bfc8..59857b3 100644 --- a/docs/contributor-guide/architecture-design.md +++ b/docs/contributor-guide/architecture-design.md @@ -40,7 +40,7 @@ graph LR Vectorization -->|"Cache"| Redis ``` -## Bot +### Bot The bot is the main component of the Courageous Comets application. It is responsible for processing messages and performing analysis on them. @@ -48,12 +48,12 @@ and performing analysis on them. The bot is built using the [discord.py](https://discordpy.readthedocs.io/en/stable/) library. It is designed to be modular and extensible, with the core features implemented on separate layers. -### Controllers +#### Controllers Controllers are responsible for handling user input and invoking the appropriate application logic. Each controller is a separate cog in the bot, allowing for easy extension and maintenance. -### Application Logic +#### Application Logic The application logic is responsible for processing messages and performing analysis on them. The logic is divided into several components: @@ -63,7 +63,211 @@ into several components: - **Sentiment Analysis**: Analyzes the sentiment of the input text. - **Vectorization**: Generates a vector representation to support similarity search. -## Storage +### Storage The bot uses Redis as a caching layer to store the results of the analysis. Redis is a fast and efficient key-value store that supports the data structures needed to enable the application logic. + +## Data Model + +The data model is designed to support a variety of analysis tasks and provide a flexible foundation for future +extensions. + +### Message + +The main entity of the data model is the `Message` object, which represents a message sent by a user. Messages +are structured as follows: + +| Field | Type | Description | +| -------------------- | ------ | ---------------------------------------------------------------- | +| `message_id` | Tag | The unique identifier of the message. | +| `user_id` | Tag | The unique identifier of the user who sent the message. | +| `channel_id` | Tag | The unique identifier of the channel where the message was sent. | +| `guild_id` | Tag | The unique identifier of the guild where the message was sent. | +| `content` | String | The text content of the message after preprocessing. | +| `timestamp` | Number | The timestamp when the message was sent. | +| `sentiment_neg` | Number | The negative sentiment score of the message. | +| `sentiment_neu` | Number | The neutral sentiment score of the message. | +| `sentiment_pos` | Number | The positive sentiment score of the message. | +| `sentiment_compound` | Number | The compound sentiment score of the message. | +| `embedding` | Vector | The vector representation of the message. | + +Messages are keyed by a namespace prefix combined with the `guild_id` and the `message_id`. This combination ensures +that messages can always be uniquely identified within the context of a guild. + +The fields with type `Tag` can be efficiently used for keyword search and filtering. This way we can quickly retrieve, +filter and aggregate messages based on the context of an interaction, such as a user, channel, or guild. + +The `embedding` field is used to store the vector representation of the message. This field is used for +similarity search and other analysis tasks that require a numerical representation of the text. For the similarity +search, we use the [cosine similarity](https://en.wikipedia.org/wiki/Cosine_similarity) metric to compare the vectors. + +## Packages & Modules + +This section describes the packages included with the project and the underlying module structure. + +### `courageous_comets` + +The application is fully contained within the `courageous_comets` package. The package is structured as follows: + +| Module | Description | +| ------------------ | -----------------------------------------------------------------------------------------| +| `cogs` | Contains the bot controllers (cogs) that handle user input. | +| `nltk` | Contains helpers for using the Natural Language Toolkit (NLTK) library. | +| `redis` | Contains the data access layer for interacting with Redis. | +| `transformers` | Contains helpers for working with Huggingface Transformers. | +| `client.py` | Contains the main application client class. | +| `__init__.py` | Entrypoint for the package. Exports the application client instance. | +| `__main__.py` | Entrypoint for the application. Responsible for setup, teardown and root error handling. | +| `enums.py` | Shared enumerations used across the application. | +| `exceptions.py` | Includes the base exception clss and custom exceptions used in the application. | +| `models.py` | Defines the entities used by the application using Pydantic models. | +| `preprocessing.py` | Contains the preprocessing logic for cleaning and normalizing text. | +| `sentiment.py` | Implements the sentiment analysis logic using the NLTK library. | +| `settings.py` | Provides input validation, default values and type hints for the app settings. | +| `vectorizer.py` | Implements the vectorization logic using the Huggingface Transformers library. | +| `words.py` | Contains the word count logic for counting the number of words in a text. | + +### `tests` + +The `tests` package organizes the test suite for the application. The package is structured as follows: + +| Module | Description | +| ------------------- | ----------------------------------------------------------------------------------| +| `conftest.py` | Contains shared fixtures and ensures NLTK and Huggingface data is loaded in CI. | +| `courageous_comets` | Includes tests that validate the behavior of each application module. | +| `integrations` | Provides tests that validate how the app interacts with Discord and the database. | + +## CI/CD Pipeline + +The CI/CD (Continuous Integration and Continuous Deployment) pipeline automates the build, test, and deployment +processes of the application. This achieves the following goals: + +1. The process ensures that code changes are consistently tested, maintaining high code quality and reliability +2. It also enables full traceability and reproducibility of any +artifact released to production. + +This section provides a comprehensive overview of each stage in the pipeline, its purpose, and its components. + +### Overview + +The diagram below illustrates the stages of the CI/CD pipeline and the flow of code changes from development to +deployment. The pipeline consists of the [development flow](#development-flow) and the release flow. + +```mermaid +graph TB + subgraph Local["Development Environment"] + subgraph Developer["Developer"] + Codebase[("Codebase")] + Commit["Commit Changes"] + Release["Create Release"] + end + subgraph Precommit["Pre-commit"] + CodeQuality["Code Quality"] + CommitQuality["Commit Quality"] + end + subgraph Commitizen["Release"] + Tag["Create Tag"] + Changelog["Generate Changelog"] + end + end + + subgraph Remote["GitHub"] + subgraph CI["Continuous Integration"] + Unit["Unit Tests"] + Integration["Integration Tests"] + PeerReview["Peer Review"] + end + subgraph Repository["Repository"] + Main[("Main Branch")] + Version[("vX.Y.Z Tag")] + end + subgraph CD["Continuous Deployment"] + Docker["Build Docker Image"] + GHCR[("GitHub Container Registry")] + MkDocs["Build Documentation"] + Pages[("GitHub Pages")] + end + end + + Codebase --> Commit + Codebase --> Release + + Commit -->|"Commit"| CodeQuality + CodeQuality --> CommitQuality + CommitQuality -->|"Push"| Unit + Unit --> Integration + Integration --> PeerReview + PeerReview -->|"Merge"| Main + + Release -->|"Bump"| Tag + Tag --> Changelog + Changelog ----->|"Push"| Version + Version --> Docker + Docker --> GHCR + Version --> MkDocs + MkDocs --> Pages +``` + +### Development Flow + +In the development flow, developers are responsible for making changes to the codebase and committing these changes +to the repository. When changes are ready, developers commit these changes, triggering the pre-commit checks. + +#### Pre-commit + +The pre-commit checks involve several steps to ensure that the codebase and the repository stay in a maintainable +state. These steps are executed locally using the [`pre-commit`](https://pre-commit.com/) framework and block +the commit if any of the checks fail. + +##### Code Quality + +First, the code is formatted and linted using [`ruff`](https://docs.astral.sh/ruff/). Next, the code is type-checked +using [`pyright`](https://github.com/microsoft/pyright). Markdown is also treated as code for this project, so +it is linted using [`markdownlint`](https://github.com/DavidAnson/markdownlint). + +##### Commit Quality + +Since commit messages are a key input to the release process, they validated using [`commitizen`](https://commitizen-tools.github.io/commitizen/). +Commitizen enforces a consistent commit message format and ensures that the messages follow the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) +specification. + +#### Continuous Integration + +Once changes are pushed to the repository, the continuous integration process starts. In this stage, a series +of tests are run to ensure the new code works as expected and does not introduce regressions. + +Unit tests are in place to validate the behavior of individual components, while integration tests validate the +interaction between components. We use the [`pytest`](https://docs.pytest.org/) framework to run the tests. + +All [pre-commit](#pre-commit) checks are also run in the CI pipeline to avoid any discrepancies between the local +and remote environments. + +When all tests and checks pass, there is a final peer review before the changes are merged into the main branch. + +### Release Flow + +The release flow is triggered when a developer bumps the version of the application and creates a new release. + +#### Release + +The first part of the release flow involves determining the version number for the new release. This is done using +[`Commitizen`](https://commitizen-tools.github.io/commitizen/), which automatically increments the version based +on the commit messages. Version numbers follow the [Semantic Versioning](https://semver.org/) specification. + +Secondly, a changelog is generated using [`commitizen`](https://commitizen-tools.github.io/commitizen/). The changelog +provides a summary of the changes included in the release, making it easier for users to understand what has been +updated. + +The new changelog is committed to the repository, and the version tag is created. This tag is used to trigger the +continuous deployment process. + +#### Continuous Deployment + +The continuous deployment process involves building the Docker image for the application and pushing it to the +[GitHub Container Registry](https://github.com/features/packages) (GHCR). The Docker image is versioned +on the release tag. + +Additionally, the documentation is built using [MkDocs](https://www.mkdocs.org/) and publishedb to GitHub Pages. +The documentation is also tagged with the release version using [mike](https://github.com/jimporter/mike). This +way, users can access the documentation corresponding to the version of the app they are using. diff --git a/docs/contributor-guide/index.md b/docs/contributor-guide/index.md index 4900974..3ddecdf 100644 --- a/docs/contributor-guide/index.md +++ b/docs/contributor-guide/index.md @@ -5,6 +5,7 @@ set up a new development environment, along with guidelines on version control, ## Contents +- [Architecture & Design](./architecture-design.md): How the application is structured and key design decisions. - [Development Environment](./development-environment.md): How to set up your development environment. - [Version Control](./version-control.md): How to manage changes using version control. - [Documentation](./documentation.md): How to write good documentation. From 13ef04702c5054e4ffccef1b6194c9aac23e8cd5 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Fri, 26 Jul 2024 18:15:56 +0000 Subject: [PATCH 102/168] =?UTF-8?q?bump:=20version=200.6.0=20=E2=86=92=200?= =?UTF-8?q?.7.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/CHANGELOG.md | 20 ++++++++++++++++++++ pyproject.toml | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 95fe011..73860d6 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,3 +1,23 @@ +## v0.7.0 (2024-07-26) + +### Feat + +- add user sentiment interaction (#54) +- set up frequency cog (#53) +- add sentiment chart (#52) +- add cog that returns information about the app (#50) +- add drop_code_blocks processor +- get message rate by duration (#49) +- calculate count of tokens across messages (#48) +- allow redis search filtering across multiple ids (#47) +- add development mode (#46) + +### Fix + +- remove reference to link from general bot description and add it to the about message +- handle PackageNotFoundError +- add __version__ and log version on startup + ## v0.6.0 (2024-07-24) ### Feat diff --git a/pyproject.toml b/pyproject.toml index 2a62f9a..63e17c6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "courageous-comets" -version = "0.6.0" +version = "0.7.0" authors = ["Courageous Comets"] description = "" readme = "docs/README.md" From fb06d02378535ab07e268751a6503ac8195e4213 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Fri, 26 Jul 2024 19:02:48 +0000 Subject: [PATCH 103/168] docs: move installation section up --- docs/README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/README.md b/docs/README.md index 31337ab..45e86ad 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,11 +1,18 @@ # Courageous Comets ☄️ -Navigating a new Discord server can be overwhelming, but it doesn't have to be. Introducing **Courageous Comets**, +Navigating a new Discord server can be overwhelming, _but it doesn't have to be_. Introducing **Courageous Comets**, the Discord bot that helps you feel at home in any server! +## Installation + +Adding the bot to your Discord server is easy - just click the button below! + + +[Add to Discord :fontawesome-brands-discord:](https://discord.com/oauth2/authorize?client_id=1262672493978714174){ .md-button .md-button--primary } + ## Features -Our bot is designed to help you: +The Courageous Comets bot is designed to help you: ### Discover and Connect Effortlessly 🚀 @@ -30,13 +37,6 @@ a positive environment. **Reward Positive Contributions**: Recognize and reward community members who make positive or relevant contributions. -## How to Install - -Click the button below to install the bot in your Discord server: - - -[Add to Discord :fontawesome-brands-discord:](https://discord.com/oauth2/authorize?client_id=1262672493978714174){ .md-button .md-button--primary } - ## Contents This documentation is divided into the following sections: From e47a754def8d1ea098536a9862190e0f5c6feca1 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Fri, 26 Jul 2024 20:00:12 +0000 Subject: [PATCH 104/168] build: set matplotlib config directory in docker image --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index 22c3ad5..554acdb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,6 +12,7 @@ LABEL org.opencontainers.image.title="Courageous Comets" # Set default environment variables ENV BOT_CONFIG_PATH=/app/application.yaml ENV LOG_LEVEL=INFO +ENV MPLCONFIGDIR=/app/matplotlib ENV NLTK_DATA=/app/nltk_data ENV HF_HOME=/app/hf_data From 75ce9dc95230514a915af9d27b0fec3e49eb6e33 Mon Sep 17 00:00:00 2001 From: Uchechukwu Orji Date: Fri, 26 Jul 2024 21:23:51 +0100 Subject: [PATCH 105/168] feat: plot message frequency graph --- courageous_comets/cogs/frequency.py | 104 +++++++++++++++++++++++++++- courageous_comets/enums.py | 6 +- courageous_comets/models.py | 15 ++++ courageous_comets/redis/messages.py | 24 ++++--- 4 files changed, 134 insertions(+), 15 deletions(-) diff --git a/courageous_comets/cogs/frequency.py b/courageous_comets/cogs/frequency.py index f6172aa..0d2764f 100644 --- a/courageous_comets/cogs/frequency.py +++ b/courageous_comets/cogs/frequency.py @@ -1,8 +1,80 @@ +import io + import discord +import matplotlib.pyplot as plt from discord import app_commands from discord.ext import commands +from matplotlib.dates import ( + AutoDateLocator, + ConciseDateFormatter, + DayLocator, + HourLocator, + MinuteLocator, +) +from courageous_comets import models from courageous_comets.client import CourageousCometsBot +from courageous_comets.enums import Duration +from courageous_comets.redis.messages import get_messages_frequency + + +def plot_message_frequency( + frequencies: list[models.MessageFrequency], + duration: Duration, +) -> io.BytesIO: + """ + Plot the frequency of messages. + + Creates a line plot of number of messages over intervals and saves it to a file. + + Parameters + ---------- + frequencies: list[MessageFrequency] + A list of message frequency. + + Returns + ------- + io.BytesIO + A plot of the frequency in memory. + + Note + ---- + Assumes list of frequencies is not empty. + """ + _, ax = plt.subplots() + # If the there's only one point, use a bar plot, otherwise a line plot + if len(frequencies) > 1: + ax.plot( + [frequency.timestamp for frequency in frequencies], # type: ignore + [frequency.num_messages for frequency in frequencies], + ) + locator = AutoDateLocator().get_locator( + frequencies[0].timestamp, + frequencies[-1].timestamp, + ) + else: + ax.bar([frequencies[0].timestamp], [frequencies[0].num_messages], width=0.01) # type: ignore + if duration == Duration.daily: + locator = DayLocator() + elif duration == Duration.hourly: + locator = HourLocator() + elif duration == Duration.minute: + locator = MinuteLocator() + else: + locator = AutoDateLocator() + formatter = ConciseDateFormatter(locator) + + ax.xaxis.set_major_locator(locator) + ax.xaxis.set_major_formatter(formatter) + + ax.set_ylabel("Number of messages.") + ax.set_title("Message Frequency") + + file = io.BytesIO() + plt.savefig(file) + file.seek(0) + + return file class Frequency(commands.Cog): @@ -11,6 +83,12 @@ class Frequency(commands.Cog): def __init__(self, bot: CourageousCometsBot) -> None: self.bot = bot + for attribute in dir(self): + obj = getattr(self, attribute, None) + if obj and getattr(obj, "is_contextmenu", False): + menu = app_commands.ContextMenu(name=obj.name, callback=obj) + self.bot.tree.add_command(menu) + @app_commands.command( name="frequency", description="Get frequency of messages over a duration.", @@ -18,6 +96,7 @@ def __init__(self, bot: CourageousCometsBot) -> None: async def frequency_command( self, interaction: discord.Interaction, + duration: Duration, ) -> None: """ Allow users to view the frequency of messages. @@ -29,7 +108,7 @@ async def frequency_command( interaction: discord.Interaction The interaction that triggered the command. duration: courageous_comets.enums.Duration - The duration to calculate the number of messages. + The duration over which to aggregate the number of messages. """ if self.bot.redis is None: return await interaction.response.send_message( @@ -42,8 +121,29 @@ async def frequency_command( "This feature is only available in guilds.", ephemeral=True, ) + frequencies = await get_messages_frequency( + self.bot.redis, + guild_id=str(interaction.guild.id), + duration=duration, + ) + if not frequencies: + return await interaction.response.send_message( + "No messages were found over the specified duration at this time.", + ephemeral=True, + ) + view = discord.Embed( + title="Message frequencies", + description="Message frequencies", + color=discord.Colour.purple(), + timestamp=discord.utils.utcnow(), + ) + chart = plot_message_frequency(frequencies, duration) + chart_file = discord.File(chart, filename="frequency.png") + view.set_image(url="attachment://frequency.png") + return await interaction.response.send_message( - "This feature is currently unavailable. Please try again later.", + embed=view, + file=chart_file, ephemeral=True, ) diff --git a/courageous_comets/enums.py b/courageous_comets/enums.py index a1e70d2..567dfd4 100644 --- a/courageous_comets/enums.py +++ b/courageous_comets/enums.py @@ -12,6 +12,6 @@ class StatisticScope(StrEnum): class Duration(IntEnum): """Number of seconds in time durations.""" - MINUTE = 60 - HOUR = 60 * 60 - DAY = 60 * 60 * 24 + minute = 60 + hourly = 60 * 60 + daily = 60 * 60 * 24 diff --git a/courageous_comets/models.py b/courageous_comets/models.py index 458f086..e7b78fa 100644 --- a/courageous_comets/models.py +++ b/courageous_comets/models.py @@ -101,3 +101,18 @@ class MessageAnalysis(Message): sentiment: SentimentResult tokens: dict[str, int] embedding: bytes + + +class MessageFrequency(BaseModel): + """Number of messages sent over a duration. + + Attributes + ---------- + timestamp: UnixTimestamp + The timestamp when the messages were sent. + nb_messages: int + The number of messages sent at `timestamp` + """ + + timestamp: UnixTimestamp + num_messages: int diff --git a/courageous_comets/redis/messages.py b/courageous_comets/redis/messages.py index 5c2fef3..3aa9ad4 100644 --- a/courageous_comets/redis/messages.py +++ b/courageous_comets/redis/messages.py @@ -363,15 +363,15 @@ async def get_tokens_count( return counter -async def get_message_rate( # noqa: PLR0913 +async def get_messages_frequency( # noqa: PLR0913 redis: Redis, *, guild_id: str, ids: list[str] | None = None, scope: StatisticScope = StatisticScope.CHANNEL, limit: int = settings.QUERY_LIMIT, - duration: Duration = Duration.HOUR, -) -> list[dict[str, str]]: + duration: Duration = Duration.hourly, +) -> list[models.MessageFrequency]: """ Get the rate of messages over an interval. @@ -393,8 +393,8 @@ async def get_message_rate( # noqa: PLR0913 Returns ------- - list[dict[str, str]] - A list of dictionaries mapping each timestamp to its count of distinct messages. + list[models.MessageFrequency] + A list of message frequency at different timestamps. """ search_scope = build_search_scope(guild_id, ids, scope) index = _get_raw_index(redis) @@ -415,15 +415,17 @@ async def get_message_rate( # noqa: PLR0913 ) # Group results by interval using the new `timestamp` property .group_by(["@timestamp"], reducer) - # Sort results by the number of messages in each interval - .sort_by(aggregations.Asc("@num_messages")) # type: ignore + # Sort results by the timestamp + .sort_by(aggregations.Asc("@timestamp")) # type: ignore ) # Execute the aggregation query on the index results = await index.aggregate(query) # type: ignore - # Deserialize all rows as dictionaries. Each row is a flat list of key-value pairs. - return [dict(itertools.batched(row, 2)) for row in results.rows] + return [ + models.MessageFrequency.model_validate_strings(dict(itertools.batched(row, 2))) + for row in results.rows + ] async def get_average_sentiment( @@ -469,4 +471,6 @@ async def get_average_sentiment( results = await index.aggregate(query) # type: ignore # Deserialize all rows as dictionaries. Each row is a flat list of key-value pairs. - return [{key: float(value) for row in results.rows for key, value in itertools.batched(row, 2)}] + return [ + {key: float(value) for row in results.rows for key, value in itertools.batched(row, 2)}, + ] From fe18c2a16a0f7ff9d2e2e54de1ce35ec53a3281b Mon Sep 17 00:00:00 2001 From: isaa_ctaylor Date: Fri, 26 Jul 2024 21:30:24 +0100 Subject: [PATCH 106/168] fix: remove unused context menu loader --- courageous_comets/cogs/frequency.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/courageous_comets/cogs/frequency.py b/courageous_comets/cogs/frequency.py index 0d2764f..3c73c88 100644 --- a/courageous_comets/cogs/frequency.py +++ b/courageous_comets/cogs/frequency.py @@ -83,12 +83,6 @@ class Frequency(commands.Cog): def __init__(self, bot: CourageousCometsBot) -> None: self.bot = bot - for attribute in dir(self): - obj = getattr(self, attribute, None) - if obj and getattr(obj, "is_contextmenu", False): - menu = app_commands.ContextMenu(name=obj.name, callback=obj) - self.bot.tree.add_command(menu) - @app_commands.command( name="frequency", description="Get frequency of messages over a duration.", From 5e601a8dee4e6af77f6fa8367cf4f2eb20856dca Mon Sep 17 00:00:00 2001 From: isaa_ctaylor Date: Fri, 26 Jul 2024 22:40:05 +0100 Subject: [PATCH 107/168] Add user interactions (#57) Co-authored-by: thijsfranck Co-authored-by: Uchechukwu Orji Co-authored-by: Thijs Franck <37906715+thijs-franck@users.noreply.github.com> --- application.yaml | 1 + courageous_comets/charts.py | 60 ++++++++ courageous_comets/cogs/keywords.py | 223 ++++++++++++++++++++++++---- courageous_comets/cogs/sentiment.py | 200 +++++++------------------ courageous_comets/models.py | 5 + courageous_comets/redis/messages.py | 8 +- poetry.lock | 19 ++- pyproject.toml | 1 + 8 files changed, 336 insertions(+), 181 deletions(-) create mode 100644 courageous_comets/charts.py diff --git a/application.yaml b/application.yaml index 730c634..6bfa055 100644 --- a/application.yaml +++ b/application.yaml @@ -1,6 +1,7 @@ cogs: - courageous_comets.cogs.about - courageous_comets.cogs.keywords + - courageous_comets.cogs.interactions - courageous_comets.cogs.messages - courageous_comets.cogs.ping - courageous_comets.cogs.sentiment diff --git a/courageous_comets/charts.py b/courageous_comets/charts.py new file mode 100644 index 0000000..48a66d2 --- /dev/null +++ b/courageous_comets/charts.py @@ -0,0 +1,60 @@ +from pathlib import Path + +import matplotlib.pyplot as plt + +from courageous_comets import models + + +def plot_sentiment_analysis( + message_id: str | int, + analysis_result: models.SentimentResult, +) -> Path: + """ + Plot the sentiment analysis of a message. + + Creates a bar chart of the sentiment analysis of a message and saves it to a file. + If the file already exists, it will be returned instead of being recreated. + + Parameters + ---------- + message_id : str | int + The id of the message. + analysis_result : SentimentResult + The result of sentiment analysis on a message. + + Returns + ------- + Path + The path to the saved image. + """ + chart_dir = Path("artifacts/charts/sentiment").resolve() + chart_dir.mkdir(parents=True, exist_ok=True) + chart_path = chart_dir / f"{message_id}.png" + + if chart_path.exists(): + return chart_path + + _, ax = plt.subplots() + ax.bar( + [ + "Negative", + "Neutral", + "Positive", + ], + [ + analysis_result.neg, + analysis_result.neu, + analysis_result.pos, + ], + color=[ + "red", + "blue", + "green", + ], + ) + ax.set_ylabel("Sentiment Score") + ax.set_title("Sentiment Analysis") + + plt.savefig(chart_path) + + return chart_path diff --git a/courageous_comets/cogs/keywords.py b/courageous_comets/cogs/keywords.py index 6b4a2fb..5104e94 100644 --- a/courageous_comets/cogs/keywords.py +++ b/courageous_comets/cogs/keywords.py @@ -1,13 +1,139 @@ +import datetime + import discord +from async_lru import alru_cache from discord import app_commands from discord.ext import commands +from courageous_comets.charts import plot_sentiment_analysis from courageous_comets.client import CourageousCometsBot from courageous_comets.models import Message -from courageous_comets.utils import contextmenu +from courageous_comets.redis.keys import key_schema +from courageous_comets.redis.messages import ( + get_message_sentiment, + get_recent_messages, +) from courageous_comets.vectorizer import Vectorizer -# from courageous_comets.redis.messages import get_recent_messages # noqa: ERA001 +# from courageous_comets.utils import contextmenu # noqa: ERA001 + + +def shorten(string: str, *, limit: int = 50) -> str: + """Trim a string if necessary given `limit`.""" + if len(string) > limit: + string = string[: limit - 3] + "..." + return string + + +SENTIMENT: dict[range, str] = { + range(-100, -50): "very negative 😡", + range(-50, -10): "negative 🙁", + range(-10, 10): "neutral 🙂", + range(10, 50): "positive 😁", + range(50, 100): "very positive 😍", +} + + +SENTIMENT_DESCRIPTION_TEMPLATE = """ +Overall the sentiment of the message is **{sentiment}**. + +Here's a breakdown of the scores: + +- Negative: {neg} +- Neutral: {neu} +- Positive: {pos} + +The compound score is {compound}. +""" + + +class Dropdown(discord.ui.Select): + """A drop down menu.""" + + def __init__(self, messages: list[discord.Message | None], bot: CourageousCometsBot) -> None: + self.messages = messages + self.bot = bot + + options = [ + discord.SelectOption( + label=message.author.display_name, + description=shorten(message.content, limit=25), + value=str(message.id), + ) + for message in messages + if message + ] + + super().__init__( + placeholder="Get more information...", + min_values=1, + max_values=1, + options=options, + ) + + async def callback(self, interaction: discord.Interaction) -> None: + """Handle the callback when the dropdown is selected.""" + await interaction.response.defer(ephemeral=True) + message_id = self.values[0] + + message: discord.Message = discord.utils.get(self.messages, id=int(message_id)) # type: ignore + + key = key_schema.guild_messages( + guild_id=message.guild.id, # type: ignore + message_id=message.id, + ) + + analysis_result = await get_message_sentiment(key, redis=self.bot.redis) # type: ignore + + if analysis_result is None: + return await interaction.response.send_message( + "No sentiment analysis found for this message.", + ephemeral=True, + ) + + color = discord.Color.green() if analysis_result.compound >= 0 else discord.Color.red() + + sentiment = next( + ( + value + for key, value in SENTIMENT.items() + if int(analysis_result.compound * 100) in key + ), + "unknown", + ) + + template_vars = { + **analysis_result.model_dump(), + "sentiment": sentiment, + } + + embed = discord.Embed( + title="Message Sentiment", + description=SENTIMENT_DESCRIPTION_TEMPLATE.format(**template_vars), + color=color, + timestamp=discord.utils.utcnow(), + ) + + chart = plot_sentiment_analysis(message.id, analysis_result) + chart_file = discord.File(chart, filename="sentiment_analysis.png") + embed.set_image(url="attachment://sentiment_analysis.png") + + return await interaction.followup.send(embed=embed, ephemeral=True, file=chart_file) + + +class DropdownView(discord.ui.View): + """A view containing a dropdown.""" + + def __init__( + self, + messages: list[discord.Message | None], + bot: CourageousCometsBot, + *, + timeout: float | None = 180, + ) -> None: + super().__init__(timeout=timeout) + + self.add_item(Dropdown(messages, bot)) class MessagesNotFound(app_commands.AppCommandError): @@ -30,11 +156,12 @@ def __init__(self, bot: CourageousCometsBot) -> None: async def _get_recent_messages( self, - user: discord.Member, *, + # user: discord.Member, + guild_id: int, + channels: list[discord.TextChannel], limit: int = 10, - channels: list[discord.TextChannel] | None = None, - ) -> list[Message]: # type: ignore + ) -> list[Message]: """ Return `limit` most recent messages, only from `channels`, if provided. @@ -47,30 +174,59 @@ async def _get_recent_messages( channels: list[discord.TextChannel] The channels to query. """ + return await get_recent_messages( + self.bot.redis, # type: ignore + guild_id=str(guild_id), + ids=[str(channel.id) for channel in channels], + limit=limit, + ) - @contextmenu(name="Show recent messages") - async def _show_recent_messages_context_menu( - self, - interaction: discord.Interaction, - member: discord.Member, - ) -> None: - """Get the most recent messages from a user.""" - await self._show_recent_messages(interaction, member) + # @contextmenu(name="Show recent messages") + # async def _show_recent_messages_context_menu( + # self, + # interaction: discord.Interaction, + # member: discord.Member, + # ) -> None: + # """Get the most recent messages from a user.""" + # await self._show_recent_messages(interaction, member) # noqa: ERA001 @app_commands.command(name="recent") async def _show_recent_messages_command( self, interaction: discord.Interaction, - member: discord.Member, ) -> None: - """Get the most recent messages from a user.""" - await self._show_recent_messages(interaction, member) + """Get the most recent messages in the server.""" + await self._show_recent_messages(interaction) + # await self._show_recent_messages(interaction, member) # noqa: ERA001 + + @alru_cache() + async def _resolve_message(self, message: Message) -> discord.Message | None: + """ + Try and resolve a message from discord. + + Parameters + ---------- + message: courageous_comets.models.Message + Represents the message to resolve + + Returns + ------- + discord.Message | None + A `discord.Message` instance if we managed to fetch the message from discord, else None + """ + channel: discord.TextChannel = self.bot.get_channel(int(message.channel_id)) # type: ignore + if channel: + resolved_message = await channel.fetch_message(int(message.message_id)) + if resolved_message: + return resolved_message + return None async def _show_recent_messages( self, interaction: discord.Interaction, - member: discord.Member, + # member: discord.Member, ) -> None: + await interaction.response.defer() author = interaction.user visible_channels = [ @@ -79,23 +235,36 @@ async def _show_recent_messages( if channel.permissions_for(author).view_channel # type: ignore ] - messages = await self._get_recent_messages(member, channels=visible_channels) + messages = await self._get_recent_messages( + guild_id=interaction.guild.id, # type: ignore + channels=visible_channels, + ) - resolved_messages = [] + resolved_messages: list[discord.Message | None] = [] for message in messages: - channel: discord.TextChannel = self.bot.get_channel(int(message.channel_id)) # type: ignore - if channel: - resolved_message = await channel.fetch_message(int(message.message_id)) - if resolved_message: - resolved_messages.append(resolved_message) - continue - resolved_messages.append(None) + resolved_message = await self._resolve_message(message) + resolved_messages.append(resolved_message) if not [m for m in resolved_messages if m]: raise MessagesNotFound - # TODO(isaa-ctaylor): Display messages + await interaction.followup.send( + embed=discord.Embed( + title=f"Recent messages in {interaction.guild.name}", # type: ignore + description="\n\n".join( + f"{message.author.mention} {discord.utils.format_dt(message.created_at, style='R')}:\n{shorten(message.clean_content, limit=60)}" # noqa: E501 + if message and message.clean_content + else "[Message Deleted]" + if not message + else "[No Message Content]" + for message in resolved_messages + ), + colour=discord.Colour.blurple(), + timestamp=datetime.datetime.now(datetime.UTC), + ), + view=DropdownView(resolved_messages, self.bot), + ) async def setup(bot: CourageousCometsBot) -> None: diff --git a/courageous_comets/cogs/sentiment.py b/courageous_comets/cogs/sentiment.py index 874c881..f99a0bd 100644 --- a/courageous_comets/cogs/sentiment.py +++ b/courageous_comets/cogs/sentiment.py @@ -1,19 +1,18 @@ import logging -from pathlib import Path import discord -import matplotlib.pyplot as plt from discord import app_commands from discord.ext import commands -from courageous_comets import models +from courageous_comets.charts import plot_sentiment_analysis from courageous_comets.client import CourageousCometsBot from courageous_comets.enums import StatisticScope from courageous_comets.redis.keys import key_schema from courageous_comets.redis.messages import ( - get_average_sentiment, get_message_sentiment, + get_messages_by_sentiment_similarity, ) +from courageous_comets.sentiment import calculate_sentiment from courageous_comets.utils import contextmenu from courageous_comets.vectorizer import Vectorizer @@ -33,7 +32,7 @@ class MessagesNotFound(app_commands.AppCommandError): } -MESSAGE_SENTIMENT_TEMPLATE = """ +SENTIMENT_DESCRIPTION_TEMPLATE = """ Overall the sentiment of the message is **{sentiment}**. Here's a breakdown of the scores: @@ -45,67 +44,6 @@ class MessagesNotFound(app_commands.AppCommandError): The compound score is {compound}. """ -USER_SENTIMENT_TEMPLATE = """ -Overall the sentiment of {user} is **{sentiment}**. - -Their average compound score is {compound}. -""" - - -def plot_sentiment_analysis( - message_id: str | int, - analysis_result: models.SentimentResult, -) -> Path: - """ - Plot the sentiment analysis of a message. - - Creates a bar chart of the sentiment analysis of a message and saves it to a file. - If the file already exists, it will be returned instead of being recreated. - - Parameters - ---------- - message_id : str | int - The id of the message. - analysis_result : SentimentResult - The result of sentiment analysis on a message. - - Returns - ------- - Path - The path to the saved image. - """ - chart_dir = Path("artifacts/charts/sentiment").resolve() - chart_dir.mkdir(parents=True, exist_ok=True) - chart_path = chart_dir / f"{message_id}.png" - - if chart_path.exists(): - return chart_path - - _, ax = plt.subplots() - ax.bar( - [ - "Negative", - "Neutral", - "Positive", - ], - [ - analysis_result.neg, - analysis_result.neu, - analysis_result.pos, - ], - color=[ - "red", - "blue", - "green", - ], - ) - ax.set_ylabel("Sentiment Score") - ax.set_title("Sentiment Analysis") - - plt.savefig(chart_path) - - return chart_path - class Sentiment(commands.Cog): """Sentiment related commands.""" @@ -121,79 +59,6 @@ def __init__(self, bot: CourageousCometsBot) -> None: menu = app_commands.ContextMenu(name=obj.name, callback=obj) self.bot.tree.add_command(menu) - @contextmenu(name="Show user sentiment") - async def show_user_sentiment( - self, - interaction: discord.Interaction, - user: discord.User | discord.Member, - ) -> None: - """ - Allow users to view the sentiment analysis of a user using a context menu. - - Generates an embed with the sentiment analysis of a user and sends it to the user. - - The embed contains a line chart of the sentiment of a user over time. - - Parameters - ---------- - interaction : discord.Interaction - The interaction that triggered the command. - user : discord.User | discord.Member - The user to analyze. - """ - logger.info( - "User %s requested sentiment analysis results for user %s.", - interaction.user.id, - user.id, - ) - - if self.bot.redis is None: - return await interaction.response.send_message( - "This feature is currently unavailable. Please try again later.", - ephemeral=True, - ) - - if interaction.guild is None: - return await interaction.response.send_message( - "This feature is only available in guilds.", - ephemeral=True, - ) - - user_sentiment = await get_average_sentiment( - redis=self.bot.redis, - guild_id=str(interaction.guild.id), - ids=[str(user.id)], - scope=StatisticScope.USER, - ) - - if not user_sentiment: - raise MessagesNotFound - - average_sentiment = user_sentiment[0]["avg_sentiment"] - - sentiment = next( - (value for key, value in SENTIMENT.items() if int(average_sentiment * 100) in key), - "unknown", - ) - - if not user_sentiment: - raise MessagesNotFound - - view = discord.Embed( - title="User Sentiment", - description=USER_SENTIMENT_TEMPLATE.format( - sentiment=sentiment, - user=user.mention, - compound=average_sentiment, - ), - timestamp=discord.utils.utcnow(), - ) - - return await interaction.response.send_message( - embed=view, - ephemeral=True, - ) - @contextmenu(name="Show message sentiment") async def show_message_sentiment( self, @@ -261,23 +126,72 @@ async def show_message_sentiment( "sentiment": sentiment, } - view = discord.Embed( + embed = discord.Embed( title="Message Sentiment", - description=MESSAGE_SENTIMENT_TEMPLATE.format(**template_vars), + description=SENTIMENT_DESCRIPTION_TEMPLATE.format(**template_vars), color=color, timestamp=discord.utils.utcnow(), ) chart = plot_sentiment_analysis(message.id, analysis_result) chart_file = discord.File(chart, filename="sentiment_analysis.png") - view.set_image(url="attachment://sentiment_analysis.png") + embed.set_image(url="attachment://sentiment_analysis.png") return await interaction.response.send_message( - embed=view, + embed=embed, file=chart_file, ephemeral=True, ) + @contextmenu(name="Show similar sentiment") + async def get_similar_messages( + self, + interaction: discord.Interaction, + message: discord.Message, + ) -> None: + """ + Allow users to view the sentiment analysis of a message using a context menu. + + Generates an embed with the sentiment analysis of a message and sends it to the user. + + The embed contains a text description of the sentiment analysis and a bar chart. + + Parameters + ---------- + interaction : discord.Interaction + The interaction that triggered the command. + message : discord.Message + The message to analyze. + """ + logger.info( + "User %s requested sentiment analysis results for message %s.", + interaction.user.id, + message.id, + ) + + if self.bot.redis is None: + return await interaction.response.send_message( + "This feature is currently unavailable. Please try again later.", + ephemeral=True, + ) + + await interaction.response.defer() + + sentiment = calculate_sentiment(message.content) + messages = await get_messages_by_sentiment_similarity( + self.bot.redis, + guild_id=str(message.guild.id), # type: ignore + sentiment=sentiment.compound, + scope=StatisticScope.GUILD, + radius=0.1, + ) + + if not messages: + raise MessagesNotFound + + # TODO(isaa-ctaylor): Display messages + return None + async def setup(bot: CourageousCometsBot) -> None: """Load the cog.""" diff --git a/courageous_comets/models.py b/courageous_comets/models.py index e7b78fa..417344b 100644 --- a/courageous_comets/models.py +++ b/courageous_comets/models.py @@ -20,6 +20,11 @@ class BaseModel(pydantic.BaseModel): from_attributes=True, ) + def __hash__( + self, + ) -> int: # make hashable BaseModel subclass for async_lru.alru_cache decorator + return hash((type(self), *tuple(self.__dict__.values()))) + class Message(BaseModel): """ diff --git a/courageous_comets/redis/messages.py b/courageous_comets/redis/messages.py index 3aa9ad4..3d07675 100644 --- a/courageous_comets/redis/messages.py +++ b/courageous_comets/redis/messages.py @@ -159,13 +159,7 @@ async def get_message_sentiment( if not data: return None return models.SentimentResult.model_validate( - dict( - zip( - fields, - (float(value) if value is not None else 0 for value in data), - strict=True, - ), - ), + dict(zip(fields, map(float, data), strict=True)), ) diff --git a/poetry.lock b/poetry.lock index 776a595..9a6e48d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -160,6 +160,17 @@ files = [ six = ">=1.6.1,<2.0" wheel = ">=0.23.0,<1.0" +[[package]] +name = "async-lru" +version = "2.0.4" +description = "Simple LRU cache for asyncio" +optional = false +python-versions = ">=3.8" +files = [ + {file = "async-lru-2.0.4.tar.gz", hash = "sha256:b8a59a5df60805ff63220b2a0c5b5393da5521b113cd5465a44eb037d81a5627"}, + {file = "async_lru-2.0.4-py3-none-any.whl", hash = "sha256:ff02944ce3c288c5be660c42dbcca0742b32c3b279d6dceda655190240b99224"}, +] + [[package]] name = "attrs" version = "23.2.0" @@ -2938,13 +2949,13 @@ telegram = ["requests"] [[package]] name = "transformers" -version = "4.43.2" +version = "4.43.3" description = "State-of-the-art Machine Learning for JAX, PyTorch and TensorFlow" optional = false python-versions = ">=3.8.0" files = [ - {file = "transformers-4.43.2-py3-none-any.whl", hash = "sha256:283c8b47cf38640c5c0caea60be0dfa948669fa48e9739b03717cbf5e8b20f11"}, - {file = "transformers-4.43.2.tar.gz", hash = "sha256:99dbbdeef9d451cdbc1c5316dce3af3dd5bb56b6cda5d0c564253e8fa7ccaf02"}, + {file = "transformers-4.43.3-py3-none-any.whl", hash = "sha256:6552beada5d826c25ff9b79139d237ab9050c6ea96b73d7fd2f8a8ba23ee76a4"}, + {file = "transformers-4.43.3.tar.gz", hash = "sha256:820c5b192bb1bf47250802901a8f0bf581e06b8fded89179d4ef08a1e903ee1c"}, ] [package.dependencies] @@ -3267,4 +3278,4 @@ test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", [metadata] lock-version = "2.0" python-versions = "~3.12" -content-hash = "549a12ff83021147e38240b6934b36e6d9b0dbf66c669dc4f2dea46d37ff81cf" +content-hash = "f62c686870613e4705ef3cad1e8f7c980c0225228bcc5093c8d91282ee985a71" diff --git a/pyproject.toml b/pyproject.toml index 63e17c6..3ed56a5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,6 +22,7 @@ redisvl = { extras = ["hiredis"], version = "0.2.3" } torch = { version = "^2.3.1+cpu", source = "pytorch-cpu" } transformers = "^4.42.4" unidecode = "^1.3.8" +async-lru = "^2.0.4" matplotlib = "^3.9.1" [tool.poetry.dev-dependencies] From 6aab1cb96029a395d51f5cb8b950aec98d79d3f1 Mon Sep 17 00:00:00 2001 From: isaa_ctaylor Date: Sat, 27 Jul 2024 06:20:28 +0100 Subject: [PATCH 108/168] fix: remove cog from erroneous merge conflict resolution (#58) --- application.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/application.yaml b/application.yaml index 6bfa055..730c634 100644 --- a/application.yaml +++ b/application.yaml @@ -1,7 +1,6 @@ cogs: - courageous_comets.cogs.about - courageous_comets.cogs.keywords - - courageous_comets.cogs.interactions - courageous_comets.cogs.messages - courageous_comets.cogs.ping - courageous_comets.cogs.sentiment From 4404a786e7409b489b6a1bd4c3c30a545e465bb7 Mon Sep 17 00:00:00 2001 From: isaa_ctaylor Date: Sat, 27 Jul 2024 06:21:11 +0100 Subject: [PATCH 109/168] fix: silence KeyboardInterrupt on application shutdown (#59) --- courageous_comets/__main__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/courageous_comets/__main__.py b/courageous_comets/__main__.py index 7259910..6739627 100644 --- a/courageous_comets/__main__.py +++ b/courageous_comets/__main__.py @@ -1,4 +1,5 @@ import asyncio +import contextlib import logging import discord @@ -29,4 +30,5 @@ async def main() -> None: await bot.close() -asyncio.run(main()) +with contextlib.suppress(KeyboardInterrupt): + asyncio.run(main()) From a9f349b8f2917f4d0adb0e7013e27c3ca9142df7 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Sat, 27 Jul 2024 05:55:04 +0000 Subject: [PATCH 110/168] fix: add exists check --- courageous_comets/redis/messages.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/courageous_comets/redis/messages.py b/courageous_comets/redis/messages.py index 3d07675..02070a3 100644 --- a/courageous_comets/redis/messages.py +++ b/courageous_comets/redis/messages.py @@ -154,10 +154,15 @@ async def get_message_sentiment( The sentiment analysis result if found, else None. """ fields = ["sentiment_neg", "sentiment_neu", "sentiment_pos", "sentiment_compound"] + + if not redis.exists(key): + return None + data = await redis.hmget(key, fields) # type: ignore if not data: return None + return models.SentimentResult.model_validate( dict(zip(fields, map(float, data), strict=True)), ) @@ -466,5 +471,10 @@ async def get_average_sentiment( # Deserialize all rows as dictionaries. Each row is a flat list of key-value pairs. return [ - {key: float(value) for row in results.rows for key, value in itertools.batched(row, 2)}, + { + key: float(value) + for row in results.rows + for key, value in itertools.batched(row, 2) + if value is not None + }, ] From d4d28b413e97c5a77db06de54dc548637d2a439d Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Sat, 27 Jul 2024 05:55:22 +0000 Subject: [PATCH 111/168] fix: restore user sentiment interaction --- courageous_comets/cogs/sentiment.py | 81 ++++++++++++++++++++++++++++- 1 file changed, 79 insertions(+), 2 deletions(-) diff --git a/courageous_comets/cogs/sentiment.py b/courageous_comets/cogs/sentiment.py index f99a0bd..6384b72 100644 --- a/courageous_comets/cogs/sentiment.py +++ b/courageous_comets/cogs/sentiment.py @@ -9,6 +9,7 @@ from courageous_comets.enums import StatisticScope from courageous_comets.redis.keys import key_schema from courageous_comets.redis.messages import ( + get_average_sentiment, get_message_sentiment, get_messages_by_sentiment_similarity, ) @@ -31,8 +32,12 @@ class MessagesNotFound(app_commands.AppCommandError): range(50, 100): "very positive 😍", } +USER_SENTIMENT_TEMPLATE = """ +Overall the sentiment of {user} is **{sentiment}**. +Their average compound score is {compound}. +""" -SENTIMENT_DESCRIPTION_TEMPLATE = """ +MESSAGE_SENTIMENT_TEMPLATE = """ Overall the sentiment of the message is **{sentiment}**. Here's a breakdown of the scores: @@ -59,6 +64,78 @@ def __init__(self, bot: CourageousCometsBot) -> None: menu = app_commands.ContextMenu(name=obj.name, callback=obj) self.bot.tree.add_command(menu) + @contextmenu(name="Show user sentiment") + async def show_user_sentiment( + self, + interaction: discord.Interaction, + user: discord.User | discord.Member, + ) -> None: + """ + Allow users to view the sentiment analysis of a user using a context menu. + + Generates an embed with the sentiment analysis of a user and sends it to the user. + The embed contains a line chart of the sentiment of a user over time. + + Parameters + ---------- + interaction : discord.Interaction + The interaction that triggered the command. + user : discord.User | discord.Member + The user to analyze. + """ + logger.info( + "User %s requested sentiment analysis results for user %s.", + interaction.user.id, + user.id, + ) + + if self.bot.redis is None: + return await interaction.response.send_message( + "This feature is currently unavailable. Please try again later.", + ephemeral=True, + ) + + if interaction.guild is None: + return await interaction.response.send_message( + "This feature is only available in guilds.", + ephemeral=True, + ) + + user_sentiment = await get_average_sentiment( + redis=self.bot.redis, + guild_id=str(interaction.guild.id), + ids=[str(user.id)], + scope=StatisticScope.USER, + ) + + if not user_sentiment: + raise MessagesNotFound + + average_sentiment = user_sentiment[0]["avg_sentiment"] + + sentiment = next( + (value for key, value in SENTIMENT.items() if int(average_sentiment * 100) in key), + "unknown", + ) + + if not user_sentiment: + raise MessagesNotFound + + view = discord.Embed( + title="User Sentiment", + description=USER_SENTIMENT_TEMPLATE.format( + sentiment=sentiment, + user=user.mention, + compound=average_sentiment, + ), + timestamp=discord.utils.utcnow(), + ) + + return await interaction.response.send_message( + embed=view, + ephemeral=True, + ) + @contextmenu(name="Show message sentiment") async def show_message_sentiment( self, @@ -128,7 +205,7 @@ async def show_message_sentiment( embed = discord.Embed( title="Message Sentiment", - description=SENTIMENT_DESCRIPTION_TEMPLATE.format(**template_vars), + description=MESSAGE_SENTIMENT_TEMPLATE.format(**template_vars), color=color, timestamp=discord.utils.utcnow(), ) From 778d0d9a16162b2e87931c6aca033ddb4d02cd35 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Sat, 27 Jul 2024 06:03:50 +0000 Subject: [PATCH 112/168] fix: handle missing sentiment data --- courageous_comets/redis/messages.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/courageous_comets/redis/messages.py b/courageous_comets/redis/messages.py index 02070a3..9edf30d 100644 --- a/courageous_comets/redis/messages.py +++ b/courageous_comets/redis/messages.py @@ -1,5 +1,6 @@ import itertools import json +import logging from collections import Counter import redis.commands.search.aggregation as aggregations @@ -15,6 +16,8 @@ from courageous_comets.redis import schema from courageous_comets.redis.keys import key_schema +logger = logging.getLogger(__name__) + # List of courageous_comets.models.Message return fields used acrosss queries # that return a list of courageous_comets.models.Message RETURN_FIELDS = ["message_id", "user_id", "channel_id", "guild_id", "timestamp"] @@ -160,11 +163,15 @@ async def get_message_sentiment( data = await redis.hmget(key, fields) # type: ignore - if not data: + has_all_fields = len(data) == len(fields) + any_none = any(value is None for value in data) + + if not has_all_fields or any_none: + logger.warning("Missing sentiment analysis data for message %s.", key) return None return models.SentimentResult.model_validate( - dict(zip(fields, map(float, data), strict=True)), + {key: float(value) for key, value in zip(fields, data, strict=True)}, ) From c5c6571a80429cf5e171eb995c6e9da4c5f23abb Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Sat, 27 Jul 2024 06:46:45 +0000 Subject: [PATCH 113/168] fix: update app description and set embed footer --- courageous_comets/client.py | 8 ++++---- courageous_comets/cogs/about.py | 20 +++++++++++++------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/courageous_comets/client.py b/courageous_comets/client.py index bc43368..6cfd734 100644 --- a/courageous_comets/client.py +++ b/courageous_comets/client.py @@ -15,11 +15,11 @@ DESCRIPTION = """ Thank you for using Courageous Comets! ☄️ -This is a Discord app that provides various statistical analyses on messages. +This bot is designed to help you: -- **Sentiment Analysis**: Analyze the sentiment of a message. -- **Word Frequency**: Analyze the frequency of words in a message. -- **Similarity Analysis**: Analyze the similarity between two messages. +- 🚀 Connect with others who share your interests. +- 🤗 Find the friendliest communities. +- 🛡️ Moderate your server with ease. """ logger = logging.getLogger(__name__) diff --git a/courageous_comets/cogs/about.py b/courageous_comets/cogs/about.py index 73cd008..0155761 100644 --- a/courageous_comets/cogs/about.py +++ b/courageous_comets/cogs/about.py @@ -39,14 +39,20 @@ async def about_command(self, interaction: discord.Interaction) -> None: interaction.user.id, ) try: + embed = Embed( + title=f"Courageous Comets ({__version__})", + description=self.description, + color=discord.Color.blurple(), + url=f"https://thijsfranck.github.io/courageous-comets/{__version__}/", + timestamp=discord.utils.utcnow(), + ) + + footer_text = f"Generated using Courageous Comets {__version__}" + + embed.set_footer(text=footer_text) + await interaction.response.send_message( - embed=Embed( - title=f"Courageous Comets ({__version__})", - description=self.description, - color=discord.Color.blurple(), - url=f"https://thijsfranck.github.io/courageous-comets/{__version__}/", - timestamp=discord.utils.utcnow(), - ), + embed=embed, ephemeral=True, ) except discord.HTTPException as e: From 6d533e29898272627fa4c3d091f7bfd346701189 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Sat, 27 Jul 2024 06:47:44 +0000 Subject: [PATCH 114/168] fix: reference current version in documentation --- .github/workflows/publish.yaml | 3 +++ docs/README.md | 2 +- docs/admin-guide/deployment.md | 4 ++-- docs/replace-token.sh | 25 +++++++++++++++++++++++++ 4 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 docs/replace-token.sh diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index a2f7f27..d0ff0d5 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -44,6 +44,9 @@ jobs: - name: Set up Git user uses: fregante/setup-git-user@v2.0.1 + - name: Build documentation + run: bash docs/replace-token.sh "" "${{ github.ref_name }}" + - name: Publish documentation run: poetry run mike deploy -b public-docs --push --update-aliases ${{ github.ref_name }} latest shell: bash diff --git a/docs/README.md b/docs/README.md index 45e86ad..d9010b8 100644 --- a/docs/README.md +++ b/docs/README.md @@ -57,4 +57,4 @@ The following team members contributed to the project: ## License -This project is published under the [MIT license](https://github.com/thijsfranck/courageous-comets/blob/main/LICENSE). +This project is published under the [MIT license](https://github.com/thijsfranck/courageous-comets/blob//LICENSE). diff --git a/docs/admin-guide/deployment.md b/docs/admin-guide/deployment.md index 9228d8f..5e8646a 100644 --- a/docs/admin-guide/deployment.md +++ b/docs/admin-guide/deployment.md @@ -22,7 +22,7 @@ The application can be deployed using Docker Compose. You can use the `docker-co GitHub repository to start the application. -[Get the Docker Compose :fontawesome-brands-docker:](https://github.com/thijsfranck/courageous-comets/blob/main/docker-compose.yaml){ .md-button .md-button--primary } +[Get the Docker Compose :fontawesome-brands-docker:](https://github.com/thijsfranck/courageous-comets/blob//docker-compose.yaml){ .md-button .md-button--primary } Download the file and save it in any directory on your system. @@ -64,7 +64,7 @@ By default, the application uses the latest version of each Docker image. To spe can add the following variables to the `.env` file: ```dotenv -COURAGEOUS_COMETS_VERSION=latest +COURAGEOUS_COMETS_VERSION= REDIS_STACK_VERSION=latest ``` diff --git a/docs/replace-token.sh b/docs/replace-token.sh new file mode 100644 index 0000000..1b75d07 --- /dev/null +++ b/docs/replace-token.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +# Check if the correct number of arguments is provided +if [ "$#" -ne 2 ]; then + echo "Usage: $0 " + exit 1 +fi + +# Assign arguments to variables +token=$1 +replacement=$2 + +# Get the directory where the script is located +docs_dir=$(dirname "$0") + +# Find and iterate over all .md files in the docs directory and its subdirectories +find "$docs_dir" -name "*.md" | while read -r file; +do + # Replace the token with the replacement in each file + sed -i.bak "s/$token/$replacement/g" "$file" + # Remove the backup file + rm "$file.bak" +done + +echo "Token '$token' replaced with '$replacement' in all .md files in the '$docs_dir' directory." From 501cb289d30764c39fa41f9dbf7eb1ee25247c89 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Sat, 27 Jul 2024 06:47:55 +0000 Subject: [PATCH 115/168] =?UTF-8?q?bump:=20version=200.7.0=20=E2=86=92=200?= =?UTF-8?q?.8.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/CHANGELOG.md | 17 +++++++++++++++++ pyproject.toml | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 73860d6..4620780 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,3 +1,20 @@ +## v0.8.0 (2024-07-27) + +### Feat + +- plot message frequency graph + +### Fix + +- reference current version in documentation +- update app description and set embed footer +- handle missing sentiment data +- restore user sentiment interaction +- add exists check +- silence KeyboardInterrupt on application shutdown (#59) +- remove cog from erroneous merge conflict resolution (#58) +- remove unused context menu loader + ## v0.7.0 (2024-07-26) ### Feat diff --git a/pyproject.toml b/pyproject.toml index 3ed56a5..16a35be 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "courageous-comets" -version = "0.7.0" +version = "0.8.0" authors = ["Courageous Comets"] description = "" readme = "docs/README.md" From aafcde31077bb067872633d21b3f545ef27d1c41 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Sat, 27 Jul 2024 06:49:36 +0000 Subject: [PATCH 116/168] ci: exclude .sh files from docs build --- mkdocs.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mkdocs.yaml b/mkdocs.yaml index 56fa85c..fbd0038 100644 --- a/mkdocs.yaml +++ b/mkdocs.yaml @@ -75,6 +75,9 @@ plugins: - mike - search +exclude_docs: | + *.sh + extra: generator: false social: From ca9f3a34dfbe6194b2c9a7d73cc0a3cddae63f76 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Sat, 27 Jul 2024 12:26:24 +0000 Subject: [PATCH 117/168] feat: refactor cogs with ui components and complete existing interactions --- courageous_comets/cogs/about.py | 37 +- courageous_comets/cogs/frequency.py | 4 + courageous_comets/cogs/keywords.py | 326 ++++++------------ courageous_comets/cogs/messages.py | 20 +- courageous_comets/cogs/sentiment.py | 212 ++++++------ courageous_comets/discord/__init__.py | 8 + courageous_comets/discord/messages.py | 87 +++++ courageous_comets/settings.py | 1 + courageous_comets/ui/__init__.py | 0 courageous_comets/ui/charts/__init__.py | 6 + .../charts/sentiment_bars.py} | 35 +- courageous_comets/ui/components/__init__.py | 0 courageous_comets/ui/components/message.py | 36 ++ .../ui/components/message_list.py | 20 ++ .../ui/components/message_sentiment.py | 31 ++ .../ui/components/search_results.py | 35 ++ courageous_comets/ui/components/sentiment.py | 28 ++ .../ui/components/user_sentiment.py | 31 ++ courageous_comets/ui/embeds/__init__.py | 3 + courageous_comets/ui/embeds/about.py | 34 ++ courageous_comets/ui/embeds/format.py | 24 ++ .../ui/embeds/message_sentiment.py | 31 ++ courageous_comets/ui/embeds/search_results.py | 29 ++ courageous_comets/ui/embeds/user_sentiment.py | 28 ++ docs/admin-guide/configuration.md | 5 + .../cogs/boilerplate.py => examples/cog.py | 0 poetry.lock | 28 +- pyproject.toml | 3 +- 28 files changed, 732 insertions(+), 370 deletions(-) create mode 100644 courageous_comets/discord/__init__.py create mode 100644 courageous_comets/discord/messages.py create mode 100644 courageous_comets/ui/__init__.py create mode 100644 courageous_comets/ui/charts/__init__.py rename courageous_comets/{charts.py => ui/charts/sentiment_bars.py} (58%) create mode 100644 courageous_comets/ui/components/__init__.py create mode 100644 courageous_comets/ui/components/message.py create mode 100644 courageous_comets/ui/components/message_list.py create mode 100644 courageous_comets/ui/components/message_sentiment.py create mode 100644 courageous_comets/ui/components/search_results.py create mode 100644 courageous_comets/ui/components/sentiment.py create mode 100644 courageous_comets/ui/components/user_sentiment.py create mode 100644 courageous_comets/ui/embeds/__init__.py create mode 100644 courageous_comets/ui/embeds/about.py create mode 100644 courageous_comets/ui/embeds/format.py create mode 100644 courageous_comets/ui/embeds/message_sentiment.py create mode 100644 courageous_comets/ui/embeds/search_results.py create mode 100644 courageous_comets/ui/embeds/user_sentiment.py rename courageous_comets/cogs/boilerplate.py => examples/cog.py (100%) diff --git a/courageous_comets/cogs/about.py b/courageous_comets/cogs/about.py index 0155761..693c646 100644 --- a/courageous_comets/cogs/about.py +++ b/courageous_comets/cogs/about.py @@ -1,19 +1,13 @@ import logging import discord -from discord import Embed, app_commands +from discord import app_commands from discord.ext import commands -from courageous_comets import __version__ +from courageous_comets.ui.embeds import about logger = logging.getLogger(__name__) -DESCRIPTION = """ -%s - -Click the link in the header to visit the documentation! -""" - class About(commands.Cog): """A cog that provides information about the app upon request.""" @@ -38,30 +32,11 @@ async def about_command(self, interaction: discord.Interaction) -> None: "User %s requested the about message using the /about command.", interaction.user.id, ) - try: - embed = Embed( - title=f"Courageous Comets ({__version__})", - description=self.description, - color=discord.Color.blurple(), - url=f"https://thijsfranck.github.io/courageous-comets/{__version__}/", - timestamp=discord.utils.utcnow(), - ) - - footer_text = f"Generated using Courageous Comets {__version__}" - embed.set_footer(text=footer_text) - - await interaction.response.send_message( - embed=embed, - ephemeral=True, - ) - except discord.HTTPException as e: - logger.exception("Could not deliver the about message.", exc_info=e) - - @property - def description(self) -> str: - """Return the body of the about message.""" - return DESCRIPTION % self.bot.description + await interaction.response.send_message( + embed=about.render(self.bot.description), + ephemeral=True, + ) async def setup(bot: commands.Bot) -> None: diff --git a/courageous_comets/cogs/frequency.py b/courageous_comets/cogs/frequency.py index 3c73c88..4749700 100644 --- a/courageous_comets/cogs/frequency.py +++ b/courageous_comets/cogs/frequency.py @@ -115,22 +115,26 @@ async def frequency_command( "This feature is only available in guilds.", ephemeral=True, ) + frequencies = await get_messages_frequency( self.bot.redis, guild_id=str(interaction.guild.id), duration=duration, ) + if not frequencies: return await interaction.response.send_message( "No messages were found over the specified duration at this time.", ephemeral=True, ) + view = discord.Embed( title="Message frequencies", description="Message frequencies", color=discord.Colour.purple(), timestamp=discord.utils.utcnow(), ) + chart = plot_message_frequency(frequencies, duration) chart_file = discord.File(chart, filename="frequency.png") view.set_image(url="attachment://frequency.png") diff --git a/courageous_comets/cogs/keywords.py b/courageous_comets/cogs/keywords.py index 5104e94..350ed2e 100644 --- a/courageous_comets/cogs/keywords.py +++ b/courageous_comets/cogs/keywords.py @@ -1,270 +1,160 @@ -import datetime +import logging import discord -from async_lru import alru_cache from discord import app_commands from discord.ext import commands -from courageous_comets.charts import plot_sentiment_analysis from courageous_comets.client import CourageousCometsBot -from courageous_comets.models import Message -from courageous_comets.redis.keys import key_schema -from courageous_comets.redis.messages import ( - get_message_sentiment, - get_recent_messages, -) +from courageous_comets.discord.messages import resolve_messages +from courageous_comets.preprocessing import process +from courageous_comets.redis.messages import get_messages_by_semantics_similarity +from courageous_comets.ui.embeds import search_results +from courageous_comets.utils import contextmenu from courageous_comets.vectorizer import Vectorizer -# from courageous_comets.utils import contextmenu # noqa: ERA001 +logger = logging.getLogger(__name__) -def shorten(string: str, *, limit: int = 50) -> str: - """Trim a string if necessary given `limit`.""" - if len(string) > limit: - string = string[: limit - 3] + "..." - return string - - -SENTIMENT: dict[range, str] = { - range(-100, -50): "very negative 😡", - range(-50, -10): "negative 🙁", - range(-10, 10): "neutral 🙂", - range(10, 50): "positive 😁", - range(50, 100): "very positive 😍", -} - - -SENTIMENT_DESCRIPTION_TEMPLATE = """ -Overall the sentiment of the message is **{sentiment}**. - -Here's a breakdown of the scores: - -- Negative: {neg} -- Neutral: {neu} -- Positive: {pos} - -The compound score is {compound}. -""" +class MessagesNotFound(app_commands.AppCommandError): + """No messages were found.""" -class Dropdown(discord.ui.Select): - """A drop down menu.""" +class Keywords(commands.Cog): + """A boilerplate cog.""" - def __init__(self, messages: list[discord.Message | None], bot: CourageousCometsBot) -> None: - self.messages = messages + def __init__(self, bot: CourageousCometsBot) -> None: self.bot = bot + self.vectorizer = Vectorizer() - options = [ - discord.SelectOption( - label=message.author.display_name, - description=shorten(message.content, limit=25), - value=str(message.id), - ) - for message in messages - if message - ] - - super().__init__( - placeholder="Get more information...", - min_values=1, - max_values=1, - options=options, - ) - - async def callback(self, interaction: discord.Interaction) -> None: - """Handle the callback when the dropdown is selected.""" - await interaction.response.defer(ephemeral=True) - message_id = self.values[0] + for attribute in dir(self): + obj = getattr(self, attribute, None) + if obj and getattr(obj, "is_contextmenu", False): + menu = app_commands.ContextMenu(name=obj.name, callback=obj) + self.bot.tree.add_command(menu) - message: discord.Message = discord.utils.get(self.messages, id=int(message_id)) # type: ignore + @app_commands.command(name="search", description="Search for related messages.") + async def search_by_topic(self, interaction: discord.Interaction, query: str) -> None: + """ + Search for related messages using the /search command. - key = key_schema.guild_messages( - guild_id=message.guild.id, # type: ignore - message_id=message.id, + Parameters + ---------- + interaction : discord.Interaction + The interaction that triggered the command. + query : str + The query to search for related messages. + """ + logger.info( + "User %s requested a search for related messages using the /search command.", + interaction.user.id, ) - analysis_result = await get_message_sentiment(key, redis=self.bot.redis) # type: ignore + if self.bot.redis is None: + return await interaction.response.send_message( + "This feature is currently unavailable. Please try again later.", + ephemeral=True, + ) - if analysis_result is None: + if not interaction.guild: return await interaction.response.send_message( - "No sentiment analysis found for this message.", + "This command can only be used in a guild.", ephemeral=True, ) - color = discord.Color.green() if analysis_result.compound >= 0 else discord.Color.red() + if not interaction.channel: + return await interaction.response.send_message( + "This command can only be used in a channel.", + ephemeral=True, + ) - sentiment = next( - ( - value - for key, value in SENTIMENT.items() - if int(analysis_result.compound * 100) in key - ), - "unknown", - ) + if not query: + return await interaction.response.send_message( + "Please provide a query to search for related messages.", + ephemeral=True, + ) - template_vars = { - **analysis_result.model_dump(), - "sentiment": sentiment, - } + await interaction.response.defer(ephemeral=True, thinking=True) - embed = discord.Embed( - title="Message Sentiment", - description=SENTIMENT_DESCRIPTION_TEMPLATE.format(**template_vars), - color=color, - timestamp=discord.utils.utcnow(), + query_processed = process(query) + query_vector = await self.vectorizer.aencode(query_processed) + + messages = await get_messages_by_semantics_similarity( + self.bot.redis, + guild_id=str(interaction.guild.id), + embedding=query_vector, + limit=5, ) - chart = plot_sentiment_analysis(message.id, analysis_result) - chart_file = discord.File(chart, filename="sentiment_analysis.png") - embed.set_image(url="attachment://sentiment_analysis.png") + resolved_messages = await resolve_messages(self.bot, messages) - return await interaction.followup.send(embed=embed, ephemeral=True, file=chart_file) + if not resolved_messages: + return await interaction.followup.send("No related messages were found.") + embed = search_results.render(query, resolved_messages) -class DropdownView(discord.ui.View): - """A view containing a dropdown.""" + return await interaction.followup.send(embed=embed) - def __init__( + @contextmenu(name="Search by topic") + async def search_by_topic_with_message( self, - messages: list[discord.Message | None], - bot: CourageousCometsBot, - *, - timeout: float | None = 180, + interaction: discord.Interaction, + message: discord.Message, ) -> None: - super().__init__(timeout=timeout) - - self.add_item(Dropdown(messages, bot)) - - -class MessagesNotFound(app_commands.AppCommandError): - """No messages were found.""" - - -class Keywords(commands.Cog): - """A boilerplate cog.""" - - def __init__(self, bot: CourageousCometsBot) -> None: - self.bot = bot - - self.vectorizer = Vectorizer() - - for attribute in dir(self): - obj = getattr(self, attribute, None) - if obj and getattr(obj, "is_contextmenu", False): - menu = app_commands.ContextMenu(name=obj.name, callback=obj) - self.bot.tree.add_command(menu) - - async def _get_recent_messages( - self, - *, - # user: discord.Member, - guild_id: int, - channels: list[discord.TextChannel], - limit: int = 10, - ) -> list[Message]: """ - Return `limit` most recent messages, only from `channels`, if provided. + Search for related messages using a context menu. Parameters ---------- - user: discord.Member - The user to get the most recent messages for. - limit: int - The number of messages to retrieve. Defaults to 10. - channels: list[discord.TextChannel] - The channels to query. + interaction : discord.Interaction + The interaction that triggered the command. + message : discord.Message + The message to use as a reference for the search. """ - return await get_recent_messages( - self.bot.redis, # type: ignore - guild_id=str(guild_id), - ids=[str(channel.id) for channel in channels], - limit=limit, + logger.info( + "User %s requested search by sentiment for message %s.", + interaction.user.id, + message.id, ) - # @contextmenu(name="Show recent messages") - # async def _show_recent_messages_context_menu( - # self, - # interaction: discord.Interaction, - # member: discord.Member, - # ) -> None: - # """Get the most recent messages from a user.""" - # await self._show_recent_messages(interaction, member) # noqa: ERA001 - - @app_commands.command(name="recent") - async def _show_recent_messages_command( - self, - interaction: discord.Interaction, - ) -> None: - """Get the most recent messages in the server.""" - await self._show_recent_messages(interaction) - # await self._show_recent_messages(interaction, member) # noqa: ERA001 + if self.bot.redis is None: + return await interaction.response.send_message( + "This feature is currently unavailable. Please try again later.", + ephemeral=True, + ) - @alru_cache() - async def _resolve_message(self, message: Message) -> discord.Message | None: - """ - Try and resolve a message from discord. + if interaction.guild is None: + return await interaction.response.send_message( + "This feature is only available in guilds.", + ephemeral=True, + ) - Parameters - ---------- - message: courageous_comets.models.Message - Represents the message to resolve + await interaction.response.defer(ephemeral=True, thinking=True) - Returns - ------- - discord.Message | None - A `discord.Message` instance if we managed to fetch the message from discord, else None - """ - channel: discord.TextChannel = self.bot.get_channel(int(message.channel_id)) # type: ignore - if channel: - resolved_message = await channel.fetch_message(int(message.message_id)) - if resolved_message: - return resolved_message - return None - - async def _show_recent_messages( - self, - interaction: discord.Interaction, - # member: discord.Member, - ) -> None: - await interaction.response.defer() - author = interaction.user + query_processed = process(message.clean_content) + query_vector = await self.vectorizer.aencode(query_processed) + + messages = await get_messages_by_semantics_similarity( + self.bot.redis, + guild_id=str(interaction.guild.id), + embedding=query_vector, + limit=6, + ) - visible_channels = [ - channel - for channel in interaction.guild.text_channels # type: ignore - if channel.permissions_for(author).view_channel # type: ignore + resolved_messages = [ + resolved_message + for resolved_message in await resolve_messages(self.bot, messages) + if resolved_message.id != message.id ] - messages = await self._get_recent_messages( - guild_id=interaction.guild.id, # type: ignore - channels=visible_channels, - ) + if not resolved_messages: + return await interaction.followup.send( + "No related messages were found.", + ephemeral=True, + ) - resolved_messages: list[discord.Message | None] = [] - - for message in messages: - resolved_message = await self._resolve_message(message) - resolved_messages.append(resolved_message) - - if not [m for m in resolved_messages if m]: - raise MessagesNotFound - - await interaction.followup.send( - embed=discord.Embed( - title=f"Recent messages in {interaction.guild.name}", # type: ignore - description="\n\n".join( - f"{message.author.mention} {discord.utils.format_dt(message.created_at, style='R')}:\n{shorten(message.clean_content, limit=60)}" # noqa: E501 - if message and message.clean_content - else "[Message Deleted]" - if not message - else "[No Message Content]" - for message in resolved_messages - ), - colour=discord.Colour.blurple(), - timestamp=datetime.datetime.now(datetime.UTC), - ), - view=DropdownView(resolved_messages, self.bot), - ) + embed = search_results.render(message.clean_content, resolved_messages) + + return await interaction.followup.send(embed=embed, ephemeral=True) async def setup(bot: CourageousCometsBot) -> None: diff --git a/courageous_comets/cogs/messages.py b/courageous_comets/cogs/messages.py index 804fe5a..10203da 100644 --- a/courageous_comets/cogs/messages.py +++ b/courageous_comets/cogs/messages.py @@ -33,7 +33,7 @@ async def on_message(self, message: discord.Message) -> None: """ await self.save_message(message) - async def save_message(self, message: discord.Message) -> None: + async def save_message(self, message: discord.Message) -> None: # noqa: PLR0911 """ Save a message on Redis. @@ -56,6 +56,24 @@ async def save_message(self, message: discord.Message) -> None: message.id, ) + if message.author.bot: + return logger.debug( + "Ignoring message %s because it's from a bot", + message.id, + ) + + if not message.clean_content: + return logger.debug( + "Ignoring message %s because it's empty", + message.id, + ) + + if self.bot.user in message.mentions and "sync" in message.clean_content: + return logger.debug( + "Ignoring message %s because it's a sync message", + message.id, + ) + text = preprocessing.process(message.clean_content) if not text: diff --git a/courageous_comets/cogs/sentiment.py b/courageous_comets/cogs/sentiment.py index 6384b72..db700f4 100644 --- a/courageous_comets/cogs/sentiment.py +++ b/courageous_comets/cogs/sentiment.py @@ -4,8 +4,9 @@ from discord import app_commands from discord.ext import commands -from courageous_comets.charts import plot_sentiment_analysis +from courageous_comets import preprocessing from courageous_comets.client import CourageousCometsBot +from courageous_comets.discord.messages import resolve_messages from courageous_comets.enums import StatisticScope from courageous_comets.redis.keys import key_schema from courageous_comets.redis.messages import ( @@ -14,6 +15,8 @@ get_messages_by_sentiment_similarity, ) from courageous_comets.sentiment import calculate_sentiment +from courageous_comets.ui.charts import sentiment_bars +from courageous_comets.ui.embeds import message_sentiment, search_results, user_sentiment from courageous_comets.utils import contextmenu from courageous_comets.vectorizer import Vectorizer @@ -24,38 +27,11 @@ class MessagesNotFound(app_commands.AppCommandError): """No messages were found.""" -SENTIMENT: dict[range, str] = { - range(-100, -50): "very negative 😡", - range(-50, -10): "negative 🙁", - range(-10, 10): "neutral 🙂", - range(10, 50): "positive 😁", - range(50, 100): "very positive 😍", -} - -USER_SENTIMENT_TEMPLATE = """ -Overall the sentiment of {user} is **{sentiment}**. -Their average compound score is {compound}. -""" - -MESSAGE_SENTIMENT_TEMPLATE = """ -Overall the sentiment of the message is **{sentiment}**. - -Here's a breakdown of the scores: - -- Negative: {neg} -- Neutral: {neu} -- Positive: {pos} - -The compound score is {compound}. -""" - - class Sentiment(commands.Cog): """Sentiment related commands.""" def __init__(self, bot: CourageousCometsBot) -> None: self.bot = bot - self.vectorizer = Vectorizer() for attribute in dir(self): @@ -101,38 +77,22 @@ async def show_user_sentiment( ephemeral=True, ) - user_sentiment = await get_average_sentiment( + await interaction.response.defer(ephemeral=True, thinking=True) + + sentiment_results = await get_average_sentiment( redis=self.bot.redis, guild_id=str(interaction.guild.id), ids=[str(user.id)], scope=StatisticScope.USER, ) - if not user_sentiment: - raise MessagesNotFound - - average_sentiment = user_sentiment[0]["avg_sentiment"] - - sentiment = next( - (value for key, value in SENTIMENT.items() if int(average_sentiment * 100) in key), - "unknown", - ) - - if not user_sentiment: + if not sentiment_results: raise MessagesNotFound - view = discord.Embed( - title="User Sentiment", - description=USER_SENTIMENT_TEMPLATE.format( - sentiment=sentiment, - user=user.mention, - compound=average_sentiment, - ), - timestamp=discord.utils.utcnow(), - ) + average_sentiment = sentiment_results[0]["avg_sentiment"] - return await interaction.response.send_message( - embed=view, + return await interaction.followup.send( + embed=user_sentiment.render(user.mention, average_sentiment), ephemeral=True, ) @@ -174,6 +134,8 @@ async def show_message_sentiment( ephemeral=True, ) + await interaction.response.defer(ephemeral=True, thinking=True) + key = key_schema.guild_messages( guild_id=message.guild.id, message_id=message.id, @@ -181,67 +143,96 @@ async def show_message_sentiment( analysis_result = await get_message_sentiment(key, redis=self.bot.redis) - if analysis_result is None: + if not analysis_result: + prepared_content = preprocessing.process(message.clean_content) + analysis_result = calculate_sentiment(prepared_content) + + embed = message_sentiment.render(analysis_result) + + chart = sentiment_bars.render(message.id, analysis_result) + embed.set_image(url=f"attachment://{chart.filename}") + + return await interaction.followup.send(embed=embed, file=chart, ephemeral=True) + + @app_commands.command( + name="sentiment_search", + description="Search for messages with similar sentiment.", + ) + async def search_by_sentiment( + self, + interaction: discord.Interaction, + query: str, + ) -> None: + """ + Allow users to search for messages with similar sentiment using a context menu. + + Parameters + ---------- + interaction : discord.Interaction + The interaction that triggered the command. + query : str + The message to use as a reference for the search + """ + logger.info( + "User %s requested search by sentiment using a custom query.", + interaction.user.id, + ) + + if self.bot.redis is None: + return await interaction.response.send_message( + "This feature is currently unavailable. Please try again later.", + ephemeral=True, + ) + + if interaction.guild is None: return await interaction.response.send_message( - "No sentiment analysis found for this message.", + "This feature is only available in guilds.", ephemeral=True, ) - color = discord.Color.green() if analysis_result.compound >= 0 else discord.Color.red() + await interaction.response.defer(ephemeral=True, thinking=True) + + prepared_content = preprocessing.process(query) + sentiment = calculate_sentiment(prepared_content) - sentiment = next( - ( - value - for key, value in SENTIMENT.items() - if int(analysis_result.compound * 100) in key - ), - "unknown", + messages = await get_messages_by_sentiment_similarity( + self.bot.redis, + guild_id=str(interaction.guild.id), + sentiment=sentiment.compound, + radius=0.1, + limit=5, ) - template_vars = { - **analysis_result.model_dump(), - "sentiment": sentiment, - } + resolved_messages = await resolve_messages(self.bot, messages) - embed = discord.Embed( - title="Message Sentiment", - description=MESSAGE_SENTIMENT_TEMPLATE.format(**template_vars), - color=color, - timestamp=discord.utils.utcnow(), - ) + if not resolved_messages: + return await interaction.followup.send( + "No related messages were found.", + ephemeral=True, + ) - chart = plot_sentiment_analysis(message.id, analysis_result) - chart_file = discord.File(chart, filename="sentiment_analysis.png") - embed.set_image(url="attachment://sentiment_analysis.png") + embed = search_results.render(query, resolved_messages) - return await interaction.response.send_message( - embed=embed, - file=chart_file, - ephemeral=True, - ) + return await interaction.followup.send(embed=embed, ephemeral=True) - @contextmenu(name="Show similar sentiment") - async def get_similar_messages( + @contextmenu(name="Search by sentiment") + async def search_by_sentiment_with_message( self, interaction: discord.Interaction, message: discord.Message, ) -> None: """ - Allow users to view the sentiment analysis of a message using a context menu. - - Generates an embed with the sentiment analysis of a message and sends it to the user. - - The embed contains a text description of the sentiment analysis and a bar chart. + Allow users to search for messages with similar sentiment using a context menu. Parameters ---------- interaction : discord.Interaction The interaction that triggered the command. message : discord.Message - The message to analyze. + The message to use as a reference for the search. """ logger.info( - "User %s requested sentiment analysis results for message %s.", + "User %s requested search by sentiment for message %s.", interaction.user.id, message.id, ) @@ -252,22 +243,49 @@ async def get_similar_messages( ephemeral=True, ) - await interaction.response.defer() + if message.guild is None: + return await interaction.response.send_message( + "This feature is only available in guilds.", + ephemeral=True, + ) + + await interaction.response.defer(ephemeral=True, thinking=True) + + key = key_schema.guild_messages( + guild_id=message.guild.id, + message_id=message.id, + ) + + # Check whether the sentiment analysis is cached + analysis_result = await get_message_sentiment(key, redis=self.bot.redis) + + if not analysis_result: + prepared_content = preprocessing.process(message.clean_content) + analysis_result = calculate_sentiment(prepared_content) - sentiment = calculate_sentiment(message.content) messages = await get_messages_by_sentiment_similarity( self.bot.redis, - guild_id=str(message.guild.id), # type: ignore - sentiment=sentiment.compound, - scope=StatisticScope.GUILD, + guild_id=str(message.guild.id), + sentiment=analysis_result.compound, radius=0.1, + limit=6, ) - if not messages: - raise MessagesNotFound + resolved_messages = [ + resolved_message + for resolved_message in await resolve_messages(self.bot, messages) + if resolved_message.id != message.id + ] + + if not resolved_messages: + return await interaction.followup.send( + "No related messages were found.", + ephemeral=True, + ) + + embed = search_results.render(message.clean_content, resolved_messages) - # TODO(isaa-ctaylor): Display messages - return None + return await interaction.followup.send(embed=embed, ephemeral=True) async def setup(bot: CourageousCometsBot) -> None: diff --git a/courageous_comets/discord/__init__.py b/courageous_comets/discord/__init__.py new file mode 100644 index 0000000..765138a --- /dev/null +++ b/courageous_comets/discord/__init__.py @@ -0,0 +1,8 @@ +import asyncio + +from courageous_comets import settings + +# Limit the number of concurrent requests to the Discord API. +SEMAPHORE = asyncio.Semaphore(settings.DISCORD_API_CONCURRENCY) + +__all__ = ["SEMAPHORE"] diff --git a/courageous_comets/discord/messages.py b/courageous_comets/discord/messages.py new file mode 100644 index 0000000..d6efc90 --- /dev/null +++ b/courageous_comets/discord/messages.py @@ -0,0 +1,87 @@ +import asyncio +import logging + +import discord +from asyncache import cached +from cachetools import LRUCache + +from courageous_comets import models +from courageous_comets.discord import SEMAPHORE + +logger = logging.getLogger(__name__) + + +async def resolve_messages( + client: discord.Client, + messages: list[models.Message], +) -> list[discord.Message]: + """ + Try and resolve a list of messages from Redis to Discord messages. + + Messages that could not be resolved are not included in the returned list. Messages that have no + content are also filtered out. + + Parameters + ---------- + client : discord.Client + The discord client to use to fetch the messages. + messages : list[models.Message] + The messages to resolve. + + Returns + ------- + list[discord.Message] + A list of `discord.Message` instances that were found. + """ + requests = [ + get_message(client, int(message.channel_id), int(message.message_id)) + for message in messages + ] + return [ + message + for message in await asyncio.gather(*requests) + if message is not None and message.clean_content + ] + + +@cached( + LRUCache(maxsize=256), + key=lambda _, channel_id, message_id: f"{channel_id}-{message_id}", +) +async def get_message( + client: discord.Client, + channel_id: int, + message_id: int, +) -> discord.Message | None: + """ + Try and fetch a message from Discord given a `channel_id` and `message_id`. + + Uses a cache to avoid fetching the same message multiple times. + + Parameters + ---------- + client : discord.Client + The discord client to use to fetch the message. + channel_id : int + The channel id of the message. + message_id : int + The message id of the message. + + Returns + ------- + discord.Message | None + A `discord.Message` instance if the message was found, else `None`. + """ + channel = client.get_channel(channel_id) + + if not channel or not isinstance(channel, discord.TextChannel): + return None + + async with SEMAPHORE: + logging.debug("Fetching message %s from channel %s", message_id, channel_id) + resolved_message = await channel.fetch_message(message_id) + + if not resolved_message: + return None + + return resolved_message diff --git a/courageous_comets/settings.py b/courageous_comets/settings.py index fab2b4e..9efb2b0 100644 --- a/courageous_comets/settings.py +++ b/courageous_comets/settings.py @@ -158,6 +158,7 @@ def setup_logging() -> None: try: DISCORD_TOKEN = read_discord_token() BOT_CONFIG_PATH = read_bot_config_path() + DISCORD_API_CONCURRENCY = read_int("DISCORD_API_CONCURRENCY", 3) NLTK_DATA_DIR = os.getenv("NLTK_DATA", "nltk_data") NLTK_DOWNLOAD_CONCURRENCY = read_int("NLTK_DOWNLOAD_CONCURRENCY", 3) PREPROCESSING_MAX_WORD_LENGTH = read_int("MAX_WORD_LENGTH", 35) diff --git a/courageous_comets/ui/__init__.py b/courageous_comets/ui/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/courageous_comets/ui/charts/__init__.py b/courageous_comets/ui/charts/__init__.py new file mode 100644 index 0000000..8aa922b --- /dev/null +++ b/courageous_comets/ui/charts/__init__.py @@ -0,0 +1,6 @@ +from pathlib import Path + +CACHE_ROOT = Path("artifacts/charts").resolve() +CACHE_ROOT.mkdir(parents=True, exist_ok=True) + +__all__ = ["CACHE_ROOT"] diff --git a/courageous_comets/charts.py b/courageous_comets/ui/charts/sentiment_bars.py similarity index 58% rename from courageous_comets/charts.py rename to courageous_comets/ui/charts/sentiment_bars.py index 48a66d2..42eebb3 100644 --- a/courageous_comets/charts.py +++ b/courageous_comets/ui/charts/sentiment_bars.py @@ -1,14 +1,17 @@ -from pathlib import Path - +import discord import matplotlib.pyplot as plt from courageous_comets import models +from courageous_comets.ui.charts import CACHE_ROOT + +CACHE_DIR = CACHE_ROOT / "sentiment_bars" +CACHE_DIR.mkdir(parents=True, exist_ok=True) -def plot_sentiment_analysis( +def render( message_id: str | int, - analysis_result: models.SentimentResult, -) -> Path: + data: models.SentimentResult, +) -> discord.File: """ Plot the sentiment analysis of a message. @@ -24,15 +27,17 @@ def plot_sentiment_analysis( Returns ------- - Path - The path to the saved image. + discord.File + The file containing the saved image. + + Notes + ----- + Charts are cached in the `CACHE_DIR` directory using the message id as the filename. """ - chart_dir = Path("artifacts/charts/sentiment").resolve() - chart_dir.mkdir(parents=True, exist_ok=True) - chart_path = chart_dir / f"{message_id}.png" + chart_path = CACHE_DIR / f"{message_id}.png" if chart_path.exists(): - return chart_path + return discord.File(chart_path, filename=f"{message_id}.png") _, ax = plt.subplots() ax.bar( @@ -42,9 +47,9 @@ def plot_sentiment_analysis( "Positive", ], [ - analysis_result.neg, - analysis_result.neu, - analysis_result.pos, + data.neg, + data.neu, + data.pos, ], color=[ "red", @@ -57,4 +62,4 @@ def plot_sentiment_analysis( plt.savefig(chart_path) - return chart_path + return discord.File(chart_path, filename=f"{message_id}.png") diff --git a/courageous_comets/ui/components/__init__.py b/courageous_comets/ui/components/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/courageous_comets/ui/components/message.py b/courageous_comets/ui/components/message.py new file mode 100644 index 0000000..a31efae --- /dev/null +++ b/courageous_comets/ui/components/message.py @@ -0,0 +1,36 @@ +import discord + +TEMPLATE = """ +{author} {timestamp}: +{content} +""" + + +def _shorten(string: str, *, limit: int = 50) -> str: + """Trim a string if necessary given `limit`.""" + if len(string) > limit: + string = string[: limit - 3] + "..." + return string + + +def render(message: discord.Message) -> str: + """ + Format a message into a string. + + Parameters + ---------- + message : discord.Message + The message to format. + + Returns + ------- + str + The formatted string. + """ + return TEMPLATE.format_map( + { + "author": message.author.mention, + "timestamp": discord.utils.format_dt(message.created_at, style="R"), + "content": _shorten(message.clean_content), + }, + ) diff --git a/courageous_comets/ui/components/message_list.py b/courageous_comets/ui/components/message_list.py new file mode 100644 index 0000000..815a817 --- /dev/null +++ b/courageous_comets/ui/components/message_list.py @@ -0,0 +1,20 @@ +import discord + +from courageous_comets.ui.components import message + + +def render(messages: list[discord.Message]) -> str: + """ + Format a list of messages into a string. + + Parameters + ---------- + messages : list[discord.Message] + The messages to format. + + Returns + ------- + str + The formatted string. + """ + return "".join(map(message.render, messages)) diff --git a/courageous_comets/ui/components/message_sentiment.py b/courageous_comets/ui/components/message_sentiment.py new file mode 100644 index 0000000..bb1e355 --- /dev/null +++ b/courageous_comets/ui/components/message_sentiment.py @@ -0,0 +1,31 @@ +from courageous_comets.models import SentimentResult +from courageous_comets.ui.components import sentiment + +TEMPLATE = """ +Overall the sentiment of the message is **{sentiment}**. + +Here's a breakdown of the scores: + +- Negative: {neg} +- Neutral: {neu} +- Positive: {pos} + +The compound score is {compound}. +""" + + +def render(data: SentimentResult) -> str: + """ + Render the sentiment analysis results into a string. + + Returns + ------- + str + The rendered sentiment analysis results. + """ + return TEMPLATE.format_map( + { + **data.model_dump(), + "sentiment": sentiment.render(data.compound), + }, + ) diff --git a/courageous_comets/ui/components/search_results.py b/courageous_comets/ui/components/search_results.py new file mode 100644 index 0000000..cab27ea --- /dev/null +++ b/courageous_comets/ui/components/search_results.py @@ -0,0 +1,35 @@ +import discord + +from courageous_comets.ui.components import message_list + +TEMPLATE = """ +The most recent messages related to your query are: + +{results} + +You searched for: `{query}` +""" + + +def render(query: str, messages: list[discord.Message]) -> str: + """ + Render a list of messages into search results. + + Parameters + ---------- + query : str + The query used to find the messages. + messages : list[discord.Message] + The messages to render. + + Returns + ------- + str + The rendered search results + """ + return TEMPLATE.format_map( + { + "results": message_list.render(messages), + "query": query, + }, + ) diff --git a/courageous_comets/ui/components/sentiment.py b/courageous_comets/ui/components/sentiment.py new file mode 100644 index 0000000..e53af82 --- /dev/null +++ b/courageous_comets/ui/components/sentiment.py @@ -0,0 +1,28 @@ +SENTIMENT: dict[range, str] = { + range(-100, -50): "very negative 😡", + range(-50, -10): "negative 🙁", + range(-10, 10): "neutral 🙂", + range(10, 50): "positive 😁", + range(50, 100): "very positive 😍", +} + + +def render(compound: float) -> str: + """ + Render the given sentiment score into a string. + + Parameters + ---------- + compound : float + The compound sentiment score. + + Returns + ------- + str + The rendered sentiment. + """ + for score_range, sentiment in SENTIMENT.items(): + if int(compound * 100) in score_range: + return sentiment + + return "unknown 😶" diff --git a/courageous_comets/ui/components/user_sentiment.py b/courageous_comets/ui/components/user_sentiment.py new file mode 100644 index 0000000..5af63e6 --- /dev/null +++ b/courageous_comets/ui/components/user_sentiment.py @@ -0,0 +1,31 @@ +from courageous_comets.ui.components import sentiment + +TEMPLATE = """ +Overall the sentiment of {user} is **{sentiment}**. +Their average compound score is {compound}. +""" + + +def render(user: str, compound: float) -> str: + """ + Render the sentiment analysis score for the given user into a string. + + Parameters + ---------- + user : str + The user's name. + compound : float + The compound sentiment score. + + Returns + ------- + str + The rendered sentiment analysis results. + """ + return TEMPLATE.format_map( + { + "user": user, + "compound": compound, + "sentiment": sentiment.render(compound), + }, + ) diff --git a/courageous_comets/ui/embeds/__init__.py b/courageous_comets/ui/embeds/__init__.py new file mode 100644 index 0000000..24aa073 --- /dev/null +++ b/courageous_comets/ui/embeds/__init__.py @@ -0,0 +1,3 @@ +from .format import format_embed + +__all__ = ["format_embed"] diff --git a/courageous_comets/ui/embeds/about.py b/courageous_comets/ui/embeds/about.py new file mode 100644 index 0000000..349fbfb --- /dev/null +++ b/courageous_comets/ui/embeds/about.py @@ -0,0 +1,34 @@ +import discord + +from courageous_comets import __version__ +from courageous_comets.ui.embeds import format_embed + +TEMPLATE = """ +{body} + +Click the link in the header to visit the documentation! +""" + + +def render(body: str) -> discord.Embed: + """ + Render the about message into an embed. + + Parameters + ---------- + body : str + The body of the about message. + + Returns + ------- + discord.Embed + The rendered embed. + """ + embed = discord.Embed( + title="About Courageous Comets", + description=TEMPLATE.format_map({"body": body}), + color=discord.Color.blurple(), + url=f"https://thijsfranck.github.io/courageous-comets/{__version__}/", + timestamp=discord.utils.utcnow(), + ) + return format_embed(embed) diff --git a/courageous_comets/ui/embeds/format.py b/courageous_comets/ui/embeds/format.py new file mode 100644 index 0000000..0404117 --- /dev/null +++ b/courageous_comets/ui/embeds/format.py @@ -0,0 +1,24 @@ +import discord + +from courageous_comets import __version__ + + +def format_embed(embed: discord.Embed) -> discord.Embed: + """ + Format the given embed for consistent branding across interactions. + + Includes a footer with the version of the app. + + Parameters + ---------- + embed : discord.Embed + The embed to format. + + Returns + ------- + discord.Embed + The formatted embed. + """ + footer_text = f"Generated using Courageous Comets {__version__}" + embed.set_footer(text=footer_text) + return embed diff --git a/courageous_comets/ui/embeds/message_sentiment.py b/courageous_comets/ui/embeds/message_sentiment.py new file mode 100644 index 0000000..62ab860 --- /dev/null +++ b/courageous_comets/ui/embeds/message_sentiment.py @@ -0,0 +1,31 @@ +import discord + +from courageous_comets.models import SentimentResult +from courageous_comets.ui.components import message_sentiment +from courageous_comets.ui.embeds import format_embed + + +def render(data: SentimentResult) -> discord.Embed: + """ + Render the sentiment analysis results into an embed. + + Parameters + ---------- + data : SentimentResult + The sentiment analysis results. + + Returns + ------- + discord.Embed + The rendered embed. + """ + color = discord.Color.green() if data.compound >= 0 else discord.Color.red() + + embed = discord.Embed( + title="Message Sentiment", + description=message_sentiment.render(data), + color=color, + timestamp=discord.utils.utcnow(), + ) + + return format_embed(embed) diff --git a/courageous_comets/ui/embeds/search_results.py b/courageous_comets/ui/embeds/search_results.py new file mode 100644 index 0000000..9f41a47 --- /dev/null +++ b/courageous_comets/ui/embeds/search_results.py @@ -0,0 +1,29 @@ +import discord + +from courageous_comets.ui.components import search_results +from courageous_comets.ui.embeds import format_embed + + +def render(query: str, messages: list[discord.Message]) -> discord.Embed: + """ + Render a list of messages into an embed. + + Parameters + ---------- + query : str + The query used to find the messages. + messages : list[discord.Message] + The messages to render. + + Returns + ------- + discord.Embed + The rendered embed. + """ + embed = discord.Embed( + title="Found related messages 🚀", + description=search_results.render(query, messages), + colour=discord.Colour.blurple(), + timestamp=discord.utils.utcnow(), + ) + return format_embed(embed) diff --git a/courageous_comets/ui/embeds/user_sentiment.py b/courageous_comets/ui/embeds/user_sentiment.py new file mode 100644 index 0000000..a86f1f0 --- /dev/null +++ b/courageous_comets/ui/embeds/user_sentiment.py @@ -0,0 +1,28 @@ +import discord + +from courageous_comets.ui.components import user_sentiment +from courageous_comets.ui.embeds import format_embed + + +def render(user: str, compound: float) -> discord.Embed: + """ + Render the sentiment analysis results for the given user into an embed. + + Parameters + ---------- + user : str + The user's name. + compound : float + The compound sentiment score. + + Returns + ------- + discord.Embed + The rendered embed. + """ + embed = discord.Embed( + title="User Sentiment", + description=user_sentiment.render(user, compound), + timestamp=discord.utils.utcnow(), + ) + return format_embed(embed) diff --git a/docs/admin-guide/configuration.md b/docs/admin-guide/configuration.md index f17b319..11f4ab6 100644 --- a/docs/admin-guide/configuration.md +++ b/docs/admin-guide/configuration.md @@ -6,6 +6,7 @@ The following environment variables are available to configure the application: | --------------------------------------------------------------------------------- | ------------------------------------------------------------------------ | -------- | ------------------ | | [`DISCORD_TOKEN`](#discord_token) | The Discord bot token. | Yes | - | | [`BOT_CONFIG_PATH`](#bot_config_path) | The path to the bot's configuration file. | No | `application.yaml` | +| [`DISCORD_API_CONCURRENCY`](#discord_api_concurrency) | The maximum number of concurrent Discord API requests. | No | `3` | | [`ENVIRONMENT`](#environment) | The environment in which the application is running. | No | `production` | | [`HF_DOWNLOAD_CONCURRENCY`](#hf_download_concurrency) | The maximum number of concurrent downloads when installing transformers. | No | `3` | | [`HF_HOME`](#hf_home) | The directory containing Huggingface Transformers data files. | No | `hf_data` | @@ -62,6 +63,10 @@ transformers: By default, the application searches for a file named `application.yaml` in the directory from which it is launched. In the Docker image, this file is located at `/app/application.yaml`. +### `DISCORD_API_CONCURRENCY` + +The maximum number of concurrent Discord API requests. By default, this is set to `3`. + ### `ENVIRONMENT` The environment in which the application is running. Set this to `development` to enable development features diff --git a/courageous_comets/cogs/boilerplate.py b/examples/cog.py similarity index 100% rename from courageous_comets/cogs/boilerplate.py rename to examples/cog.py diff --git a/poetry.lock b/poetry.lock index 9a6e48d..714345c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -161,16 +161,19 @@ six = ">=1.6.1,<2.0" wheel = ">=0.23.0,<1.0" [[package]] -name = "async-lru" -version = "2.0.4" -description = "Simple LRU cache for asyncio" +name = "asyncache" +version = "0.3.1" +description = "Helpers to use cachetools with async code." optional = false -python-versions = ">=3.8" +python-versions = ">=3.8,<4.0" files = [ - {file = "async-lru-2.0.4.tar.gz", hash = "sha256:b8a59a5df60805ff63220b2a0c5b5393da5521b113cd5465a44eb037d81a5627"}, - {file = "async_lru-2.0.4-py3-none-any.whl", hash = "sha256:ff02944ce3c288c5be660c42dbcca0742b32c3b279d6dceda655190240b99224"}, + {file = "asyncache-0.3.1-py3-none-any.whl", hash = "sha256:ef20a1024d265090dd1e0785c961cf98b9c32cc7d9478973dcf25ac1b80011f5"}, + {file = "asyncache-0.3.1.tar.gz", hash = "sha256:9a1e60a75668e794657489bdea6540ee7e3259c483517b934670db7600bf5035"}, ] +[package.dependencies] +cachetools = ">=5.2.0,<6.0.0" + [[package]] name = "attrs" version = "23.2.0" @@ -215,6 +218,17 @@ files = [ {file = "braceexpand-0.1.7.tar.gz", hash = "sha256:e6e539bd20eaea53547472ff94f4fb5c3d3bf9d0a89388c4b56663aba765f705"}, ] +[[package]] +name = "cachetools" +version = "5.4.0" +description = "Extensible memoizing collections and decorators" +optional = false +python-versions = ">=3.7" +files = [ + {file = "cachetools-5.4.0-py3-none-any.whl", hash = "sha256:3ae3b49a3d5e28a77a0be2b37dbcb89005058959cb2323858c2657c4a8cab474"}, + {file = "cachetools-5.4.0.tar.gz", hash = "sha256:b8adc2e7c07f105ced7bc56dbb6dfbe7c4a00acce20e2227b3f355be89bc6827"}, +] + [[package]] name = "certifi" version = "2024.7.4" @@ -3278,4 +3292,4 @@ test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", [metadata] lock-version = "2.0" python-versions = "~3.12" -content-hash = "f62c686870613e4705ef3cad1e8f7c980c0225228bcc5093c8d91282ee985a71" +content-hash = "7abf0795cda093b153a5895a03f471dd2a972412a1a00e3a6249ec16b94b8f18" diff --git a/pyproject.toml b/pyproject.toml index 16a35be..831a0b4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,8 +22,9 @@ redisvl = { extras = ["hiredis"], version = "0.2.3" } torch = { version = "^2.3.1+cpu", source = "pytorch-cpu" } transformers = "^4.42.4" unidecode = "^1.3.8" -async-lru = "^2.0.4" matplotlib = "^3.9.1" +asyncache = "^0.3.1" +cachetools = "^5.4.0" [tool.poetry.dev-dependencies] commitizen = "3.27.0" From 7aa6e76b74cbb14e7a5e469729811eb619d4ff21 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Sat, 27 Jul 2024 12:31:38 +0000 Subject: [PATCH 118/168] test: fix broken test --- tests/integrations/test__messages.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/integrations/test__messages.py b/tests/integrations/test__messages.py index d0ae573..31b6857 100644 --- a/tests/integrations/test__messages.py +++ b/tests/integrations/test__messages.py @@ -31,6 +31,7 @@ async def test__messages_on_message__message_saved_to_redis( message.clean_content = "The quick brown fox jumps over the lazy dog." message.id = 1 message.author.id = 1 + message.author.bot = False message.channel.id = 1 message.guild.id = 1 From 0ab3a8fcc906dc57675c06f3f305b5941b421116 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Sat, 27 Jul 2024 12:40:25 +0000 Subject: [PATCH 119/168] fix: avoid error when no sentiment data is available for a user --- courageous_comets/cogs/sentiment.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/courageous_comets/cogs/sentiment.py b/courageous_comets/cogs/sentiment.py index db700f4..07a11dd 100644 --- a/courageous_comets/cogs/sentiment.py +++ b/courageous_comets/cogs/sentiment.py @@ -86,8 +86,11 @@ async def show_user_sentiment( scope=StatisticScope.USER, ) - if not sentiment_results: - raise MessagesNotFound + if not sentiment_results or "avg_sentiment" not in sentiment_results[0]: + await interaction.followup.send( + f"No sentiment data found for {user.mention}.", + ephemeral=True, + ) average_sentiment = sentiment_results[0]["avg_sentiment"] From 0f1805b5ab7eace03a2d78d8924482a4ba7f2534 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Sat, 27 Jul 2024 15:12:17 +0000 Subject: [PATCH 120/168] refactor: move message processing to a separate module --- courageous_comets/client.py | 2 + courageous_comets/cogs/keywords.py | 34 +++++++++---- courageous_comets/cogs/messages.py | 65 +++++------------------- courageous_comets/cogs/sentiment.py | 38 ++++++++++---- courageous_comets/processing.py | 79 +++++++++++++++++++++++++++++ 5 files changed, 147 insertions(+), 71 deletions(-) create mode 100644 courageous_comets/processing.py diff --git a/courageous_comets/client.py b/courageous_comets/client.py index 6cfd734..2b83dfa 100644 --- a/courageous_comets/client.py +++ b/courageous_comets/client.py @@ -11,6 +11,7 @@ from courageous_comets import settings from courageous_comets.nltk import init_nltk from courageous_comets.redis import init_redis +from courageous_comets.vectorizer import Vectorizer DESCRIPTION = """ Thank you for using Courageous Comets! ☄️ @@ -44,6 +45,7 @@ class CourageousCometsBot(commands.Bot): """ redis: Redis | None = None + vectorizer = Vectorizer() def __init__(self) -> None: super().__init__( diff --git a/courageous_comets/cogs/keywords.py b/courageous_comets/cogs/keywords.py index 350ed2e..4b94bec 100644 --- a/courageous_comets/cogs/keywords.py +++ b/courageous_comets/cogs/keywords.py @@ -4,13 +4,14 @@ from discord import app_commands from discord.ext import commands +from courageous_comets import preprocessing from courageous_comets.client import CourageousCometsBot from courageous_comets.discord.messages import resolve_messages -from courageous_comets.preprocessing import process +from courageous_comets.processing import process_message +from courageous_comets.redis.keys import key_schema from courageous_comets.redis.messages import get_messages_by_semantics_similarity from courageous_comets.ui.embeds import search_results from courageous_comets.utils import contextmenu -from courageous_comets.vectorizer import Vectorizer logger = logging.getLogger(__name__) @@ -24,7 +25,6 @@ class Keywords(commands.Cog): def __init__(self, bot: CourageousCometsBot) -> None: self.bot = bot - self.vectorizer = Vectorizer() for attribute in dir(self): obj = getattr(self, attribute, None) @@ -75,13 +75,13 @@ async def search_by_topic(self, interaction: discord.Interaction, query: str) -> await interaction.response.defer(ephemeral=True, thinking=True) - query_processed = process(query) - query_vector = await self.vectorizer.aencode(query_processed) + query_processed = preprocessing.process(query) + embedding = await self.bot.vectorizer.aencode(query_processed) messages = await get_messages_by_semantics_similarity( self.bot.redis, guild_id=str(interaction.guild.id), - embedding=query_vector, + embedding=embedding, limit=5, ) @@ -122,7 +122,7 @@ async def search_by_topic_with_message( ephemeral=True, ) - if interaction.guild is None: + if message.guild is None: return await interaction.response.send_message( "This feature is only available in guilds.", ephemeral=True, @@ -130,13 +130,25 @@ async def search_by_topic_with_message( await interaction.response.defer(ephemeral=True, thinking=True) - query_processed = process(message.clean_content) - query_vector = await self.vectorizer.aencode(query_processed) + key = key_schema.guild_messages( + guild_id=message.guild.id, + message_id=message.id, + ) + + if not self.bot.redis.exists(key): + await process_message( + message, + redis=self.bot.redis, + vectorizer=self.bot.vectorizer, + ) + + content_processed = preprocessing.process(message.clean_content) + embedding = await self.bot.vectorizer.aencode(content_processed) messages = await get_messages_by_semantics_similarity( self.bot.redis, - guild_id=str(interaction.guild.id), - embedding=query_vector, + guild_id=str(message.guild.id), + embedding=embedding, limit=6, ) diff --git a/courageous_comets/cogs/messages.py b/courageous_comets/cogs/messages.py index 10203da..fc2ce00 100644 --- a/courageous_comets/cogs/messages.py +++ b/courageous_comets/cogs/messages.py @@ -3,13 +3,8 @@ import discord from discord.ext import commands -from courageous_comets import preprocessing from courageous_comets.client import CourageousCometsBot -from courageous_comets.models import MessageAnalysis -from courageous_comets.redis import messages -from courageous_comets.sentiment import calculate_sentiment -from courageous_comets.vectorizer import Vectorizer -from courageous_comets.words import tokenize_sentence, word_frequency +from courageous_comets.processing import process_message logger = logging.getLogger(__name__) @@ -19,7 +14,6 @@ class Messages(commands.Cog): def __init__(self, bot: CourageousCometsBot) -> None: self.bot = bot - self.vectorizer = Vectorizer() @commands.Cog.listener(name="on_message") async def on_message(self, message: discord.Message) -> None: @@ -33,7 +27,7 @@ async def on_message(self, message: discord.Message) -> None: """ await self.save_message(message) - async def save_message(self, message: discord.Message) -> None: # noqa: PLR0911 + async def save_message(self, message: discord.Message) -> None: """ Save a message on Redis. @@ -50,54 +44,23 @@ async def save_message(self, message: discord.Message) -> None: # noqa: PLR0911 message.id, ) - if not message.guild: - return logger.debug( - "Ignoring message %s because it's not in a guild", - message.id, - ) - - if message.author.bot: - return logger.debug( - "Ignoring message %s because it's from a bot", - message.id, - ) - - if not message.clean_content: - return logger.debug( - "Ignoring message %s because it's empty", - message.id, - ) - - if self.bot.user in message.mentions and "sync" in message.clean_content: - return logger.debug( - "Ignoring message %s because it's a sync message", - message.id, - ) - - text = preprocessing.process(message.clean_content) + validation_errors = { + "bot": message.author.bot, + "empty": not message.clean_content, + "sync": self.bot.user in message.mentions and "sync" in message.clean_content, + } - if not text: + if any(validation_errors.values()): return logger.debug( - "Ignoring message %s because it's empty after processing", + "Ignoring message %s, reason: %s", message.id, + validation_errors, ) - embedding = await self.vectorizer.aencode(text) - sentiment = calculate_sentiment(text) - tokens = tokenize_sentence(text) - - key = await messages.save_message( - self.bot.redis, - MessageAnalysis( - user_id=str(message.author.id), - message_id=str(message.id), - channel_id=str(message.channel.id), - guild_id=str(message.guild.id), - timestamp=message.created_at, - embedding=embedding, - sentiment=sentiment, - tokens=word_frequency(tokens), - ), + key = await process_message( + message, + redis=self.bot.redis, + vectorizer=self.bot.vectorizer, ) return logger.info( diff --git a/courageous_comets/cogs/sentiment.py b/courageous_comets/cogs/sentiment.py index 07a11dd..808bb7c 100644 --- a/courageous_comets/cogs/sentiment.py +++ b/courageous_comets/cogs/sentiment.py @@ -8,6 +8,7 @@ from courageous_comets.client import CourageousCometsBot from courageous_comets.discord.messages import resolve_messages from courageous_comets.enums import StatisticScope +from courageous_comets.processing import process_message from courageous_comets.redis.keys import key_schema from courageous_comets.redis.messages import ( get_average_sentiment, @@ -18,7 +19,6 @@ from courageous_comets.ui.charts import sentiment_bars from courageous_comets.ui.embeds import message_sentiment, search_results, user_sentiment from courageous_comets.utils import contextmenu -from courageous_comets.vectorizer import Vectorizer logger = logging.getLogger(__name__) @@ -32,7 +32,6 @@ class Sentiment(commands.Cog): def __init__(self, bot: CourageousCometsBot) -> None: self.bot = bot - self.vectorizer = Vectorizer() for attribute in dir(self): obj = getattr(self, attribute, None) @@ -144,11 +143,22 @@ async def show_message_sentiment( message_id=message.id, ) + if not await self.bot.redis.exists(key): + logger.debug("Message %s is not previously saved. Processing it.", message.id) + await process_message( + message, + redis=self.bot.redis, + vectorizer=self.bot.vectorizer, + ) + analysis_result = await get_message_sentiment(key, redis=self.bot.redis) - if not analysis_result: - prepared_content = preprocessing.process(message.clean_content) - analysis_result = calculate_sentiment(prepared_content) + if analysis_result is None: + logger.warning("Could not find analysis result for message %s.", message.id) + return await interaction.followup.send( + "No analysis results were found.", + ephemeral=True, + ) embed = message_sentiment.render(analysis_result) @@ -259,12 +269,22 @@ async def search_by_sentiment_with_message( message_id=message.id, ) - # Check whether the sentiment analysis is cached + if not await self.bot.redis.exists(key): + logger.debug("Message %s is not previously saved. Processing it.", message.id) + await process_message( + message, + redis=self.bot.redis, + vectorizer=self.bot.vectorizer, + ) + analysis_result = await get_message_sentiment(key, redis=self.bot.redis) - if not analysis_result: - prepared_content = preprocessing.process(message.clean_content) - analysis_result = calculate_sentiment(prepared_content) + if analysis_result is None: + logger.warning("Could not find analysis result for message %s.", message.id) + return await interaction.followup.send( + "No related messages were found.", + ephemeral=True, + ) messages = await get_messages_by_sentiment_similarity( self.bot.redis, diff --git a/courageous_comets/processing.py b/courageous_comets/processing.py new file mode 100644 index 0000000..547ec58 --- /dev/null +++ b/courageous_comets/processing.py @@ -0,0 +1,79 @@ +import asyncio +import logging + +import discord +from redis.asyncio import Redis + +from courageous_comets import preprocessing +from courageous_comets.models import MessageAnalysis +from courageous_comets.redis import messages +from courageous_comets.sentiment import calculate_sentiment +from courageous_comets.vectorizer import Vectorizer +from courageous_comets.words import tokenize_sentence, word_frequency + +logger = logging.getLogger(__name__) + + +async def process_message( + message: discord.Message, + *, + redis: Redis, + vectorizer: Vectorizer, +) -> str | None: + """ + Process a message and save it to Redis. + + The following steps are taken to process the message: + + - Clean the message content. + - Encode the message content. + - Calculate the sentiment of the message. + - Tokenize the message content. + + Parameters + ---------- + message : discord.Message + The message to process. + redis : Redis + The Redis connection. + vectorizer : Vectorizer + The vectorizer to use for encoding the message. + + Returns + ------- + str | None + The id of the saved message or None if the message was ignored + """ + if not message.guild: + return logger.debug( + "Ignoring message %s because it's not in a guild", + message.id, + ) + + text = preprocessing.process(message.clean_content) + + if not text: + return logger.debug( + "Ignoring message %s because it's empty after processing", + message.id, + ) + + embedding, sentiment, tokens = await asyncio.gather( + vectorizer.aencode(text), + asyncio.to_thread(calculate_sentiment, text), + asyncio.to_thread(tokenize_sentence, text), + ) + + return await messages.save_message( + redis, + MessageAnalysis( + user_id=str(message.author.id), + message_id=str(message.id), + channel_id=str(message.channel.id), + guild_id=str(message.guild.id), + timestamp=message.created_at, + embedding=embedding, + sentiment=sentiment, + tokens=word_frequency(tokens), + ), + ) From 84e7a42e9497453ee9937bf5c8dde88f8e3a29f9 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Sat, 27 Jul 2024 15:12:32 +0000 Subject: [PATCH 121/168] fix: show channel in search results --- courageous_comets/ui/components/message.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/courageous_comets/ui/components/message.py b/courageous_comets/ui/components/message.py index a31efae..61a252b 100644 --- a/courageous_comets/ui/components/message.py +++ b/courageous_comets/ui/components/message.py @@ -1,7 +1,7 @@ import discord TEMPLATE = """ -{author} {timestamp}: +{channel} {author} {timestamp}: {content} """ @@ -27,8 +27,14 @@ def render(message: discord.Message) -> str: str The formatted string. """ + channel = ( + f"{message.channel.mention}" + if isinstance(message.channel, discord.abc.GuildChannel) + else "" + ) return TEMPLATE.format_map( { + "channel": channel, "author": message.author.mention, "timestamp": discord.utils.format_dt(message.created_at, style="R"), "content": _shorten(message.clean_content), From a53569315c63ed0e2d87b5b2c2bec9d25cf1a525 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Sat, 27 Jul 2024 15:16:47 +0000 Subject: [PATCH 122/168] fix: increase search results max preview length --- courageous_comets/ui/components/message.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/courageous_comets/ui/components/message.py b/courageous_comets/ui/components/message.py index 61a252b..39d53f7 100644 --- a/courageous_comets/ui/components/message.py +++ b/courageous_comets/ui/components/message.py @@ -6,7 +6,7 @@ """ -def _shorten(string: str, *, limit: int = 50) -> str: +def _shorten(string: str, *, limit: int = 200) -> str: """Trim a string if necessary given `limit`.""" if len(string) > limit: string = string[: limit - 3] + "..." From d8df2d8be27e24153fc2eebb629eb0e6da0aae83 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Sat, 27 Jul 2024 17:01:56 +0000 Subject: [PATCH 123/168] feat: add details to frequency embed --- courageous_comets/cogs/frequency.py | 96 +++---------------- courageous_comets/ui/charts/frequency_line.py | 71 ++++++++++++++ .../ui/embeds/message_frequency.py | 71 ++++++++++++++ 3 files changed, 156 insertions(+), 82 deletions(-) create mode 100644 courageous_comets/ui/charts/frequency_line.py create mode 100644 courageous_comets/ui/embeds/message_frequency.py diff --git a/courageous_comets/cogs/frequency.py b/courageous_comets/cogs/frequency.py index 4749700..afb630a 100644 --- a/courageous_comets/cogs/frequency.py +++ b/courageous_comets/cogs/frequency.py @@ -1,80 +1,12 @@ -import io - import discord -import matplotlib.pyplot as plt from discord import app_commands from discord.ext import commands -from matplotlib.dates import ( - AutoDateLocator, - ConciseDateFormatter, - DayLocator, - HourLocator, - MinuteLocator, -) - -from courageous_comets import models + from courageous_comets.client import CourageousCometsBot from courageous_comets.enums import Duration from courageous_comets.redis.messages import get_messages_frequency - - -def plot_message_frequency( - frequencies: list[models.MessageFrequency], - duration: Duration, -) -> io.BytesIO: - """ - Plot the frequency of messages. - - Creates a line plot of number of messages over intervals and saves it to a file. - - Parameters - ---------- - frequencies: list[MessageFrequency] - A list of message frequency. - - Returns - ------- - io.BytesIO - A plot of the frequency in memory. - - Note - ---- - Assumes list of frequencies is not empty. - """ - _, ax = plt.subplots() - # If the there's only one point, use a bar plot, otherwise a line plot - if len(frequencies) > 1: - ax.plot( - [frequency.timestamp for frequency in frequencies], # type: ignore - [frequency.num_messages for frequency in frequencies], - ) - locator = AutoDateLocator().get_locator( - frequencies[0].timestamp, - frequencies[-1].timestamp, - ) - else: - ax.bar([frequencies[0].timestamp], [frequencies[0].num_messages], width=0.01) # type: ignore - if duration == Duration.daily: - locator = DayLocator() - elif duration == Duration.hourly: - locator = HourLocator() - elif duration == Duration.minute: - locator = MinuteLocator() - else: - locator = AutoDateLocator() - formatter = ConciseDateFormatter(locator) - - ax.xaxis.set_major_locator(locator) - ax.xaxis.set_major_formatter(formatter) - - ax.set_ylabel("Number of messages.") - ax.set_title("Message Frequency") - - file = io.BytesIO() - plt.savefig(file) - file.seek(0) - - return file +from courageous_comets.ui.charts import frequency_line +from courageous_comets.ui.embeds import message_frequency class Frequency(commands.Cog): @@ -116,6 +48,12 @@ async def frequency_command( ephemeral=True, ) + if duration not in Duration: + return await interaction.response.send_message( + "Invalid duration provided.", + ephemeral=True, + ) + frequencies = await get_messages_frequency( self.bot.redis, guild_id=str(interaction.guild.id), @@ -128,20 +66,14 @@ async def frequency_command( ephemeral=True, ) - view = discord.Embed( - title="Message frequencies", - description="Message frequencies", - color=discord.Colour.purple(), - timestamp=discord.utils.utcnow(), - ) + embed = message_frequency.render(frequencies, duration) - chart = plot_message_frequency(frequencies, duration) - chart_file = discord.File(chart, filename="frequency.png") - view.set_image(url="attachment://frequency.png") + chart = frequency_line.render(frequencies, duration) + embed.set_image(url=f"attachment://{chart.filename}") return await interaction.response.send_message( - embed=view, - file=chart_file, + embed=embed, + file=chart, ephemeral=True, ) diff --git a/courageous_comets/ui/charts/frequency_line.py b/courageous_comets/ui/charts/frequency_line.py new file mode 100644 index 0000000..884809e --- /dev/null +++ b/courageous_comets/ui/charts/frequency_line.py @@ -0,0 +1,71 @@ +import io + +import discord +import matplotlib.pyplot as plt +from matplotlib.dates import ( + AutoDateLocator, + ConciseDateFormatter, + DayLocator, + HourLocator, + MinuteLocator, +) + +from courageous_comets import models +from courageous_comets.enums import Duration + + +def render( + frequencies: list[models.MessageFrequency], + duration: Duration, +) -> discord.File: + """ + Plot the frequency of messages. + + Creates a line plot of number of messages over intervals and saves it to a file. + + Parameters + ---------- + frequencies: list[MessageFrequency] + A list of message frequency. + + Returns + ------- + discord.File + A plot of the frequency in memory. + + Note + ---- + Assumes list of frequencies is not empty. + """ + _, ax = plt.subplots() + # If the there's only one point, use a bar plot, otherwise a line plot + if len(frequencies) > 1: + ax.plot( + [frequency.timestamp for frequency in frequencies], # type: ignore + [frequency.num_messages for frequency in frequencies], + ) + locator = AutoDateLocator().get_locator( + frequencies[0].timestamp, + frequencies[-1].timestamp, + ) + else: + ax.bar([frequencies[0].timestamp], [frequencies[0].num_messages], width=0.01) # type: ignore + if duration == Duration.daily: + locator = DayLocator() + elif duration == Duration.hourly: + locator = HourLocator() + elif duration == Duration.minute: + locator = MinuteLocator() + formatter = ConciseDateFormatter(locator) + + ax.xaxis.set_major_locator(locator) + ax.xaxis.set_major_formatter(formatter) + + ax.set_ylabel("Number of messages.") + ax.set_title("Message Frequency") + + file_ = io.BytesIO() + plt.savefig(file_) + file_.seek(0) + + return discord.File(file_, filename="message_frequency.png") diff --git a/courageous_comets/ui/embeds/message_frequency.py b/courageous_comets/ui/embeds/message_frequency.py new file mode 100644 index 0000000..b5ba057 --- /dev/null +++ b/courageous_comets/ui/embeds/message_frequency.py @@ -0,0 +1,71 @@ +import discord + +from courageous_comets import models +from courageous_comets.enums import Duration +from courageous_comets.ui.embeds import format_embed + +DURATION_NAME = { + Duration.daily: "day", + Duration.hourly: "hour", + Duration.minute: "minute", +} + +TEMPLATE = """ +The chart below shows the number of messages sent by users on this server per **{duration}**. + +Times shown in the chart are in UTC. +""" + + +def render(frequencies: list[models.MessageFrequency], duration: Duration) -> discord.Embed: + """ + Render the frequency of messages. + + Creates an embed of the frequency of messages over intervals. + + Parameters + ---------- + frequencies: list[MessageFrequency] + A list of message frequency. + + Returns + ------- + discord.Embed + An embed of the frequency. + """ + template_vars = { + "duration": DURATION_NAME[duration], + } + + embed = discord.Embed( + title="Message frequencies", + description=TEMPLATE.format_map(template_vars), + color=discord.Colour.purple(), + timestamp=discord.utils.utcnow(), + ) + + total_messages = sum(frequency.num_messages for frequency in frequencies) + peak_frequency = max(frequencies, key=lambda frequency: frequency.num_messages) + + time_style = "D" if duration == Duration.daily else "t" + most_active_time = discord.utils.format_dt(peak_frequency.timestamp, style=time_style) + + embed.add_field( + name="Total", + value=total_messages, + inline=True, + ) + + embed.add_field( + name="Peak", + value=peak_frequency.num_messages, + inline=True, + ) + + embed.add_field( + name="Most active time", + value=most_active_time, + inline=True, + ) + + return format_embed(embed) From d57edffdfd3e753f2736f4265c8d192b7a4ce39b Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Sat, 27 Jul 2024 17:02:16 +0000 Subject: [PATCH 124/168] feat: harmonize sentiment embeds --- courageous_comets/cogs/sentiment.py | 14 ++++-- courageous_comets/redis/messages.py | 33 ++++++++----- courageous_comets/ui/charts/sentiment_bars.py | 38 +++++++++++++-- .../ui/components/message_sentiment.py | 31 ------------ .../ui/embeds/message_sentiment.py | 30 +++++++++++- courageous_comets/ui/embeds/user_sentiment.py | 48 +++++++++++++++---- 6 files changed, 133 insertions(+), 61 deletions(-) delete mode 100644 courageous_comets/ui/components/message_sentiment.py diff --git a/courageous_comets/cogs/sentiment.py b/courageous_comets/cogs/sentiment.py index 808bb7c..d9e4a79 100644 --- a/courageous_comets/cogs/sentiment.py +++ b/courageous_comets/cogs/sentiment.py @@ -85,16 +85,22 @@ async def show_user_sentiment( scope=StatisticScope.USER, ) - if not sentiment_results or "avg_sentiment" not in sentiment_results[0]: + if not sentiment_results: await interaction.followup.send( f"No sentiment data found for {user.mention}.", ephemeral=True, ) - average_sentiment = sentiment_results[0]["avg_sentiment"] + average_sentiment = sentiment_results[0] + + embed = user_sentiment.render(user, average_sentiment) + + chart = sentiment_bars.for_user(average_sentiment) + embed.set_image(url=f"attachment://{chart.filename}") return await interaction.followup.send( - embed=user_sentiment.render(user.mention, average_sentiment), + embed=embed, + file=chart, ephemeral=True, ) @@ -162,7 +168,7 @@ async def show_message_sentiment( embed = message_sentiment.render(analysis_result) - chart = sentiment_bars.render(message.id, analysis_result) + chart = sentiment_bars.for_message(message.id, analysis_result) embed.set_image(url=f"attachment://{chart.filename}") return await interaction.followup.send(embed=embed, file=chart, ephemeral=True) diff --git a/courageous_comets/redis/messages.py b/courageous_comets/redis/messages.py index 9edf30d..256849c 100644 --- a/courageous_comets/redis/messages.py +++ b/courageous_comets/redis/messages.py @@ -427,6 +427,7 @@ async def get_messages_frequency( # noqa: PLR0913 # Execute the aggregation query on the index results = await index.aggregate(query) # type: ignore + # Deserialize all rows as dictionaries. Each row is a flat list of key-value pairs. return [ models.MessageFrequency.model_validate_strings(dict(itertools.batched(row, 2))) @@ -441,7 +442,7 @@ async def get_average_sentiment( ids: list[str] | None = None, scope: StatisticScope = StatisticScope.CHANNEL, limit: int = settings.QUERY_LIMIT, -) -> list[dict[str, float]]: +) -> list[models.SentimentResult]: """ Get the average sentiment of messages for the given ids and scope. @@ -462,15 +463,23 @@ async def get_average_sentiment( search_scope = build_search_scope(guild_id, ids, scope) index = _get_raw_index(redis) - # Define a reducer to calculate the average sentiment compound score and alias the result as - # "avg_sentiment" - reducer = reducers.avg("@sentiment_compound").alias("avg_sentiment") + # Define reducers to calculate the average scores for each sentiment type + avg_sentiment = reducers.avg("@sentiment_compound").alias("sentiment_compound") + avg_negativity = reducers.avg("@sentiment_neg").alias("sentiment_neg") + avg_neutrality = reducers.avg("@sentiment_neu").alias("sentiment_neu") + avg_positivity = reducers.avg("@sentiment_pos").alias("sentiment_pos") # Build the aggregation query query = ( aggregations.AggregateRequest(f"{search_scope!s}") .limit(0, limit) - .group_by([f"@{scope}"], reducer) + .group_by( + [f"@{scope}"], + avg_sentiment, + avg_negativity, + avg_neutrality, + avg_positivity, + ) ) # Execute the aggregation query on the index @@ -478,10 +487,12 @@ async def get_average_sentiment( # Deserialize all rows as dictionaries. Each row is a flat list of key-value pairs. return [ - { - key: float(value) - for row in results.rows - for key, value in itertools.batched(row, 2) - if value is not None - }, + models.SentimentResult.model_validate( + { + key: float(value) + for row in results.rows + for key, value in itertools.batched(row, 2) + if value is not None + }, + ), ] diff --git a/courageous_comets/ui/charts/sentiment_bars.py b/courageous_comets/ui/charts/sentiment_bars.py index 42eebb3..e0d01cb 100644 --- a/courageous_comets/ui/charts/sentiment_bars.py +++ b/courageous_comets/ui/charts/sentiment_bars.py @@ -1,3 +1,6 @@ +import io +from pathlib import Path + import discord import matplotlib.pyplot as plt @@ -8,7 +11,7 @@ CACHE_DIR.mkdir(parents=True, exist_ok=True) -def render( +def for_message( message_id: str | int, data: models.SentimentResult, ) -> discord.File: @@ -39,6 +42,35 @@ def render( if chart_path.exists(): return discord.File(chart_path, filename=f"{message_id}.png") + _plot(data, chart_path) + + return discord.File(chart_path, filename=f"{message_id}.png") + + +def for_user(data: models.SentimentResult) -> discord.File: + """ + Plot the sentiment analysis of a user. + + Creates a bar chart of the sentiment analysis of a user and saves it to a temporary file. + + Parameters + ---------- + analysis_result : SentimentResult + The result of sentiment analysis on a user. + + Returns + ------- + discord.File + The file containing the saved image. + """ + file_ = io.BytesIO() + _plot(data, file_) + file_.seek(0) + + return discord.File(file_, filename="user_sentiment.png") + + +def _plot(data: models.SentimentResult, target: Path | io.BytesIO) -> None: _, ax = plt.subplots() ax.bar( [ @@ -60,6 +92,4 @@ def render( ax.set_ylabel("Sentiment Score") ax.set_title("Sentiment Analysis") - plt.savefig(chart_path) - - return discord.File(chart_path, filename=f"{message_id}.png") + plt.savefig(target) diff --git a/courageous_comets/ui/components/message_sentiment.py b/courageous_comets/ui/components/message_sentiment.py deleted file mode 100644 index bb1e355..0000000 --- a/courageous_comets/ui/components/message_sentiment.py +++ /dev/null @@ -1,31 +0,0 @@ -from courageous_comets.models import SentimentResult -from courageous_comets.ui.components import sentiment - -TEMPLATE = """ -Overall the sentiment of the message is **{sentiment}**. - -Here's a breakdown of the scores: - -- Negative: {neg} -- Neutral: {neu} -- Positive: {pos} - -The compound score is {compound}. -""" - - -def render(data: SentimentResult) -> str: - """ - Render the sentiment analysis results into a string. - - Returns - ------- - str - The rendered sentiment analysis results. - """ - return TEMPLATE.format_map( - { - **data.model_dump(), - "sentiment": sentiment.render(data.compound), - }, - ) diff --git a/courageous_comets/ui/embeds/message_sentiment.py b/courageous_comets/ui/embeds/message_sentiment.py index 62ab860..ce4c836 100644 --- a/courageous_comets/ui/embeds/message_sentiment.py +++ b/courageous_comets/ui/embeds/message_sentiment.py @@ -1,9 +1,13 @@ import discord from courageous_comets.models import SentimentResult -from courageous_comets.ui.components import message_sentiment +from courageous_comets.ui.components import sentiment from courageous_comets.ui.embeds import format_embed +TEMPLATE = """ +Overall the sentiment of the message is **{sentiment}**. +""" + def render(data: SentimentResult) -> discord.Embed: """ @@ -21,11 +25,33 @@ def render(data: SentimentResult) -> discord.Embed: """ color = discord.Color.green() if data.compound >= 0 else discord.Color.red() + template_vars = { + "sentiment": sentiment.render(data.compound), + } + embed = discord.Embed( title="Message Sentiment", - description=message_sentiment.render(data), + description=TEMPLATE.format_map(template_vars), color=color, timestamp=discord.utils.utcnow(), ) + embed.add_field( + name="Negative", + value=f"{int(data.neg * 100)}%", + inline=True, + ) + + embed.add_field( + name="Neutral", + value=f"{int(data.neu * 100)}%", + inline=True, + ) + + embed.add_field( + name="Positive", + value=f"{int(data.pos * 100)}%", + inline=True, + ) + return format_embed(embed) diff --git a/courageous_comets/ui/embeds/user_sentiment.py b/courageous_comets/ui/embeds/user_sentiment.py index a86f1f0..75472c7 100644 --- a/courageous_comets/ui/embeds/user_sentiment.py +++ b/courageous_comets/ui/embeds/user_sentiment.py @@ -1,28 +1,58 @@ import discord -from courageous_comets.ui.components import user_sentiment +from courageous_comets.models import SentimentResult +from courageous_comets.ui.components import sentiment from courageous_comets.ui.embeds import format_embed +TEMPLATE = """ +Overall the sentiment of {user} is **{sentiment}**. +""" -def render(user: str, compound: float) -> discord.Embed: + +def render(user: discord.User | discord.Member, data: SentimentResult) -> discord.Embed: """ - Render the sentiment analysis results for the given user into an embed. + Render the sentiment analysis results into an embed. Parameters ---------- - user : str - The user's name. - compound : float - The compound sentiment score. + data : SentimentResult + The sentiment analysis results. Returns ------- discord.Embed The rendered embed. """ + color = discord.Color.green() if data.compound >= 0 else discord.Color.red() + + template_vars = { + "user": user.mention, + "sentiment": sentiment.render(data.compound), + } + embed = discord.Embed( - title="User Sentiment", - description=user_sentiment.render(user, compound), + title="Message Sentiment", + description=TEMPLATE.format_map(template_vars), + color=color, timestamp=discord.utils.utcnow(), ) + + embed.add_field( + name="Negative", + value=f"{int(data.neg * 100)}%", + inline=True, + ) + + embed.add_field( + name="Neutral", + value=f"{int(data.neu * 100)}%", + inline=True, + ) + + embed.add_field( + name="Positive", + value=f"{int(data.pos * 100)}%", + inline=True, + ) + return format_embed(embed) From 83a9009f910f66dc00e9e6b0db12202c27941106 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Sat, 27 Jul 2024 17:02:44 +0000 Subject: [PATCH 125/168] refactor: remove unused component --- .../ui/components/user_sentiment.py | 31 ------------------- 1 file changed, 31 deletions(-) delete mode 100644 courageous_comets/ui/components/user_sentiment.py diff --git a/courageous_comets/ui/components/user_sentiment.py b/courageous_comets/ui/components/user_sentiment.py deleted file mode 100644 index 5af63e6..0000000 --- a/courageous_comets/ui/components/user_sentiment.py +++ /dev/null @@ -1,31 +0,0 @@ -from courageous_comets.ui.components import sentiment - -TEMPLATE = """ -Overall the sentiment of {user} is **{sentiment}**. -Their average compound score is {compound}. -""" - - -def render(user: str, compound: float) -> str: - """ - Render the sentiment analysis score for the given user into a string. - - Parameters - ---------- - user : str - The user's name. - compound : float - The compound sentiment score. - - Returns - ------- - str - The rendered sentiment analysis results. - """ - return TEMPLATE.format_map( - { - "user": user, - "compound": compound, - "sentiment": sentiment.render(compound), - }, - ) From bdab551a7a0fcd5e49d88f31e1c7fdd7199a818b Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Sat, 27 Jul 2024 17:28:01 +0000 Subject: [PATCH 126/168] style: add spacing between blocks of code --- courageous_comets/ui/charts/frequency_line.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/courageous_comets/ui/charts/frequency_line.py b/courageous_comets/ui/charts/frequency_line.py index 884809e..23703b4 100644 --- a/courageous_comets/ui/charts/frequency_line.py +++ b/courageous_comets/ui/charts/frequency_line.py @@ -38,6 +38,7 @@ def render( Assumes list of frequencies is not empty. """ _, ax = plt.subplots() + # If the there's only one point, use a bar plot, otherwise a line plot if len(frequencies) > 1: ax.plot( @@ -56,6 +57,7 @@ def render( locator = HourLocator() elif duration == Duration.minute: locator = MinuteLocator() + formatter = ConciseDateFormatter(locator) ax.xaxis.set_major_locator(locator) From dd13726b5aa295bb27c661840bf84383f709050d Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Sat, 27 Jul 2024 20:28:44 +0000 Subject: [PATCH 127/168] feat: add praise interaction for user and message sentiment --- courageous_comets/cogs/sentiment.py | 13 ++++- courageous_comets/ui/views/__init__.py | 0 courageous_comets/ui/views/sentiment.py | 76 +++++++++++++++++++++++++ 3 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 courageous_comets/ui/views/__init__.py create mode 100644 courageous_comets/ui/views/sentiment.py diff --git a/courageous_comets/cogs/sentiment.py b/courageous_comets/cogs/sentiment.py index d9e4a79..cb447cc 100644 --- a/courageous_comets/cogs/sentiment.py +++ b/courageous_comets/cogs/sentiment.py @@ -18,6 +18,7 @@ from courageous_comets.sentiment import calculate_sentiment from courageous_comets.ui.charts import sentiment_bars from courageous_comets.ui.embeds import message_sentiment, search_results, user_sentiment +from courageous_comets.ui.views.sentiment import SentimentView from courageous_comets.utils import contextmenu logger = logging.getLogger(__name__) @@ -98,9 +99,12 @@ async def show_user_sentiment( chart = sentiment_bars.for_user(average_sentiment) embed.set_image(url=f"attachment://{chart.filename}") + view = SentimentView(user, average_sentiment) + return await interaction.followup.send( embed=embed, file=chart, + view=view, ephemeral=True, ) @@ -171,7 +175,14 @@ async def show_message_sentiment( chart = sentiment_bars.for_message(message.id, analysis_result) embed.set_image(url=f"attachment://{chart.filename}") - return await interaction.followup.send(embed=embed, file=chart, ephemeral=True) + view = SentimentView(message.author, analysis_result) + + return await interaction.followup.send( + embed=embed, + file=chart, + view=view, + ephemeral=True, + ) @app_commands.command( name="sentiment_search", diff --git a/courageous_comets/ui/views/__init__.py b/courageous_comets/ui/views/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/courageous_comets/ui/views/sentiment.py b/courageous_comets/ui/views/sentiment.py new file mode 100644 index 0000000..dfdf7d5 --- /dev/null +++ b/courageous_comets/ui/views/sentiment.py @@ -0,0 +1,76 @@ +import random +from typing import override + +import discord + +from courageous_comets import models + +STICKERS = [ + 751606065447305216, + 754109076933443614, + 772972089963577354, + 781324722394103808, + 809207198856904764, +] + + +class SentimentView(discord.ui.View): + """ + A view for interacting with a user's sentiment analysis results. + + Attributes + ---------- + user : discord.User | discord.Member + The user whose sentiment is being analyzed. + data : models.SentimentResult + The sentiment analysis results. + """ + + def __init__(self, user: discord.User | discord.Member, data: models.SentimentResult) -> None: + super().__init__() + self.user = user + self.data = data + + self.add_item(PraiseButton(user, data)) + + +class PraiseButton(discord.ui.Button): + """ + A button to praise a user. + + Attributes + ---------- + user : discord.User | discord.Member + The user to praise. + """ + + def __init__(self, user: discord.User | discord.Member, data: models.SentimentResult) -> None: + super().__init__( + style=discord.ButtonStyle.success, + label="Send Praise", + emoji="🎉", + disabled=data.compound < 0, + ) + self.user = user + self.data = data + + @override + async def callback(self, interaction: discord.Interaction) -> None: + # Fetch a random sticker to send with the praise + sticker = await interaction.client.fetch_sticker(random.choice(STICKERS)) + + # Send the praise to the user + await self.user.send( + f"You've been praised by {interaction.user.mention}!", + stickers=[sticker], # type: ignore + ) + + # Send a confirmation message + await interaction.response.send_message( + f"Sent praise to {self.user.mention}!", + ephemeral=True, + ) + + self.disabled = True + self.label = "Praise Sent" + self.emoji = "✅" From 48935f48bfd0c590c8896213b95b6a5c9de5a807 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Sat, 27 Jul 2024 20:29:34 +0000 Subject: [PATCH 128/168] refactor: remove redundant code --- courageous_comets/ui/views/sentiment.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/courageous_comets/ui/views/sentiment.py b/courageous_comets/ui/views/sentiment.py index dfdf7d5..a09b482 100644 --- a/courageous_comets/ui/views/sentiment.py +++ b/courageous_comets/ui/views/sentiment.py @@ -70,7 +70,3 @@ async def callback(self, interaction: discord.Interaction) -> None: f"Sent praise to {self.user.mention}!", ephemeral=True, ) - - self.disabled = True - self.label = "Praise Sent" - self.emoji = "✅" From 10f5750a70eda84664296e2092e5ead0761e8f0c Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Sun, 28 Jul 2024 04:12:49 +0000 Subject: [PATCH 129/168] =?UTF-8?q?bump:=20version=200.8.0=20=E2=86=92=200?= =?UTF-8?q?.9.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/CHANGELOG.md | 21 +++++++++++++++++++++ pyproject.toml | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 4620780..9101419 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,3 +1,24 @@ +## v0.9.0 (2024-07-28) + +### Feat + +- add praise interaction for user and message sentiment +- harmonize sentiment embeds +- add details to frequency embed +- refactor cogs with ui components and complete existing interactions + +### Fix + +- increase search results max preview length +- show channel in search results +- avoid error when no sentiment data is available for a user + +### Refactor + +- remove redundant code +- remove unused component +- move message processing to a separate module + ## v0.8.0 (2024-07-27) ### Feat diff --git a/pyproject.toml b/pyproject.toml index 831a0b4..2de8a62 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "courageous-comets" -version = "0.8.0" +version = "0.9.0" authors = ["Courageous Comets"] description = "" readme = "docs/README.md" From d26c9b11abbf3568b69fde28f40488cf0bc6d27f Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Sun, 28 Jul 2024 04:45:47 +0000 Subject: [PATCH 130/168] fix: improve logging for all interactions --- courageous_comets/cogs/frequency.py | 28 ++++++++++++++ courageous_comets/cogs/keywords.py | 36 +++++++++++++++++- courageous_comets/cogs/sentiment.py | 59 ++++++++++++++++++++++++++--- 3 files changed, 115 insertions(+), 8 deletions(-) diff --git a/courageous_comets/cogs/frequency.py b/courageous_comets/cogs/frequency.py index afb630a..a3630d7 100644 --- a/courageous_comets/cogs/frequency.py +++ b/courageous_comets/cogs/frequency.py @@ -1,3 +1,5 @@ +import logging + import discord from discord import app_commands from discord.ext import commands @@ -8,6 +10,8 @@ from courageous_comets.ui.charts import frequency_line from courageous_comets.ui.embeds import message_frequency +logger = logging.getLogger(__name__) + class Frequency(commands.Cog): """Frequency related commands.""" @@ -36,19 +40,37 @@ async def frequency_command( duration: courageous_comets.enums.Duration The duration over which to aggregate the number of messages. """ + logger.info( + "User %s requested a frequency chart %s using the /frequency command.", + interaction.user.id, + interaction.id, + ) + if self.bot.redis is None: + logger.error( + "Could not answer frequency request %s due to Redis being unavailable.", + interaction.id, + ) return await interaction.response.send_message( "This feature is currently unavailable. Please try again later.", ephemeral=True, ) if interaction.guild is None: + logger.debug( + "Could not answer frequency request %s due to it being used outside of a guild.", + interaction.id, + ) return await interaction.response.send_message( "This feature is only available in guilds.", ephemeral=True, ) if duration not in Duration: + logger.debug( + "Could not answer frequency request %s due to an invalid duration.", + interaction.id, + ) return await interaction.response.send_message( "Invalid duration provided.", ephemeral=True, @@ -61,6 +83,10 @@ async def frequency_command( ) if not frequencies: + logger.debug( + "Could not answer frequency request %s due to no messages being found.", + interaction.id, + ) return await interaction.response.send_message( "No messages were found over the specified duration at this time.", ephemeral=True, @@ -71,6 +97,8 @@ async def frequency_command( chart = frequency_line.render(frequencies, duration) embed.set_image(url=f"attachment://{chart.filename}") + logger.debug("Returning frequency chart for frequency request %s.", interaction.id) + return await interaction.response.send_message( embed=embed, file=chart, diff --git a/courageous_comets/cogs/keywords.py b/courageous_comets/cogs/keywords.py index 4b94bec..fa798cf 100644 --- a/courageous_comets/cogs/keywords.py +++ b/courageous_comets/cogs/keywords.py @@ -45,29 +45,46 @@ async def search_by_topic(self, interaction: discord.Interaction, query: str) -> The query to search for related messages. """ logger.info( - "User %s requested a search for related messages using the /search command.", + "User %s requested a search for related messages %s using the /search command.", interaction.user.id, + interaction.id, ) if self.bot.redis is None: + logger.error( + "Could not answer search request %s due to Redis being unavailable.", + interaction.id, + ) return await interaction.response.send_message( "This feature is currently unavailable. Please try again later.", ephemeral=True, ) if not interaction.guild: + logger.debug( + "Could not answer search request %s due to it being used outside of a guild.", + interaction.id, + ) return await interaction.response.send_message( "This command can only be used in a guild.", ephemeral=True, ) if not interaction.channel: + logger.debug( + "Could not answer search request %s due to it being used outside of a channel.", + interaction.id, + ) return await interaction.response.send_message( "This command can only be used in a channel.", ephemeral=True, ) if not query: + logger.debug( + "Could not answer search request %s due to it not having a query.", + interaction.id, + ) return await interaction.response.send_message( "Please provide a query to search for related messages.", ephemeral=True, @@ -88,10 +105,13 @@ async def search_by_topic(self, interaction: discord.Interaction, query: str) -> resolved_messages = await resolve_messages(self.bot, messages) if not resolved_messages: + logger.debug("No related messages were found for search request %s.", interaction.id) return await interaction.followup.send("No related messages were found.") embed = search_results.render(query, resolved_messages) + logger.debug("Returning search results for search request %s.", interaction.id) + return await interaction.followup.send(embed=embed) @contextmenu(name="Search by topic") @@ -111,18 +131,27 @@ async def search_by_topic_with_message( The message to use as a reference for the search. """ logger.info( - "User %s requested search by sentiment for message %s.", + "User %s requested search by topic %s for message %s.", interaction.user.id, + interaction.id, message.id, ) if self.bot.redis is None: + logger.error( + "Could not answer search request %s due to Redis being unavailable.", + interaction.id, + ) return await interaction.response.send_message( "This feature is currently unavailable. Please try again later.", ephemeral=True, ) if message.guild is None: + logger.debug( + "Could not answer search request %s due to it being used outside of a guild.", + interaction.id, + ) return await interaction.response.send_message( "This feature is only available in guilds.", ephemeral=True, @@ -159,6 +188,7 @@ async def search_by_topic_with_message( ] if not resolved_messages: + logger.debug("No related messages were found for search request %s.", interaction.id) return await interaction.followup.send( "No related messages were found.", ephemeral=True, @@ -166,6 +196,8 @@ async def search_by_topic_with_message( embed = search_results.render(message.clean_content, resolved_messages) + logger.debug("Returning search results for search request %s.", interaction.id) + return await interaction.followup.send(embed=embed, ephemeral=True) diff --git a/courageous_comets/cogs/sentiment.py b/courageous_comets/cogs/sentiment.py index cb447cc..a7f3346 100644 --- a/courageous_comets/cogs/sentiment.py +++ b/courageous_comets/cogs/sentiment.py @@ -60,18 +60,27 @@ async def show_user_sentiment( The user to analyze. """ logger.info( - "User %s requested sentiment analysis results for user %s.", + "User %s requested sentiment analysis results %s for user %s.", interaction.user.id, + interaction.id, user.id, ) if self.bot.redis is None: + logger.error( + "Could not answer sentiment request %s due to Redis being unavailable.", + interaction.id, + ) return await interaction.response.send_message( "This feature is currently unavailable. Please try again later.", ephemeral=True, ) if interaction.guild is None: + logger.debug( + "Could not answer sentiment request %s due to user not being from a guild.", + interaction.id, + ) return await interaction.response.send_message( "This feature is only available in guilds.", ephemeral=True, @@ -87,6 +96,7 @@ async def show_user_sentiment( ) if not sentiment_results: + logger.debug("No data found for sentiment request %s.", interaction.id) await interaction.followup.send( f"No sentiment data found for {user.mention}.", ephemeral=True, @@ -101,6 +111,8 @@ async def show_user_sentiment( view = SentimentView(user, average_sentiment) + logger.debug("Sending sentiment analysis results for %s.", interaction.id) + return await interaction.followup.send( embed=embed, file=chart, @@ -129,18 +141,27 @@ async def show_message_sentiment( The message to analyze. """ logger.info( - "User %s requested sentiment analysis results for message %s.", + "User %s requested sentiment analysis results %s for message %s.", interaction.user.id, + interaction.id, message.id, ) if self.bot.redis is None: + logger.error( + "Could not answer sentiment request %s due to Redis being unavailable.", + interaction.id, + ) return await interaction.response.send_message( "This feature is currently unavailable. Please try again later.", ephemeral=True, ) if message.guild is None: + logger.debug( + "Could not answer sentiment request %s due to not message being from a guild.", + interaction.id, + ) return await interaction.response.send_message( "This feature is only available in guilds.", ephemeral=True, @@ -164,7 +185,7 @@ async def show_message_sentiment( analysis_result = await get_message_sentiment(key, redis=self.bot.redis) if analysis_result is None: - logger.warning("Could not find analysis result for message %s.", message.id) + logger.debug("No data found for sentiment request %s.", interaction.id) return await interaction.followup.send( "No analysis results were found.", ephemeral=True, @@ -177,6 +198,8 @@ async def show_message_sentiment( view = SentimentView(message.author, analysis_result) + logger.debug("Sending sentiment analysis results for %s.", interaction.id) + return await interaction.followup.send( embed=embed, file=chart, @@ -204,17 +227,26 @@ async def search_by_sentiment( The message to use as a reference for the search """ logger.info( - "User %s requested search by sentiment using a custom query.", + "User %s requested search by sentiment %s using a custom query.", interaction.user.id, + interaction.id, ) if self.bot.redis is None: + logger.error( + "Could not answer sentiment request %s due to Redis being unavailable.", + interaction.id, + ) return await interaction.response.send_message( "This feature is currently unavailable. Please try again later.", ephemeral=True, ) if interaction.guild is None: + logger.debug( + "Could not answer sentiment request %s due to not being triggered from a guild.", + interaction.id, + ) return await interaction.response.send_message( "This feature is only available in guilds.", ephemeral=True, @@ -236,6 +268,7 @@ async def search_by_sentiment( resolved_messages = await resolve_messages(self.bot, messages) if not resolved_messages: + logger.debug("No data found for sentiment request %s.", interaction.id) return await interaction.followup.send( "No related messages were found.", ephemeral=True, @@ -243,6 +276,8 @@ async def search_by_sentiment( embed = search_results.render(query, resolved_messages) + logger.debug("Sending sentiment analysis results for %s.", interaction.id) + return await interaction.followup.send(embed=embed, ephemeral=True) @contextmenu(name="Search by sentiment") @@ -262,18 +297,27 @@ async def search_by_sentiment_with_message( The message to use as a reference for the search. """ logger.info( - "User %s requested search by sentiment for message %s.", + "User %s requested search by sentiment %s for message %s.", interaction.user.id, + interaction.id, message.id, ) if self.bot.redis is None: + logger.error( + "Could not answer sentiment request %s due to Redis being unavailable.", + interaction.id, + ) return await interaction.response.send_message( "This feature is currently unavailable. Please try again later.", ephemeral=True, ) if message.guild is None: + logger.debug( + "Could not answer sentiment request %s due to message not being from a guild.", + interaction.id, + ) return await interaction.response.send_message( "This feature is only available in guilds.", ephemeral=True, @@ -297,7 +341,7 @@ async def search_by_sentiment_with_message( analysis_result = await get_message_sentiment(key, redis=self.bot.redis) if analysis_result is None: - logger.warning("Could not find analysis result for message %s.", message.id) + logger.debug("No data found for sentiment request %s.", interaction.id) return await interaction.followup.send( "No related messages were found.", ephemeral=True, @@ -318,6 +362,7 @@ async def search_by_sentiment_with_message( ] if not resolved_messages: + logger.debug("No search results found for sentiment request %s.", interaction.id) return await interaction.followup.send( "No related messages were found.", ephemeral=True, @@ -325,6 +370,8 @@ async def search_by_sentiment_with_message( embed = search_results.render(message.clean_content, resolved_messages) + logger.debug("Sending sentiment analysis results for %s.", interaction.id) + return await interaction.followup.send(embed=embed, ephemeral=True) From dd9bd7733d2e720eb61cb0627386e3bc146fa7a5 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Sun, 28 Jul 2024 05:58:50 +0000 Subject: [PATCH 131/168] refactor: split up keywords and sentiment cogs, improve cog documentation --- application.yaml | 8 +- courageous_comets/cogs/frequency.py | 9 +- courageous_comets/cogs/keywords/__init__.py | 0 .../search_command.py} | 116 +----- .../cogs/keywords/search_context_menu.py | 118 ++++++ courageous_comets/cogs/messages.py | 9 +- courageous_comets/cogs/ping.py | 9 +- courageous_comets/cogs/sentiment.py | 380 ------------------ courageous_comets/cogs/sentiment/__init__.py | 0 .../cogs/sentiment/message_context_menu.py | 133 ++++++ .../cogs/sentiment/search_command.py | 106 +++++ .../cogs/sentiment/search_context_menu.py | 139 +++++++ .../cogs/sentiment/user_context_menu.py | 121 ++++++ 13 files changed, 662 insertions(+), 486 deletions(-) create mode 100644 courageous_comets/cogs/keywords/__init__.py rename courageous_comets/cogs/{keywords.py => keywords/search_command.py} (50%) create mode 100644 courageous_comets/cogs/keywords/search_context_menu.py delete mode 100644 courageous_comets/cogs/sentiment.py create mode 100644 courageous_comets/cogs/sentiment/__init__.py create mode 100644 courageous_comets/cogs/sentiment/message_context_menu.py create mode 100644 courageous_comets/cogs/sentiment/search_command.py create mode 100644 courageous_comets/cogs/sentiment/search_context_menu.py create mode 100644 courageous_comets/cogs/sentiment/user_context_menu.py diff --git a/application.yaml b/application.yaml index 730c634..4aa6ae1 100644 --- a/application.yaml +++ b/application.yaml @@ -1,9 +1,13 @@ cogs: - courageous_comets.cogs.about - - courageous_comets.cogs.keywords + - courageous_comets.cogs.keywords.search_command + - courageous_comets.cogs.keywords.search_context_menu - courageous_comets.cogs.messages - courageous_comets.cogs.ping - - courageous_comets.cogs.sentiment + - courageous_comets.cogs.sentiment.message_context_menu + - courageous_comets.cogs.sentiment.search_command + - courageous_comets.cogs.sentiment.search_context_menu + - courageous_comets.cogs.sentiment.user_context_menu - courageous_comets.cogs.frequency dev-cogs: - jishaku diff --git a/courageous_comets/cogs/frequency.py b/courageous_comets/cogs/frequency.py index a3630d7..4b30c10 100644 --- a/courageous_comets/cogs/frequency.py +++ b/courageous_comets/cogs/frequency.py @@ -14,7 +14,14 @@ class Frequency(commands.Cog): - """Frequency related commands.""" + """ + A cog that provides frequency analysis for messages over a duration. + + Attributes + ---------- + bot : CourageousCometsBot + The bot instance. + """ def __init__(self, bot: CourageousCometsBot) -> None: self.bot = bot diff --git a/courageous_comets/cogs/keywords/__init__.py b/courageous_comets/cogs/keywords/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/courageous_comets/cogs/keywords.py b/courageous_comets/cogs/keywords/search_command.py similarity index 50% rename from courageous_comets/cogs/keywords.py rename to courageous_comets/cogs/keywords/search_command.py index fa798cf..fc1897b 100644 --- a/courageous_comets/cogs/keywords.py +++ b/courageous_comets/cogs/keywords/search_command.py @@ -7,33 +7,33 @@ from courageous_comets import preprocessing from courageous_comets.client import CourageousCometsBot from courageous_comets.discord.messages import resolve_messages -from courageous_comets.processing import process_message -from courageous_comets.redis.keys import key_schema from courageous_comets.redis.messages import get_messages_by_semantics_similarity from courageous_comets.ui.embeds import search_results -from courageous_comets.utils import contextmenu logger = logging.getLogger(__name__) -class MessagesNotFound(app_commands.AppCommandError): - """No messages were found.""" +class SearchCommand(commands.Cog): + """ + A cog for the /search command. + This cog allows users to search for related messages using the /search command. -class Keywords(commands.Cog): - """A boilerplate cog.""" + Attributes + ---------- + bot : CourageousCometsBot + The bot instance. + """ def __init__(self, bot: CourageousCometsBot) -> None: self.bot = bot - for attribute in dir(self): - obj = getattr(self, attribute, None) - if obj and getattr(obj, "is_contextmenu", False): - menu = app_commands.ContextMenu(name=obj.name, callback=obj) - self.bot.tree.add_command(menu) - @app_commands.command(name="search", description="Search for related messages.") - async def search_by_topic(self, interaction: discord.Interaction, query: str) -> None: + async def search_by_topic( + self, + interaction: discord.Interaction, + query: str, + ) -> None: """ Search for related messages using the /search command. @@ -114,93 +114,7 @@ async def search_by_topic(self, interaction: discord.Interaction, query: str) -> return await interaction.followup.send(embed=embed) - @contextmenu(name="Search by topic") - async def search_by_topic_with_message( - self, - interaction: discord.Interaction, - message: discord.Message, - ) -> None: - """ - Search for related messages using a context menu. - - Parameters - ---------- - interaction : discord.Interaction - The interaction that triggered the command. - message : discord.Message - The message to use as a reference for the search. - """ - logger.info( - "User %s requested search by topic %s for message %s.", - interaction.user.id, - interaction.id, - message.id, - ) - - if self.bot.redis is None: - logger.error( - "Could not answer search request %s due to Redis being unavailable.", - interaction.id, - ) - return await interaction.response.send_message( - "This feature is currently unavailable. Please try again later.", - ephemeral=True, - ) - - if message.guild is None: - logger.debug( - "Could not answer search request %s due to it being used outside of a guild.", - interaction.id, - ) - return await interaction.response.send_message( - "This feature is only available in guilds.", - ephemeral=True, - ) - - await interaction.response.defer(ephemeral=True, thinking=True) - - key = key_schema.guild_messages( - guild_id=message.guild.id, - message_id=message.id, - ) - - if not self.bot.redis.exists(key): - await process_message( - message, - redis=self.bot.redis, - vectorizer=self.bot.vectorizer, - ) - - content_processed = preprocessing.process(message.clean_content) - embedding = await self.bot.vectorizer.aencode(content_processed) - - messages = await get_messages_by_semantics_similarity( - self.bot.redis, - guild_id=str(message.guild.id), - embedding=embedding, - limit=6, - ) - - resolved_messages = [ - resolved_message - for resolved_message in await resolve_messages(self.bot, messages) - if resolved_message.id != message.id - ] - - if not resolved_messages: - logger.debug("No related messages were found for search request %s.", interaction.id) - return await interaction.followup.send( - "No related messages were found.", - ephemeral=True, - ) - - embed = search_results.render(message.clean_content, resolved_messages) - - logger.debug("Returning search results for search request %s.", interaction.id) - - return await interaction.followup.send(embed=embed, ephemeral=True) - async def setup(bot: CourageousCometsBot) -> None: """Load the cog.""" - await bot.add_cog(Keywords(bot)) + await bot.add_cog(SearchCommand(bot)) diff --git a/courageous_comets/cogs/keywords/search_context_menu.py b/courageous_comets/cogs/keywords/search_context_menu.py new file mode 100644 index 0000000..2f02e0f --- /dev/null +++ b/courageous_comets/cogs/keywords/search_context_menu.py @@ -0,0 +1,118 @@ +import logging + +import discord +from discord import app_commands +from discord.ext import commands + +from courageous_comets import preprocessing +from courageous_comets.client import CourageousCometsBot +from courageous_comets.discord.messages import resolve_messages +from courageous_comets.processing import process_message +from courageous_comets.redis.keys import key_schema +from courageous_comets.redis.messages import get_messages_by_semantics_similarity +from courageous_comets.ui.embeds import search_results + +logger = logging.getLogger(__name__) + + +class SearchContextMenu(commands.Cog): + """A boilerplate cog.""" + + def __init__(self, bot: CourageousCometsBot) -> None: + self.bot = bot + + menu = app_commands.ContextMenu( + name="Search by topic", + callback=self.search_by_topic, + ) + self.bot.tree.add_command(menu) + + async def search_by_topic( + self, + interaction: discord.Interaction, + message: discord.Message, + ) -> None: + """ + Search for related messages using a context menu. + + Parameters + ---------- + interaction : discord.Interaction + The interaction that triggered the command. + message : discord.Message + The message to use as a reference for the search. + """ + logger.info( + "User %s requested search by topic %s for message %s.", + interaction.user.id, + interaction.id, + message.id, + ) + + if self.bot.redis is None: + logger.error( + "Could not answer search request %s due to Redis being unavailable.", + interaction.id, + ) + return await interaction.response.send_message( + "This feature is currently unavailable. Please try again later.", + ephemeral=True, + ) + + if message.guild is None: + logger.debug( + "Could not answer search request %s due to it being used outside of a guild.", + interaction.id, + ) + return await interaction.response.send_message( + "This feature is only available in guilds.", + ephemeral=True, + ) + + await interaction.response.defer(ephemeral=True, thinking=True) + + key = key_schema.guild_messages( + guild_id=message.guild.id, + message_id=message.id, + ) + + if not self.bot.redis.exists(key): + await process_message( + message, + redis=self.bot.redis, + vectorizer=self.bot.vectorizer, + ) + + content_processed = preprocessing.process(message.clean_content) + embedding = await self.bot.vectorizer.aencode(content_processed) + + messages = await get_messages_by_semantics_similarity( + self.bot.redis, + guild_id=str(message.guild.id), + embedding=embedding, + limit=6, + ) + + resolved_messages = [ + resolved_message + for resolved_message in await resolve_messages(self.bot, messages) + if resolved_message.id != message.id + ] + + if not resolved_messages: + logger.debug("No related messages were found for search request %s.", interaction.id) + return await interaction.followup.send( + "No related messages were found.", + ephemeral=True, + ) + + embed = search_results.render(message.clean_content, resolved_messages) + + logger.debug("Returning search results for search request %s.", interaction.id) + + return await interaction.followup.send(embed=embed, ephemeral=True) + + +async def setup(bot: CourageousCometsBot) -> None: + """Load the cog.""" + await bot.add_cog(SearchContextMenu(bot)) diff --git a/courageous_comets/cogs/messages.py b/courageous_comets/cogs/messages.py index fc2ce00..2a247a7 100644 --- a/courageous_comets/cogs/messages.py +++ b/courageous_comets/cogs/messages.py @@ -10,7 +10,14 @@ class Messages(commands.Cog): - """A cog that listens for messages from discord.""" + """ + A cog that listens for messages from discord and forwards them to processing. + + Attributes + ---------- + bot : CourageousCometsBot + The bot instance. + """ def __init__(self, bot: CourageousCometsBot) -> None: self.bot = bot diff --git a/courageous_comets/cogs/ping.py b/courageous_comets/cogs/ping.py index fc17625..aa3cdcf 100644 --- a/courageous_comets/cogs/ping.py +++ b/courageous_comets/cogs/ping.py @@ -4,7 +4,14 @@ class Ping(commands.Cog): - """A cog containing a simple ping command.""" + """ + A cog containing a simple ping command. + + Attributes + ---------- + bot : commands.Bot + The bot instance. + """ def __init__(self, bot: commands.Bot) -> None: self.bot = bot diff --git a/courageous_comets/cogs/sentiment.py b/courageous_comets/cogs/sentiment.py deleted file mode 100644 index a7f3346..0000000 --- a/courageous_comets/cogs/sentiment.py +++ /dev/null @@ -1,380 +0,0 @@ -import logging - -import discord -from discord import app_commands -from discord.ext import commands - -from courageous_comets import preprocessing -from courageous_comets.client import CourageousCometsBot -from courageous_comets.discord.messages import resolve_messages -from courageous_comets.enums import StatisticScope -from courageous_comets.processing import process_message -from courageous_comets.redis.keys import key_schema -from courageous_comets.redis.messages import ( - get_average_sentiment, - get_message_sentiment, - get_messages_by_sentiment_similarity, -) -from courageous_comets.sentiment import calculate_sentiment -from courageous_comets.ui.charts import sentiment_bars -from courageous_comets.ui.embeds import message_sentiment, search_results, user_sentiment -from courageous_comets.ui.views.sentiment import SentimentView -from courageous_comets.utils import contextmenu - -logger = logging.getLogger(__name__) - - -class MessagesNotFound(app_commands.AppCommandError): - """No messages were found.""" - - -class Sentiment(commands.Cog): - """Sentiment related commands.""" - - def __init__(self, bot: CourageousCometsBot) -> None: - self.bot = bot - - for attribute in dir(self): - obj = getattr(self, attribute, None) - if obj and getattr(obj, "is_contextmenu", False): - menu = app_commands.ContextMenu(name=obj.name, callback=obj) - self.bot.tree.add_command(menu) - - @contextmenu(name="Show user sentiment") - async def show_user_sentiment( - self, - interaction: discord.Interaction, - user: discord.User | discord.Member, - ) -> None: - """ - Allow users to view the sentiment analysis of a user using a context menu. - - Generates an embed with the sentiment analysis of a user and sends it to the user. - The embed contains a line chart of the sentiment of a user over time. - - Parameters - ---------- - interaction : discord.Interaction - The interaction that triggered the command. - user : discord.User | discord.Member - The user to analyze. - """ - logger.info( - "User %s requested sentiment analysis results %s for user %s.", - interaction.user.id, - interaction.id, - user.id, - ) - - if self.bot.redis is None: - logger.error( - "Could not answer sentiment request %s due to Redis being unavailable.", - interaction.id, - ) - return await interaction.response.send_message( - "This feature is currently unavailable. Please try again later.", - ephemeral=True, - ) - - if interaction.guild is None: - logger.debug( - "Could not answer sentiment request %s due to user not being from a guild.", - interaction.id, - ) - return await interaction.response.send_message( - "This feature is only available in guilds.", - ephemeral=True, - ) - - await interaction.response.defer(ephemeral=True, thinking=True) - - sentiment_results = await get_average_sentiment( - redis=self.bot.redis, - guild_id=str(interaction.guild.id), - ids=[str(user.id)], - scope=StatisticScope.USER, - ) - - if not sentiment_results: - logger.debug("No data found for sentiment request %s.", interaction.id) - await interaction.followup.send( - f"No sentiment data found for {user.mention}.", - ephemeral=True, - ) - - average_sentiment = sentiment_results[0] - - embed = user_sentiment.render(user, average_sentiment) - - chart = sentiment_bars.for_user(average_sentiment) - embed.set_image(url=f"attachment://{chart.filename}") - - view = SentimentView(user, average_sentiment) - - logger.debug("Sending sentiment analysis results for %s.", interaction.id) - - return await interaction.followup.send( - embed=embed, - file=chart, - view=view, - ephemeral=True, - ) - - @contextmenu(name="Show message sentiment") - async def show_message_sentiment( - self, - interaction: discord.Interaction, - message: discord.Message, - ) -> None: - """ - Allow users to view the sentiment analysis of a message using a context menu. - - Generates an embed with the sentiment analysis of a message and sends it to the user. - - The embed contains a text description of the sentiment analysis and a bar chart. - - Parameters - ---------- - interaction : discord.Interaction - The interaction that triggered the command. - message : discord.Message - The message to analyze. - """ - logger.info( - "User %s requested sentiment analysis results %s for message %s.", - interaction.user.id, - interaction.id, - message.id, - ) - - if self.bot.redis is None: - logger.error( - "Could not answer sentiment request %s due to Redis being unavailable.", - interaction.id, - ) - return await interaction.response.send_message( - "This feature is currently unavailable. Please try again later.", - ephemeral=True, - ) - - if message.guild is None: - logger.debug( - "Could not answer sentiment request %s due to not message being from a guild.", - interaction.id, - ) - return await interaction.response.send_message( - "This feature is only available in guilds.", - ephemeral=True, - ) - - await interaction.response.defer(ephemeral=True, thinking=True) - - key = key_schema.guild_messages( - guild_id=message.guild.id, - message_id=message.id, - ) - - if not await self.bot.redis.exists(key): - logger.debug("Message %s is not previously saved. Processing it.", message.id) - await process_message( - message, - redis=self.bot.redis, - vectorizer=self.bot.vectorizer, - ) - - analysis_result = await get_message_sentiment(key, redis=self.bot.redis) - - if analysis_result is None: - logger.debug("No data found for sentiment request %s.", interaction.id) - return await interaction.followup.send( - "No analysis results were found.", - ephemeral=True, - ) - - embed = message_sentiment.render(analysis_result) - - chart = sentiment_bars.for_message(message.id, analysis_result) - embed.set_image(url=f"attachment://{chart.filename}") - - view = SentimentView(message.author, analysis_result) - - logger.debug("Sending sentiment analysis results for %s.", interaction.id) - - return await interaction.followup.send( - embed=embed, - file=chart, - view=view, - ephemeral=True, - ) - - @app_commands.command( - name="sentiment_search", - description="Search for messages with similar sentiment.", - ) - async def search_by_sentiment( - self, - interaction: discord.Interaction, - query: str, - ) -> None: - """ - Allow users to search for messages with similar sentiment using a context menu. - - Parameters - ---------- - interaction : discord.Interaction - The interaction that triggered the command. - query : str - The message to use as a reference for the search - """ - logger.info( - "User %s requested search by sentiment %s using a custom query.", - interaction.user.id, - interaction.id, - ) - - if self.bot.redis is None: - logger.error( - "Could not answer sentiment request %s due to Redis being unavailable.", - interaction.id, - ) - return await interaction.response.send_message( - "This feature is currently unavailable. Please try again later.", - ephemeral=True, - ) - - if interaction.guild is None: - logger.debug( - "Could not answer sentiment request %s due to not being triggered from a guild.", - interaction.id, - ) - return await interaction.response.send_message( - "This feature is only available in guilds.", - ephemeral=True, - ) - - await interaction.response.defer(ephemeral=True, thinking=True) - - prepared_content = preprocessing.process(query) - sentiment = calculate_sentiment(prepared_content) - - messages = await get_messages_by_sentiment_similarity( - self.bot.redis, - guild_id=str(interaction.guild.id), - sentiment=sentiment.compound, - radius=0.1, - limit=5, - ) - - resolved_messages = await resolve_messages(self.bot, messages) - - if not resolved_messages: - logger.debug("No data found for sentiment request %s.", interaction.id) - return await interaction.followup.send( - "No related messages were found.", - ephemeral=True, - ) - - embed = search_results.render(query, resolved_messages) - - logger.debug("Sending sentiment analysis results for %s.", interaction.id) - - return await interaction.followup.send(embed=embed, ephemeral=True) - - @contextmenu(name="Search by sentiment") - async def search_by_sentiment_with_message( - self, - interaction: discord.Interaction, - message: discord.Message, - ) -> None: - """ - Allow users to search for messages with similar sentiment using a context menu. - - Parameters - ---------- - interaction : discord.Interaction - The interaction that triggered the command. - message : discord.Message - The message to use as a reference for the search. - """ - logger.info( - "User %s requested search by sentiment %s for message %s.", - interaction.user.id, - interaction.id, - message.id, - ) - - if self.bot.redis is None: - logger.error( - "Could not answer sentiment request %s due to Redis being unavailable.", - interaction.id, - ) - return await interaction.response.send_message( - "This feature is currently unavailable. Please try again later.", - ephemeral=True, - ) - - if message.guild is None: - logger.debug( - "Could not answer sentiment request %s due to message not being from a guild.", - interaction.id, - ) - return await interaction.response.send_message( - "This feature is only available in guilds.", - ephemeral=True, - ) - - await interaction.response.defer(ephemeral=True, thinking=True) - - key = key_schema.guild_messages( - guild_id=message.guild.id, - message_id=message.id, - ) - - if not await self.bot.redis.exists(key): - logger.debug("Message %s is not previously saved. Processing it.", message.id) - await process_message( - message, - redis=self.bot.redis, - vectorizer=self.bot.vectorizer, - ) - - analysis_result = await get_message_sentiment(key, redis=self.bot.redis) - - if analysis_result is None: - logger.debug("No data found for sentiment request %s.", interaction.id) - return await interaction.followup.send( - "No related messages were found.", - ephemeral=True, - ) - - messages = await get_messages_by_sentiment_similarity( - self.bot.redis, - guild_id=str(message.guild.id), - sentiment=analysis_result.compound, - radius=0.1, - limit=6, - ) - - resolved_messages = [ - resolved_message - for resolved_message in await resolve_messages(self.bot, messages) - if resolved_message.id != message.id - ] - - if not resolved_messages: - logger.debug("No search results found for sentiment request %s.", interaction.id) - return await interaction.followup.send( - "No related messages were found.", - ephemeral=True, - ) - - embed = search_results.render(message.clean_content, resolved_messages) - - logger.debug("Sending sentiment analysis results for %s.", interaction.id) - - return await interaction.followup.send(embed=embed, ephemeral=True) - - -async def setup(bot: CourageousCometsBot) -> None: - """Load the cog.""" - await bot.add_cog(Sentiment(bot)) diff --git a/courageous_comets/cogs/sentiment/__init__.py b/courageous_comets/cogs/sentiment/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/courageous_comets/cogs/sentiment/message_context_menu.py b/courageous_comets/cogs/sentiment/message_context_menu.py new file mode 100644 index 0000000..95c5c62 --- /dev/null +++ b/courageous_comets/cogs/sentiment/message_context_menu.py @@ -0,0 +1,133 @@ +import logging + +import discord +from discord import app_commands +from discord.ext import commands + +from courageous_comets.client import CourageousCometsBot +from courageous_comets.processing import process_message +from courageous_comets.redis.keys import key_schema +from courageous_comets.redis.messages import get_message_sentiment +from courageous_comets.ui.charts import sentiment_bars +from courageous_comets.ui.embeds import message_sentiment +from courageous_comets.ui.views.sentiment import SentimentView +from courageous_comets.utils import contextmenu + +logger = logging.getLogger(__name__) + + +class MessagesNotFound(app_commands.AppCommandError): + """No messages were found.""" + + +class SentimentMessageContextMenu(commands.Cog): + """ + A cog that provides sentiment analysis for a message using a context menu. + + Attributes + ---------- + bot : CourageousCometsBot + The bot instance. + """ + + def __init__(self, bot: CourageousCometsBot) -> None: + self.bot = bot + + menu = app_commands.ContextMenu( + name="Show message sentiment", + callback=self.show_message_sentiment, + ) + self.bot.tree.add_command(menu) + + @contextmenu(name="Show message sentiment") + async def show_message_sentiment( + self, + interaction: discord.Interaction, + message: discord.Message, + ) -> None: + """ + Allow users to view the sentiment analysis of a message using a context menu. + + Generates an embed with the sentiment analysis of a message and sends it to the user. + + The embed contains a text description of the sentiment analysis and a bar chart. + A view is attached to the message to allow users to interact with the sentiment analysis. + + Parameters + ---------- + interaction : discord.Interaction + The interaction that triggered the command. + message : discord.Message + The message to analyze. + """ + logger.info( + "User %s requested sentiment analysis results %s for message %s.", + interaction.user.id, + interaction.id, + message.id, + ) + + if self.bot.redis is None: + logger.error( + "Could not answer sentiment request %s due to Redis being unavailable.", + interaction.id, + ) + return await interaction.response.send_message( + "This feature is currently unavailable. Please try again later.", + ephemeral=True, + ) + + if message.guild is None: + logger.debug( + "Could not answer sentiment request %s due to not message being from a guild.", + interaction.id, + ) + return await interaction.response.send_message( + "This feature is only available in guilds.", + ephemeral=True, + ) + + await interaction.response.defer(ephemeral=True, thinking=True) + + key = key_schema.guild_messages( + guild_id=message.guild.id, + message_id=message.id, + ) + + if not await self.bot.redis.exists(key): + logger.debug("Message %s is not previously saved. Processing it.", message.id) + await process_message( + message, + redis=self.bot.redis, + vectorizer=self.bot.vectorizer, + ) + + analysis_result = await get_message_sentiment(key, redis=self.bot.redis) + + if analysis_result is None: + logger.debug("No data found for sentiment request %s.", interaction.id) + return await interaction.followup.send( + "No analysis results were found.", + ephemeral=True, + ) + + embed = message_sentiment.render(analysis_result) + + chart = sentiment_bars.for_message(message.id, analysis_result) + embed.set_image(url=f"attachment://{chart.filename}") + + view = SentimentView(message.author, analysis_result) + + logger.debug("Sending sentiment analysis results for %s.", interaction.id) + + return await interaction.followup.send( + embed=embed, + file=chart, + view=view, + ephemeral=True, + ) + + +async def setup(bot: CourageousCometsBot) -> None: + """Load the cog.""" + await bot.add_cog(SentimentMessageContextMenu(bot)) diff --git a/courageous_comets/cogs/sentiment/search_command.py b/courageous_comets/cogs/sentiment/search_command.py new file mode 100644 index 0000000..3f2e89f --- /dev/null +++ b/courageous_comets/cogs/sentiment/search_command.py @@ -0,0 +1,106 @@ +import logging + +import discord +from discord import app_commands +from discord.ext import commands + +from courageous_comets import preprocessing +from courageous_comets.client import CourageousCometsBot +from courageous_comets.discord.messages import resolve_messages +from courageous_comets.redis.messages import get_messages_by_sentiment_similarity +from courageous_comets.sentiment import calculate_sentiment +from courageous_comets.ui.embeds import search_results + +logger = logging.getLogger(__name__) + + +class SentimentSearchCommand(commands.Cog): + """ + A cog that provides sentiment search using a the /sentiment_search command. + + Attributes + ---------- + bot : CourageousCometsBot + The bot instance. + """ + + def __init__(self, bot: CourageousCometsBot) -> None: + self.bot = bot + + @app_commands.command( + name="sentiment_search", + description="Search for messages with similar sentiment.", + ) + async def search_by_sentiment( + self, + interaction: discord.Interaction, + query: str, + ) -> None: + """ + Allow users to search for messages with similar sentiment using a context menu. + + Parameters + ---------- + interaction : discord.Interaction + The interaction that triggered the command. + query : str + The message to use as a reference for the search + """ + logger.info( + "User %s requested search by sentiment %s using a custom query.", + interaction.user.id, + interaction.id, + ) + + if self.bot.redis is None: + logger.error( + "Could not answer sentiment request %s due to Redis being unavailable.", + interaction.id, + ) + return await interaction.response.send_message( + "This feature is currently unavailable. Please try again later.", + ephemeral=True, + ) + + if interaction.guild is None: + logger.debug( + "Could not answer sentiment request %s due to not being triggered from a guild.", + interaction.id, + ) + return await interaction.response.send_message( + "This feature is only available in guilds.", + ephemeral=True, + ) + + await interaction.response.defer(ephemeral=True, thinking=True) + + prepared_content = preprocessing.process(query) + sentiment = calculate_sentiment(prepared_content) + + messages = await get_messages_by_sentiment_similarity( + self.bot.redis, + guild_id=str(interaction.guild.id), + sentiment=sentiment.compound, + radius=0.1, + limit=5, + ) + + resolved_messages = await resolve_messages(self.bot, messages) + + if not resolved_messages: + logger.debug("No data found for sentiment request %s.", interaction.id) + return await interaction.followup.send( + "No related messages were found.", + ephemeral=True, + ) + + embed = search_results.render(query, resolved_messages) + + logger.debug("Sending sentiment analysis results for %s.", interaction.id) + + return await interaction.followup.send(embed=embed, ephemeral=True) + + +async def setup(bot: CourageousCometsBot) -> None: + """Load the cog.""" + await bot.add_cog(SentimentSearchCommand(bot)) diff --git a/courageous_comets/cogs/sentiment/search_context_menu.py b/courageous_comets/cogs/sentiment/search_context_menu.py new file mode 100644 index 0000000..6d058ef --- /dev/null +++ b/courageous_comets/cogs/sentiment/search_context_menu.py @@ -0,0 +1,139 @@ +import logging + +import discord +from discord import app_commands +from discord.ext import commands + +from courageous_comets.client import CourageousCometsBot +from courageous_comets.discord.messages import resolve_messages +from courageous_comets.processing import process_message +from courageous_comets.redis.keys import key_schema +from courageous_comets.redis.messages import ( + get_message_sentiment, + get_messages_by_sentiment_similarity, +) +from courageous_comets.ui.embeds import search_results + +logger = logging.getLogger(__name__) + + +class MessagesNotFound(app_commands.AppCommandError): + """No messages were found.""" + + +class SentimentSearchContextMenu(commands.Cog): + """ + A cog that provides sentiment search using a context menu. + + Attributes + ---------- + bot : CourageousCometsBot + The bot instance. + """ + + def __init__(self, bot: CourageousCometsBot) -> None: + self.bot = bot + + menu = app_commands.ContextMenu( + name="Search by sentiment", + callback=self.search_by_sentiment, + ) + self.bot.tree.add_command(menu) + + async def search_by_sentiment( + self, + interaction: discord.Interaction, + message: discord.Message, + ) -> None: + """ + Allow users to search for messages with similar sentiment using a context menu. + + Parameters + ---------- + interaction : discord.Interaction + The interaction that triggered the command. + message : discord.Message + The message to use as a reference for the search. + """ + logger.info( + "User %s requested search by sentiment %s for message %s.", + interaction.user.id, + interaction.id, + message.id, + ) + + if self.bot.redis is None: + logger.error( + "Could not answer sentiment request %s due to Redis being unavailable.", + interaction.id, + ) + return await interaction.response.send_message( + "This feature is currently unavailable. Please try again later.", + ephemeral=True, + ) + + if message.guild is None: + logger.debug( + "Could not answer sentiment request %s due to message not being from a guild.", + interaction.id, + ) + return await interaction.response.send_message( + "This feature is only available in guilds.", + ephemeral=True, + ) + + await interaction.response.defer(ephemeral=True, thinking=True) + + key = key_schema.guild_messages( + guild_id=message.guild.id, + message_id=message.id, + ) + + if not await self.bot.redis.exists(key): + logger.debug("Message %s is not previously saved. Processing it.", message.id) + await process_message( + message, + redis=self.bot.redis, + vectorizer=self.bot.vectorizer, + ) + + analysis_result = await get_message_sentiment(key, redis=self.bot.redis) + + if analysis_result is None: + logger.debug("No data found for sentiment request %s.", interaction.id) + return await interaction.followup.send( + "No related messages were found.", + ephemeral=True, + ) + + messages = await get_messages_by_sentiment_similarity( + self.bot.redis, + guild_id=str(message.guild.id), + sentiment=analysis_result.compound, + radius=0.1, + limit=6, + ) + + resolved_messages = [ + resolved_message + for resolved_message in await resolve_messages(self.bot, messages) + if resolved_message.id != message.id + ] + + if not resolved_messages: + logger.debug("No search results found for sentiment request %s.", interaction.id) + return await interaction.followup.send( + "No related messages were found.", + ephemeral=True, + ) + + embed = search_results.render(message.clean_content, resolved_messages) + + logger.debug("Sending sentiment analysis results for %s.", interaction.id) + + return await interaction.followup.send(embed=embed, ephemeral=True) + + +async def setup(bot: CourageousCometsBot) -> None: + """Load the cog.""" + await bot.add_cog(SentimentSearchContextMenu(bot)) diff --git a/courageous_comets/cogs/sentiment/user_context_menu.py b/courageous_comets/cogs/sentiment/user_context_menu.py new file mode 100644 index 0000000..876a843 --- /dev/null +++ b/courageous_comets/cogs/sentiment/user_context_menu.py @@ -0,0 +1,121 @@ +import logging + +import discord +from discord import app_commands +from discord.ext import commands + +from courageous_comets.client import CourageousCometsBot +from courageous_comets.enums import StatisticScope +from courageous_comets.redis.messages import get_average_sentiment +from courageous_comets.ui.charts import sentiment_bars +from courageous_comets.ui.embeds import user_sentiment +from courageous_comets.ui.views.sentiment import SentimentView +from courageous_comets.utils import contextmenu + +logger = logging.getLogger(__name__) + + +class SentimentUserContextMenu(commands.Cog): + """ + A cog that provides sentiment analysis for a user using a context menu. + + Attributes + ---------- + bot : CourageousCometsBot + The bot instance. + """ + + def __init__(self, bot: CourageousCometsBot) -> None: + self.bot = bot + + menu = app_commands.ContextMenu( + name="Show user sentiment", + callback=self.show_user_sentiment, + ) + self.bot.tree.add_command(menu) + + @contextmenu(name="Show user sentiment") + async def show_user_sentiment( + self, + interaction: discord.Interaction, + user: discord.User | discord.Member, + ) -> None: + """ + Allow users to view the sentiment analysis of a user using a context menu. + + Generates an embed with the sentiment analysis of a user and sends it to the user. + The embed contains a line chart of the sentiment of a user over time. + A view is attached to the message to allow users to interact with the sentiment analysis. + + Parameters + ---------- + interaction : discord.Interaction + The interaction that triggered the command. + user : discord.User | discord.Member + The user to analyze. + """ + logger.info( + "User %s requested sentiment analysis results %s for user %s.", + interaction.user.id, + interaction.id, + user.id, + ) + + if self.bot.redis is None: + logger.error( + "Could not answer sentiment request %s due to Redis being unavailable.", + interaction.id, + ) + return await interaction.response.send_message( + "This feature is currently unavailable. Please try again later.", + ephemeral=True, + ) + + if interaction.guild is None: + logger.debug( + "Could not answer sentiment request %s due to user not being from a guild.", + interaction.id, + ) + return await interaction.response.send_message( + "This feature is only available in guilds.", + ephemeral=True, + ) + + await interaction.response.defer(ephemeral=True, thinking=True) + + sentiment_results = await get_average_sentiment( + redis=self.bot.redis, + guild_id=str(interaction.guild.id), + ids=[str(user.id)], + scope=StatisticScope.USER, + ) + + if not sentiment_results: + logger.debug("No data found for sentiment request %s.", interaction.id) + await interaction.followup.send( + f"No sentiment data found for {user.mention}.", + ephemeral=True, + ) + + average_sentiment = sentiment_results[0] + + embed = user_sentiment.render(user, average_sentiment) + + chart = sentiment_bars.for_user(average_sentiment) + embed.set_image(url=f"attachment://{chart.filename}") + + view = SentimentView(user, average_sentiment) + + logger.debug("Sending sentiment analysis results for %s.", interaction.id) + + return await interaction.followup.send( + embed=embed, + file=chart, + view=view, + ephemeral=True, + ) + + +async def setup(bot: CourageousCometsBot) -> None: + """Load the cog.""" + await bot.add_cog(SentimentUserContextMenu(bot)) From 0eca4c719a2440558aeaa3edad5980d95dfbcb6c Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Sun, 28 Jul 2024 05:59:41 +0000 Subject: [PATCH 132/168] refactor: remove redundant decorator --- courageous_comets/cogs/sentiment/message_context_menu.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/courageous_comets/cogs/sentiment/message_context_menu.py b/courageous_comets/cogs/sentiment/message_context_menu.py index 95c5c62..324bd98 100644 --- a/courageous_comets/cogs/sentiment/message_context_menu.py +++ b/courageous_comets/cogs/sentiment/message_context_menu.py @@ -11,7 +11,6 @@ from courageous_comets.ui.charts import sentiment_bars from courageous_comets.ui.embeds import message_sentiment from courageous_comets.ui.views.sentiment import SentimentView -from courageous_comets.utils import contextmenu logger = logging.getLogger(__name__) @@ -39,7 +38,6 @@ def __init__(self, bot: CourageousCometsBot) -> None: ) self.bot.tree.add_command(menu) - @contextmenu(name="Show message sentiment") async def show_message_sentiment( self, interaction: discord.Interaction, From 4b2d39d0608392bc03cfe4d7add25ea3f95d1ec3 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Sun, 28 Jul 2024 06:00:45 +0000 Subject: [PATCH 133/168] refactor: remove redundant error class --- courageous_comets/cogs/sentiment/message_context_menu.py | 4 ---- courageous_comets/cogs/sentiment/search_context_menu.py | 4 ---- 2 files changed, 8 deletions(-) diff --git a/courageous_comets/cogs/sentiment/message_context_menu.py b/courageous_comets/cogs/sentiment/message_context_menu.py index 324bd98..9568a7f 100644 --- a/courageous_comets/cogs/sentiment/message_context_menu.py +++ b/courageous_comets/cogs/sentiment/message_context_menu.py @@ -15,10 +15,6 @@ logger = logging.getLogger(__name__) -class MessagesNotFound(app_commands.AppCommandError): - """No messages were found.""" - - class SentimentMessageContextMenu(commands.Cog): """ A cog that provides sentiment analysis for a message using a context menu. diff --git a/courageous_comets/cogs/sentiment/search_context_menu.py b/courageous_comets/cogs/sentiment/search_context_menu.py index 6d058ef..1da3823 100644 --- a/courageous_comets/cogs/sentiment/search_context_menu.py +++ b/courageous_comets/cogs/sentiment/search_context_menu.py @@ -17,10 +17,6 @@ logger = logging.getLogger(__name__) -class MessagesNotFound(app_commands.AppCommandError): - """No messages were found.""" - - class SentimentSearchContextMenu(commands.Cog): """ A cog that provides sentiment search using a context menu. From 7eb7810a906289da4c7172815b5c7f35771e4419 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Sun, 28 Jul 2024 06:02:30 +0000 Subject: [PATCH 134/168] refactor: simplify messages cog --- courageous_comets/cogs/messages.py | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/courageous_comets/cogs/messages.py b/courageous_comets/cogs/messages.py index 2a247a7..91fde66 100644 --- a/courageous_comets/cogs/messages.py +++ b/courageous_comets/cogs/messages.py @@ -25,18 +25,7 @@ def __init__(self, bot: CourageousCometsBot) -> None: @commands.Cog.listener(name="on_message") async def on_message(self, message: discord.Message) -> None: """ - When a message is received, save it to Redis. - - Parameters - ---------- - message : discord.Message - The message to save. - """ - await self.save_message(message) - - async def save_message(self, message: discord.Message) -> None: - """ - Save a message on Redis. + When a message is received, forward it to processing. Ignore messages that are not in a guild or if the bot is not connected to Redis. From 692a87583e16418bc2e5dc87f2e8f2d48dfc12fa Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Sun, 28 Jul 2024 06:03:44 +0000 Subject: [PATCH 135/168] refactor: log database event as error --- courageous_comets/cogs/messages.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/courageous_comets/cogs/messages.py b/courageous_comets/cogs/messages.py index 91fde66..0b9eb66 100644 --- a/courageous_comets/cogs/messages.py +++ b/courageous_comets/cogs/messages.py @@ -35,7 +35,7 @@ async def on_message(self, message: discord.Message) -> None: The message to save. """ if not self.bot.redis: - return logger.warning( + return logger.error( "Ignoring message %s because the bot is not connected to Redis", message.id, ) From 713bef0a07d14cea64d8405f98208536235443e2 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Sun, 28 Jul 2024 06:04:50 +0000 Subject: [PATCH 136/168] refactor: log message processed as debug event --- courageous_comets/cogs/messages.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/courageous_comets/cogs/messages.py b/courageous_comets/cogs/messages.py index 0b9eb66..0a4aac6 100644 --- a/courageous_comets/cogs/messages.py +++ b/courageous_comets/cogs/messages.py @@ -59,8 +59,8 @@ async def on_message(self, message: discord.Message) -> None: vectorizer=self.bot.vectorizer, ) - return logger.info( - "Saved message %s to Redis with key %s", + return logger.debug( + "Processed message %s, saved with key %s", message.id, key, ) From 05051840968aba47d8b80931cf81adceba8d8289 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Sun, 28 Jul 2024 10:15:35 +0200 Subject: [PATCH 137/168] feat: add user keyword frequency (#60) --- application.yaml | 1 + .../cogs/keywords/user_context_menu.py | 103 ++++++++++++++++++ courageous_comets/redis/messages.py | 2 +- courageous_comets/ui/charts/keywords_bars.py | 31 ++++++ courageous_comets/ui/embeds/user_keywords.py | 33 ++++++ 5 files changed, 169 insertions(+), 1 deletion(-) create mode 100644 courageous_comets/cogs/keywords/user_context_menu.py create mode 100644 courageous_comets/ui/charts/keywords_bars.py create mode 100644 courageous_comets/ui/embeds/user_keywords.py diff --git a/application.yaml b/application.yaml index 4aa6ae1..d4b9c21 100644 --- a/application.yaml +++ b/application.yaml @@ -2,6 +2,7 @@ cogs: - courageous_comets.cogs.about - courageous_comets.cogs.keywords.search_command - courageous_comets.cogs.keywords.search_context_menu + - courageous_comets.cogs.keywords.user_context_menu - courageous_comets.cogs.messages - courageous_comets.cogs.ping - courageous_comets.cogs.sentiment.message_context_menu diff --git a/courageous_comets/cogs/keywords/user_context_menu.py b/courageous_comets/cogs/keywords/user_context_menu.py new file mode 100644 index 0000000..ed935b8 --- /dev/null +++ b/courageous_comets/cogs/keywords/user_context_menu.py @@ -0,0 +1,103 @@ +import logging + +import discord +import discord.ext +import discord.ext.commands + +from courageous_comets.client import CourageousCometsBot +from courageous_comets.enums import StatisticScope +from courageous_comets.redis.messages import get_tokens_count +from courageous_comets.ui.charts import keywords_bars +from courageous_comets.ui.embeds import user_keywords + +logger = logging.getLogger(__name__) + + +class KeywordsUserContextMenu(discord.ext.commands.Cog): + """ + A cog that provides keyword analysis for a user using a context menu. + + Attributes + ---------- + bot : CourageousCometsBot + The bot instance. + """ + + def __init__(self, bot: CourageousCometsBot) -> None: + self.bot = bot + + menu = discord.app_commands.ContextMenu( + name="Show user interests", + callback=self.show_keywords, + ) + self.bot.tree.add_command(menu) + + async def show_keywords( + self, + interaction: discord.Interaction, + user: discord.User | discord.Member, + ) -> None: + """ + Allow users to view the most commonly used keywords of a user using a context menu. + + Generates an embed with the most commonly used keywords of a user and sends it to the user. + The embed contains a bar chart of the keywords used by the user. + + Parameters + ---------- + interaction : discord.Interaction + The interaction that triggered the command. + user : discord.User | discord.Member + The user to analyze. + """ + logger.info( + "User %s requested keywords %s for user %s.", + interaction.user.id, + interaction.id, + user.id, + ) + + if self.bot.redis is None: + logger.error( + "Could not answer search request %s due to Redis being unavailable.", + interaction.id, + ) + return await interaction.response.send_message( + "This feature is currently unavailable. Please try again later.", + ephemeral=True, + ) + + if not interaction.guild: + logger.debug( + "Could not answer search request %s due to it being used outside of a guild.", + interaction.id, + ) + return await interaction.response.send_message( + "This command can only be used in a guild.", + ephemeral=True, + ) + + await interaction.response.defer(ephemeral=True, thinking=True) + + tokens = await get_tokens_count( + self.bot.redis, + guild_id=str(interaction.guild.id), + scope=StatisticScope.USER, + ids=[str(user.id)], + ) + + embed = user_keywords.render(user, tokens) + + chart = keywords_bars.render(tokens) + embed.set_image(url=f"attachment://{chart.filename}") + + return await interaction.followup.send( + embed=embed, + file=chart, + ephemeral=True, + ) + + +async def setup(bot: CourageousCometsBot) -> None: + """Load the cog.""" + await bot.add_cog(KeywordsUserContextMenu(bot)) diff --git a/courageous_comets/redis/messages.py b/courageous_comets/redis/messages.py index 256849c..bf6f0e8 100644 --- a/courageous_comets/redis/messages.py +++ b/courageous_comets/redis/messages.py @@ -322,7 +322,7 @@ async def get_tokens_count( ids: list[str] | None = None, scope: StatisticScope = StatisticScope.CHANNEL, limit: int = settings.QUERY_LIMIT, -) -> Counter: +) -> Counter[str]: """ Get the count of tokens across messages. diff --git a/courageous_comets/ui/charts/keywords_bars.py b/courageous_comets/ui/charts/keywords_bars.py new file mode 100644 index 0000000..018d272 --- /dev/null +++ b/courageous_comets/ui/charts/keywords_bars.py @@ -0,0 +1,31 @@ +import io +from collections import Counter + +import discord +import matplotlib.pyplot as plt + + +def render(counter: Counter[str]) -> discord.File: + """ + Render a bar chart of the top given keywords. + + Parameters + ---------- + counter: Counter[str] + The keywords and their counts. + """ + keywords, counts = zip(*counter.most_common(10), strict=True) + + _, ax = plt.subplots() + ax.bar(keywords, counts) + ax.set_ylabel("Count") + ax.set_title("Top keywords") + + # Rotate the x-axis labels 45 degrees to keep them readable + plt.xticks(rotation=45, ha="right") + + file_ = io.BytesIO() + plt.savefig(file_) + file_.seek(0) + + return discord.File(file_, "top_keywords.png") diff --git a/courageous_comets/ui/embeds/user_keywords.py b/courageous_comets/ui/embeds/user_keywords.py new file mode 100644 index 0000000..fc01b81 --- /dev/null +++ b/courageous_comets/ui/embeds/user_keywords.py @@ -0,0 +1,33 @@ +from collections import Counter + +import discord + +from courageous_comets.ui.embeds import format_embed + + +def render(user: discord.User | discord.Member, keywords: Counter[str]) -> discord.Embed: + """ + Render the top keywords used by a user. + + Parameters + ---------- + user: discord.User | discord.Member + The user to show the keywords for. + keywords: Counter[str] + The keywords and their counts. + """ + embed = discord.Embed( + title="User interests", + description=f"Here are the top keywords that {user.mention} has used in their messages.", + color=discord.Color.blurple(), + timestamp=discord.utils.utcnow(), + ) + + for keyword, count in keywords.most_common(3): + embed.add_field( + name=keyword, + value=f"{count} times", + inline=True, + ) + + return format_embed(embed) From c4cd8466e4b3baf7435a2efcf28f31965544b816 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Sun, 28 Jul 2024 08:41:38 +0000 Subject: [PATCH 138/168] fix: ensure consistent styling on embed titles --- courageous_comets/ui/embeds/message_frequency.py | 2 +- courageous_comets/ui/embeds/search_results.py | 2 +- courageous_comets/ui/embeds/user_sentiment.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/courageous_comets/ui/embeds/message_frequency.py b/courageous_comets/ui/embeds/message_frequency.py index b5ba057..340cb1b 100644 --- a/courageous_comets/ui/embeds/message_frequency.py +++ b/courageous_comets/ui/embeds/message_frequency.py @@ -38,7 +38,7 @@ def render(frequencies: list[models.MessageFrequency], duration: Duration) -> di } embed = discord.Embed( - title="Message frequencies", + title="Message Frequency", description=TEMPLATE.format_map(template_vars), color=discord.Colour.purple(), timestamp=discord.utils.utcnow(), diff --git a/courageous_comets/ui/embeds/search_results.py b/courageous_comets/ui/embeds/search_results.py index 9f41a47..267bf96 100644 --- a/courageous_comets/ui/embeds/search_results.py +++ b/courageous_comets/ui/embeds/search_results.py @@ -21,7 +21,7 @@ def render(query: str, messages: list[discord.Message]) -> discord.Embed: The rendered embed. """ embed = discord.Embed( - title="Found related messages 🚀", + title="Search Results 🚀", description=search_results.render(query, messages), colour=discord.Colour.blurple(), timestamp=discord.utils.utcnow(), diff --git a/courageous_comets/ui/embeds/user_sentiment.py b/courageous_comets/ui/embeds/user_sentiment.py index 75472c7..290addb 100644 --- a/courageous_comets/ui/embeds/user_sentiment.py +++ b/courageous_comets/ui/embeds/user_sentiment.py @@ -31,7 +31,7 @@ def render(user: discord.User | discord.Member, data: SentimentResult) -> discor } embed = discord.Embed( - title="Message Sentiment", + title="User Sentiment", description=TEMPLATE.format_map(template_vars), color=color, timestamp=discord.utils.utcnow(), From 8404385685e466d9726001d20de28cf9e8b9728d Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Sun, 28 Jul 2024 08:42:44 +0000 Subject: [PATCH 139/168] fix: capitalize embed title --- courageous_comets/ui/embeds/user_keywords.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/courageous_comets/ui/embeds/user_keywords.py b/courageous_comets/ui/embeds/user_keywords.py index fc01b81..3942268 100644 --- a/courageous_comets/ui/embeds/user_keywords.py +++ b/courageous_comets/ui/embeds/user_keywords.py @@ -17,7 +17,7 @@ def render(user: discord.User | discord.Member, keywords: Counter[str]) -> disco The keywords and their counts. """ embed = discord.Embed( - title="User interests", + title="User Interests", description=f"Here are the top keywords that {user.mention} has used in their messages.", color=discord.Color.blurple(), timestamp=discord.utils.utcnow(), From 9747325e766d43fc52ca325bb4fc44fe3c8437d7 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Sun, 28 Jul 2024 12:18:11 +0200 Subject: [PATCH 140/168] feat: add topics command that lists the most used keywords (#61) --- application.yaml | 1 + .../cogs/keywords/topics_command.py | 125 ++++++++++++++++++ .../cogs/keywords/user_context_menu.py | 6 +- courageous_comets/ui/embeds/popular_topics.py | 34 +++++ 4 files changed, 161 insertions(+), 5 deletions(-) create mode 100644 courageous_comets/cogs/keywords/topics_command.py create mode 100644 courageous_comets/ui/embeds/popular_topics.py diff --git a/application.yaml b/application.yaml index d4b9c21..db25308 100644 --- a/application.yaml +++ b/application.yaml @@ -2,6 +2,7 @@ cogs: - courageous_comets.cogs.about - courageous_comets.cogs.keywords.search_command - courageous_comets.cogs.keywords.search_context_menu + - courageous_comets.cogs.keywords.topics_command - courageous_comets.cogs.keywords.user_context_menu - courageous_comets.cogs.messages - courageous_comets.cogs.ping diff --git a/courageous_comets/cogs/keywords/topics_command.py b/courageous_comets/cogs/keywords/topics_command.py new file mode 100644 index 0000000..b99be79 --- /dev/null +++ b/courageous_comets/cogs/keywords/topics_command.py @@ -0,0 +1,125 @@ +import logging + +import discord +import discord.ext +import discord.ext.commands + +from courageous_comets.client import CourageousCometsBot +from courageous_comets.enums import StatisticScope +from courageous_comets.redis.messages import get_tokens_count +from courageous_comets.ui.charts import keywords_bars +from courageous_comets.ui.embeds import popular_topics + +logger = logging.getLogger(__name__) + + +class TopicsCommand(discord.ext.commands.Cog): + """ + A cog that provides keyword analysis for a guild, channel or user using a slash command. + + Attributes + ---------- + bot : CourageousCometsBot + The bot instance. + """ + + def __init__(self, bot: CourageousCometsBot) -> None: + self.bot = bot + + @discord.app_commands.command( + name="topics", + description="Show the most commonly used keywords.", + ) + async def show_keywords( + self, + interaction: discord.Interaction, + scope: StatisticScope = StatisticScope.GUILD, + ) -> None: + """ + Allow users to view the most commonly used keywords in the server. + + Replies with an embed with the most commonly used keywords in the given scope. + The embed contains a bar chart of the keywords used in the server. + + Parameters + ---------- + interaction: discord.Interaction + The interaction that triggered the command. + scope : StatisticScope + The scope of the keywords to show. + """ + logger.info( + "User %s requested the most commonly used keywords %s in %s using the /topics command.", + interaction.user.id, + interaction.id, + scope.name, + ) + + if self.bot.redis is None: + logger.error( + "Could not answer topics request %s due to Redis being unavailable.", + interaction.id, + ) + return await interaction.response.send_message( + "This feature is currently unavailable. Please try again later.", + ephemeral=True, + ) + + if not interaction.guild: + logger.debug( + "Could not answer topics request %s due to it being used outside of a guild.", + interaction.id, + ) + return await interaction.response.send_message( + "This command can only be used in a guild.", + ephemeral=True, + ) + + if scope == StatisticScope.CHANNEL and not interaction.channel: + logger.debug( + "Could not answer topics request %s due to it being used outside of a channel.", + interaction.id, + ) + return await interaction.response.send_message( + "This command can only be used in a channel.", + ephemeral=True, + ) + + await interaction.response.defer(ephemeral=True, thinking=True) + + ids = ( + [str(interaction.channel.id)] # type: ignore + if scope == StatisticScope.CHANNEL + else [str(interaction.user.id)] + if scope == StatisticScope.USER + else None + ) + + keywords = await get_tokens_count( + self.bot.redis, + guild_id=str(interaction.guild.id), + scope=scope, + ids=ids, + ) + + if not keywords: + logger.debug( + "Could not find any keywords for scope %s.", + scope.name, + ) + return await interaction.followup.send( + f"No keywords found for scope {scope.name}.", + ephemeral=True, + ) + + embed = popular_topics.render(scope, keywords) + + chart = keywords_bars.render(keywords) + embed.set_image(url=f"attachment://{chart.filename}") + + return await interaction.followup.send(embed=embed, file=chart, ephemeral=True) + + +async def setup(bot: CourageousCometsBot) -> None: + """Load the cog.""" + await bot.add_cog(TopicsCommand(bot)) diff --git a/courageous_comets/cogs/keywords/user_context_menu.py b/courageous_comets/cogs/keywords/user_context_menu.py index ed935b8..c01f15b 100644 --- a/courageous_comets/cogs/keywords/user_context_menu.py +++ b/courageous_comets/cogs/keywords/user_context_menu.py @@ -91,11 +91,7 @@ async def show_keywords( chart = keywords_bars.render(tokens) embed.set_image(url=f"attachment://{chart.filename}") - return await interaction.followup.send( - embed=embed, - file=chart, - ephemeral=True, - ) + return await interaction.followup.send(embed=embed, file=chart, ephemeral=True) async def setup(bot: CourageousCometsBot) -> None: diff --git a/courageous_comets/ui/embeds/popular_topics.py b/courageous_comets/ui/embeds/popular_topics.py new file mode 100644 index 0000000..3982598 --- /dev/null +++ b/courageous_comets/ui/embeds/popular_topics.py @@ -0,0 +1,34 @@ +from collections import Counter + +import discord + +from courageous_comets.enums import StatisticScope +from courageous_comets.ui.embeds import format_embed + + +def render(scope: StatisticScope, keywords: Counter[str]) -> discord.Embed: + """ + Render the top keywords used in the given scope. + + Parameters + ---------- + user: StatisticsScope + The scope to show the keywords for. + keywords: Counter[str] + The keywords and their counts. + """ + embed = discord.Embed( + title="Popular Topics", + description=f"Here are the top keywords for the current **{scope.name.lower()}**.", + color=discord.Color.blurple(), + timestamp=discord.utils.utcnow(), + ) + + for keyword, count in keywords.most_common(3): + embed.add_field( + name=keyword, + value=f"{count} times", + inline=True, + ) + + return format_embed(embed) From 7a5fd4d6697b81fce7756a94cd69ac44d5343eee Mon Sep 17 00:00:00 2001 From: Uchechukwu Orji Date: Sun, 28 Jul 2024 13:25:33 +0100 Subject: [PATCH 141/168] feat: frequency time divisions (#62) --- courageous_comets/cogs/frequency.py | 6 +++- courageous_comets/redis/messages.py | 45 ++++++++++++++++++++++++++--- 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/courageous_comets/cogs/frequency.py b/courageous_comets/cogs/frequency.py index 4b30c10..957b66f 100644 --- a/courageous_comets/cogs/frequency.py +++ b/courageous_comets/cogs/frequency.py @@ -87,6 +87,7 @@ async def frequency_command( self.bot.redis, guild_id=str(interaction.guild.id), duration=duration, + limit=10_000, ) if not frequencies: @@ -104,7 +105,10 @@ async def frequency_command( chart = frequency_line.render(frequencies, duration) embed.set_image(url=f"attachment://{chart.filename}") - logger.debug("Returning frequency chart for frequency request %s.", interaction.id) + logger.debug( + "Returning frequency chart for frequency request %s.", + interaction.id, + ) return await interaction.response.send_message( embed=embed, diff --git a/courageous_comets/redis/messages.py b/courageous_comets/redis/messages.py index bf6f0e8..e28a620 100644 --- a/courageous_comets/redis/messages.py +++ b/courageous_comets/redis/messages.py @@ -1,3 +1,4 @@ +import datetime import itertools import json import logging @@ -369,6 +370,34 @@ async def get_tokens_count( return counter +def _calculate_duration_range(duration: Duration) -> tuple[float, float]: + """Calculate the lower and upper bounds of a duration. + + Parameters + ---------- + duration: courageous_comets.enums.Duration + The duration to be used for aggregation. + + Returns + ------- + tuple[float, float] + The lower and upper bounds of the duration as UNIX timestamps. + """ + upper = datetime.datetime.now(datetime.UTC) + match duration: + case Duration.minute: + lower = upper - datetime.timedelta(minutes=60) + case Duration.hourly: + lower = upper - datetime.timedelta(hours=24) + case Duration.daily: + lower = upper - datetime.timedelta(days=7) + case _: + error_message = f"Unhandled duration: {duration}" + raise ValueError(error_message) + + return (lower.timestamp(), upper.timestamp()) + + async def get_messages_frequency( # noqa: PLR0913 redis: Redis, *, @@ -403,6 +432,12 @@ async def get_messages_frequency( # noqa: PLR0913 A list of message frequency at different timestamps. """ search_scope = build_search_scope(guild_id, ids, scope) + lower_timestamp, upper_timestamp = _calculate_duration_range(duration) + filter_expression = ( + search_scope + & (Num("timestamp") >= lower_timestamp) # type: ignore + & (Num("timestamp") <= upper_timestamp) # type: ignore + ) index = _get_raw_index(redis) # Define a reducer to count distinct message IDs and alias the result as "num_messages" @@ -410,19 +445,21 @@ async def get_messages_frequency( # noqa: PLR0913 # Build the aggregation query query = ( - aggregations.AggregateRequest(f"{search_scope!s} @timestamp:[0 inf]") + aggregations.AggregateRequest(str(filter_expression)) .limit( 0, limit, ) # Create a new property `timestamp` rounded to the start of the interval .apply( - timestamp=f"minute(@timestamp) - ((minute(@timestamp) % {duration.value}))", + timestamp=f"floor(@timestamp) - (floor(@timestamp) % {duration.value})", ) # Group results by interval using the new `timestamp` property .group_by(["@timestamp"], reducer) - # Sort results by the timestamp - .sort_by(aggregations.Asc("@timestamp")) # type: ignore + # Sort results by the timestamp and return at most 60 entries. We never + # get more than 60 groups because the smallest time division is 60 + # minutes according to our duration values. + .sort_by(aggregations.Asc("@timestamp"), max=60) # type: ignore ) # Execute the aggregation query on the index From 4619536c54adc2941271b6c25277804e18288cfd Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Sun, 28 Jul 2024 12:46:17 +0000 Subject: [PATCH 142/168] =?UTF-8?q?bump:=20version=200.9.0=20=E2=86=92=200?= =?UTF-8?q?.10.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/CHANGELOG.md | 23 +++++++++++++++++++++++ pyproject.toml | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 9101419..34feebd 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,3 +1,26 @@ +## v0.10.0 (2024-07-28) + +### Feat + +- frequency time divisions (#62) +- add topics command that lists the most used keywords (#61) +- add user keyword frequency (#60) + +### Fix + +- capitalize embed title +- ensure consistent styling on embed titles +- improve logging for all interactions + +### Refactor + +- log message processed as debug event +- log database event as error +- simplify messages cog +- remove redundant error class +- remove redundant decorator +- split up keywords and sentiment cogs, improve cog documentation + ## v0.9.0 (2024-07-28) ### Feat diff --git a/pyproject.toml b/pyproject.toml index 2de8a62..bafe549 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "courageous-comets" -version = "0.9.0" +version = "0.10.0" authors = ["Courageous Comets"] description = "" readme = "docs/README.md" From 8e1ec1297368887fd68417fcd5d21c6601d765a4 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Sun, 28 Jul 2024 14:45:58 +0000 Subject: [PATCH 143/168] feat: lock dependencies for final version BREAKING CHANGE: dependencies are no longer updated --- poetry.lock | 2 +- pyproject.toml | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/poetry.lock b/poetry.lock index 714345c..a3704ac 100644 --- a/poetry.lock +++ b/poetry.lock @@ -3292,4 +3292,4 @@ test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", [metadata] lock-version = "2.0" python-versions = "~3.12" -content-hash = "7abf0795cda093b153a5895a03f471dd2a972412a1a00e3a6249ec16b94b8f18" +content-hash = "ef6f37a7a9199d3bdd8393ce47d75330f5e701c720359a3b0213c835c9e3d5f9" diff --git a/pyproject.toml b/pyproject.toml index bafe549..70059b0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,10 +8,13 @@ license = "MIT" [tool.poetry.dependencies] python = "~3.12" +asyncache = "0.3.1" +cachetools = "5.4.0" coloredlogs = "15.0.1" contractions = "0.1.73" discord-py = "2.4.0" jishaku = "2.5.2" +matplotlib = "3.9.1" nltk = "3.8.1" pydantic = "2.8.2" pynacl = "1.5.0" @@ -19,12 +22,9 @@ python-dotenv = "1.0.1" pyyaml = "6.0.1" redis = { extras = ["hiredis"], version = "5.0.7" } redisvl = { extras = ["hiredis"], version = "0.2.3" } -torch = { version = "^2.3.1+cpu", source = "pytorch-cpu" } -transformers = "^4.42.4" -unidecode = "^1.3.8" -matplotlib = "^3.9.1" -asyncache = "^0.3.1" -cachetools = "^5.4.0" +torch = { version = "2.4.0+cpu", source = "pytorch-cpu" } +transformers = "4.43.3" +unidecode = "1.3.8" [tool.poetry.dev-dependencies] commitizen = "3.27.0" From 7ab7ff61e2ee005d2d89d24388f85fed795f0786 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Sun, 28 Jul 2024 14:47:55 +0000 Subject: [PATCH 144/168] =?UTF-8?q?bump:=20version=200.10.0=20=E2=86=92=20?= =?UTF-8?q?1.0.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/CHANGELOG.md | 10 ++++++++++ pyproject.toml | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 34feebd..edacfba 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,3 +1,13 @@ +## v1.0.0 (2024-07-28) + +### BREAKING CHANGE + +- dependencies are no longer updated + +### Feat + +- lock dependencies for final version + ## v0.10.0 (2024-07-28) ### Feat diff --git a/pyproject.toml b/pyproject.toml index 70059b0..29233ee 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "courageous-comets" -version = "0.10.0" +version = "1.0.0" authors = ["Courageous Comets"] description = "" readme = "docs/README.md" From 2e5637c5a0b79ac76bf67af986b60cd530a50c82 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Sun, 28 Jul 2024 15:02:11 +0000 Subject: [PATCH 145/168] build: downgrade torch and lock to patch version --- poetry.lock | 72 +++++++++++++++++++++++++++++++++++++++++--------- pyproject.toml | 2 +- 2 files changed, 60 insertions(+), 14 deletions(-) diff --git a/poetry.lock b/poetry.lock index a3704ac..cc99f8b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1088,6 +1088,20 @@ files = [ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] +[[package]] +name = "intel-openmp" +version = "2021.4.0" +description = "Intel OpenMP* Runtime Library" +optional = false +python-versions = "*" +files = [ + {file = "intel_openmp-2021.4.0-py2.py3-none-macosx_10_15_x86_64.macosx_11_0_x86_64.whl", hash = "sha256:41c01e266a7fdb631a7609191709322da2bbf24b252ba763f125dd651bcc7675"}, + {file = "intel_openmp-2021.4.0-py2.py3-none-manylinux1_i686.whl", hash = "sha256:3b921236a38384e2016f0f3d65af6732cf2c12918087128a9163225451e776f2"}, + {file = "intel_openmp-2021.4.0-py2.py3-none-manylinux1_x86_64.whl", hash = "sha256:e2240ab8d01472fed04f3544a878cda5da16c26232b7ea1b59132dbfb48b186e"}, + {file = "intel_openmp-2021.4.0-py2.py3-none-win32.whl", hash = "sha256:6e863d8fd3d7e8ef389d52cf97a50fe2afe1a19247e8c0d168ce021546f96fc9"}, + {file = "intel_openmp-2021.4.0-py2.py3-none-win_amd64.whl", hash = "sha256:eef4c8bcc8acefd7f5cd3b9384dbf73d59e2c99fc56545712ded913f43c4a94f"}, +] + [[package]] name = "jinja2" version = "3.1.4" @@ -1512,6 +1526,24 @@ files = [ {file = "mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443"}, ] +[[package]] +name = "mkl" +version = "2021.4.0" +description = "Intel® oneAPI Math Kernel Library" +optional = false +python-versions = "*" +files = [ + {file = "mkl-2021.4.0-py2.py3-none-macosx_10_15_x86_64.macosx_11_0_x86_64.whl", hash = "sha256:67460f5cd7e30e405b54d70d1ed3ca78118370b65f7327d495e9c8847705e2fb"}, + {file = "mkl-2021.4.0-py2.py3-none-manylinux1_i686.whl", hash = "sha256:636d07d90e68ccc9630c654d47ce9fdeb036bb46e2b193b3a9ac8cfea683cce5"}, + {file = "mkl-2021.4.0-py2.py3-none-manylinux1_x86_64.whl", hash = "sha256:398dbf2b0d12acaf54117a5210e8f191827f373d362d796091d161f610c1ebfb"}, + {file = "mkl-2021.4.0-py2.py3-none-win32.whl", hash = "sha256:439c640b269a5668134e3dcbcea4350459c4a8bc46469669b2d67e07e3d330e8"}, + {file = "mkl-2021.4.0-py2.py3-none-win_amd64.whl", hash = "sha256:ceef3cafce4c009dd25f65d7ad0d833a0fbadc3d8903991ec92351fe5de1e718"}, +] + +[package.dependencies] +intel-openmp = "==2021.*" +tbb = "==2021.*" + [[package]] name = "mpmath" version = "1.3.0" @@ -2733,6 +2765,19 @@ files = [ [package.extras] widechars = ["wcwidth"] +[[package]] +name = "tbb" +version = "2021.13.0" +description = "Intel® oneAPI Threading Building Blocks (oneTBB)" +optional = false +python-versions = "*" +files = [ + {file = "tbb-2021.13.0-py2.py3-none-manylinux1_i686.whl", hash = "sha256:a2567725329639519d46d92a2634cf61e76601dac2f777a05686fea546c4fe4f"}, + {file = "tbb-2021.13.0-py2.py3-none-manylinux1_x86_64.whl", hash = "sha256:aaf667e92849adb012b8874d6393282afc318aca4407fc62f912ee30a22da46a"}, + {file = "tbb-2021.13.0-py3-none-win32.whl", hash = "sha256:6669d26703e9943f6164c6407bd4a237a45007e79b8d3832fe6999576eaaa9ef"}, + {file = "tbb-2021.13.0-py3-none-win_amd64.whl", hash = "sha256:3528a53e4bbe64b07a6112b4c5a00ff3c61924ee46c9c68e004a1ac7ad1f09c3"}, +] + [[package]] name = "tenacity" version = "8.5.0" @@ -2907,34 +2952,35 @@ files = [ [[package]] name = "torch" -version = "2.4.0+cpu" +version = "2.3.1+cpu" description = "Tensors and Dynamic neural networks in Python with strong GPU acceleration" optional = false python-versions = ">=3.8.0" files = [ - {file = "torch-2.4.0+cpu-cp310-cp310-linux_x86_64.whl", hash = "sha256:0e59377b27823dda6d26528febb7ca06fc5b77816eaa58b4420cc8785e33d4ce"}, - {file = "torch-2.4.0+cpu-cp310-cp310-win_amd64.whl", hash = "sha256:53c3f75fa4ef0726e494ebef003b17d8a61c3c9fa4630b465610b462bf06c3de"}, - {file = "torch-2.4.0+cpu-cp311-cp311-linux_x86_64.whl", hash = "sha256:14a7a8b595347dddca594f9e448b93ce68ce4f871acbd32cf04bda7c03664c0c"}, - {file = "torch-2.4.0+cpu-cp311-cp311-win_amd64.whl", hash = "sha256:3b3cb9a6c17b5a4cea42bb37a243bfbad7659cef6d9b4ee29cb793bdf20f482c"}, - {file = "torch-2.4.0+cpu-cp312-cp312-linux_x86_64.whl", hash = "sha256:78dbf5f2789933a7ea2dabeead4daa44679b1e0d8eb35ddb7071c8ab7b181eb3"}, - {file = "torch-2.4.0+cpu-cp312-cp312-win_amd64.whl", hash = "sha256:f59c53a1c3247efb3700f9f78bdd289712177037a85b5519b9ecdef7c77c1fee"}, - {file = "torch-2.4.0+cpu-cp38-cp38-linux_x86_64.whl", hash = "sha256:08753c3d776ae49dc9ddbae02e26720a513a4dc7997e41d95392bca71623a0cd"}, - {file = "torch-2.4.0+cpu-cp38-cp38-win_amd64.whl", hash = "sha256:9f376f5a14eb04a44974c3a9dfd857a68090acb435b98e62bbf523baeefac85e"}, - {file = "torch-2.4.0+cpu-cp39-cp39-linux_x86_64.whl", hash = "sha256:040abaee8affa1bb0f3ca14ca693ba81d0d90d88df5b8a839af96933a7fa2d29"}, - {file = "torch-2.4.0+cpu-cp39-cp39-win_amd64.whl", hash = "sha256:441fbf517c46fee6782a4289ffe49f701d0a52e3533ab5397ce395da165d921d"}, + {file = "torch-2.3.1+cpu-cp310-cp310-linux_x86_64.whl", hash = "sha256:d679e21d871982b9234444331a26350902cfd2d5ca44ce6f49896af8b3a3087d"}, + {file = "torch-2.3.1+cpu-cp310-cp310-win_amd64.whl", hash = "sha256:500bf790afc2fd374a15d06213242e517afccc50a46ea5955d321a9a68003335"}, + {file = "torch-2.3.1+cpu-cp311-cp311-linux_x86_64.whl", hash = "sha256:a272defe305dbd944aa28a91cc3db0f0149495b3ebec2e39723a7224fa05dc57"}, + {file = "torch-2.3.1+cpu-cp311-cp311-win_amd64.whl", hash = "sha256:d2965eb54d3c8818e2280a54bd53e8246a6bb34e4b10bd19c59f35b611dd9f05"}, + {file = "torch-2.3.1+cpu-cp312-cp312-linux_x86_64.whl", hash = "sha256:2141a6cb7021adf2f92a0fd372cfeac524ba460bd39ce3a641d30a561e41f69a"}, + {file = "torch-2.3.1+cpu-cp312-cp312-win_amd64.whl", hash = "sha256:6acdca2530462611095c44fd95af75ecd5b9646eac813452fe0adf31a9bc310a"}, + {file = "torch-2.3.1+cpu-cp38-cp38-linux_x86_64.whl", hash = "sha256:cab92d5101e6db686c5525e04d87cedbcf3a556073d71d07fbe7d1ce09630ffb"}, + {file = "torch-2.3.1+cpu-cp38-cp38-win_amd64.whl", hash = "sha256:dbc784569a367fd425158cf4ae82057dd3011185ba5fc68440432ba0562cb5b2"}, + {file = "torch-2.3.1+cpu-cp39-cp39-linux_x86_64.whl", hash = "sha256:a3cb8e61ba311cee1bb7463cbdcf3ebdfd071e2091e74c5785e3687eb02819f9"}, + {file = "torch-2.3.1+cpu-cp39-cp39-win_amd64.whl", hash = "sha256:df68668056e62c0332e03f43d9da5d4278b39df1ba58d30ec20d34242070955d"}, ] [package.dependencies] filelock = "*" fsspec = "*" jinja2 = "*" +mkl = {version = ">=2021.1.1,<=2021.4.0", markers = "platform_system == \"Windows\""} networkx = "*" sympy = "*" typing-extensions = ">=4.8.0" [package.extras] opt-einsum = ["opt-einsum (>=3.3)"] -optree = ["optree (>=0.11.0)"] +optree = ["optree (>=0.9.1)"] [package.source] type = "legacy" @@ -3292,4 +3338,4 @@ test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", [metadata] lock-version = "2.0" python-versions = "~3.12" -content-hash = "ef6f37a7a9199d3bdd8393ce47d75330f5e701c720359a3b0213c835c9e3d5f9" +content-hash = "9806c9e24ea140d62a53762b540a7f9e79ef4cf087e99fa97c78616771411acf" diff --git a/pyproject.toml b/pyproject.toml index 29233ee..58b81b7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,7 +22,7 @@ python-dotenv = "1.0.1" pyyaml = "6.0.1" redis = { extras = ["hiredis"], version = "5.0.7" } redisvl = { extras = ["hiredis"], version = "0.2.3" } -torch = { version = "2.4.0+cpu", source = "pytorch-cpu" } +torch = { version = "~2.3.1+cpu", source = "pytorch-cpu" } transformers = "4.43.3" unidecode = "1.3.8" From ce641cca7d8b7efa6c14ece528f2feed881c6b53 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Mon, 29 Jul 2024 13:05:24 +0000 Subject: [PATCH 146/168] docs: extend technical documentation and start adding use cases --- docs/README.md | 15 +- docs/admin-guide/deployment.md | 31 +-- .../development-environment.md | 203 ++++++------------ docs/contributor-guide/index.md | 1 + docs/contributor-guide/secrets-management.md | 135 ++++++++++++ docs/contributor-guide/testing.md | 17 +- docs/contributor-guide/version-control.md | 4 +- docs/user-guide/data-privacy.md | 15 ++ docs/user-guide/getting-started.md | 41 ++++ docs/user-guide/index.md | 10 + docs/user-guide/installing-the-bot.md | 31 +++ mkdocs.yaml | 6 + 12 files changed, 337 insertions(+), 172 deletions(-) create mode 100644 docs/contributor-guide/secrets-management.md create mode 100644 docs/user-guide/data-privacy.md create mode 100644 docs/user-guide/getting-started.md create mode 100644 docs/user-guide/index.md create mode 100644 docs/user-guide/installing-the-bot.md diff --git a/docs/README.md b/docs/README.md index d9010b8..a284428 100644 --- a/docs/README.md +++ b/docs/README.md @@ -3,21 +3,14 @@ Navigating a new Discord server can be overwhelming, _but it doesn't have to be_. Introducing **Courageous Comets**, the Discord bot that helps you feel at home in any server! -## Installation - -Adding the bot to your Discord server is easy - just click the button below! - - -[Add to Discord :fontawesome-brands-discord:](https://discord.com/oauth2/authorize?client_id=1262672493978714174){ .md-button .md-button--primary } - ## Features -The Courageous Comets bot is designed to help you: +Our bot is designed to help you: ### Discover and Connect Effortlessly 🚀 -**Channel and Message Search**: Easily find channels and messages that match your interests. Whether you're into -gaming, coding, or just chatting, Courageous Comets helps you quickly locate the content you care about. +**Channel and Message Search**: Easily find channels and messages that match your interests. Courageous Comets +helps you quickly locate the communities you care about. **Similar Message Finder**: Found a message you like? Use the bot to find related messages across different channels. Stay informed and engaged without endless scrolling. @@ -30,7 +23,7 @@ sentiment analysis to highlight the friendliest spaces and people. **Community Member Insights**: Curious about someone’s activity and attitude? Ask the bot for a summary and get a quick overview of their contributions and demeanor. -### Moderate with Ease 🛡️ +#### Moderate with Ease 🛡️ **Toxic Behavior Detection**: Moderators can use Courageous Comets to spot toxic behavior and spam to maintain a positive environment. diff --git a/docs/admin-guide/deployment.md b/docs/admin-guide/deployment.md index 5e8646a..49bc5be 100644 --- a/docs/admin-guide/deployment.md +++ b/docs/admin-guide/deployment.md @@ -1,6 +1,16 @@ # Deployment -This section provides instructions on how to deploy the application in a production environment. +This section provides instructions on how to deploy the Courageous Comets application in a production environment. +Follow the steps below to set up the application. + +The application is deployed using [Docker](https://www.docker.com/) [Docker Compose](https://docs.docker.com/compose/). +It consists of the following services: + +- [**courageous-comets**](https://github.com/thijsfranck/courageous-comets/pkgs/container/courageous-comets): + The Courageous Comets application. +- [**redis-stack**](https://hub.docker.com/r/redis/redis-stack-server): The Redis instance used to store data. + +By the end of this guide, all services will be running as Docker containers on your system. ## Checklist @@ -14,10 +24,6 @@ This section provides instructions on how to deploy the application in a product ## Get the Docker Compose File -!!! NOTE "Prerequisites" - The application is distributed as a Docker image. Please ensure that you have [Docker](https://www.docker.com/) - and [Docker Compose](https://docs.docker.com/compose/) installed on your system before proceeding. - The application can be deployed using Docker Compose. You can use the `docker-compose.yaml` file provided in the GitHub repository to start the application. @@ -26,14 +32,6 @@ GitHub repository to start the application. Download the file and save it in any directory on your system. -## Services - -The Docker Compose file defines the following services: - -- [**courageous-comets**](https://github.com/thijsfranck/courageous-comets/pkgs/container/courageous-comets): - The Courageous Comets Discord application. -- [**redis-stack**](https://hub.docker.com/r/redis/redis-stack-server): The database used by the application. - ## Configuration Before starting the application, you need to configure the application settings. Create a `.env` file in the same @@ -55,6 +53,9 @@ DISCORD_TOKEN= Replace `` with your Discord bot token. +!!! DANGER "Security Warning" + Keep your Discord bot token secure and do not share it with anyone! + ??? QUESTION "Where do I find my Discord bot token?" See the configuration section for instructions on [how to obtain a Discord bot token](./configuration.md#discord_token). @@ -75,6 +76,10 @@ Replace `latest` with the tag corresponding to the version you want to use. ## Start the Application +!!! NOTE "Prerequisites" + Please ensure that [Docker](https://www.docker.com/) and [Docker Compose](https://docs.docker.com/compose/) + are installed on your system, and that the Docker daemon is running. + Once you have set up the configuration, you can start the application using Docker Compose. Open a terminal and navigate to the directory where you saved the `docker-compose.yaml` file. Run the following command: diff --git a/docs/contributor-guide/development-environment.md b/docs/contributor-guide/development-environment.md index b656098..4b54643 100644 --- a/docs/contributor-guide/development-environment.md +++ b/docs/contributor-guide/development-environment.md @@ -7,21 +7,17 @@ Follow the steps below to set up your development environment. ## Environment Setup -You can set up the development environment using either the [automated](#automated-setup) or [manual](#manual-setup) -setup process. +You can set up the development environment using either the [development container](#using-the-development-container) +or following the [manual](#manual-setup) setup process. -### Automated Setup +### Using the Development Container The project includes a [development container](https://containers.dev) to automatically set up your development -environment. +environment, including the all tools and dependencies required to develop the application locally. !!! NOTE "Prerequisites" [Docker](https://www.docker.com) must be installed on your system to use the development container. -??? TIP "GitHub Codespaces" - If your system does not support Docker, you can use a [GitHub Codespace](https://docs.github.com/en/codespaces/getting-started/quickstart) - to install a development container in the cloud. - #### Quick Start See the video installation guide below for a step-by-step tutorial on installing the development container with @@ -47,6 +43,15 @@ For more details, refer to the setup guide for your IDE: - [Visual Studio Code](https://code.visualstudio.com/docs/devcontainers/tutorial) - [PyCharm](https://www.jetbrains.com/help/pycharm/connect-to-devcontainer.html) +#### Services + +The development container includes the following services for local development: + +| Service | Description | Address | +| ------------ | ------------ | ----------------------------------------- | +| Redis | Database | [`localhost:6379`](http://localhost:6379) | +| RedisInsight | Database GUI | [`localhost:8001`](http://localhost:8001) | + ### Manual Setup If you prefer to set up the development environment manually, follow the steps below. @@ -83,151 +88,50 @@ Next, install the pre-commit hooks to ensure that your code is formatted and lin poetry run pre-commit install ``` -## Secrets Management - -To use our team's shared Discord bot token, you will need to retrieve it from the `.env.lock` file in the project -root directory. This section will guide you through the process of decrypting the file to access the token. - -??? QUESTION "Can I use my own Discord bot token?" - Yes, you can use your own Discord bot token. - - First, create a new bot account on the [Discord Developer Portal](https://discord.com/developers/applications). - Generate a token for your bot account and create a `.env` file in the project root directory. Add the following - line to the file: - - ```plaintext - DISCORD_TOKEN= - ``` - - If you choose to use your own token, you can skip the steps below. - -### Install Tools - -First, you will need to install [`age`](https://github.com/FiloSottile/age) and [`SOPS`](https://github.com/getsops/sops) -on your system. Follow the instructions for your operating system below. - -=== "Windows" - - Open a PowerShell terminal and run the following command to install `SOPS`: - - ```bash - winget install -e --id Mozilla.SOPS - ``` - - To install `age`, download the [latest binary for Windows](https://github.com/FiloSottile/age/releases) and - add your `age` binary to the system `PATH`. - -=== "macOS" - - Open a terminal and run the following command: - - ```bash - brew install age sops - ``` - -=== "Linux" - - Download the [`SOPS` binary](https://github.com/getsops/sops/releases) for your platform. For instance, if - you are on an amd64 architecture: - - ```bash - curl -LO https://github.com/getsops/sops/releases/download/v3.9.0/sops-v3.9.0.linux.amd64 - ``` - - Move the binary into your `PATH`: - - ```bash - mv sops-v3.9.0.linux.amd64 /usr/local/bin/sops - ``` - - Make the binary executable: - - ```bash - chmod +x /usr/local/bin/sops - ``` +This will set up the pre-commit hooks to run automatically when you commit changes to the repository. - Finally, install `age`: +#### Redis Database - ```bash - sudo apt-get install -y age - ``` +The application requires a Redis database to run. We recommend setting up a local Redis instance using Docker +for development purposes. -=== "Development Container" - - If you are using the development container, the tools are already installed! 🎉 - -### Generate Keys - -Next, you will need to generate a new key pair using `age`. Run the following command from the root directory of -the project: +To start a Redis instance using Docker, run the following command: ```bash -age-keygen -o > secrets/keys.txt +docker run -d -p 6379:6379 -p 8001:8001 redis/redis-stack:latest ``` -This will create a new key pair and save it to the `secrets/keys.txt` file. Share your public key with the team -so it can be registered. - -!!! DANGER "Security Warning" - Only your public key can be safely shared. Do not share the private key with anyone! +This will start a Redis server on port `6379` and a RedisInsight GUI on port `8001`. -??? QUESTION "Where can I find my public key?" - You can find your public key in the `secrets/keys.txt` file or in the terminal output after generating the - key pair. +## Configuring your Environment -??? TIP "Development Container Automation" - On initial setup, the key pair is generated automatically in the development container. +To run the application, you will need to provide the following configurations in a `.env` file at the project +root directory. -### Registering a new Public Key +### Discord Token -!!! NOTE "Prerequisite" - This step needs to be performed by a team member who already has access to the `.env` file. +The application requires a Discord bot token to run. It should be stored in the `.env` file as follows: -To register a new public key, first extend the `.sops.yaml` file in the project root directory. -Add the public key to the list of `age` keys. Each key is separated by a comma and a newline. - -```yaml -creation_rules: - - age: >- - , - , - +```dotenv +DISCORD_TOKEN= ``` -Next, [encrypt](#encrypting-secrets) the `.env` file with the updated list of keys and push it to the repository. - -### Decrypting Secrets - -Once your public key is added to the `.env.lock` file, you can decrypt the file to access the Discord bot token. -First, pull the latest changes from the repository: +The repository includes an encrypted `.env.lock` file with the shared Discord bot token for our team. Follow the +[Secrets Management](./secrets-management.md) guide to decrypt the file and start using the token. -```bash -git pull -``` +??? QUESTION "Can I use my own Discord bot token?" + Yes, you can use your own Discord bot token. If you do so, there's need to decrypt the `.env.lock` file. -!!! NOTE "Prerequisite" - `SOPS` requires the `SOPS_AGE_KEY_FILE` environment variable to be set to the path of your private key file. - This is automatically set up in the development container. +### Redis Configuration -Next, run the following command to decrypt the `.env.lock` file: +If you're not using the development container, you will need to configure the Redis connection in the `.env` file: -```bash -sops decrypt --input-type dotenv --output-type dotenv .env.lock > .env +```dotenv +REDIS_HOST=localhost +REDIS_PORT=6379 ``` -This will decrypt the file and save the contents to a new `.env` file in the project root directory. You can now -access the Discord bot token. - -!!! DANGER "Security Warning" - Do not commit your decrypted `.env` file to version control or share the contents with anyone! - -### Encrypting Secrets - -To encrypt the `.env` file after making changes, run the following command: - -```bash -sops encrypt .env > .env.lock -``` +This configuration assumes you are running a local Redis instance on the default port. ## Running the Application @@ -239,13 +143,13 @@ poetry run python -m courageous_comets The application should now be online and ready to respond to input from your Discord server. -## Building the Application +## Building the Docker Image !!! INFO "Production Builds" - Production builds are fully automated. See the [GitHub Actions](./version-control.md#github-actions) section - of the version control page for more information. + The release process is fully automated and does not require you to build the docker image locally. See the + [GitHub Actions](./version-control.md#github-actions) section of the version control guide for more information. -To run a local build for testing, first build the Python package with Poetry: +Before building the Docker image, first build the Python package with Poetry: ```bash poetry build -f wheel @@ -254,16 +158,33 @@ poetry build -f wheel This will create a `.whl` file in the `dist` directory. Next, build the Docker image as follows: ```bash -docker build -t courageous-comets . +docker build -t ghcr.io/thijsfranck/courageous-comets:latest . +``` + +## Running the Docker Container + +Once you have [built the Docker image](#building-the-docker-image), use the following command to run the production +container locally: + +```bash +docker run -i --env-file .env ghcr.io/thijsfranck/courageous-comets:latest ``` -Finally, run the Docker container: +This will run the application just as it would in production, using your local `.env` file and Redis instance. + +## Running the Docker Compose Stack + +To run the application in a production configuration including the Redis database, you can use the Docker Compose +stack. + +First, build the Docker image as described in [the previous section](#building-the-docker-image). Then, run the +following command: ```bash -docker run -i --env-file .env courageous-comets +docker-compose up ``` -This will run the application just as it would in production, using your local `.env` file. +This will start the application and the Redis database in separate containers using your local `.env` file. ## Running the Documentation diff --git a/docs/contributor-guide/index.md b/docs/contributor-guide/index.md index 3ddecdf..5748ee2 100644 --- a/docs/contributor-guide/index.md +++ b/docs/contributor-guide/index.md @@ -7,6 +7,7 @@ set up a new development environment, along with guidelines on version control, - [Architecture & Design](./architecture-design.md): How the application is structured and key design decisions. - [Development Environment](./development-environment.md): How to set up your development environment. +- [Secrets Management](./secrets-management.md): How to manage secrets securely in the project. - [Version Control](./version-control.md): How to manage changes using version control. - [Documentation](./documentation.md): How to write good documentation. - [Testing](./testing.md): How to test the application. diff --git a/docs/contributor-guide/secrets-management.md b/docs/contributor-guide/secrets-management.md new file mode 100644 index 0000000..54a1579 --- /dev/null +++ b/docs/contributor-guide/secrets-management.md @@ -0,0 +1,135 @@ +# Secrets Management + +To use our team's shared Discord bot token, you will need to retrieve it from the `.env.lock` file in the project +root directory. This section will guide you through the process of decrypting the file to access the token. + +## Install Tools + +First, you will need to install [`age`](https://github.com/FiloSottile/age) and [`SOPS`](https://github.com/getsops/sops) +on your system. Follow the instructions for your operating system below. + +=== "Windows" + + Open a PowerShell terminal and run the following command to install `SOPS`: + + ```bash + winget install -e --id Mozilla.SOPS + ``` + + To install `age`, download the [latest binary for Windows](https://github.com/FiloSottile/age/releases) and + add your `age` binary to the system `PATH`. + +=== "macOS" + + Open a terminal and run the following command: + + ```bash + brew install age sops + ``` + +=== "Linux" + + Download the [`SOPS` binary](https://github.com/getsops/sops/releases) for your platform. For instance, if + you are on an amd64 architecture: + + ```bash + curl -LO https://github.com/getsops/sops/releases/download/v3.9.0/sops-v3.9.0.linux.amd64 + ``` + + Move the binary into your `PATH`: + + ```bash + mv sops-v3.9.0.linux.amd64 /usr/local/bin/sops + ``` + + Make the binary executable: + + ```bash + chmod +x /usr/local/bin/sops + ``` + + Finally, install `age`: + + ```bash + sudo apt-get install -y age + ``` + +=== "Development Container" + + If you are using the development container, the tools are already installed! 🎉 + +## Generate Keys + +??? TIP "Using the development container" + The development container automatically generates a key pair for you on initial setup. You public key will + be shown in the terminal output. You can also find it later in the `secrets/keys.txt` file. + +Next, you will need to generate a new key pair using `age`. Run the following command from the root directory of +the project: + +```bash +age-keygen -o > secrets/keys.txt +``` + +This will create a new key pair and save it to the `secrets/keys.txt` file. Share your public key with the team +so it can be registered. + +!!! DANGER "Security Warning" + Only your public key can be safely shared. Do not share the private key with anyone! + +??? QUESTION "Where can I find my public key?" + You can find your public key in the `secrets/keys.txt` file or in the terminal output after generating the + key pair. + +## Registering a new Public Key + +!!! NOTE "Prerequisite" + This step needs to be performed by a team member who already has access to the `.env` file. + +To register a new public key, first extend the `.sops.yaml` file in the project root directory. +Add the public key to the list of `age` keys. Each key is separated by a comma and a newline. + +```yaml +creation_rules: + - age: >- + , + , + +``` + +Next, [encrypt](#encrypting-secrets) the `.env` file with the updated list of keys and push it to the repository. + +## Decrypting Secrets + +Once your public key is added to the `.env.lock` file, you can decrypt the file to access the Discord bot token. +First, pull the latest changes from the repository: + +```bash +git pull +``` + +!!! NOTE "Prerequisite" + `SOPS` requires the `SOPS_AGE_KEY_FILE` environment variable to be set to the path of your private key file. + This is automatically set up in the development container. + +Next, run the following command to decrypt the `.env.lock` file: + +```bash +sops decrypt --input-type dotenv --output-type dotenv .env.lock > .env +``` + +This will decrypt the file and save the contents to a new `.env` file in the project root directory. You can now +access the Discord bot token. + +!!! DANGER "Security Warning" + Do not commit your decrypted `.env` file to version control or share the contents with anyone! + +## Encrypting Secrets + +To encrypt the `.env` file after making changes, run the following command: + +```bash +sops encrypt .env > .env.lock +``` + +This will encrypt the file and save it to the `.env.lock` file. You can now commit the changes to version control. diff --git a/docs/contributor-guide/testing.md b/docs/contributor-guide/testing.md index b7c615e..df82f92 100644 --- a/docs/contributor-guide/testing.md +++ b/docs/contributor-guide/testing.md @@ -5,17 +5,24 @@ and accelerate our development pace. ## Structure -Test modules should be located in the same directory as the module they cover. Test modules should be named -`test__*.py` (e.g.,`test__example.py`). Individual test methods within those modules should be prefixed with -`test__` (e.g., `test__my_function`). +Test modules should be located in the `tests` directory at the root of the project. The `tests` directory is further +divided into subdirectories for unit tests and integration tests. Each unit tests module should have a corresponding +module in the `courageous_comets` package. ??? EXAMPLE "Test Module Structure" ```plaintext project_root/ ├── courageous_comets/ │ ├── __init__.py - │ ├── example.py - │ └── test__example.py + │ └── example.py + ├── tests/ + | ├── conftest.py + │ ├── courageous_comets/ + │ │ ├── __init__.py + │ │ └── test__example.py + │ └── integrations/ + │ ├── __init__.py + │ └── test__integration.py └── ... ``` diff --git a/docs/contributor-guide/version-control.md b/docs/contributor-guide/version-control.md index 5440b15..5f39335 100644 --- a/docs/contributor-guide/version-control.md +++ b/docs/contributor-guide/version-control.md @@ -53,7 +53,7 @@ before each commit. The pre-commit hooks include: - Linting and formatting with [Ruff](https://docs.astral.sh/ruff/) -- Commit message validation with [Commitlint](https://commitlint.js.org) +- Commit message validation with [Commitizen](https://commitizen-tools.github.io/commitizen/) ??? QUESTION "What if the pre-commit hooks fail?" If the pre-commit hooks fail, you will need to address the issues before committing your changes. Follow the @@ -116,7 +116,7 @@ The project includes automated checks to ensure the code meets the quality stand - All [pre-commit hooks](#automated-checks) must pass - Type checking with [Pyright](https://github.com/microsoft/pyright) -- Unit tests with [pytest](https://docs.pytest.org/en/stable/) +- Running all tests with [pytest](https://docs.pytest.org/en/stable/) ??? QUESTION "What if the automated checks fail?" If any of the automated checks fail, please address the issues before requesting a review. Feedback from the diff --git a/docs/user-guide/data-privacy.md b/docs/user-guide/data-privacy.md new file mode 100644 index 0000000..c02d71c --- /dev/null +++ b/docs/user-guide/data-privacy.md @@ -0,0 +1,15 @@ +# Data Privacy + +From the moment the bot is added to the server, it scans all messages sent by users to gather the data necessary +for responding to interactions. + +While raw messages are not stored, the bot processes messages to identify keywords. The identified keywords, along +with their count and byte representation, are stored in the database. Additionally, the Discord IDs for the message, +user, channel, and server are recorded. + +For displaying messages as part of search results, the bot interacts with the Discord API to fetch the message +content. The user who requested the search results can view the message content, even if they were not part of +the original conversation. Messages are cached in the bot's memory for a limited time to improve performance. + +Apart from interactions with the Discord API, no data is shared with third parties, and all data is securely stored +on the bot's server. diff --git a/docs/user-guide/getting-started.md b/docs/user-guide/getting-started.md new file mode 100644 index 0000000..36b8f73 --- /dev/null +++ b/docs/user-guide/getting-started.md @@ -0,0 +1,41 @@ +# Getting Started + +This guide will help you get started using the Courageous Comets Discord bot. You'll learn how to use the bot +to explore a new server and connect with its members. + +We've all experienced the feeling of joining a new server and not knowing where to start. With Courageous Comets, +you can quickly get an overview of what a server is about and whether or not it's a good fit for you. + +## Finding your way around + +Let's say you've just joined the Python Discord server and you are interested in web development with Django. +Let's use the search feature to find the most recent messages related to those topics. + +Type the following command in any channel: + +```plaintext +/search query:"web development with Django" +``` + +The bot will return a list of messages that are relevant to your query. The search results include references to +the channels and message authors, so you can get an idea of where the conversation is happening. + +The search feature is also available as a context menu option when you right-click on a message. This allows you +to search for similar messages to the one you selected. + +## Discovering new communities + +If you're looking for a new community to join, you can ask the bot to find channels that match your interests. +Let's say you're excited to learn more about programming and are looking for people who feel the same way. + +Try the following command: + +```plaintext +/sentiment_search query: "excited to learn about programming" +``` + +The bot will return a list of messages that share your sentiment. Again, the results include the channels and authors +of the messages to help you explore further. + +The sentiment search feature is also available as a context menu option when you right-click on a message. This +allows to find messages with similar sentiments to the one you selected. diff --git a/docs/user-guide/index.md b/docs/user-guide/index.md new file mode 100644 index 0000000..ca98efe --- /dev/null +++ b/docs/user-guide/index.md @@ -0,0 +1,10 @@ +# User Guide + +Welcome to the user guide for the Courageous Comets Discord bot! This guide is intended for users of the app and +for those who want to learn more about its features and capabilities. + +## Contents + +- [Getting Started](./getting-started.md): An overview of the bot's basic features. +- [Installing the bot](./installing-the-bot.md): How to add the bot to your Discord server. +- [Data Privacy](./data-privacy.md): Information on how the bot handles your data. diff --git a/docs/user-guide/installing-the-bot.md b/docs/user-guide/installing-the-bot.md new file mode 100644 index 0000000..7652722 --- /dev/null +++ b/docs/user-guide/installing-the-bot.md @@ -0,0 +1,31 @@ +# Getting Started + +This guide will help you get started with the Courageous Comets Discord bot. You'll learn how to invite the bot +to your server and use its basic features. + +## Installation + +Adding the bot to your Discord server is easy - just click the button below! + + +[Add to Discord :fontawesome-brands-discord:](https://discord.com/oauth2/authorize?client_id=1262672493978714174){ .md-button .md-button--primary } + +## Initial Setup + +Once the bot is added to your server, you will need to make the bot's interactions available to your users. You +can do so by mentioning the bot and using the `sync` command as shown below: + +```plaintext +@Courageous Comets sync +``` + +After running the `sync` command, the bot will confirm the number of interactions that are now available. + +??? TIP "Run the `sync` command regularly" + It's a good idea to run the `sync` command regularly to stay up-to-date with the latest features and interactions. + +You're all set! The bot is now ready to use on your server. + +!!! SUCCESS "Results may vary initially" + The quality of responses will improve over time as the bot collects more data. The more active your server + is, the faster the bot will learn. diff --git a/mkdocs.yaml b/mkdocs.yaml index fbd0038..e8efa55 100644 --- a/mkdocs.yaml +++ b/mkdocs.yaml @@ -37,6 +37,11 @@ theme: name: Switch to system preference nav: + - Users: + - user-guide/index.md + - Getting Started: user-guide/getting-started.md + - Installing the Bot: user-guide/installing-the-bot.md + - Data Privacy: user-guide/data-privacy.md - Administrators: - admin-guide/index.md - Deployment: admin-guide/deployment.md @@ -46,6 +51,7 @@ nav: - contributor-guide/index.md - Architecture & Design: contributor-guide/architecture-design.md - Development Environment: contributor-guide/development-environment.md + - Secrets Management: contributor-guide/secrets-management.md - Version Control: contributor-guide/version-control.md - Documentation: contributor-guide/documentation.md - Testing: contributor-guide/testing.md From ddc1b4b24cb6d22c630e302c34c3895c5c928082 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Mon, 29 Jul 2024 16:37:58 +0000 Subject: [PATCH 147/168] docs: fix page heading --- docs/user-guide/installing-the-bot.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/user-guide/installing-the-bot.md b/docs/user-guide/installing-the-bot.md index 7652722..f10a0df 100644 --- a/docs/user-guide/installing-the-bot.md +++ b/docs/user-guide/installing-the-bot.md @@ -1,4 +1,4 @@ -# Getting Started +# Installing the Bot This guide will help you get started with the Courageous Comets Discord bot. You'll learn how to invite the bot to your server and use its basic features. From 260692bae61bf1adf04dc3696f3f6db7d2704f1e Mon Sep 17 00:00:00 2001 From: Uchechukwu Orji Date: Mon, 29 Jul 2024 18:18:27 +0100 Subject: [PATCH 148/168] docs: reword storage section (#63) --- docs/contributor-guide/architecture-design.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/contributor-guide/architecture-design.md b/docs/contributor-guide/architecture-design.md index 59857b3..1ce058c 100644 --- a/docs/contributor-guide/architecture-design.md +++ b/docs/contributor-guide/architecture-design.md @@ -65,8 +65,8 @@ into several components: ### Storage -The bot uses Redis as a caching layer to store the results of the analysis. Redis is a fast and efficient key-value -store that supports the data structures needed to enable the application logic. +The bot uses Redis as a database layer to store the results of the analysis. Redis is a fast and efficient key-value +store that offers search and query features needed to enable the application logic. ## Data Model @@ -120,7 +120,7 @@ The application is fully contained within the `courageous_comets` package. The p | `__init__.py` | Entrypoint for the package. Exports the application client instance. | | `__main__.py` | Entrypoint for the application. Responsible for setup, teardown and root error handling. | | `enums.py` | Shared enumerations used across the application. | -| `exceptions.py` | Includes the base exception clss and custom exceptions used in the application. | +| `exceptions.py` | Includes the base exception class and custom exceptions used in the application. | | `models.py` | Defines the entities used by the application using Pydantic models. | | `preprocessing.py` | Contains the preprocessing logic for cleaning and normalizing text. | | `sentiment.py` | Implements the sentiment analysis logic using the NLTK library. | From bbfa49ab16a2b5ab657380a747d8886d338c62a6 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Mon, 29 Jul 2024 17:29:34 +0000 Subject: [PATCH 149/168] ci: add prettier as default markdown formatter --- .devcontainer/devcontainer.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 6b3966b..90f7a35 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -17,11 +17,12 @@ "DavidAnson.vscode-markdownlint", "KnisterPeter.vscode-commitizen", "tamasfe.even-better-toml", - "-ms-python.autopep8" + "-ms-python.autopep8", + "esbenp.prettier-vscode" ], "settings": { "[markdown]": { - "editor.defaultFormatter": "DavidAnson.vscode-markdownlint" + "editor.defaultFormatter": "esbenp.prettier-vscode" }, "[python]": { "editor.defaultFormatter": "charliermarsh.ruff" From 0d0bd48c746607429c51b2ab623492b576825354 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Mon, 29 Jul 2024 19:16:57 +0000 Subject: [PATCH 150/168] docs: add getting started guide and format markdown documents with prettier --- .markdownlint.json | 2 + docs/README.md | 2 +- docs/admin-guide/configuration.md | 2 + docs/admin-guide/deployment.md | 8 +- docs/assets/user-guide/about.png | Bin 0 -> 136436 bytes docs/assets/user-guide/frequency.png | Bin 0 -> 74327 bytes docs/assets/user-guide/praise.png | Bin 0 -> 117209 bytes docs/assets/user-guide/semantics-search.png | Bin 0 -> 124553 bytes docs/assets/user-guide/sentiment-search.png | Bin 0 -> 101814 bytes docs/assets/user-guide/topics.png | Bin 0 -> 41624 bytes docs/assets/user-guide/user-sentiment.png | Bin 0 -> 152337 bytes docs/contributor-guide/architecture-design.md | 69 +++++---- .../development-environment.md | 5 + docs/contributor-guide/documentation.md | 3 + docs/contributor-guide/secrets-management.md | 6 + docs/contributor-guide/testing.md | 6 + docs/contributor-guide/version-control.md | 17 ++- docs/user-guide/getting-started.md | 135 ++++++++++++++++-- docs/user-guide/installing-the-bot.md | 5 +- mkdocs.yaml | 31 ++-- 20 files changed, 233 insertions(+), 58 deletions(-) create mode 100644 docs/assets/user-guide/about.png create mode 100644 docs/assets/user-guide/frequency.png create mode 100644 docs/assets/user-guide/praise.png create mode 100644 docs/assets/user-guide/semantics-search.png create mode 100644 docs/assets/user-guide/sentiment-search.png create mode 100644 docs/assets/user-guide/topics.png create mode 100644 docs/assets/user-guide/user-sentiment.png diff --git a/.markdownlint.json b/.markdownlint.json index 86e4dcc..4448dcb 100644 --- a/.markdownlint.json +++ b/.markdownlint.json @@ -10,6 +10,8 @@ "MD033": { "allowed_elements": [ "img", + "figcaption", + "figure", "source", "video" ] diff --git a/docs/README.md b/docs/README.md index a284428..6d19a7f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -23,7 +23,7 @@ sentiment analysis to highlight the friendliest spaces and people. **Community Member Insights**: Curious about someone’s activity and attitude? Ask the bot for a summary and get a quick overview of their contributions and demeanor. -#### Moderate with Ease 🛡️ +### Moderate with Ease 🛡️ **Toxic Behavior Detection**: Moderators can use Courageous Comets to spot toxic behavior and spam to maintain a positive environment. diff --git a/docs/admin-guide/configuration.md b/docs/admin-guide/configuration.md index 11f4ab6..ec9a5e7 100644 --- a/docs/admin-guide/configuration.md +++ b/docs/admin-guide/configuration.md @@ -31,6 +31,7 @@ Your token should have the following scopes: - `bot` !!! DANGER "Security Warning" + Do not share your token with anyone! ## Optional Settings @@ -126,4 +127,5 @@ The password of the Redis server. Set this variable if your Redis server require is set by default. !!! DANGER "Security Warning" + Do not share your Redis password with anyone! diff --git a/docs/admin-guide/deployment.md b/docs/admin-guide/deployment.md index 49bc5be..2761882 100644 --- a/docs/admin-guide/deployment.md +++ b/docs/admin-guide/deployment.md @@ -1,3 +1,5 @@ + + # Deployment This section provides instructions on how to deploy the Courageous Comets application in a production environment. @@ -27,7 +29,6 @@ By the end of this guide, all services will be running as Docker containers on y The application can be deployed using Docker Compose. You can use the `docker-compose.yaml` file provided in the GitHub repository to start the application. - [Get the Docker Compose :fontawesome-brands-docker:](https://github.com/thijsfranck/courageous-comets/blob//docker-compose.yaml){ .md-button .md-button--primary } Download the file and save it in any directory on your system. @@ -40,6 +41,7 @@ directory as the `docker-compose.yaml` file. The sections below will guide you through setting up a minimal configuration to start the application. ??? QUESTION "What other configuration options are available?" + Refer to the [configuration](configuration.md) section for a complete list of the available options. ### Discord Token @@ -54,9 +56,11 @@ DISCORD_TOKEN= Replace `` with your Discord bot token. !!! DANGER "Security Warning" + Keep your Discord bot token secure and do not share it with anyone! ??? QUESTION "Where do I find my Discord bot token?" + See the configuration section for instructions on [how to obtain a Discord bot token](./configuration.md#discord_token). ### Image Versions @@ -72,11 +76,13 @@ REDIS_STACK_VERSION=latest Replace `latest` with the tag corresponding to the version you want to use. ??? QUESTION "Where can I find previous versions of the image?" + Previous versions of the Courageous Comets image are available on the [GitHub Container Registry](https://github.com/thijsfranck/courageous-comets/pkgs/container/courageous-comets). ## Start the Application !!! NOTE "Prerequisites" + Please ensure that [Docker](https://www.docker.com/) and [Docker Compose](https://docs.docker.com/compose/) are installed on your system, and that the Docker daemon is running. diff --git a/docs/assets/user-guide/about.png b/docs/assets/user-guide/about.png new file mode 100644 index 0000000000000000000000000000000000000000..49721f42ba0d1dda8e4b06ca61b6d459d11d135b GIT binary patch literal 136436 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

}OOBq`W1 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�~pMv@8;<0g)Pc?*8{MmVpq7oB{?95v@ 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


P;cmO z*$?i-in+>abM;)Yzt)Zc_F8$OARcpE(w~k(6*)$Hazxv(XJVjD_ARu1dFOe^V#ewT3;nO29mxXf1wli z(E&{VXE4i9IP_Am#jacdH*p0^1<}(b5gL2gd4dwOWJPYsfE^(lf^G^t{!ta!RH@>6 z1b3wr8({NygR_pQYHPd0Q^i5kv)?tR+u6#O1`i+)|CfTRn?m35{xeXfP=uOh~H(j z+;H-8j9UCs7nAog3LjNs@T0)0ncKwHy$zPHf-o_^iPy<(G3LKc2HgzW)Dx_~n zeY-C`5$y_Q$23IN8X6yN9Ob){W!H;t_U6IXV%(EsnCNkA~t0?@5hxfAvt$enB_GvOU zaTkNqT-}#aOCTDj6VJ)iOisYj$eYI#pgyD!y(3$Qz=Nqsq#*7x@YkXXmqdfnj^D{~ zB-|u<;jM=gJYhFW{3xb9m#3ua11!(aJZUBtF>Dg!P{t2q8DndGkEG*xtx#f49CydB zc;tTS7HCVJVNZA?<`(cf4SG0qCP6!uCfQ5YeHW>P4vucT5)F8t_)qb;A6@?#w};CP z>!AM~keyr)BB5`&@AZfQ`nm5U%=-Kq@3s(g{8L;^+?0Ea{GgD~3)W{Cb4lrah4#*n z>`J1Okj-~=-}*J@oha8phV@9N8d9*~(19n|#_YlePyP;=6fI}R;9}pxUqogbxInda->v!v5lYS`K2D<{<{F%vY$@of?ZjN!-b`ow<6wjt-EB>%5!)#0yf-W&dh5 zwGm6T8X`D} zXEZR?-DvYhiH~sxo!f5a^9&6SPgXL`Y~^xLmT_{o9TBt3TcVnp2C$7G$d`rDc+Op) z1iYLx&uTwa$%LZ-ln1!c{o++gVi|c&+l`w8k+JGc4`}3dC@3V0s)};@h=z+hUTLO@ zfID2juSty!Nb(`l{iFwOZ(}>m1ZWw&wwLDB4muI2w^`CXj@TWeeu-W|pwdUA_=74e z%7@n4SGldL0aQ7mtZl3y~}XqF|W#4E+ouUvKX7+ zmw)X(RPkdCw7C^qxBKAG?L?h-q}=841CP{I4Sf4m=o=UBWmhkXhc!~C$ESDF%Gu?> zxEueOM0?m}dsjxnTFg876&{aL&Og^TZrAF(L;E#7JaoZ_Qb4L_CXu2Cdz8pVF_u}A z?-cLHvAdRjvD#=ephUd-hPCNiT7BkVejNpT{}=M(-8+|)T?~o=alecNB-6N}#Z{!~ zEfxK%Ff#nbh_bF8n?s!`m+!%k&LRy~TbsQIVFHc5Re$@;ZDGrmQamDJs?9sC#&NvZ zi`PmzDz!E?HflbK5&DMu2h*LQ*;3w#m=H%WnEBMYdw2C`q`zip-e%3`X6%~Yl#bb$ z@@dHE(pX>bjGabi<3B-eoOrlkcxdyHF;Vn7bq1?7bcvv0zt z)Ca8P{G32Cl>C+4a(ixzeEc-Ex5wo|u*S5y(BI8X<&h zrmcNlHIUnMMA<(>Tdsh|Y40eKMddtFxpB|UUO&Dr!T6YQ{!827Vv;A30f3n>adq?L zxG#g=wSORBb;O1ijrr>uOCbaioKd85eU+Qu&Q)<5bjMkQ_jlZn!r%G37kcWt&VFny zIFQEU1$Z!Mu(gW7e@PV^eApEg?UoygPN}B{gvnPSfS#{dFGafsrl=**wM%;*4OG{j z-LuYmT~Jr=^TLxUnb6bK7J~KEM1qv5^=N0qmdvd_nBHJNBykCF6)Tjc-*|?cxk1nK zffq!_i{JaJsjByYi*|PhE)M6O0L@V5d>^GSso=DCHCbg~TmDTEi?o>NAdndC4)^wN zmzQP{c}QN!`PVDv_QS$U)2DB%>)08+T;|QAmtSiVuV4FaUeTnuXb2M9#8LBi5)7oS|*>m5UDT7xDYfI&hst7lQstF7F% zJT&)r+VVvr6PMGOdW9~l7SsSduhPhEdPslldn4t0-V@Bti#Sm7-4@|JtfH^E@uLHY zYQJ%x7?E)4{`T!H zR|B=|5N;iH695+QE`H;g8`!$t>*k>2@xWPZ+;VdHY{dVWE4j>YjMacTQRAxh2j*&}k3O8ahEjDDN3cnIf_ zk$9Lh5>g@AsZ9STC3DV^lCYh%_h~m)D=l&G?}RlABO~P)k|g%;iylZiv^dk$E44cu z!{L`tq!wqC@a|-w%e&P9WFb8mDV9YUT)Lm4sD)@RGljw+;};bT5zfXAbwQjuXy04Q z6w%}c^;I;rFC{nV+TZ@Sa$W=xt_qCMNJ)-@%X>C?5)o2x5bNqSF;Stanoc-`P!I<) z1O4QR=hq=UvNde#}{t!dHP5X)<2^jlWrb$NX5CtI>QMgLmUAr53B2 zngSABW1y~^f3B<^<#y{dNd8=B>lAuQg=rhTeQUD-a8dUK-4*bI-)A=raVX={CjXc- z^6UXhDNXthFz8Bz;$`>-4aBR?9v%d3xM(O7&qkJ;wwq_Z>(8VXVSPB^`dui${$izv z-wRj3D?-yyN-%Q`1;ww=F02T9wUpmjJM${E)*Bsc@dza$A80!oV)kkLsBo!F(nGpYFm__P!?)TV|~m zvEZ^mbhGVza%KF-?LiBl_fInVB)LeB(*CLL^K%I3=@5|j))taPVR}G*iG7Z#8%fI@ zf~SxDbx6U9`v%0)WiXDE-_U!sb;iH|brP+Am7rw{PR56tnFCEb_zE__Gp`W2@`p9cQ0~_mGgZ% zE$E$po@Ezyo{-_$YADAwx7u*r&o)ph_H9Ysg=ao`l^)c^K!GJrL7y z1DYYpFB1Zly%NOQkWt2|@uRPN|D9`({L2#f*B!UZ>tLR~jMt>E1AcksCNKZ^{6*HN z5f=5HB41LgJ{~*3ACRN}(RCq>9LI(torLso`s>S`(W&*`xdX=$6bjhU*1-2ho=N2E zv%T&VlO}dWXCmV@uLMT!7FfEjag{m(mj~Ul_!tbpB3FDc>MW#LV$HPhVYugcpAt7b z4+>=mi&Ul>pPsnDnp5@kY!Ic$IzY&P;hR}fQ9pcH&{6uv#@5!cw(RA{%oN6)7Uy~K z4@?}a!teRN+o-;WF`IM*eRbg*T&yXd`R-W7nj1`FEK(RW)zmId6P$tljhqI_ip=x{xeU}1Ro@er8x z$*Bw^_$Nxd)Efu&<=`K7^BeBARs;r!Cbc~E_}|L2J@fiHUbW)3qihk`o z5<^%?hue|>d|Il$+l=j`xVMSoFabD`k&+P1y--?pWfB%iuQHc1q3Z4MWwDw}MI}dq z_}W==JmL{S#0LWC;*9aSU&?}fb8k?!e#}=T|NG}5RpY~5 z-%cQii;us6I>0?PmLesj9L_M16omBg9a{ndZK~=ZHk4M&$H&LwTszIK79Xq%d9wbE z3&v_|K=JThW@Q<#F_bbpSXAT_0+o`tH;NcRF5rdHitYyzS_+07x~0m{tY0tjRYQ#+ zd^56nKn%H>^dCkCLXMu2iwm0riC{u|m%4M;`k+lrZq{Qd7#7h6^j#Y=cy5`00@ZPk~-HLIWa(Zc!R#?sGdFxycjwOiT2huHH&WyMboo41|tzMTnCmyPwIyzQ0j3P8o6=g(7@l$5CT?CBW0TPvx|IPr>>W-N7b1 zR$lWnGU{!mw_(~dxGXSHt7ghijj%CFtmmQ3Zu~|NR7)rCX}~a7+9ocwmvotg>1e=3Y0(`%J(k%=Yy(>{HJF6n`)KyZ){qa}as=jg zKScYPl}A6#n1fa21FF%EfOW1;iIv}^A9lG*iVd9R-q=2t(-4_&0~US=Srw7EJajj* zlpNU8|6VkjecxB-N%;_m=zV8*5-@+d8}?u0Il$xOF;QM!ks=IC+5P$mn8(WNN~joT@GGEacRmjwmh zvWu!{1>GhK3jZ-JbnF!2m*=Zf#{1%K@1CIA^vpuL$7pC~Ruq{{cor)N!|TN`Lt{~5 zsYkAcmj9^xnEm;ioNeZZGl@a+BvBw0A!Ui2a+e-m{_K#1BSj(Zf^0`^R&6D}OpR>E zNI$H9Pn6LT)e|O7ClR3UHjyWA-Fvf0u%&2J%TATZ_CD$53kMAvUa4JFVZqV z!7e*-mi(_!oh_ZZeqgzmuD}hxQ1ZXK)5pQZ7B|q~M~<&nAEX!L0A{hX6RU4maOK$i9BB;ZzBcgkQbe&FN z9=~njZG2e0y6(tWlDrYHsds{Rs+s~!5q1u*=IcrE_ams6#O^XLByW(XCLV2=Zsc-z zO>fhXM1H!lOs|k+jpP1|uGF*Z*uP=lV+kqslmGN+LeM?hD2)=CC#z@@(ktG^m};%1 zB|V#`FpPSyO>vX1poE}=YKy>d<-td>U>>7Y9|Rs|5*K|3>J?ykc-Te#3@eMg98^C2 zXF5RrDh>IAKW*uls(}0+`=&nO&ug562Wp+QfXu#?&-Lo|UyNwPku(faU3>z;x~2Zp zVe!%bN{G8&irm;x7ivLY^K^Q;^}B8}W8~+Fx3RU5n14ObDF?@i1<--L-rcPc3{St{ zZ2ujN{E`hL{y1dn7{$#SI;v+MwJn&A+?KbeagQ$1GaMrhHIQ=2l?h|Cm=oMGUwvpM zFH1km#wS6wxS4WK=l1pvjL3{++lUjRrlyl@e&5Usk2`%=t7I>x;-^VM-2TWZ1kPr* z%unml&=O6xOpLfU6u?#Fc|w~xPv$LWfb72taa@~XIlVeX_3UHZH@IRdxTnRnSF7Gm zVw}MLUBRPzB$)M6v^%-3D^}(IB46@vN)<&xtUZ+`hav0zqoafUu*lb@r`?x%wm%R# zQ(DIOE<_ztrg7OtRZ{#t?<+1zkdIHNvsPwvxu&8pPqcZH;PPFRucN<^%51m}S@e{9 zGU+#O{u6~>Sz{6r;u!}7bb6Ty0OB*#GloM0Uq+EgqS^!(MiI5U7d~V)l&eaMx@q8C z;z~}?GcvY&?vZ3dgJrEpwA7S6ioWaRnoE>3Y_d0glS^^)Rz(gYv(DkGORC7{-h#dQ zKO$_L&r1Ji<$#_3|E?VTKf5m|XuHhx@b6~*-!k!oXbL1V|GzF00;D;||8MspgU@KM X{LUj>c`M*3$V*j8OR-wsGVFfS3o8NubUpaqLx2W9Ii`~QcR{ojS9bt_cU}M9 zPzki}@Bx4XkPsI5>Z)_JY@&rVy8xV-45%dLS6|VSko*#dXLatzqwO&bA2+p^@j4x5 z?Y^>)Tl(rD6MkGUk3wm#57j{*I+5n+T(sz7vh&RNdjk$V_p?Ig>nLjVE~i7Xy6tX? zl5xmha`4(qj}f`;%kA3a>ocn81ra`8anF{g-oiVTOc+j#5mJYmTUoKD4R>^OTwh;X zTU#fla;1wdBl$oS_N3PjIvIyzaz7CCs`s9-np3fpAXK~+J3bgFGeGzlK=dWgsiQ@@ z!eUbiHe0^WPD@DW`?x7Nf?&yX2A@xBW1~7XR;Vy(qLC9NoCNJR z!Q-todc3!u*iM^^-qBO>5AtepXuzE6HhjJ!;hNZ7`#k`trNRNRpEcpp1QB?mPf8#i zSvL7*8YF0Qb8~&$7sbWJgGmgNEW=fh!73J+-K{OHKd$2=BO}w(?*Z!2i9?N2&BiEd zufIUn{Bj%h$6BT@=s+lDPpU}l#a_0%Cj8+7_y}XyCz*q zF_-5d)&NQ?&lCkM!$>r58<4c+@RZcr!tCL(-#9*neV9abp&(1_(T!=n`|F z){gQDp-7I_RFWp78p()^J}uClezkkT{XW#XTApz6P@>^68+$iCY~2bw$eFYamp&(# za^*=T8d6Gar6G|B%ubW4jAxhK|wJc9ZWRGLwP;{~8qJ`{SX#wMJpr>C+_bu3E2x zP<6A9`ju^M7`z!%Q~VCxPszgYASp5;E2nGOr6*nh9w3k~8gZX*JhJZ_Cx&p72B0++ zJr*)_7*3oMB4UXk!uk&k4EXy)2MWf-$8W9u`-_f`Cr=toOHH+RaL89I5+{cSLUW`G zEM}d;bXuSbWztPqKA;kEDQao3x!Ml+LOququ&}b`7ZR727BeyLTwQ(N--nl>j8jg> z&!#q?IZ8=#sPKH*uOeJMB|u{)A!Q-}5d2*oN5~+(WxCCF*P*PcwB;jSo{?&h4Wa5? zUYh#a<%XA%>5>^-l%5VDIQ4oD9y?7BJOTXc8`k{Vj`2i!?cdk?utq0#gM2Um@2<~b zLgk=}`fyTxlj{zu`&?z!81pTx6$`HA(P)H^qQU8g&qMTX{t(R~fssMVbp3$8^>1 z{mB;yKojJ#qk6>1W5Wm$CdWdMi2WV;j96S;t|`-=9FgZsDiK?-zp$|I@ZcV#$NO@( ziHJ%0=XypnBO}Ai%xtmF%Hv_b5C$50Xvae66f)VJ@3j{#fPzwNwb*K){tm!pj*G{2 zxjEu|p^z(?PUmsySYDRNw&n3S>8wrn;J3D*MMJoM`JR}V=u|RmR-*wRAnuExZLKUn z-kFPeb02JP&T~VKmgXI@v$G>*-(`AUrF47)e09Bfsj-lFTE1|xTTqyvYP6IIReJzd zQ&7IE^FmcUc-8#Kz{qw|8BOzUI_U=`EAbi!v+}a40Yz(U2{P{$L-kYnXr_?-Hwt|8 zm?ddIdqL63#qSh6u|OFEZLe?fGXm(ImW3WG62RFIkDc?zBxOiMei6PI!rtV;0im;L zvy{X;fFo%qvE<6@jtCYZjTuyF0*Fp1Nk~)`4KM=mP*WrZ0_; zue*w3iy%@X0a>M`p8SrwnwpD=i7F)nKGISaB~3EVXYnCa)pDvjWnV2=JR5!ZeS-@h z2P^0*r?3$Ano_xdv$tn-pXj%hV0FB_yl&N@fiY1Yt0~STZ8*SAGj6sUIbS!~OXic+ zUJiV|)J9#;h!kns7ntp9)i-b`WN6U7;QxR^=bIX{QVY9VGPRzvH5T1C!=Z_tCTs~` zqg8yf3uMv{)k4Z2S1GNg7JivekIc#rm)C`_t|?)9Qx;zjnH% zCqvj6N?|6KXuaDA8y_Ese@B3Z6!>_1IyyLLVQJ}TYisN1IAzKLJMNSj{FM~ScPsA> ze2r|^)m3pPQ2d58doKqGe1{)N=9D2p+tKQ~=X}l@H;tCeZ%m>mez=$49dK`f0`f45 z3dmCSvKQ9&Hts$f#|Re`hb+`bvh59#O1!l9XCyH_kLcWzH3?|7dHyA@8eilh@;Qna z34|;9iHhiC?r+2$?A}RUr}v4=;Km~T?fpv>{W-iLh7=V4uVeRwPzyU*A^uDi9ELby zW9-oP9811vg7qiw3=u&afd&3nIbG8n&S?vy#T%uO_k;N}G&B@+rsZar#f62`;Tq^~Hk$P}3qT4so{G_#&>aXpp7k9a5tM?*A~CX) zRDgS}xbm^f!|q7n6D)$CfI)m8N9+-k0*UpLF~XJk!mlWfvumfopqmdd?4)CdKs)uZ z{>~?WLua^9+C5*lLBN>G$~xLfx|$z!1c!QV4+V2Fbc8p?agB8a9bC>XG4Z~KBv?qb z8fC(ymG$+ft8Hys^f)2HNP&W+G2jYw$LGu%{Blj#ujvFd(=>_{-CP@#m6cUhRFqXz zrl(nxhmGSxPUm;5$Kdsbvt}I#dT*rxUtvgPr5SM^6+-7?Z`x{kQF){5=h^XobU-px z>gmaoFuj~k>4*IvKynZU`uScW>5Dgy2|J7IEZ6b0>~AD-ZZdb ztgES;VuyavFf@x>iBl7a*=Hl`i9Raqi%!(_WzCkrQti-|CxK8;k&|algp(9{TWS#I{m5_jdQWlRP zY4Gy$?cw3U^3qa<+F3)&=la@OE>3P#!ke+}LAUqY?<0yuUxC+FuUvDBL-9~tG<;_J zs~<~Jjb(IYGzl~iQV0vArnOv$1dY6q{Q4Lte|CTLC-35YFp@|{qSb8d&!Bd3JI%1i z1j{H|Dv2ITU8RK$^fe!*^voF(k1Xk=FT@WP^_=PV~*>QxHg$U$+N0fR{4-$$J(;_bFiZC zE9(|u&(>gLlGn3ma1<^!;j(W2 z*K{tYnZ?DDG>D(!kWLa4Pr0GX2;vgbNa(b7xGDc=73_eaEqz;#ll&CC2&;QA!qq~w z0pGx31vJo6NuQr|Z7-H@qn1{Zlyg?fU%@)ZRbzrEnl3&$arEKW1!6)?u|Ko3YH;^C zn4%iuby&jkt_5OHUeHVV_dHJ6!Ac3Rt%hVP=xDAKoSB_PjV*G~gpLFf+{YL$7Iyzw zOfBi$G>WKQ;S{^acsqoFCisi$$Kq2Rm7Lt0Cu@ZbU6Be4W;~4m!EAmK5{Xz61b-3@ z4GpBXZxQ_wP*C0@1?pEOZ*q7ac$z)$1wwZ6)@+AoK?OyzKEOBgLc zbn@lBJh=oD^4}^*Ch5`(j$lq(VbzO}A2|(K>~e+S+pGFIPpppt(cRC96o9YvN$G5D zWvL&zM5US~Q;K+OO=YDXt|)yHI}3{~!N(Xb$-Mfy;xnBNM)S~E^(5YJ2j6qT6&oG) z^L^vsOAP?z$(LF@a6L^fy2Dbxa}?MQugxQxlq+VrxVYF@Yh54j_8lqVd+7^kSJE||3vW~p#t{S)fBms@X3Gx{LS{csAKy?s@tBa4fFJMO z=;=a(SU8|5Nd5nIb#A8pi2m?c?SyN(%OVLup%nLXI*Qy-i__5@%aOFP;4&u^Ldf<& zGe+Fw-96%*?J&x5+Ryw>@&-e_K^U7p7LKNbO(Pgxa8Jj9*cg$t()4N%^d!{tiwkmz zs-~u$S>33UrCPB3RaX_!wzG3i+>%Gi<+ZlxA0n;AYS1kUR74K+!?iBtV!dbAIFIZ~3#syZIl-e{GUmlxrnJy;x) zOT;JWdhM@D9UkJ9y712)&eo5n*|h3g&@$siu+}=UDdL-$hyfh#kjr!zUY3-h8SbU` znO+ax^73nQc9W$r?3?eeGK8_MwHMaYDJJ-R4qWTZ?%iAR@}UluP~)q(MEu^ml+dty?w#zRC^6%**bU`pLccz z%uz73oOOmVywSg+=CK;wy68tu0)`u`2_Xvw=Ky5H6Y9E?3|n`>b+$5?|n@n zxtu@N-YkL$%d{sqc<_-hiM8~9ltiQOurU@6pYFq?bbXS*qB5(z?xl8ir8U~2=-2OL z^3$7p=UlnWvnXCqO>zu%_!q9X8LrYa36>AWk3lG_YH+6Xy8RwI^GOHhhb6@&6&2p7 zd&W&0YlcvOVfyE{Z=%r*(&^c$8|!&zTCXde_u&_CbZ7Ujmc|4G{r42&XNRLi!A%Oh z4&KZRHZW{T#BE{fFq~-$@V+t8ssKYJG+;67JzO=rj8g@<9rbBlXsrK2(tcXmOB50s zj)t)3-?>qy*~aa55l-=NxZa{#zQuQa74{^>&3!*Ys99YlN2h*430xg7?oP_B_?$k! zF3k5A%B6vc;qHc;n2^^~n6f(Mv%$ccfCN1~eMCg@QJe|8Nc7`pTg^sV@TL5{!uAeY`W$ykylc>%yqza^oEIx-7t~%;b0tF zHDyWX&aAfE+zAbpS;>vhpz1y-l&6ug+?;cZzcB)NADs(QDWn}08&zt>61qH7=t~R2Tw*U^kmgmVm#aMM* z6MCqBysLl%?d>(2mo(q}8hJ-{F6E&l;nxT)Cp!E+vH1an^pz%tVuzaAVXlRUAHR4% z+f~lw1N)B?7RAM-Qa&aNr8R>%4BAb$<6O2L2>u{oFtxO-c?SUeTKb-9!go=tFJlvy z&(mA2-mOBS$b(-JV|*xV7D`$0i)tKLHuui`|9B1#R$}%xH#D6sLjJPVvt)96)#a2D zfHYPr&*^7i?tD3FSVqP(eQhi{&#(X5>H5+6cwGuj(4{xMAAUbRMeut;fZLs~!eHdSn zIT~Mt`Q80Z>zxn7<^G5jk*%=s+@ejJhN^0t#jKoc1RKbBkLmACuS#9OPpn=Pg zV-xZunz7(Ko+X@Li6^#xhBzb4C@!uq32= zEv&AxH~CEY@Ve&KmdQXch&fZo+jO@Y>~e>ZFS~I{?grv0BfWM~0#z$ij#po+wdjdC z7(X#F>A(1!d@b%-jWou3pAe4A`4xh3y3&FQ(3DtY^-?*JJ(HO=(%G&3u53&g9A*{^ z8rL~7G?JNJ){49d2*xjBx3w~ld|7@?9#J?{tCtLuNAkiCL3)Nv>buyW zd32oeURuAGnyYy;G2qShiy4;S*z=xh$X@hAc14r{vW{^yCdmeMBt^L5tbvbcB1zIB zQlNimXbF3|+u6qDD+q)~dD^x%y0rjXCnxo~qfL_xw~M)R=?GB^?MFCh=%+-88#C== ze7@mxkPWzRPfvm`+s&u+GSWX#kgphMXrO%0H+z0fr|#;gb?PFLD@u4S0df7XfP}<3 zXcj)V%aQMCXD=T|l6mj@sdg#JyNnDCJFJLp1qC4zu?&CCB(1awfurT5lcSBN$C9GB zI5J@A%6X}y!+Si(4NM+#3@>~M#ppoJqnOv#IQJ-T^WMCPZq-+2<{c3^-pQE)INvBf zZ_pIJs@xQr=y-+b;o{@(*OlQ1z5CE*g81&;&08E^?|aD=htcP>Q4!~prN?d@VsJTb z_o21iE=O;C=jNayBe$xyE$EJK&+@DdZF2yCNiGFyt?QG6AubQK#P97#i{@qyOsQ8n zBtm9XVN!k7rtyoUX|I}K7V}!sGB^N=4)A?waxW7#(B3a6vp&mYX8GeHU!hYdmk}Bf zRgKixK(i5xQ7vcK(^VUcmmujaM1qEl$8|RqawMAIbdWoFU|wQz2a@hIsB);fx|yQ+ zipI1szB&Gaulzo|QfN}-CqyT2-0nNFKaz`7Nze8Z2=0yAYN|t*xztre<}C{1#)YFv zgxg{PzVCkz`{hv%jBSmiV6`uxeuF^6N@ss zP}Kr{RoL9UJf??Xk4Q;}K5p;of{9INAO4-QEIL5K%)D47GwFDeKNx~osgptEnaD4n zCt6l{Rax>if?Q(2($KutxKHR4V0$)(j)P;uo9cCO6(=_maYCFB6XWy@QlLGh8mFpCj-so#iAD<|(KBw4;u#76I1D?Y zw^0iqSP9Yt_;be-}xexb;Q00<3}f@&X}tai}5y^HZ5p$^64F`ru# z_$1H*eRQw6PWfsP_un-<7)wxy|lRCZ= zwSM)zJ&_^ivOH4?+Zj&droEN)cD^a%@tAPpIxG}BQ}tqZQ7y5jeL8m}(gJ)pHYNh@ z)EH22>6=N_f+}e~GedxASVTe;-e%YJ>M$JV^2xb5UdrWsqKvOeiWsn1h_aDgGt;v( zEDQZ1tzLV~I1BJ9G$r#st#*I8AHh>MoBOd&F7kXa36*HDiLntJMO5k1YaO}y&?^l8 z=62vq;`lGczt5^)Ch3Rq@X_aV9vchZz}R^AYHxa4zTEQnufLF>^Xc4lBNI?lr>2{x znW_oPf&#dwM~)hljzKrMsGUwy$+^t8h@ruXC;kl!d$ z^z{KAjyhq$*S9@32ZNU(zTc8jihs|#X&d6VK`l}iRoW4^h4l^Fmf$Zs0UAL^wEv$N z0t%>}#7#9gY7Adk|2MJFp)mfiaN^&G?n|A*To8Kg0!2jhT7>@o{R^pI#%7U88#dlM zC%h~pMcCjwZTrBJb(%7{+xB2CWo6}MY56J<*P~9O)jw#WeghV!DcmmDz*iNOmY1H# zL^-+Y`KO1&Q+2OIHtGahs&@@CW!@M4CYzfE8~V8h`i~4695x#PQ*Y#WOH_1S(rM}F znDJ!BpC2#k@6v^Am+?*CH!`bKtjO!E@V>^FuxJnqnecomqDPShVXhvcfx0yrHfF2N z)*87Vh16XM2N!2!(DLyVH^7Cui#siZ;NfC_=`~OkGehC#ucHPC%g|T4#HETLtIhU!msMx#8a#y^;-N zO&)}clD*ZE>*2K@Nw24G%BYX3kSC|1rK|l{`+cj(YS$5XV(Ycz+#OL>R)O{%5-k`v z1+wyfaeN`&us<=v2|Nr5$K)zY+~i0Z!p`#g3H{L#XGt)z-R|4!!JaCP_3FlmulU=5 zkZ(}PuxQAG3j9d?LsiggY9!%UQfRO!1iuM>^UJr}{S+lJ2Ne9Anq5XZzKi|CNXdM= zjk`%_>5E@t$@yOPj{W^}rY%}Gk}mJ>@3j_D+WFd1fnsh^3H8LyQ+rpQM%m!t+eo6Z z+p~2to3=}I-Hn~>-@zx*Fn_Z4C&#$emENJOzDji+H0}T$iGM6t#dK*U9 zthN*bV~k5lMn+3U7Dn=;5S!<-(Lg{$161%k!hJ2V+;L;0OuXb+4io2vR90Q5cbr^Y z5Z#yao(N6vmwQ`fWmW+8?onrN%8PLA6dstKc1n!RC@C$41(*jj`nb#p568BLP32?{ zZvA0Cp|V%y#A-iu)P5p_hAMp$pJ9kiTy(AsUAK>B<7MMkaDne@wLlYn~1%A1Mn+g{bi)+?)DQ9P#vW8e0uVhc7|Vg_YT}S5JRQX@mRvksH8RZK-Jy)<5SBu+_17 z7OQG}#lXbDNkf~RSep2y2<5AiyACf5R$(%7vt$eVRzgzW1&c}Z%s;>oz$2rCCmX!s zN8nWK!ysj*GQQTIA<k*79~AOm5Qt&FPb$bGzd0f7RJFCY|Hr+V9LOqeO7t=jKSUH1DN!vf)}tzj-5^FqMYY&dWM+!HmM}3@ zA;bM0swpZbl-djsT(_urs-{bm4r#=SJ5A5%^v>G zAAW+Q6j*hz)iIT6pa2?Wbp=feqpKASzj!=mOl#O0hM=;W4|lu}Nk!LmcaOCXMXO1J zFe~{T+N-|h`LaM6kPnIaGr^$nzg0truL%KyfY^MsNN9gUDDm<@AT^6caMr;`KN9%H zlB%QmaN+@0&_ALg!U)?}&6}H>6|j`G6mK$7dDlz+!CpB&x6^7lzCK>Jg)H{2uB-a$ zIG>As`$5a*@bFSF0GDZ<@6S8n2>O4A%nn*_X@8P0VVx|HjvusA5Bk7D-B_E;3dpnl zN=?(;8A;z>U(aUWgYq>>z6c!{8nUdO|LL}|wJszUgiKiD{hU-Nn;s|L^{GFEG-hmY zvL#ypvEHkF{Q2P>J{!j`)o&^@5;5W|=38SyaNG0D+KxD?D#|VQJ3jw#Uo}w*o6|tR z!GQ*TQT%BAyH|I|!@5(bEU=R@eSk6w@wWdP^d78%6BfibHsEk0g5>yg^_I0pSy<~K zx|JvQyBQkwCxe^4)mL2Fg8HRtf(jZcb}ybDF5UTIs$iJ&e&~OE6W9%%`m)BCd2^P> zF=Ke`n9G`WWx=NHA^W{sbrD6^%BJm5M$`8A6OM{>!AVZK-4Sskx6Rk0gTn*N*tn`l zlPbUt(T*lRRF_dbI*E9NYQwx+*-10a=UZ0IX1%3>!Q{+@id$_c$w!W>1sLK4l!2Xs zf?qFQqZtSK#=cWM1$6}~MPDVm^xJ%II^F`H(gUM%u6sI=y4o_& z9;bBMxuh|_TU$HE$L%h*&Y?SMZ=a4Df8ywRegIayUN;Bb-V_;#=0$$cW5ja+1r*oU zGqkD;!4Wo>zN+OKx;oxYodKUg7(Q+8L?xG@UVoCt<}~Xd4l-549{T57!M)wRsAp%k zllxb@Bf3k?49~WO8Bc`VH^1VG%_fOuKO}?RB5Rnw&|r5-d`CkW#^d2_{!4X4Jnf6= zHw+1kZ6j57%ekjPhBgfv%atd-z)lKEN{wvWCnAZ zc0)vA3W9}!A`nYc|0p#Ekw->@h%xdXA}+u}lZ`6=n)fU40H>rkx50zme1xh9UVRV(hh%c(9lR_FNG&y7(8?Jwj ziFZ-Ay7zg#I1LDw`B5Fy@BO?T?F9U5CInq#Jka*fadCgzQ`2LHqKsuk4~QX=;m75i z0ODiFA_&Z|(82*VsNaH&Z+-JSr2S$5Oeo{3=gbh2D}HvSDtc>EA~gF(IsvrA-j9GU zs1if;*Gnd%UlH<=)|?zK2uh#H_h|($K31V?ds}Mum4V!3Ng6Mbw{?x8KKMW?pSfio5d1QcJp5GpJ&<=uz zjDjo`%c94|wPo8?cFnUQH{tVOG}V`#`E=Xzz3oXuNtGet$K&1tmYJzR{7K1Yd&> zXt?~Q-fiNiP~)^5whQqRUZkA_1cWSjGV1r!%gr~>&RY#mJpVcu zHo0LCJ@Ir`{)C7~G$x2ZwPkfQ=X%*E$1&$GXfvr@B#1PqE~b|(mEAoP`+2$TJHyKeU`fdRCZgn%FGzH3A6`=$m#x|)^RF5lm9 z#n3aHd5VYhw01{rksGg3XAQO6^T+Z>6ZE5Dl{jRpeX{yt#EeN-phbW3Z9 z9FZW}IFZHBV})E~Err@4+b2dRkID~vLK;d4N(>z$0-*dvv49h#%DTpzRho+dw-6-a zOCK?yuL7c3s{qQZT8x`mhev~B#CN2J-QHyvc$_~S6jH?xd&xeSDn&@8_$1H?Fv22G zllbORf8+4qKv03TGB?Yg$?h^|_-`#%>PCn^n`6+KjInWH5H1Kh=IjoMmncs(ZD^Uo zteoOgC$ybUHqIR+3(bl1iH)$KvoTZO7?W_2AV#vFvq<2v=rr&s);UWgE}&#>?icw>Lx9@$Y0dKc0s)|%Wzg+*MXpJkcfE%f4x ztu!gfp^&ZMg0j?7*L>d}XtXrn)&1qBZ!b{Q?Ntlx1I1!3SD{Y0A3+t|=U6QHcwh3s zWLhGbK}K^)@l>_h>s{A#-UVaMY5DzrO}7&8PbOV^9!pQ{mNZk|)O zRJO~@m@54!5fb5;QQ*)`r((?$w9}Pq`Cy;|;`*kWz@{czlM`g|dixa0FrDIOW#JVm zn!m)zhewt)FJx^;m_1XbK@qQx^M@ihf&ezoF-qqC&#;V;qR$9dM!d={CZ@gZ2W4TP zy?%7`D6I}KPUTpK7*jJNy8BN>lv`u|Zj-`7$mtu;!>Z@A4cT3UI9TN68E5%!BJ@a` z4=!711TKs?{r79s$`K5JWLaW~1{%{0&Guns_#tFEl^!GT@V+=-Vq4Z3nGTA?Zyg~B zGet7}v_}o*U#IegT-y+lCgxjV+RrSBqJ9W7Gvam78xak5nc43wEPuP%fAnBU639SM zNT7pAhK&6vXRDX^SH4}4{rmCBo0-+kcJ2++lCk6<8WiXtTSJas?CK-|TM2VW4aLB* zDT>S(WVdTAp{Cx!@~wX)Z&ML{EV6CrgQ0n@_R4=$2K-49V5(>H@9O0G3_nN?O;D25wx^0SR<{Edlpi7)>qAHuTk^-o zegDTmi5v71L2!-Mn{K3fL~dX%V>evd*N#RY1#Lot8wmp?PcEg{UK>+jjBYot;~=Ux z@%BuU80;cvKML1_?SJ4G>=gUTln#5H95d!z&q4}kN=!b-H_ov*dE>$N3vu^s8iWz| zQ2!2Cx6v)J-2x+8OuH|a9%x5LmikQ~1}#rMHTdC~2{*%gX33Gqu!I zHBGc(z;l2>e#9tubDSZgVv5S_`;uYz^h=BWl;Gj%a`bL2nAm%)@hOANaW5K2*L9`0 zH<%m|2M4E8+)#V7l^?BZH`Dv#(k~NE__jV0TI5xdceTYYtdh<~=`N zZ`W&cA1uLRU}6AkeKEP;iSfAfxAqPW@DA~A-=s)m{Fd_&sl9! zq1Jj7MS+C+W4dJa%WG%Y_V)W3C~&8jJLkh@&%6-#_xIQJTC6NBA4EkmcpVbS`vbaM zfxGobVtoTRz_%pqaC)HRiJn|yL^>@dHT7V9ePyPAH^a_zx|nr%c-WJLqha>xUQRe5 zSc*J$B6ea}vyO<%^RD0{6;&^16mRMm#`*qniG0NjzRYU( z#^}@Ge~*HKvPmT^Nyxl2t*XoGxOZ00ouray>g@b%FU26jRZZ6t^ziUH?)b!@$JEq4zuxl#Ogodj$I+@IbCuAzOV7 zz;8L^JIePeojZ<<&pR<8$S&5p8GO3Dw7fH{$!L1kURh)S;_$}HUA~92O}PAtDi1+H zPf*r!I))ZY{x&R>&8K?XRaA6$S=Viwb?~!`57&2i)Ku0(bRRyx$W42D`f`nyhuhPS zpl~07QOcAG1M&Fy7%Ho-fQ^jIq3g$be}8}B=+B7Ia8Or2Jw2^HKkq3M*cmvAf#83) z_Wf2dHcma!%2b|!QQP7BnYP$y_4(A2o>ai9L_{xkG+ zvlwG7s9(8N5`hmA-)f0g7D~;{9u3VN!XhFgo?8mvI{@G*aOoRk2B*Wq=%~%qUW|K- z^~oP9a|?4Q5<06M=CD^NfJQl24I(Qoy*JBn5JHs!`9xRy^AmtOEE*w7h8^}PlnFQ7 zxLTrYMlA<@eLW=P93x~(5##-b`}^HYaLOS1&?C6@@nrGz&*e(%^W#0zC~viE zPxwAamHf=CIMuXi=^(Zmz253ub-iS@VX*n$L4pwiJ}m{t{zmucS+?0^0ne+}BO4$r zA}Z>Ib$`-)db~XA9gyzi!*zacciQs!P5|w_Dy^ka9U|g6=%eWwY}MuED^340+v@rc zA#R+c8?z7;$?X_2F(?vFPDCI!E)MFS`-pGBo;5En;3K{q+8d8Jn2Ew9o4L@L1mUr9=%?3BR`v zov5mEyTq1G{jxp#R}7ThZ%Tbv6mzT&fLCN2_ORH^z@#?=zoJ@+GolZC8Y)d{xX#c z-FhMb0|NtSw`79xO{zB$7KW9G?TsXUUHgtYZ0vCR5tzFg-f=pdDVa70`WUD=JZ`=K zcXzIr3RF~LU~>op3xJSRM*#|u9T@=_4f}*xWK2vlzqp>E0iYn+cv$0m2QD*74^wXr z6${>Q7{k-$MZ#b$Yb`75G&nla((;ED?>K2tl@S>R5Q`$@cDVW3lCFI^CJ*3pFoN7g zNrEch{T4H*p`ATUq|?owJuF>psU#x0KVY@Ec#8ylTW!s}!+5D@vz$W<%^4V^<$Zh# zo0(Bbmu=SX9+dHiegK5aG(=$6%XVL%ey~dAz5B0VG3&AVD1F?BV|*+TDDXIO z0Ih+Rse2$)N)ob|zI^5AeYf5lLnfL>PE|-w1vW+tM=7ybxXDexqN;jz#bZ0LA- z!MtAd*@!8{#wjWF0`21ly?29Aibb%1NG};Cf++uP9;@M9FW)9-L)`#0uwC-1w}#r z@LDht@a=fP$3k77UlvGzOm>8=~&CCe)KDJk2JsX_# z#au=#Ee*>S*3?)oT-8i{&V@(DMSDMOvh@pHC>SN>@$OGfj(*7lSoEy^g=7=1x>%(! z`+V8t_{;cIM#HxR0M{reWBW0Ppya~c-Cd_4y4rr5rnL)60PXSSbhX}xPa-z#_53RQ z!=zXgQNN4sDs_BvG#Wbk_|(+Z-VrB_G8ypac_u9pB0!DR+Ils8aS`hEcmuXHSlL*O zj14omeKMkSTHN0Q8k&nBi|YP}zJ!?YKH;A=@ZF6D;|PZ$u@kE z)pK{(@SZktesLa(U+wg_O}!01uO|oSuZvlhYwS&^A^G}%yu}n9T^WD|b`g@3+u7d% z0)7HIIy#{6bJcR?;)eT_UbnR~7Ho>I>-rerQj&goI&X}MAx8vSTX_eQygtlu4=L`V zHh6AJIyu#flWR3yDZdmUgVd7^MEl_^Pr@@WS1ihoV0Ux#a)~+YztJeI23tQcM%QF1 zvR;_5n{OciXEzr*f9}kru&f{ZPzhfm^l*uZGrM117kWf#u#hGvj7znfA9PJ^1P%@ne&;n3ceQ8Y|%oLvs-w; zTTh2Q`z?+=IF47Jbeplvea+R9)|@6eO`T9DC;6+qhz&RVZxG7yWMPzO2^1(Y`KqE= zcYas_MkjDmP^DC(_HMs$chpCJrZfd?)grOK0i@H|wjwos+x7Xt>K621TZ0u>+mogC~jnC;c(p{GkY3+KUO1|UsGM};^_F>(JTNKUJUg|29s5jK*eg4 zk*0`xH|UE(g8s*Z1Yj%!r$b(k>sQZNUw>cX&<~}tzgG6{TpiAi&i6MeOuJs~jbG!l zYY>H=?eFS-ou2ygbSCV=e!I}L02b)24+F6y%ZN{;kx=8~1n?fWo^1xR|nmGWt zRjpo+uQW8}yiCk=ApIQwoQPG_y z6#P;m4nH(OR0LA4osWlA9+Np)hfPU*EmxAeNw>jCNlzY24V9x`E+MZCrU0nPySo@1 zg@J+I8Sx-Jm0SVGW+mH*UPK^?XQMhbAP5>;%}}$n(Z1sEbVIzwXiO&nIy=iFWAceKVgM!Zc9!(q??hgX}f zPIt3E{{4?)Shld$`M(L1|E~h(|NHkkhwmTm3JM}C>05+erh3XC8a1ZlIU+OpJkhCK zJ+r;DO;>WvB*yK&$xt9``>9fm$)E?hQca)mj$1-PLcLYL5mY{hllZBO{CFjZ4{lE1P+kR+*zF3sf)opSboXN={eaW96(ki*F-#t9!_IY_y)%BX@^0j^= zGilTEJcEjRUBj+tjf>N2j!!C zSZyXCWOj9J&`iqF6rS||{hQIBq!eb)#Wa z6B83~s6>Wjaqr(_dtXl%baq;#*RY!}&Ck2-4Anq=hJl6FGc(%{y2ghHeH>JU=(bru zkLd{+=}_to3>IkTCqZRVqfL zr;ZIMD)l0N;5zEY(b-$Vl2CNm5^vwqCkB-!x*HzrWm7 zDN~zXm>Ect%8%u&-tD5IL^akwrak0JoWX7CmL1D8d}Z$)+uSRF7EK*ySMrFjRYvws>S#> zik0DU!pRFNm3dlR?J|G-=;`U9)2t)lb6%=az4C=(HYsp(bNi$`p!@FWkH;<1OBc6h ztNwlHd)m0zSa8crXAf_Vmv7b5_lf^k3-A|}Pfc*K-ipup2=_HM)3C{YThhBZJ6nJ< z4h5eV8F)wZrDp$729rt#m)p5ioP40KPp#g$Y~0lR^#gE@XlG`|yu8d)uG8``%)a`Q z&t(LoX z>hEtaPa_`rd3ng*d)WrPkt07jCr3u;si<}b84}+FU#0Sx)OB{VGVuA_R~cPjqj(w> zePuEkF+dDCCzsgX-nCi!?e|&>-Tr(1FQr_@5jZ)otz8SY?Y%n(BD|xPpOWkx95ic8 z^-ND!{+Yz>N!rud-(Vje&x@^o>z4~5AtB<-+lI_cT?>mtFm^ki%=Oz55LfM8oUQeo z-sFgc;o-$w9n9=c7KC(d#8JtWYt$}e&AMC$&#EW2t>Wl-c9=5KYt{`Z9uL>Aa(O%; zzpi%Vr14ebw`8(Ev~Y0DRj4=LbnohFJsoYdx3~Wde#f9$cO)9&=$wm+)^N`4;03d1 zo%`j>bJ^gD%;!jnbY0_V;*+PU-2$03=chTl!CFkf)5}Wj(?cHw42E%V|Zf>t=@9q8a z;AAnH&aDcfU!T<%(09ki#;A7r8aI0hO-3_KhIU4J(J(QYO~?2nM61oai=X%R{rsS+ zjd|Sn#y;kbBP?f#o0}ixOC{0J(A=4miAHKw8xMo&x0Y*}FT=aIw5F!10FJ|Y;&0qt zltL_drq&(#-(&{{U6oJD0HfVRSL_BxiQUih&E8(BuGibm*OR%*zw`Z^Rz2ZYSB{aJ z`uh49u^8c^n->g8gdDct#@0Go{tC(&W&l7e`Ks$7UE}{_?k#|-eBZyp2N6(FkQOAQ zr5mJEq!9$^PzeD6X^<3<5Cj3~l5UWYZcso#5RmSc?vB0q{rBB@ci#W(?9S}W?iuGh z>eq8P=Q;OtU)S}iyI?)qfOV|FvmsuB<1&!m<8(bhDa+2m;SSc#MNt&6aifcJw*yGF zQ(SM@V8idvQgRTvm?)oDN#P_ zQMavL@rVwZumF0cgy9;e%|t<`H{;)6f835WP2vx8Nd2BcfB<~du+@PUUq4x9+!+N9 zHd#FSK-%!y+S=dU*RC!#e2l@sHP}2Cmx!2k%f7KUd@U>t!Pc65fdm8wqImUZDesP@ zNr%=u|C^0AaB~5KUGO%c?%_G)w_**B`!l?XqbiSVhoxJv#s z<3|FtAKrHa2|wP+x7aMKBU(cfBoi0E`#D4oBZEH0pJpnwiL%wp;m~eMxTmPA%E|AA z++tUq6P-bt>~qWVEC|6T@5P z$fzg}ePaYcd;9^eVHV^M@Y~XyJnlkt?w?W~P*PHY>P-C5=OKP}L7qX)yY9KR&_L9{ z^t`;)N2B6WQuaMbb(EA%j*gB#Yv>MtH{VS+zQ+(3CnO|1&(Cj`kVNs4NIAkd(ookR z7k;+}+^?#tDvYi@Ny4v6M@~*om^9vz3ER%keh9ja{%WTAg9_08-yPQ1+YXe7Z+$3% zV%*&9tykk%*ZDh%&)P#%bGfQ?>FYVQfs~Zd=A^K4vasO0`PbU!D{9d>!R96&V;r^X z1^O5|HOk2%j*h6S7OMxFqIZeO{~h_KB?PaHRak!@cRusX^EvOFdHa(;w8nLb)uybo zqXYWp!`_*e{e4T;J~C1hi3(GU(+^5npPU@azb%vDf)Z1=P2MS6L;qm@-*aDK3l&s@St>FUY~ z*xZ>_Wo2zz+rzH;RJ}V``CF&M7g|Mr8yyv4)vFyC?tMrnXXQ47iQxrcrbi*F)5bU% zmq~DvesozGH&uAd7v~i<3k#HpANhGjbK8~kmWR=k4XzEQhvc()^Sw=yt_K@7Z|@N} zvuxeJ#DQvTGxziO_#`7M3$-!gNhs~Vy?<7PMRRlgT%3SPItZ>Vy0!o5@6XH0aoMFy zfsr?sI2JK9H$T|xcy<9mf|cn+dqDQu+8g8%ypuXtr;gLj)aF!o*F&6Dm;JtlzZOGx zw6cl*`jAq$J5 zypFt_9An0U>ZnW4#)K4M<*QT2KZA9>sc`#rE})1hDt>B=67rapQhiB<`1<)_-52Sq zeG=+@b`4rJ_mi-+wt$-Iot+&?f2;!7r+=@Ho$ca6-pj?&HZ>w6rw|+(x@j^g%)qbp zt&gZr^%-gr+BGo;2Zz_MW3>268$!sIww)sh2(W#;0l_K4(5E*q8Cg?|=Piz5Mk?)2 zLqJOX#f)|Je6VTgx$3m$ET3b3zkdQYeFz0TUB#P8+u2qF_tSkB;@yuOFL}ASdHG7m z1m~ecKiC+NH^Lr5Eh^G49d%!1o_*TjJTsdtc$u#HxKcMHBZD?l;(S%JGr5x7d7{0y z{nw}f%_GeUyQQ`KE=X{JObINHh`R>r>UY*RukHa**eSAfN9yZ;x^pL4!~-my5n*8{ zURWV+fT7$Z`6R>L+1WXiuis*H>mnXjew$9sx5}gC;#bI1tc6etkrJ{;cLjvDWY*UK zp}DStqf%;RVjVGcP3-alwXeUAsi9KipI7htNRtO8)leZCsO4_2Gy57~7(9?dOVL3; zzr-C6lF81^Wt`2sgT%$hTUe~j8yTZC4zzxej(FsU`8x%68Pp4UoCGe1AyPK-B;*WNRg{q0-88_osWD!@6` zCJS7rzc*T+JyB3p1YXC+4^LmJE{5ZkUw%GmM2C#BJQvq$)GlRPt-p({t*xr+4n^7% z41LUjfxhtYa5-gbSt}Xhf5gu+m0qyt8Z?B!eM(oafAiSoc>W#dJq5TEqB}g07w2?zi4=em|w*H)UmIRuk0#=iF6{CMNz@YSL3} zb1toFYx_#lr*Yl7ZD-fEqvtldr2ca(goc(D5{sS}>E976fBbCOJ}x`^;h&Y3qV&q| zpFaLD>X7T)A}9}PFoSgh=Kk>e{vElLg^t(JeZ+MoZa~xnN*b?9jvC3Q!EHImx6HBe zPM$mSH~B|~d$&#;+(OBP^?%mR=FyS{sY_bDAdDF4GKugP2>O8wz*@iA_wU~UK|j&k zXEUf$`2CQTH7Oz<6&V@P+Ff)}NaEyYWX#R}79b=uIu^Q~$SFjQ?bUWM&eK0IKtoM^ zhb*ZX=qh#fznxJmu6xTW&z}=s9?RoazcUb~k^A_ugZ7&yPYEZ>(y7Mx3Qs^s4C>v? zjExnIZt|9+0yw2(dW!yEo8;%uy_wHcmnDuI&d{!j7900YVU+q(JWfHp%7X2+%topm zR*&R|yDdin09AS(a*2E7kc`3KWAN>(GDRo2LY+Db zZEydjrt#f{uD-hZ7Gktu6r&*hQdO0&)CzlJYV(EF#hJ_7Ut?bgn4ZhY$k=mn$tx?* zZ3@y0xu1OLHo>OgaJ%q6uBw9g3w=46*N56@Vkn3f@yJTKPDypH+;b&DPR zg%#Q{T4p941~xgECP-GfVNcF8F);}YWJx3g(HgO!V=#Przf5TfiRO`h)1Z$!7?N%j zkmps5!k2Hr_LgAAt+V~opqj6t)rd1&4F`r#1~Z1EKapP|&u!y1U~Aoe;t?JktYB}y z_XCsY*r&DIUEHJT=bihBKl$NNtjQyeO@7xM$SJttW1E|scS>wcm$vz=C)J)kyZ@;7 zV7$t%cP6Z7!Pd>amR=#D%%q#-W}K9>^YF19(e=@-4~3IX6u6@A@IRkcWM(ctRvUm4 zuaxvCABoSz z$T%tt{F7Ic(Q6B{o6-%^F#jEGy{MlQF>}i~ETW}nlOKBT3H!W9M~~gcM4t6ty{Oc;87j>{m{tl_0{8Hq7uad`>(bk(~a>evW8gy zj+cyX#n)P=5ky8oZpCVlW$E01r{rVquiOgjslq*N*{uHVE(MBlPq6{BH?MEh65exW zc`7SAGU2{xzWG<4h|N-O{O}cLj1al9uCDG^!OPRnhY1Z|V6%ws%*o4}d;3VVHe7-3 znZ9_EC>0x9o6*fvrAw8+xBcpq^j%k@AQC7kNh#x_rnWY-PJJ!ZRysQAWskM#FE}_j z3`N=ZUWyw18ekA2-)!=EFl$vaN8OO0f3|!@XzlIoMX~A#O#F>pbD?yvdKVZNC|diy z0kg^zUH`l5Q3o5_Nyo>4+ZR#x@lm*yVDw~6)w3`|TF z1ytEpv62KhIKex>>r&}ZXRGB#M*bc9QnA&2DWk0&7aUv`7#vY8VKt_N$eX9p|@0P>3q3nOFLDSwvd#hjqZzr<8i`+|XnW|ocV zQg_|I|2Co)W-TBHLvm?p>AL@D1P-xQZ1JnM9ra@Y!XVo2q0_H9IUO2DWKa##j|Cn; zpPd{Y_jAG4(}Xr6A{>Hml$GGN*3{H4k3Kk$nNO#IvbD0ZV$|x9UFYYA4i&x+iaCl` z=R)hxpFr5*}w(8Zv0xn+O=g*%f2^rh! zxld?R$V#}m>FXyI6&1aIpAuCbb;|7YO0p%s!U`wj810yp*L3*Xi;fPKp>Ol)My5+Q zts|ZVSgUeya1^OC5wRVtrT5D|le8N@vSDCgm~WPS!&_5UX1`@9Xl%SdzPRZ9!=Tdn zqWP=1&(BX3S^4>IUcM}O(c%Z!0Q33EQ@NEfl@MwNfqxH~nAmj7p30xr=p@}xV{~Zo z+lOtKk@1((T!M7)J&%3c&fQ|Tsi6=3mpWuz=#w_espU+E@{mL7Z94N z&r~&vv@Bo0hOUKk(&enyWj8Q{Mo_Ru@ghDyzW_fHHi=W>`}biyDzBgrd}LfH6yoPs zof}6I+L-8 zYDNRol#=#>tFQ`px(9OWTJr0zdZ}YoSIn8AZK4||!Y@KwdaBeKCYguu|7bRkiOA^^Ob$9@qD^}eaH=Iee-0Mt znEsBLSzVIP>bJAAtGIssu5z1@+fhKvT$PfOx{5jr6N~V>y@%W5pL)-Sn;4#HKF!Y0 zueP4j=M;U&$T~XPkWralSw}6(2jP>Ap1DErkiQc|Ku&J6bfEWAU)445utle44z8BqRWx_Q`PjA00SM>EDKX~-O_u}w_{|9BKM12jNQ;{W9 zMQ;;h%fCeiQ-AeYtXEGe`iF)r-k9&`)-F%hxk^wBiY@!Uh`M@Zr_;f8zms=E#8$BD zLU|x2Cbk%Qw?tG{n|Cy2HIw?7gd{Eu2gk{33Wr&8d1<=xI;fsx?uN)e_^;pIxat32 zi6*`+%vVf=MDx|_f);ItU4A(rEzPGeq|oq63VCAP$ecor6%ry)E#R$!AX|HTy6m3y zhWIyM85ub*Z2&B@X}>vy8!*~Pp!OD0QS479{Teqt1PO(DUP{CV%5=_ihaH^w{Lq!}LYpRq?=b@r4E`D(o zh{qINq+4rWTwm`Y^HxUY?UwYVVqu{b40x@a-PI1eySs@qi-QBLgMEXx$vmB11CTGp z%DS-+kagfb#8Z(83|#z~djb_GCpTw(qJ~e1IYE~R4ej_@mNFzj*dOF;(Pd`dM&G?S zcRk#kpl3Q8eu*L`Df#MPoPVNA!NT19ndoH*ms#&u@sKn!D5Lk|N$hNaY_5%oja}fd zPV#BVhCWQI)MVSami3+9!Nvq8auI)qhl_jt>PuXv@VdH=8mtD6@f%U`5w0r(3>E*Z ztokQltpuck@+CSl@`_4oJ0O$v8^*rRTehB$Aee+*)+T&Ky2YTq(GXX$`KjRa{uUJ# zm4`wcogL<0?>d5!7V6I!mW# z$96@L`Y;|T^t2nY$GD$ph6jBw{&%^uke5kU?w#7a=+(V#vn?OZypMh3b5xXmy)BxS zqJhDbL-kl79=(Q3ft+&BgSqRBk=2gtqks}V#w1Dza$2qK)yZx{Q?D^)+n=np5b-O@8feHayEadCgY(pcX>qt3oIr3Cp zD>5dgT~rYCn4Y)GX%U2*_v&F=nc#PVP8ZZ+uy=M`s>5fEBBpNqw=_%Hb-%=UXP&_m z4e9;6UTb=|5gv|<3>UlCWM@x5S65XH|N1B3zy~@Tp1dI-`H*jAW$CKfo@9bbYBm28 zk`NQ1TSIv7?3n9HyoGg-{Q)EAE##_2RKJwOz(%|xBA&gK*-CqY9cT`iXVu&7T4*KA z%oq@v-q&Nbb(om9Iu>p`a$Fl8o3B8g>gy||JcVZ07UCzMOQq0X13e!B{yfU`V7Jcp z_BJRk$b>5Qmn5L(UMoWLI6LAH76$s=_3z4GsKlrkqZ5@jb!BD1M*jt%gPOYKrwM}i z`upR2;)DD3!o_=ti{=4ag@X{aj|vOZRASLLker?5kCa;JHiUgi7$5HKg<2}U z6Bqk2Ky|8C_8PLgu^6WvhlPb@q)7596Q~m1-}3VEAM`tAKcJz_FAWcCa&Xw7qWTOB z3{dF5sj!-WWC7~kv*TT)cf4z{2lS^f7y{{9Mj;@jA|BRlg-b-#-}@B93)ml1Q&S73 zyk#Ob;GB^M+%&`T z13JdI&IQCPIKS(|?$WDQb4N^&nzyU$L5=1AOh*TNuy;g@HDS|69!|@P9LdVdTkjkW z4GqmQQ?>4aE8K!d>5k+>_@&@0c`t3zM6d`?@|R%sG28fq~U>lXSGji5gTfnb!}s;GHce`)f($nK*K(t4Xn++)+?afKXNBe@q<~H`Km9 zeF$4|>(YNU_%H&ZirK1$Yw>Z-ay9*DXqa9*qoSr_VP<}(v$yy*uD`D@z)qn5V;6MAmfnHKR4^o>?^I2nBL!u8F%@QJVb3eX+KVx{>l#|1Ne1=C{c=fw( zbJ%g~U~A(FxEc{QQJ@BHJ3zbZGP|(UP=n(nN99P_4%>GTd;2%ynp#8t9yFa zvj|O3V_SJcaeExV@XRFfG2}6Vpf0dug1n|d4I=| zxTKAN3ZDS%!|!#$)}{UXg9i2X#}I7WYGlWim7UE_DKg_EgX4im980W=rNMr}0~0wX zH~S--opi&dh9=S49R>7V2REToKU&Q#JSuAH_O#Kz-QUyG=Z`^|Fb~8748(9}10Cja zw%pMj-(l(d<$q`a`2KOr)=>IXyqT!BSSpNWGbm?8quitL0jr7FZ-3GquISG0jt*Hl zIVx(ntKio>4OJ2AR7OxzQdxem;hw=Pg}w{Aj}7REnwEPQSYQ@np%xJ=P5|M;5w}&&NUcX+3@=VC0{Utmcunx&VsT=26hEZOgVL0e4T7o0w---&k5^DI}J>o}rp8otSP43&Vl!0-$`rCor%{Ik`FC#?NGC zrpATEx*YXV?u00+hKs!VsjzzSTrF2d+GH`dKP%`~P(W%*i=Tj?AQJ-vm`U_%owqyd zT^Q9{C)_jF#dHJ`lfuHbhYZ?+g0vUWy#9EeahP=71Mt7Km9H&?^i@fWlrFq%i>05s zr-hBpcG0`2Bx?zh^DzmbD@y&8Yn|Wk}i+r*Hf%Wg9%s&&~#~gf0&s=1+b5 zZI<8vThxhFs~YSVtlG6P+0rRb2MY{(7Hn$k+-sk|1N!Cq_3JWna@V}->n}jUrJO8K zXL#WaN)Ui5bk)@}x~Gt%xzIn0!d<|~l?>Z$`yW!cxw~Z#L?s#vH;y)bgu>IYMj5-_Wznu3uAL~0s?N} zaPaUbUW^~DO|z>O*+qkb@F;)(&t|S@(@1Q9({x^Gt*rAL@5K6_G=Eq-F#e2Z*3@}S zgcE!}YXhV6IyfLpi}Sc?CgreQ5d zV@onz()_V1JICiHy1JwY2tFHR1B!BS&%6iM^Y0VmL?$NQK>)#l`GnPlLRU?*%z9Jg zCHKN#`Dl$IJ;_#AoxQ<7gGquzY4!D2iv9m5d^0W2Crz0bHXPeq68v98J&xKPr^Toy z-xvLEo_P!X+$(BG7K%_r^F6pAqNmTA{%kl~atrxC4NClMNLK>??u*`*)uAF(F(7gP z3Y;&5K5}F-P{# z%7#9wmUgVE9RKlA)iitpC74d1?H-`0|T{EI}7FA|Y^ zySPXE^8MA*Hih&JltVq$nv}z%q6!Oe`jnspjiGhpBh2%Y!9l!!81a^lNI`H2Goiec zl&@0O-@!q^F;<=aHn=Tt@~2f+IxAUWAx5UA-SJBtF2Cks6#&Q2_Ut;+F4Ggk>2S+O z<^03}?l>OL#Istro%fjNKCmfH*dFuoc}B#@q~GxUzs;zAcE3&w8BmkJdX;dN%p19S z7!s=1T*^DyDY7VHU|^KceUk3)?VawyrJ?;N{Zks$IS;5QL_KQl|Kak-M#n3An6T#> zFVsXt05TjBl2#KRpO=%{OG}Fxq;2Qj*Qa`cKcn;XA^!dQ&>@rqdK4BBi4*<#GlcfZ zOo1};@|eqYwJ2IpHhQ6alKN9}$sK@26OmWu`_^5v^@j_N|8MXMekmbJ2IGm?Z1PwSXUAd=*9L{aG3GA*ubTq`s$e1-jl;ZyVHiHMfG_2H2 zn23ZOb4vSuU*F(pm7OV!6k#p?V2I1lKjBxtyGDD=x=X~o+~2md7F*gDLY`MxC}O*J z;{%l&lT4>fbYxV4PDO}lyOjydiSt@~je(os8@zKT zqyJwTsr2#DNmrs^MQ(}QS1eJwpq#H?8!5t~qfI%rNjmT%!mHyQI$bVPYV%WbOI85x zhMlJGAJDk{7>5*FJ^O+X_^#U5w{9tXEeMht0o>ZGE9Zao$fUv5X-mw4uc_yAWF%M@ zVu~u1wck}6ju73u?Ux$<^6_Id{?;8L$fbrwboYk&YjY#_0zqmfrl5|OSY#%i>nl7L zmD$$r>nh; z?6tf!z>R|iyFjhY<X0=AbX}laSWnhca{a&pwuOn7$8tByD|Tb! z`QSPF$Q_tgTC!Erur@?xguDcXT3$_ExUcW010b&fgwWLMD+r5eAQFsB%#5+ zzO94pnYa9*6rv(FXGd>Bqv+Yk`kQ_MH2&W(S0Pc3pq)FZK8fX;fR^jmug*ffIzs$` zori78jBIQM-_8GmIVDTcE-d^DBO8a)LZl;zZlBY? z2>nlT!GFU<{C_d}2HrxyhL6&^l@d;Y!u!YP}SMWftEmxRQ> z-9EqTE?gHx)U*4<++r0F&I7VXTmR z=*S&`ck`dq)6)%w_j**-RVYO?_baG;0Lr^+H5h-fP z59hJ8xGA2YnxzoiWyp?(*(;h2T}yLoN9$6d-oDeuFdVtW@osM!blT+ve?;XTs}mr@ zZ_42Zo3EHlt%iJ$B*4MP!=vv1Kry%c=vVu2fJDj-!ZTmCp5-p!gvZLca*1JLawG5@ zkIsW`t^oQ|g)aGe?HlK;-&j!2m0b=u&i^)wASEK7o@) z3Q!(!GTuVIsCzQXAGM7)TzVa`UC`MS%~_9E9`iYvKG=(3mp>9+Ew>+Y^GH8#%5%0M-UzK+;$ zqf95OqCqJ}oauEE5cS3#0oO(9IebBFo>FZCf9$iOXTUmv; zl2IHA)E5>e$z=dbJu+w_2Im);YfEeR6bd@N;o{`P*JV8!%eKfyL3~v1AO{DM7iV@g z9z=D%zI=D??6a~yBXnh@%I!|xgba;zb2@4c4izOOh>ia6+d$V$`V+~&I!DESS*ks> z@}0jr(d1Mx;D%bZm!*p!z7$CM@p8Yo44;5ty}wOWO>OV7y}gSu8uB?1+O5G3JbLue z(dU5rYiMATaBuDX!)sN4rlMkVSSWhu&Tai4KM4qkLde%E4KHc|wx}H1mLd|9GdCY! zJ?dkV7I*ySi7_^^K2bUbhLcbV`H?pV0XQw8+5>}EC4?^aIOvG_A(TZ*ob&8qpfKWjo;eybL%w68!X!8S~CaIs4%6l_(e-~i~ph1 z;9r)a?B2hBN6L*$^YfYAF3d^ze&@tB$tWmrb8zridKST0IbCx1g#Qui43jh@cp1Gu z5)f(X?93}J&hhkIg_md5n*|@S%iPcIv5HNp$9_YDgDhGlUlNQ*b{0N;QvAV=D;EZ-sNm7jJq; zLWqvonf8E)L&$j<&N9-N+1(`rM$XUw$>&mfI$C#!?>B91irn4Kk?Ep_5JxVq&fgwO z>+AALFoOJSttI8R^$kmTVWc)N`dvhqwZgig{`7Doc7xNQr~ceKNOGCpFlvhc07(=t zaIgToi6UliukieNc}mLr@+e2~7w-)%A?l{cI)>lw=U4G^ASI-zv;3;74QcmQUz?fz zkb1KH#MW+?T;yGTUS3*eW?bTVNXJX?+o6g{xJf`uedqRVMn-)eK0X~CEq;OEzRg@V zR#q&;+|sH4ij|uNto6uk6#Ic6YgDubB4(tv_w7YXegRBDT5y(DMn)Fi+_Acv&M5B5 z!FtQwHXsUO3Q7E~R-@d};1{|krlP_T-I>U3mU{gYAq52m39ej1puykCi@$&VrDtZI zY)qU^Pv5;7*5c1lorRJb*CcGJ#*6!HD=0Yz-e!yQyEZ{CpF zq(i>n@3(e5TwHTKxb=EQM)({Gl)^j(@v&M`&NP?l`3cO(-s%_;u#{9#)=<7yX2*0G z#W`|Wls@qFAThaVK*`324P=|KQF?7{S8uPFlD^XGD6tGxYzl$bMn*@okM}_K2~Gb} z_wL=%nNJ=BCRuj z(1qRF=e7gZ#P@e2J_BkC)pCE|q16Z?bR;F~Y7P4pc1qi z#TUEnv8L&Is-dq$j)=IMyC?qS%e>*+`;8s0;v4im&+XaAq2!CS6!HNk5+H_UjC?n9 z#((k4Ty)~1$A@?Ir>LwO&+&LLpC8R62fpJdyemsg8K5bWaya zb8>OPejFUCyrun}1UWe<#?OE6!E-@Rc?{>Op){UiQ-F7~g#z4;UsZEn{R}C{%Zp#- zXh=!X2TY{lTGLqD&~W>3zuk=XCJ%Jgjg-q%AC?ufvq8B)7!;Y1FrKHuhB)I8C`yss zkEeOf4TxS`90|G)d|k>}^0)U41 zlE65Pe#AI%Jo-sWO6n0Q7XJvx%a{+5k?NkS7pbYKDU>r(2~CDzk9f0#J#DKU8WFDS z`RWP5_Fwzi){gOUASyYTu?Q? zdX<)+9~>Nf8-cwdASeiGQwMZpkpfoZk8D*)mAH^zntu%kH?0SfYHQDwljQ`>EQmar zZ^HC`^GB$f15J2k}ZB%wB}&av$yjS{UnX? z!jzkz`hClcl(ck5dwW-sFeVtJbxM4PHEiGtg6g<6>_9*yNhu32)b_@*A&7E|Y4`W` znwwiBB_vy#Ti{%W**U!HtCV|o9%uK?L!Hl#0!w$D<%59monU4w>IH^;+xZlml#0p7 z$mD>XPt^0^c6Jz3mL=H;&hiT(%mt0kBL<)A}hm7@qX=Q*z_W;_&bY*Znn@+1BEpKk4vZQ8xkO z3OZKUnLMC&o7U8by6@bM$Ezo^IWBr=-GN)ETmNp|{6T0Nb;XOvE+71={Yr9w`6ndc zWhspek9NJayU%IFj`k3Ifv}%sDJ7vG_v2C0o(MvR{?YYZ8@574MIIp`Z%rUk;2fRE zeBbqvGLzd_(8)z8 zDFmWD_Uwz)^Nyd;tdD*dq!yMGS4qhU>^9`oWR41*sy;irh8Z*o8s$k54>wF7GC`-P zFZlic?03|Am#qnKNvdCFXvmr8=H!6BYBIi+23jjEW<%ZR3dRCxb*pT7gs8mbGQd1J z@5v#P?Q?z~mt^I;xr4cRu3vQ=3@^Pc{^Yxa1Ao5&Hm-#es{nQWpCu0O@B1QH8;!e4P^1%`pZ?1*x8>-Ne%S%d3Vkw2)Hk+LIn~??0Djq78rU zo(N7a|KKfuGjUBQsmQuhnHxv+TRIsJYhZCDhunMAd&za zPxYRcBFC$2NH0L68hm`fxBT?;i!OS}s$Z$5qaz>n>sPyyQ~YF>vH_?p!RceS^qYVX zKRjc3EY@jrqk+G%r-yC9vJ-m`$H;gggj}%M1UxD+vgnEm3c3@zK$k(U9M5+X6n)TY zyh+4BMq_c#g&5W6_hRRCD&F+``bEfWk*A?-cV$DvWqzGpQqrdu%^)p}j%-~>ZhU7N zg23Yds>f(Q0wWR?yA^0#e*^`6+?;gX`b!%mFY`!HP)l1|PF^08)W_@G*%%mNuRNNu z>^5#i`ogWPttFhLsL+^tVj^qkV|OS-)MRCIAXb2rYA5iU0Ui$zmtNhwg*=VoC!uGw zPeC(@HioOM7{d+$HeWXi=sF~L|sF@XFujX#btF)Ad7ffcapFL zcw)DU3{b^DVsB!yn9|~(geKb4-GSysyuk%St;fNd)=M++34vLYSj1MU_Z$P6o?dRW zvKlQhX0nM0i%Mc;q?+Y@_48nzrQu^x{o|>6cd!@7+@-L7#U=K&y|?!*pnHvtVnCR1 zJ`yNtkNzK807J3M@$rPTwh){MV&YgLR{iDC?-o~zswd0A%Y3G<`jh2SHm00DJ14PI z3r~3NX1;gi#ikJITOY{Td-g9y@=2vGIRFv!=Gl3N|13Hh`q7|OhxK@JauoT+?YbAN zcr&vc6)fuhJ}#uuBU`wp%IvbK{XN&NpgGpJbBtE$i%cseH7Wef~_I6Ua)Uw?vS>=24aQd|Bkn+Is4_j&= z{`>QO9%U;pHy@y$ z2VqoqOG+nm-0MBR5%*pm@7}$LIP8cpt$TO)IlUjwy^r1H_`_kOa!})hi#xkxd&@{l zT;;{u@SkW_y#>*&yC;@cNg<63wxCM=-PJyi9TyV=a75sib8S}_^E13>2s;l)N0&Jq z!Tm3m7_4ew&@}-z4o&giAm9rjWTGH$SqI!> zhkH5wq*zZLh`eci3;T|RM$xU-mKN&0y?sAui?jNH)?eyQj*g5yo@v1-k#OZoZD=W9 z*MUhYEP~6l`gg*=Um94**UU`E#WmT-j~{cseEk|EjVNK|dJ^Cv92wcXvV%b?QiSL= z$doI4k80_0vs_X-q)|g?|R;Qz%c~Dt7T^NN=f-eMMMvC^RqP@tXdYk zQ80;EuGVq>omIZi8Dw1n7Y%MFdkmrtjm;&8rCB|JGBC`|&FXu`lI#G-1ycp6GXy+) z!M`|7>Ej~w$Yp0BOWB*=8Y4X;1FTJ=S9y2y3j}bSMeg^&u&3&Jv}|njchglFJcEhJ z?%$W^wT+3nr=z)du&qB-P9@R6_D23Sk)ro+D_l2I#+sZ!=`0lFll1A%r(DH1yoqqI z6G`H46HH!uUY=qUFKA`uk4oyoCR#K5i`!;QtKf`IB0@j^5153=!-wq%|z*9@iiE|=Bc_V-oB(9Y_6zK=m4 zkLM0IFf2#!z5O?vVb-sEG{!EF32oD$se`_ihm`Ym&;-*UU#qmX#!dN(;p{t4DWJM$ zQqjO<7R{zdNu`z>khfX}7D>RPc7~*uo%}3suA9HkhT}(wZhQ<3!sCmBYB@XWZ7q&N zga^(93^;(yk8LVUVxTB5Z%O7YBBr2_U%dXn6b>^ysTR1naiWioj(%`(0DuBMGlF=5 z55=X*1PztgXSJv(ZYR8jfE z@QfZ#6o*VP4#u4iVrCBN4)Dv$O8*(2>#r}eMfyK>L@SsF8sC@qXVyshKGCfsk-04e zCPlBPh>GtzC`fDT50aJp?HvFuFR{hIq1@#TyN-0F88t7C-n!l-VObWxt92)3ilUsh zEUzRL_%PWA+3D#r!KV4S`S_~2`EYOEL;#SHGl*hju&L4ouP?K>tjagQB(pycQ9$2)=!X&vK7Q21n}g*f=#8Exmej32K&G zBTY@&g?>HfSZDYI__@TlXGTuxEk?-ILq7eMajC+)-2klH8Ssp5~)_gq- z$BJKvet-_Nt{SgpVmuo&!G9XIxma3UJhNTdAuEe)35a-1x`hI)1R7-Aj9hbb0HC1{ zBS6?=g8>UpyF8kMJyKs;HDMkv8=vyGdyNAF zt;6kx-6T_NMdu&T9^=skd+u$ioG+21V0P;B^H znC0mC4neopExwzw4*UC8X)nH?KRUoRG^-6DmfacmQz2u-LT9-9~^QnKW*ArvA(!c4cSJ z%pzjHip#WOeONl<=}Z0k)IYBQym`S_3}FZoUqo)ddu?jERPmc_xyk2eCLK(^gU<&o zo);aTwfzYN{t$HYN7N)MB&7AzpBh)e&#?_+so;;P{jZgjl#s16mn{ubDbY+I*p-#1 zEdGA{JpGix%nTxa0|Fk}4qHo0XV%r}nMj+syPwAtd%oT>z6s|&>h0A)R|K>bikPvw zv69jTw?=gD-5F)}gYxCXc+h7vf#5(AJw9$2;DLH)9N~7W=@p(lZ*rmhyaJw8D_*yw zZ*?_?D98ti?Zc^<@Hm+74OdGLNIuBMms2;646&lWaDE|<6Ww{ogZ$0Jl=<-vZBG&g z9}my|`dC|EiIdmma|B#1fco6P!Xk(%t}UxAEMyfe>K2CJ3>IX5>3e+|TFMk015L(n zJD&|0hsKbZE!bkw&|ski1_e>ew@9?ak;Ky}BuvBEI9giA%iD9VPMhIZe$Umj!K&)2 zlM^fmt`2rT-f7N|pFeyBE`t+?Rmh`=X4Ye*ql-$4T3ns$oI-hjfdxhb;Kbj;*t)IX zX=SA(r!ZRQs&OZ8U(0dNA~HJCdZN~{?@OTFVs+_AH^(2gB0Y;batvW-@Yv1Q{I1DK z+1{u-g*1+j$;sETLIc)}|5&u38(@cikfJ$IUn~2^%JHW;*$OZ}UQW)v!>M)(=5{vS z3Mmzk+!gU!I5ScytJx}CP7x|i+%FIex+|jVlpw--r z76c(I0N_Dd6wiy)-30#9$3LV>OG_#&&m)$-;3uGap@zMd*QqBPMt1(6#Uv_mP*hMlX~_wSw97uSsOg{`e|(taZ1^~DF!f@UNz zO0k%F?cMpOH1LnxtKz$>MoyCwkI#$m&D~>qpD@3_)*|94y4j<2jk2`pyvtN18Rb2R z@WGzB7wSannlV|jh=#3aTNcu5VJ|S#HEyTDZQEMw5-qy3^{TnKikRo!KnzEkf&0Sc zO-%QunQu4azizf03B2h4>g!617$Px3E`9Y_OcYL-A5%e}b}FyIfp|@SL`CccKT;)5 z-+e&-{@{Huo|E-~W#Qr}%qQ-n{br^sb--rX`gLU4Wah0V4Q?T$ zqhpC($;I2%Zp-p=Poc{`t#OItyc1ce2fk-+EE+&KFrXmSTI|c4gyJB^4feFlmc`D!Nv7Br-nAKoK^ER(AKzxOn} zoA=R+M@mif?YjSdKx=`czrLX($hrPQe1UiQM!-T{jub!B^eOfJ@^(~MezV@CNwN7k z0-?n;xE+w3_rJ0B-SJrW@7otjWMmUcWRp==$R;6sZ?g9eS!K`cy=Cvc_ev7V2q9!k zS=l`2b^o5oeZ(^L?Jjc^t<%5OF^tiT5ytdlYkc%(ZD5I0m)m zBN7q_1fTtnwx0#?n{VsnY)2DdQ2$D`t{~w-L3D`(5x!{@k$Dgh-Scj#haP^}M+f6S zB+{4xc<(l$o4$S*EUIq)f4s;4&rfunnIN=Gza0c#D_}!@Cs1sBz=2QDYNCiqr2yD% ze+%c&!=-6!d@j#`gA4{20M^1EFlL0FEyg)Y*8_S29)!sB%x|~W`3`WLPP1znGUUL6 zS(FP9Z!#c%#<&sCevZ>PcaM;;)?_FaoN~lMblIl2kAACVu6tw_T6!fS$l73Ov^+O5 z%9*#TEGi-s_7NO(OyjXl<*`Mgpl^Z!f(7TbI*61)LRU4zY|z%oW&E`24$gWKG}Hkn zUL8oUK!7Ca<19ZXb7pz}n|aAQ4-~}Vg9}i7!Kmn3aK8mZjG0>V!GVFRliyceh8)28 z>Na^SnT3w}b`s#CWR4tQ)Pnt35QQCRg--W}+3(yJ{Kx^n@D*G0FM?`KIgCAbK_Cv* zYL{ziX~9)3f+w2mv|i!2O?|Juek}GjhlwJk0KD3nHo&w1bWfF|&sFGbS^}{wiEJR`O3#%Ys znSZ@_kJGphY!fl51xNoZc6NfpGWgGFm-CzvAU5=2BhEkM=1#z?fp$Omy-iO{c%A=l zJciGKq20amOnz6Z{xD+wN?x!A?0SnP@Osy4H9-RyEuhLip0%z7a7vVw1b#6%-wa{* zg0Zg~xOA@!M21Vyz#|6;)tJl-2>R#$em_Z(gBJzw)N?Jx9A?4yIQ}TD>^OsGW#GO9 zy_5v-_W~(=2Di03u=c>^l0t_@4^SNoW{#xsRvL5yfrZnq@y)^7$Z!&Smas2Oj8G8V>jRH3Ff8=i z`Ck2c(HoW^OqK^(ISiajBafs{B>*G=ZcIZ}6~+Jpt-cPN8SAw(;^PndNe$pNk+QcR z#%h-X@$4A>e+*!x85!QcKE{j+o!h|6y&c%I>B6SlG-lWMo}8PR5?6;c5%!9G=jFe# z9P#mZyif^!-%Hm78P^f+X^bmH#F!p()dx${rh;J%C=~yI^}mA9DJ^ij85kI}Z=?Ie zXB)(F+;^MT0q=&Z1E8-BR8nd79Uj2GK|YsQI$2tR7qkTU;r+;s@^7bkB}!mzG)qB= zX!L+4CS?6o_;OProRq!5a&~1;@Ni7rzTVz3=M0ej#pc+@~XO~M0(N-eag0|LN7ENJm!jTf#U6Bc0YsLnv5^pcow;9T`z8og|WMp5Fxzvy>6D_k$`hVtHz6z)=^Ps*FUbT$ZuYv;?6NPt3nu z{PDe(#QLrvlYQy79$&hT$0nrvFuJG}n*mIHJ4dNujhKDk4 z!6i5fv-oCg8jwqZ{}wV1LAwL!mEd^eN$hul_kO`+JtHcZ*r8c>(f9fiF8&&3E#Qmw zEL9DzJoP$p;G&DT|BN5HJ$S~CQ@~ssl!%N(v+x{&vzPaf>o;4|Pt*+gV{ou!2ZLVN zrl2<%cf5(%phgA;DYhhq4F03lB!j6<7gAjZ(KiBvj%-QcxkX@HWikBWMI1bv1esN; ztH1C@s~~RqUY{?*TX-+HA2MXpvR%;%F3dHS-{^yTIgNS(-t0C%XC)E7zBqv8F#6?- zdg&wt7BXxCv3K3s5;UPvH}J&jv~9Tpn{+E2_;%b{p&ZK12bw4eK#5eZKY*n^@L1RB zp#KV>k#aExb&Pc1%3gpNb=n9?hCfr>kX`dCNrwFT!D+5{u8$A(LuUT#7`&dEvfw2gn zcSL0n8}blE-Yf-@LN=BZ8*5yxnH!1S3$7u6bvuEs8N0WRmp}}#P1u}Xy*Rwk-!F$I z5`0KDk@YT*ji$PK;jFWfrG@|f-~nvi#H6HKs3<9N^a(PU)VRT~0Qm-HZjPcf4C*m?EQZm` z!Szyr61R2U2aF~mIE0yMiAjSyJ(zQCS9bbcca_sm0bi=bsvt$ERXiwcgs#)bx{m%$wJeZeNU1IXlmt+$?rYV|(9iv^rR zzbnZfn|y?cAbRk30C@OZLc9{k7~eih()ZkiT?Yi49QgeZV_+-8{<=Kr_X#sfhQ(_K zLKZlaDuIFz@*IG7XbGqTlI05E5=T(z!kz^_{S)m&p$+cne>PjO5@*{uzFTn>=#df+)NnyH}B@-evPE zgkJ{;;N+1(xs2uN$RN{v`Kjmbr{Q58K$2jFWt;t?P_gPK$8=z<2zj4DG`>2U?(8^O z4u_ZuGAHQLPT!wzZ|yd%M1igz?EGHJ%H;3E>TQ_&{a*O0NxAp~W0>g- z)`X#thj-)`{938+Nz+l7%Y zZ>p5gA}opmNqK4Y~(<$DG3PE{3VW#)(nH@wd!6w_tXfTs=%#HE;~ z@EZgc%i5Vf<7yyUeEtkWmLY!F@SJ-IHS19st)F%|*i{05B9E~I z1a`lM;vmvcQ;RYuKnRVz`y{AfQ;`vr;0qHIA118;-lTf7Qw{a#^U5iZzX90cDR{qF zH5Do0P9?vWVsYK@h(s3YGHBF0JExThKWXpd<8!`zn{^l#U-HNrn2M`1tEi~N#l>M# z!_?%arthmxcOwTNZ8e9S67ne^9N1p_O#IhHp0W8dGBRmZFE~19)9~q&Owur2f(&?V zLwutncmX&10M8=pi6g8`;Lt*}7Yq5e$;!_Dx>=VX6q18^NIffGdYvo<-w`_bIcMLg zr>KbMO)OFvL>fiFlfjv?Gr0NK|E@ABgmI2&AYnbH!B3G}+j9r{{e9eE7;5_WfaUU; z6$E@ZT1>d;Fs$bxTY+ogww{9E3$ONd`ag`nP^YcqYEUB38(=hu1?&dcTsAznP~XCj z9Xd&DyhNTl5H^sfL>Jl}!*)qOeZP1FN7YC&r+DQw*qyw{{|XGdNHR{b?|JLc@4U!- zvwNCZFU#fBk?C+b~CQqr7opw!xOd=Lt%-J;17< zIo2*#y^Y1uMGgO*ND>kxXz{hK42Z#pKd&ELF5U6%%3kxpWHLhJF^1(9L9zi%M`Z2H z0t?2IpMb>hr}!b-|5UJt@eX`#kdfTPB7rW)Dx|~X1CT~7y`vKR_x;tlRN*&|X}#Cr zL;4$L95u|?j7x?0*MPk-WKu8;CvN=G3GTr2uCmx2$PFM8IJeHr7e1LauGeO7*~o=Q zypi`_sO5aO6_XkW4B*ynLbletp7}7~2NVjd!?QLGi`Q4jkf?i~Z5F<#d|G2Rk{T6! z6H4fN(wNUASxF2az-#Eastx9^J&gj8ZV}uYVIW5C^j{!qf}G#&Px~4VwInupU%u|WozsGyM7vO^=9V! zh*_;zPRQ<0I0C?IR$6TwS{T>wk zZ$}3QArNB>E2*gHfUsBDvQ+|U(o3{)w;&_U#7GGmn(k>ocQm?7uo)iwy^A3^4XPmuGsFZA_EBn^Y5 zbl-uXq!LLu_{ft*4*c_nkyRa6XWNv6)vR+uHvx*so`YXSl)2unUpe<17u}yHtsG1M z@|C@z0D@f@5Dn!FEMc%3jg)%zIAy_eyBtnRpTFI=Ao!=Gq(H*IvbO{FLG0{*E{_+p zIE-LEiM89fwsXzqC`{Vu7g@9DJe-9a&SPluR5>KJC7IM{DJC> zNb)Xr&~qwpE^6wxxkZ4Y3c=5SNO5}8<&NNAKRJ5YmoM9|ug0H1<5N@5V9?X; z_qc$f97Gd%hH0|uR~dFk@-C2h%P7Tbvfc+D=t0K-ywrj`LR<`LuF_m@sMqjN- zS$?&~+u3~hRn{ZZ-acSq2}`m~bc(`Q=SR13ZC=ZyPIOs2ExV8uT~2i!B?9{~EG>9N zm=zcj)CR%nUPAPzx{YzH8xjOXmj)r?~lG@jwBE4=?qj zks?JIiRYiAc#kJ3OW89X)2?X&76zOjX!fPkC1Q&i<9R$`ruGVK7XQ4fP zF?&HfQbqgHCs&H;vVZYraR{5-_%r8`5buNr-j0f^b<#p1)`kT^&db;w$7-wMDqcjF z$1l>+2n1S;K-bi4o2#vmb6RE1>Y+cqMpt(1hb`i`npa<6#cxWPmWUcXW{&uDYt1|3 zf4Kl`pD*y997>8C9}sfubTrzq+i4z+?|CPbYedT2O|pxYm9GrHiEzk-2aCc^xfI3Q z6s4n=udV?Pc9PKPL=4wJliGBjkh}IaK@gp#3eN2eu`&(my4!orx=bNzvi6zDoKG3t z-c%p`wzujmtv5&|_1zARUKZudQm z=d?81pU0Ss8HJTpl5-!)jj#2U($Q#3s9?X$kbR679kS6T@zO2bYVB{Iub2LwQuR?M zU1WHnnI4ZIwG^G{hTS;?UvB^j&jg~@e=jJ;v3&o9tNHti5T_z;BvWPNzJgz*lI+Ul zSDHAUC+Z1=gC434qxaq}UJ8=iI4dg8lU>Ho9|9GZ>%)x=(p+Aav)-6kPxI;- zB^$@7I?s8ODXOTGUi=JQ-Q?WMk-q`>Of4qux=g0=1TnfjE-S79&M{Vstm)|&;OhWbihXW5w-7YrI#-)w)qE6(;gDpXDO z{6*^KQ=0&Lr(0V#LbdZrWpNa0%H_Hr5@daL2l%c9jYW-Ns+|QISkVnhS&bEK7SNd>7-+qmYzafO5k?yBdgo>m)M|7 zwT2Rs%E_hC5Tyk^AzSybf*E4in9SA=-YTY09>o*nDHBx2to9WL(ObcCzso7}0~3`) z@d2I4B3AW}P;?Eci2XLLPl^&MDb*#d1vn|(f_wFqr9w{^+Wy4p&yT+wSK#|=-zbnt8~Pb$7E$ZstfXNM3lHA>*kW#w znC^8W)}a)9&eyX|yu38j(nIvSNL4RaosF3DG3O&OGEUK9l++QTUan?{WttcFF&$T;Y)h7DObq{w2fymt0%MS_L8;dhLPUK zp#}6B9Fq^IG78cXNrgFC==BfcUZi464$(!Vb7(j7yR$@;bmEP%(5^g*gntA_8$HF;gBQ0Br)Ry?9%YuO{~54< zRDqzVjG?z$rEeD;Df%3tW;Ve1i%vq*fK0_@1LmW=_`)NHfr9KnI z3p=sdi67sm|EYRw+B{R7Mlcfq5;$RO`}9{)L1 zODaw@M??CHr!k78Qni(3nKpmVhj52Bfvy&Rqt|}rHvcH=`QB?&w#rawMQO-NTBUr? zj{ikpbX&zml72qg{YcAK3BhMAjX8FVJ3aeK^{Z;HM+fmcRSEO{lKY)CKWD3pE4|cC zyO+-N=pq|meo4;EiY|*&^7husT=97AXX|nkS9Rvfxi07Xvg!-T7yL8nJ$=oAF9Mln z?XGd}rhaAW8_#Ek0Pp;q7GF$^Rby&{xdC}SrZ~HBs$3`XG^3U2_FyY~B`VmED%?~) zyj?F>789!acpQ=O3+2C6PIVAWqgKA4>Q5ueLOS*3IhQ1Hii?7QUQ_Ldy-V>mbK$3_ zT3QD+UUc3utWy8GJ>~v&P#1Zb`9mzw?4(ZAT=9|c*)y$v^%RvfUlRF z%OW?9AG?NIrB z75qeV?Z3z%es9fsT`Bf`NGknuLUZgP@`&)-&E=DHoSlZ{%9;OKa3fV#D#e?XlL@02 zvkR0OuTR9zOXoWHXPhRM%j!MyY0Q+P_LhG%QSWVW<%v;mu4IL1)04;KQpDu0cr0w1 z>pbfwd0n-z#A5G~#Ooo%V!d!uu2mV|SMp(xT&qNslU6i=qVkLEMEYp}CgNoAZ|e>3 zvbLd-Jukalvnp@A4@}Gq`WaTLUnnzA-j6C=Hr6>RNDiw#RIzL3(0h$bdgZIe*K;Z! zL!Ndl{G3AQ+4#<3@Y77H)a(v>vxNY;S7q8I@&A(YE9aCZHmwO~9GRndZ)mZRt)VVS zmyf)eSY~bDi_%JezJ1RHQ&L5O-mGRtHIS(#rnu#Yd%f=G5J{KwEC6M{-I2d0z3j|N zz&Kr+xpU_f_nGfW<>k#zO`3ppOobDSL1#*;rBjUOH1MwzjM2g3e9n@CU%Li;2Gn#S zRB-Fgft?wvsQ>J}$&m2b=-T`3?H=`TeC6Vo+IZqW+>1MvXG=eSynnmG6^54?&AEBzJ1w#o7iaxm(ze?+!s5jMspsYE1>pPCEBpJk5(m0~~vV`{F&S@12Q9Hy|G5KT&N zd?UuKL$ovB_7CD%@OK5iHibU3HHEqLQ)}fmdn73?J;HZHI^V{Rsc0r%f~8HP%?uQ; z2YrMJ5%&Zd+7FRf^zjep&hz;{Y@HWQVj;t;+VxgyAVC4V778+b|85bC;>t>2DCYsY z0F`Ww`<`+B{)HEztz)qEk8VgQSpF39-uPGGUcPhYSe&PPq|;Hkc$ksjx0M=0<$KT< z!L_2baJh6C+{tQxdg8tkivHUFTV;G08##f%YYF8RrMs0exP7*dzXT{VlM__lC#YQW zSjMWB_x(<6_>l5>%*HhaJO*l_$tBTXDX7lEc$9_XeZ1rtnLQsB^;*xh)*n+JR(d}Y z3sGY7urk+@jJpNt+mjCgnLUn_qX$SJ4aQnpHqOn>0hGdPJF5zw!Qa12gL7FgK%hv* zl;HkHm|a{Ca4>Ra+N#q$lv2=`>jaEzx=f1@H&~-gb8jVQ^x=`4L9GBEH*aEYKz?^< zPBENWcJIz0S0DBDbehB8u8192d?|hYdn2AK;ZTmASz$%MP(#eZ{$Q_$A@b(zEBk9*l~Ht1PAvBTZXt zK1v^eY)1T!%uz(5hT=ik@JinMWENFaTtPJtKBL4SGozpouwM|YozYcSUpqM6ZP{iE zG9qEqJ%J&{FcVCURp<`8J`W!nkG+L^thy%I4>EA$%hrF=X|Y}%RQ`PHdT*u}G8*fF zWO~ycCJZQd`cnk=ubZLPD8%v8k!daYKNDJt6@+A7ip0 zJm7S#I5qFMy#c5FLpsXAz+R)M70pCbt$)FxW4W#C09|hHP1h6>=ST% z-c-lmD1;l1$>pBAd|B}TvP>Om%=SSWo=(6Rku3pDR@^`_=z9D)4(>II**H{qo68tv z)U(7fT=P2;I#szv4tzg+(xK%7{!VJDs(%Fy3=Dj`Z<5v}p_AB$lvkfZ3ZHyn%JR|f z`8U;6rPEeS%atb)*H;Dq;s_%bCub`u6TB{)RP&U-uHksAPqmfSRhRT{l} zXnq(ghaMt#ry0LH{>aN$6*3!JpcP8tUh!S$+43bUL zpU_y!rQR>a#9@^z_EhjIw>ql9*Y7j@eCzCCx{BxR(F6TT5IF^i`lzPQW=6YH?uLfh%2hQXspkGyxP2Vsn; z+oJa|F$sxO>Ejf+a&>r+Vjlr4>9m?G0j!^q2oy+3(4Yaw(l-ze?S0QF27!yT2e?O< z0{9MPl-*}3A(N`A`8>Xl{( zy$Mdmjq=Y$eK@!r?sS{K9LYEF58h){q8ze7zR9aS-)*MxK#AoWi^EeA^h(+cLD7f8 zy6bo(h_hQzj8gQI6w39O3>Z5JVOwEpTb790*&T56UG|WR2>TIo9Y!jbs?ZVVlaG$- zl_9=FhZHz3PtpXj6~Df1jkrX>){IDBcnI}GY8rKLF|gTIx9ufAoI89lT6 zgqe=%?BIZ2!S6{@R#8lNO01_w$Z7z$UkCEE&K_8Ez4`ruHj*rkz;NtI$p=d*$v76n zhV%b^z?tjtEq~f5q2WPwFAf6yEo102=1D8+A#v&SM}E3tWE-=lm!Q4}wJTdSzNGez zcs);L6=G~UnjpKe`C>^sO@TQ#m^OVqToyGwr-$g?OO?XZXPFOJ%1{oO;O=c9n;F0R z@ugz8h0P)YNIH-4rZzUqN~uy3X#92-a|aH0zprQsCz|bFt^fIK)YdJDUV#Aj_Cr{yHkzd8B zS$%0|gp>UT`4c5Jd;5Z%o5afa56e)njfo_`M#{7+?|Wy6YocukAiqSWoUL6fJv%{3 zlLh5xAzWp{Hv6(hbt!SEW@z1c}DL$i46tHvc zFV26#Z$pOj;6rrCejL}r1D&Z2f+@rq@+LD|c(T|(({KJFzia1zW25h{i7jJK0NFW5XxE2oRl6J*84^HEgibP?qA+Ug<{(vwJWsP_*rzZRBV|;P!rQTRMv1~Rzt-F{ z2mW9lqLImNFi{TP^zVjFE;L@iCd497zY-)-OAp@*#-^k+J1mK{&VQ-dX+K^7l7JiF zxX>T0(5$8*2<6N8B7C*?9xN+g|8N`x2dSwFz4LFc?x+ryv!&(+8M8?H8AOxhg($rM zXp!W#B0OX)){sW%wytq30_Nw|W-Y96BuD@kzg5}eOM zP7h+F8N+U)`%?NqO9~8t{$GH;SQzR=H+u`?27C=2l&j!(MCH7FQ!`#m7z}0(N@Ngb z#42+cnStH{jG`@|h(L0HOc208rE%`}!70&gaK(gfJ$SUs7uM$I2d;aB<(8?JYO?Z> zNB+JwG%|v0Mes!dzX;is3hq6&UgI$+W#Ic7@cudCS~guf^==;j!q5F1n$OS|vMc`w z;P)2F8eG8)6a$W$SuIn-CopJ-`H=*n^67j_2dB_j230(`(@(=dVdu5az%$|iJv$4F zEL)O5#+OmyYeBeJ|2sm+d91ouE2wDyb8gNF;f{}1QySPGzwx)x&y~_d^TYjRJRW_+ zRkC)FPy%qTU^t3sVm<=vXaOiRFn$r)2oiAHIoUS2Zh_7ij5Z|@gR=_yR{X|&7%6hd zv+@~GSHMpjn!7OFD{gpob8{0+B!H35Eve+$?gMu%!%nFmBfnlLetDk`P;9a6#n)eWekU<{F z_7cyDmY^B8svn>JlE(WdH=GTARJm9+@9y8M&WtOE$Pr|ENW8?)(uJ(4w5nw|#Su;2;v!$CLJp%0_*|$8H*YJ@#9UlF$|~60kde8^iT%xytw&`2Fwokz!{cFGA8<-K z>6I81z7BPkTecc`l|Za+b@&u?e~|F&4(|81oE^>jQOWlN*;!Ez zA49Nm6dcX6*)KB5rOm)@6h*x#Axofr!C>!lGj$#X;w9Xy$omy{r@kF&Zbst?Wg{Gf zkJRU}-dHYWeCMJ8HaIxgkiUg;Fw4S$&eGKLrrkCw-@}<|=D>ibR*o20h;}SE8Pa=6 ztsKw^>YXyY49j*AW9v05lTMh3I_^M!;g-pMgliHQ}_#h%Uhm}lPbIEXoQ9`+f#n2g_j4d?-z(8MhbN*18} z)BwCj-nXe`GW?u6=MFVpI^BP?zwxoL2drHR%@;DgH!KDS-DyR&}^90ycT49Gj8Uhm8{L_K^xI-l$s} zrkXjYtdQjZ>+1z=Q~e(e)ke>YC5(o5H#V44bHn6Xphf|3<+G;T_S=rP)d6f^qXWhm zv;olIhD37(^mmW%y765QPnIu;Bm)l>K2~sBxvo4jdUA7}gBRrtV}PLL=YPDtp*Ua$ z0aZ)Kz~ImExmKd>;t{?NTZ<9bOKJHZ3U2MFeg=~GvAG&#@v(&J)1Q|@mGRYigN6Ly zTahCsVZJ0g`>|&`a6S(qnQZsH_Az4E$ey`Z$B!yb2rmUrc9#@UrlC-kdz=FAY$V{} z6H8<2f%PCRC0`Yo0RznxPML39?~S^yizf+f+-~j1)%Bnv(oxEF)9>f8TDzQ!BQvdN zxZ;8|JJ0ou*D!g2)AB2Av#vhOY=HLW&W(F@P5vXhKRM)N5PU2$OmTLq&;r;}uY{G$ z!oi+{MgKCj4#plg&XrGZ_Z#m$JN}WUyySGwep|9BRh^1rDTsy+kxrbN``9>vlXXm(U^*il{t(#Xx5ka(d`219$DVK=&Qg9BI>VhRLEarm7*rE-jfS zjK-?5f2^tlxejqY1hByYdj}Xn50i$W+I|`(;JP)jp5@E7b3^!V@WX^hnJ#(CZ;Qhb z<}6wa@6}{Wb=u{9HR17iz~ia1o~}TWNT8PrkSLKP9RmXh6ENXBy>g`0N_n416-WQ< zA?ebJl}%3PbB$h==P{aN*g%7ig7bzAkIrF7kRb;Un#{PCWtP}WtoG2wVm@moP+C7h zOZNwi3uBNp?OqS6$Y^hH8(R}JTMgd)<_^mRvfJ=#Nl}seMow@*#?M>%>V-;lq0`b# ztmtSCxB%P>E-CPMXhxBapSZr$4~!qXsZt=9!?<-YDr|3MMG)Q()c|-qWmXYhWKV2G zK!1o!vyc2|I_>7KYG%v!Ui*U^D|=WsVH4+Iz3cu73pE&rf+SReEC}enKAi^iah!4t z54V;a+@bIOr}Nqe%9-~f1C_kqAd zEgtk&(D&r;5>SS|Z@M6h7NkuQZUf}%v!NJANtma!)P+l5HTMAJ%Y3p+@4q~C=QMkp7ExFbFOu88-z1U*BVrl> zfj2-urjG-Q+5o(L=t%=lQvQ1aUW_!C<<}2DV6f&YoF)R=0v0J7D>E}F+|z)j3&VCe z$Uu!*R#XIJA%P(5yU2ThGYxVgpf@+t#B2bw^yBbw>t;b($Ho4z;om_VpeA4iioJzX z2t$JA(q(>XU#c;=05%Ah8SItX3klbdRY<__h9Nn6hWQFpPZ4;az()l}R7k&Z=OTbG z!yI7a>zoO_aLI5I%s1CHz)y!WPAN}{DM6-m5~zWPp3kUh+1OwL3K&lSwd=zN+SCF} zYB))KIy=Gf?J`%~!{f9>trQ5#?#R(;(7G2J^gkvvjby71`4XXa8w)Y!DwV)BnO$Uc z)Ph!~M2DY<;)$&+MhdY|p<2lrvV8HHux|)>t zG`rNv*%`U~k$4NT6b@MR@moDnE|rDiW}q~X8?nLP9B|j@D+DmgD?G@z>0xsx3;SL{ zR^$nxC~w@NUK@DsK>p2yCo0RFu<% zG;bjT@MRfXf%KYkaGVRAPgs(M^D^=*QWJ+)1EM#6g#7z=mDhjI=IF-CL=9QJDr7Ac zaiPgMm|O3ocYT-1qP6SMJEtlrDsGw)5S=T!VIA2(fUSY?$a zz~%}lj#fs(FgVM&`lMmbt`!(I{5W@Phm;*v{Y%548sU;3vw;HKrwA?8KnYbFN80Vxp2s4iT^?;%K#qq9 zW>pUm4;WD4VSo__h5(MIn?cdk+9SCsV;yq8U5JL`OnuN|g2UiFaxuY3DyWtKvQlI` z1fmm65PZGd7uqB5Pca&2rjb2)50GgyBpqH&Dngls6!Nrl@k^I+HNNYgVdA8pQ=TwT z;+CqGv;s#S=zJhK1;QeHS|?zgMW@XPGp;P@)jML5(1s=`{bqBF(;aIH$8_S@ zP2Aw#^#eHWlvF_g4{jGTk22lwhdW&Cp=*TcS7;dPGl=l!F+OHi`?Ft5{PI4edgec0 zR$#%Ra6S2J)H;9lD^9@&!Z*+$32?3R;mn(P0}G5-;0(7nd<< z!o}IUircV|j7?Y)yC6>h%KiAG%!7jixYWi!BW~?tu~w>(<&M1^FeiiV9FBZh`HglI zh^`zq->blBdtXoiNl|ZJOSOjG5CETodQ_g8XETNX;VGk3vb)M&%4*wzF}ULMYQqw_ zgy4h#ifxEd^6{dtFR-B5J5Fe(^mjf|m{{8iwTr#0PeJjNk5#u*eE}7{I4eULiVH8q zx9>HkSiKx1;ie$bY5d_7u3dfw{CJifu8_&^ULA_GKmn5Gl)5>lQg_?YEq%+~UjT1l zR%UDk(zFL;*vdp|+S*oJ&B*Kv(@wK zHI1Nn-WNJ>?T%+s0oviyAS1T6zhXe-s@B9t_pkc)Kz)80=Xq0(CIN0X!%)8*v`L~I z3blV&CNp6_Lmt{lZ9}=bEJliucZ&oKRM*m;o-I}13|H?w|LjI3SyaaN;fCLR2_Uo4epd3NW823Z*?Wsg4e+aOGVSW_0y>-_0$4Lf7I2~=ytd?xm(|rXa1~` zh^zt~-%Dsm8mqysWFXfAXg|QzZ`U6(ZU8JmaS$^k&yL=8;KfIIWtk)z7U^(=+bK*m z^il)@F^)$ld1@|jj9@!ALJQ&@5acU#8oyZ81EH3fl++%$-k`XF4H?To_H^c#{1fH~ zW}BOsV-!-!E;On9#%@*ku;iX$z;m^0+mlKyINq6Y1{yd+rn~W{cqV{Vs;jX-w;}5uw}5T|ErFK{eE^<) zG;|1nNcIa#rS^9PMfmjdNenulZ97adbT!d8=#h&S01Ws8+Y6ZMz)S|53UY6`@0+)P zaEZjEVfllmq4R93)M5o9RAuXK3;si?w#$EWF&{T4)pT@pNFQm#dnMHasX%^y{`dee zE0NrFxP@?%M~~A+#s_?cZH`;fY|P4JmCcOjRLbECxOh5 zI><;L=;Z)_vFWu;Y#+7gRtWrQa|T4evTYwKcMs@4T8Be*>7ylx*c3lDwj-AOp; z$U3|)I9g+K?kBOxT;H-h@l|uO^12=&@_L-P52yoHY15l`UdiAc;z3OKI5Y&5cyj{$ zFS?*m;iClV_Je?;$t{R}wKJbNeg>$z{cYj$kSl>NB4o3t?h4^ z`^SI3>UdAkx2-!Nt0)wy*^o32f=(dR_I#I1?l=wB!S|s>dlSSx`T!P!#tVqrOkp$u za-h_Ken^jx9)5c~-ZSk(l+lBGmFyC-_jG`aodsb7T4jmeaLUfjoy)Y(FRo$IZU1V* zA;{)^A?XtH;@GIVe*7-kJH4rFb3=VGQi!JAe~zu|XEy}6hHEHaSG;BhmV8M6GKeI^ zLZ-JN8oUHfyR>xGj7_&;G@dfJT77c?ZPz&v6=T#(fz~gTr^NQ>ym=%ahpjmIONf#& z0HOBA7R>WSsv#-LrQzF)DoKVvdo;yJcfipGa`k7u-Dv;iV&68gZ=gbJ3I_)Q$6M1G zFyR9lEW`r?1IU`wqN6$Nel#Jwwg2YquR-7h5)n~RltD-Sj9utbx50W`geU?U&ws-> zmx@|>vTSBR^njZAunORo{%k%gM+d>2n6Ejv455A<1x(_|L$LKBV8{PNI>7aN0ebh_^B2a20Sm^`H6`Z2_XVM;Pl+UY)JT^P?mmi;AD zpi6tlCqLi?H)8-448%b8Vo_f`3@+7f>ml!YDlT*4`c~v8$O~|Sd%Q(q?Y?fd`@8srUG znVEK64|`n#o8iH&SN?NlCyZda%l)`>DCHu1>|t_*01N*To-l z0e11oo1+RV!vB$Tj@)PNnWUle*JnyZ&}u#r^;%K-kG~V=@)+9=_3$YW8wd+aES%o> zmsO!V;Nm@P1;xQlWa9vs|4`4cu&}(pCjk0{_m~1WJimi3$aab-=^bC}7^2h-wlC1hd@ZmoFh5OVSJymeRZam_NK;IyZ zb=l{OJPer;dMyzPU)%u8-V7vkFn3^uFUp9_3a^qtac=D(kcPfpm#$x!+Rukti2)e5 z9lQboH~pOb;3Sv&RwqqM{u%!*o>L9bvo6LYMt)vdBQ~BB)YnL4l$G>b9|u80u{JOs zVV+kuK62oN#MlvjLNvsj&3oiwiq9XB$@|#>3R>Wvxd@Jd&d#+}949w=W3Z)NTe$SwtJ+Ve%+NQUJFQH@PyW~n zbMQZ=w?%5B%|Q_Yve7m;C83Bt5(b@RkB`4!|E|5;t>>vO$qI$Oi%%Y3aK}TOav@FdgO&Mf_p(_&;cwJaBqh>z!bJXTmUgNvfEPG{%;oY zd<RmnW*)JE3eAXm0{rKL3kr7L^$uh{cHNUY@1>~|{nckoDgMOng?WRb9ib#Gif z?7CM6H+#PS!)!qTR0Pt6i`T$$dery>2_J!!4Jk;20=!55-p0`xm;!_rSXNF`*aB!{ z4vG;Z<`xbm1Hb~cBW4hgfOP@ZCOWNd<=uvWcWLoa0`{z5XE6>6$xPp?Q;@wPg;a&N zc4c=(nrixyJ&9g=1;pihb+)^y*%dZ>-vu?fQU1G_HozT$(gV3w>)jwDGBePl!TW

lEJ!8G&)H8Ii9H6kHQ=G_1dw0kH`_;?7wD&qltse^^K=r>h)q^h6TUkZ?he|X*V z&@)RJTf9C{mO>o|CY|^!-IFWX;@RQIdq<40BxjWzf3Mz{JC`To=u6pn!Z1$NW#6V7fKtplMwr#2d;gUDdUf z?eh5@*lQpi2$UBCB8lPB8MjU?8;_|Fe7!$ATJ?1FBRZfRfqbJ8Oi4zR2^+Cl6H+$b zR96&=f%G#I*`ey5wh^asQ+M;fx8+xTdqbJ1UGm>hgrWrF2?-zX*9i+wo(*R-P0!8g z)VVCSNoML8c=j}X>!Bz6W-TM+2k&;<-Q)uoht`v-IITKlLM&>H7^Xe zKb+~ShUAGqJKKGZGZ*1j!&&H;slEpJ%uq6x?_}kPsZ(LlO@>nJe@*5eX!N*??RI+Z z@gfzc`0Y@(p_qb7RE(3W$*5MZd8q2T0qwK|GU#BODDeU3+W=Xu4?644xq<@Lcz05!wmU#;|WlbNMLwG!)10) zJp~A26))^0rvHl>1AC?XZ-bu~GtKD>Vt4g@o>9HX&{6ifFBd^5;@jPIZuix3HZcsm z0y}+Ck4b;oq~=N`A2=?SPTszDVOgCdO&=i5oA*sQNg;9M$`q&2|C;JfdByd$0p4u} zX=*Ksb+?HC)mt~F1Aa}u%jZO%=IlSQ)5YeIMTj9x@Jxp4AVhO}aXSiEF;=0&x9L~c zuBd3AW4x;Y6>`q=IK6@r@S>1#9Wmr_&i|In@9|b@xhFDBM1)vmAJ5ptU7wO#Fy`e5nq-UdVcd*@z)1i%s4^WlHz zoAv+dv%m?;U=(}UcevNI)fCC1@(hi@9?IuKe26H`L1Y$eVLw}bpje=+iDPLjxNhnP zRy^sbP2#AX&NA$r0P7~^RTlvuAZkhKmA;f0f~5B9`H*dHRaRG*mPZ*P>&fnFq^I;4Ar~mDo-~Yw-VRn7EPPYVFcP0qa*gjo^@_V}OGg*NWQS9Z#7!k|@?++yi z`4X;3ZN$NJDm&4IJ$8!%W`;7l0rG}l6MCV^7fa@dzA)lX+21bS5s6C(zhuc{A;cV1 zRkNCBHweJ|8H9jrkl$;|vusMFB>DS0Lk5aj!B31nP((>na~5lsO;!4P6wSN=GNW^n zg<~4e-J5o2#X}GlrCkbi`2zf|wIQl3xq2SJ+IZx8IQ4%JzdKepS{=((WFrQVPwOA` zdAK(CikY8vgnYSl1gCWE_E-1lJ^hS)f9L5G&mO;fMWv^D9&XV~XRy$eA!L2t*aM}b zDJ0RDk(#y}VU}@*lPvz_oC41n=wE?=ngiltO*IkOxU~;WL@)&-yhK5VX(-{~7~gQD zZZ4}hJ23K_nhAwm#OpnFl|J5`Z-eQA!!XUEEQ5I>k)dp!Lkas(X$zbhegsxAAfPc! z7YXfrp8>GynN~1fa6cU_1MJdRd5I)`Dag|t>ZcjR5LwNm?!@I2R%4g?*A>E5{;m6CjriXb5JVA--3x+|78u;JwB7@&wpXZV9YB zYJu+I%WWE73`k4hVrKKGeeKz=1IHbYmHU40q=Q=2H&GumM=I=Zc&L+3D0-=gG9&^K zWTm~W6I5#J;qs=YrHBk5l4rz%aYIxHD4CWs0wknQ{9#`m&Fa00!u15SXjV7Rtb#2k^g-?xm!}@~5U#W6; z0(1xmE*q)da4G!u@p0yY!UBzoDM3AWOVh{K_*v`q>wa2Z>Q&|Jhad_YRL!UcvISvA z8UC6R@UW|e1)dpHRUQ~b-$`C=4}%`H|C_F(v&$Lv27g3|WGmyl(tA9 zQ{R+X#+CEhFX!dbCbd8XlfjmpKiZTE2#*?Rw(It`BLF!toAJ?n6?-~5p(A#>^A4ei z-A8nh;Y~qNoSH;!RP-4`@yjxg$)VWJ~Jh=p>aBbr_4MCdG?K@qoW&pfq?XBl{SK0|9qIum9LNrn4%+W z2T&o9Iy`$L%d<{I(@A7uM>=x}s3O3ic1F9zN-we0sed*MRS?QgK{oq)UiSV9cedNW zBV8zk+`Fpk=cf`NFYwO|$FFSqX4+`U$TuK-)@A*ZWVt`WD>2#-=x54^>;WX&bXncEeUM#rF)Sax| zB;c1bwN`u>A!+OM!uikfOZ2^j0)&;CM;9Sq>07VUNxS(L7xjg1mtZi*xe)bt-d|y! zz)<|}{8mJg$t0Tgnbr6J1x2_W!+fjPlfNw`GM;puB*RFwgxf%qR_@B#(Z*)iwOV}- zJ9rW@*SWKQ^kGtAxA*rM4^RT2lJ*bS6Lzpl7vyS|pm+rCX@4UcD&DnlULE!R*M-?W z|8G{|6eWg_zhIm)e{QxqM2=7}*PMMKa5_g0I|0v z`Gxf--Wp{G>_6_ab~Y~tGE~J2HG3w$8Q&19NOZs^teK41mV3&?uXcOtey_V&Vvkni zI#W4xq`op-C_&On`IVsQAKSr`^F;!TUygkZwRjW3Ipl}%r=!UgiKVjY?) zUG6)TPCncBUf|%3U^KwR+X-MR2G!I{HV9GjZPnY)*sCmTUvT#Td3a_36(1wLfJ8`q zsJgwF?7$e%?nGsO%6=>UCi?3=t?NeFlNNWHeQVMMFT?2KG0XPDQ|ms;p4K;|mmWVb zZ;M9OmUbLAlE)d{QuoPQ$ly;IoF82`9FH^e|(Z3eigoS zhn4B)tkzRUCrJ_QdT90`|NgVATTA{xtJ*!G*zqYeMEvzR>j+Cc_tSl z15YbK8UYtaoY^m^zV(h5IFe0X6c%U@i-JOYzgGQ_zj^k)LOYqZnsKY8-J`y_zKN_eJ2Uxe5*B%>+@P;bzA!h}dH)m$>j~&h%{TJBk8ExY1~d>) zcRA+1d4Kp~>`K`;-U>F6J0dw0@3 zs=a#WYr^47@$i%x3K|f1-MfXs&g^f{(k~4W zu)N=a?NaV(bJ4ONiDuY~=-bz~AamIrbDWzw&k^a`@`D^&!t`rHlxy&#}@!i@VtlS+h_);A;Gb%+6|x5h!`M>>ed%J>6$3 z>9+i9r{a?F*096te%o9`=`WB;l(EOIFvigelNmzlRQr_Gjr^gA*1iZ zqcI&Gog~~m&`OqWbacVj=TO{$o4TfF_cPag>qojd_(%jbHr-F%sse2v;Tj`E?0-w& z^uM()mfsVnHGBBQtTel5l(|x|J$C~(XT^r(-o%AA2`MQkn<(nH;6y{b1=^J%mTpbE z)$bf?UlWm+@g5U&z_jIS3>8YW<(g7omefaUKUA6VB}{VInvDL&PM+i=z%y~PLM9u| z=-$BIax%9=i}`|-rJ84C*%I>r#U}iD?su1ZYSptmR^7j5Bbc^}QrydSe*mMZUX(T* zsCo;xOv*Rvme_x%nKWmabWw(?RE- zyN`AZ0XRTvS%<9!010brC-U*dz?tQn>Bi$;);Kh+I5m{Xy}GAO2S<^!?rWA<>vCn| zHpvF23Wk7cDY3d4h0qKgq^7$jb9+-oN?Gs+Ld9G+o}u4k74VdRZ2x^Arf1GG-1?0@ z#l^dqk9(I3xR3XbLSBI7?<^v@DlSnTYVZTaL6y-|k2ascwPG5W)NjGYb(gEogLYxZ^B z459u7@;rRFtYQ^WlH|0pDhk{V-@i9UH_X&wU9EsEMzkW71j@cK8J7}$1HGj-179JM zjMhes{3A2&nM_e>`vn8l{Hg7&gbEd>w%m20p;eXl+9dbpm@V#a%c^Y|kb^Cmzs~Hi z-<{OhTx}F)8Uu~WV-Fkx{yk7%BXYrlI`gk>09fo0!qLtx`VDr8-@aK0Vf(12@{} zfGSX>sWWVFn9FwQp6f#R5lE@65|YGUDT1)9U448!SXy4XpYsPfHU`PPD4&E^oxR7V zqwn+8;U>)^87n#o)SwwM*9127Yu5Omvs?z%rD`=ZzZM^mh3KSu$~isTer~uV+AFhs z01tPfwB;UK0nOpR&$iY-m9#K1R{`uMlFQ#(EdkS^V(x9YRg>PtY~hfXQd#B@6m#MG z+rt_}0;ZiJOCxxPOXn+FVmMm@HaKM@m$B?M1CV$|lchKP5_E~-)_6j@pwI+kbxiHd z()>KffX`|C6UG}FzBK|=p^ZIbV>hzKTAS4B->w(E&V7q(_ryOMHL~5b7+(2ErsPF) zwhU@;w9l7fd0_ecR6*&7x48rEk87lsS-y}+9NQL-^q;iN_N+dJbv}X1Dpen@ zzjL?3cH!XeOSmX@MA>4qVYNh0Z}o`GlCve#rQu#cgP=WpnHS zo(+Q*({thX2gx>xH(67(_&*}s*Dwzb&DHy+rF_J5@`#VAQs><_`7Ni|!Tc*slo`;> zSRVsBXT=9=r9>4S{1n{)srb=Qf-{I^pRZf9?-TKl516-j4{Vn@v`-oiZGSR^2K2;y zm+~hi<4#x9Z;!w1n@$zAPb8VZjUEX57SUJV4l>b*t14m<%9)7gZW$aL2vT&t2ZVl$ z3!1$7;yi~I_DQ6ON+fIgW&Zya0B0e8%h@whIr8cO<|HD6!p`Ej306#h)7btigneQ0 zhIz6)EPZCIi|hJx)9?L7Ij2V3QT+iv2UNk%xW4 z&U#iYHiCTHclzGf+^(~FdshWe=SK96t*=0@EyOygt0`~}{ zC%>h4^ZpF(DGQZ$P2r)oUtM0&5%D@BslIh?i;vo9t@&%lWVn6hDPic^NaH$3(}L%N zl11Fh)%&bBf)>vv)qOV6M$*6JGSTf2-}*C3BXXvCbP6qDbFXqj^}NNi2D;|mf??)Y z&3GPC|GW3Lq(r={h#7l_&sv|pO^81sSU2%T$4Ew3$9%O^t-q_)RURg;pLkmwnXk*4 zr2F#Seai%THFuqbT6_cZeE*T z=svg14%fEniR<+|% z*R*_rim%4S2nd{Qohhn{N#xdBq~hi1)8nglCvLm!x9(R*^XDn7XDnzCio%Zscy0HJ z>$nHG@pB0y&erVt6R}8rwuqJoBu(%|VPV11wMFJ?#ls1Yz{7aO-ZjaG&}JU1dpp5V z8zExIIqmvZHP2#bW4wa!WkrR5c!mA^sOhV;=sL8YE~&1Zl$bk0_kN(2r2iV@WBVm)k_J#3qfl&w;C3%~36Tx^L(-y5={)VCzt_wxr1>ywEx{g|pB zTm1&&z0>krGC@Z%!M`%T1&ETByY@$3wO479`;;MWX>7qLDV`FRW5i+M!;hIPqlEE! ztzcmFXR2acc7rH5uB|KknEiUMQ&}RbFZkqJqzfi#lF31MFiF-CGa}yyR4llcN8jI-CV`KhZ{rtYj(}Wk&m_&N?o3AYKM`F z@-h9B-MP@0pU~aRz9pim5cLV5D~<}GjG=Ad`P$scKwY(xB%S_`HEva(iEtUmcbuI3 z4f?DO=LAX*lzm@X?uxKhE!Wlbnby{L{@(d@HrGxkFrQr@dNdf{qS3n65@K)_7zlPfdobAU5yH3`-Jl?`m+UBq_p^5&T zU4caFk0HR}FLUp*C9Nql1_zH<7$Ay!i@~9OcDU~dQ?N927!y^#9br0%R>L3c3nvzH zG`T?fv0EP6@s*OjdjTi^;QHYhbh8QFcFzHU?thtkZ+AJaLQzfyB4^d)zZ^lIvThI` zjxlpn-P5ZiaC4KRHf2xdG zf6W~Lofu2RM{;8A>$YlB7Jl=dUb5!_&=n z{giNg{e~%gcu4tcCF>2TR*Fvm# zXvfkQBO+oECB_mKnwR|@f8Osw{&`NFBp;7FO4UM2j{XrA2Q!EGKd71;Gk(S}aFz5lWc_6FB($%z|^V)qw_EhuqlJeaGe{H1qDg<}xs(lYA93z%)t!S*3!2e$*Nl6W0KPK5k@r~(50_E3 zzI^SYau(|kpL*NjbMriE?r*v-G5Ea6#k~Trv@3jA&)$VVJmK8A{=Fm1{|q%JYHKdX zj45YmLib}=9T#|VkTg#9`^@`plTZNR<~01=Y-YNHLap_?9&d!ZGR=bOR(EI8dVAzA zb4*5^o|G1?;|;0vOpeQ+Ju`VJhP zxLEX#L;Ze>wJVrDbmii*=@5DmmH>*ov1PPe;NJy3TNeD^pkq(!YhLULWvhJQm9Ly5f)4(R zVOcqcENoi5Z{JbO!tgiYu+wI7f)ukhgMP*}t=v%tj-}!yP_PR{a(jF8o!L9sEZL#< zjrGAR7w6wcF1@o$uS0yFrc}MQ!B?;;fX{8~So$B9k)*>>?8dSIwAPCd6HlX|0(c`t(krA$!x59C` z(f>N573lg6mu^-eEQT`~ZqtES{SRT7&bNkQnBC%Jb3JN=5PxHetsiuSbdLMe4%ttf z75O*VCD(qt@pMx^CWww;)#cpJH=e(e#7J`d@@~%JgZ}FwwY2mU{?E`xv|=)1S=hiE zq17SfD$XNVBv_P?ZyCgHNJ_h64Bc7EO}iM!M4{DT4UX#djfivD8SX8UP!6<;7WT>~ zP;QI^DiKn8jUm$wMx7Q>YYkK^H8#JaD6HX_;Wy@f8=Lnn1{p>Yw8P`HcX%yQMyigL z&Fqu6TsrYA)b0$i#1pZi=k#~R6TRgFLQvr(0|UM1Pfy6CrN`>q#Xd_T$4owsd^V(W zUlqw=D}xwy(uv9t)~8eq#=wN;j0f;;&R+{GwzbwYoZENs*~Cb52#(3$E@7Om!)=?s zw=;;$H0X-2_wn2TZtknE>&tgsPBAr#T2g`f-L-WrjO>It4V>AXu&{&qExM@|k-jtK zNz~VPgS4@?!M5H+pJ31agrFrgts$@{bd=>Au-w8f#`$Z~@iW=6%M)>Qaxbhkh2U@q z0ZH5NFNbkgjDud|@8gv(dDyE%(Y)LQ7R^=P><`DphCw{u*Za5n+qQlx^>W6iit8|q z@~*<<32Cu<8scnBL=~5b&^R9J9^2|TCrdkKd$ae+yALyB>Yb)=~Ta!kuYI9kGB&_D7DQ#3Y??=3bGH-P z*(r2RuCo{)4ROaF)pS4M+4}x7!q9Wa-T*) zlk)apq2ySwV@_-J&m(Gi<*bnTq;IqBu=1y$AKUR0Iw=}PL6zlGGt*{npU9OjJaW>P zJb31yHjR>q$_d>VC>_Dxp~16;%uiSy3+J>6E<0NH(+N!$4mAr)OHMbHbGO2CB-069 z32R`!l(SM5pbmSc)!5CV4I&qR!$>ra%E^G+k3Ux#bR%-@Y$4X^7r!rSbmm$9?Wg{E zH~8D?(UudrkGHY=<~HoGNSUQ-Zd)70tMkcq%z+_vUQyrU-R+g-vFDGP?39(B$jcMe zh^;SqL`*_oxPAc%{!DK4x_!xrV5%!_TB06bKG`H6c22H=#g!I{6Qo< zGBWHT?K=M!4ZgHB<=l|Tb?oZXqYZ!h(AjM2u^*b^9u3UQ%m-Jmi&!=i_%RPL7NSa=J@prA zPD0QGcHpRQZv61_zj{46hOFjS7BV>$E5UQ&ZmxR!J_t7B>F1(>NUe#B0QEL@3EPYn!s(R&-}{Fj%771e!WP84Z!{42-4XimT}| zPj|hvL$k)>cZ|YEwg?2@_XBDxTbq&QX*3j@EQgJ+uo4joG^k#W6NGUPM z&24Q7@AjrYSy+(4bb50N^I>*}^vNV&FUAX|CsH#!wT^4nH&HimbNcSv#>kl)i8g$v zHZGHqXaMpOVj%I%_*?!VcZ2Vj#n1m3er0(X{27H`4-qe?D*vBGF>#OBE7Q0~;cXiq z3x=ZPBkE2VXYb0|LTE=@ZalzZ3k@7WakJAxD803pILy z+e^6F2<3&-boN^dhPUvtglKacNWXaG#MocKe7%$pKOW~>8B^@RtIeoo;~ z*rna4AOgkE1KgWk)Jgr%c5oQk@Rgnyum+4b!}`)9Ye9YCOX=T!*a0*uy;|846-(oW zN*9T(a3~BFJnn@XPv1SNtmEn)l>R(0Z0-=1Z>2tR==?#TA%e&{PzGg3MeN7@7pyCpLfWZe`tgdA>Q%-*_y0yxpF;772YGIW3l83BDiN z^LFB3WgUNnrv2&$E!`KCrVv3xo|>bu`2bwB(kN6B$7C7W((BTcHCV*uDQ)+qS+x!7 zrR+3mapJXAmvQ<2zdB?dqoR>jw2~8&$99%;-lJpaN8rnh{f;6#w_S)T(`3Qho}76W z!+k*58RLoiD=9Ib+v(RSAjWo6NukeYS~tSU7<0YowZYl2^Fy*Pu{thhY5zyc<;{%& zBEC!vr9WDzR9?kKO4@M+`w;OI=gOe?R`hc#W6e=^7qQq?`Fw@%hZ%2*_x+f}gKp<@ z38Y20A7Mhv>n&FsUf9s{b^Hh8e&YHl|_4ASE030>W%l+G~Trkg{;0rx;ltJy$LMe)rjTc2sOp+1Xtckn$sgOce7Q*lsjG8|zhWmnvbx)sl`Gf`-)%!m9#x9eG8GSC< zdS`Re!a(1sWz!`EBLS@%%ZxOMfjpf&Q>*VYZbgb+jJ?tgJ~qeuhtQ}PLgx20ba)Ro z{dOY1gIcM>`MtDO{U?>r_(H!dnRk#csn2tWMoP=Hh1Ds&(1tndTsfsKz|t1o?B>Nd(ik_9=inAIPr zE>$xsgB6W%yDxgH<$Y26ZuJjX%V=pv6Yo98&Z&*}W)T`wRBR{f zQLe$5jSkL8)XwqO)6ra>O5=b%GU6W&XnVMO{_PL@mFnB?;1VwU2`#eMJD7rR2Pk%L zf^WkMt;-W=j0u;hr%|EhsoaUJx8H+|PC*0^0H5Qf09^!@^ys>j5M4xT6Jz!IHyIUW ze+%a)0sj5 zQtfrgQjg}$b;gWJUOf zuAAL~SG#-XV+<4%JE=78m!svVndL8JMr8X@+WHN`197oTY<3h&QXj%ldF(yR4qj z7sq(O^!RjUOaY7Th9S}nM7MZ^PCd!*$UweZd>^f3;$`4|3DO4~=qU-BGhE3N34W`-qt zb=~gQi1uE{8~6v#kq`K>?-JDi)BKW_ZlRsd&F3{(S^?A4$<(e?Gx%P+zHagmX2J2@ zPtu4fd_<6&I_E;;Z zIP^sx^)zv`99LqrS*f{qBMlAd9$rntpOKKmG`+ad2-2JDY-)5y+p0P?*pk26a-)ys zUh{wsclJ<14O&S)H($~6@ybuJQm6tXUx6t1bg%TmM9DJ1c5)t>N%Kp7KZKk{I25(JMVP+^m*5O zWX*1rRp63j>a}=hgFGKZIz7%#cV?$fFFg)ySa!=D%0A6PLwil`4r^z2)qC%y)e1zZ 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 I%)5a91K7FWD*ylh literal 0 HcmV?d00001 diff --git a/docs/assets/user-guide/semantics-search.png b/docs/assets/user-guide/semantics-search.png new file mode 100644 index 0000000000000000000000000000000000000000..11f533fbc5de665742b34c7db3583b6ecd6e6417 GIT binary patch literal 124553 zcmb5VWmp_tur=DaOMn0&1W1BA!QFyukl+&B-C@uG0fM``ySqbhcXtTxZeQm;_uTvU ze)9}6Jw4sCckR8ZYSpSBd08PGvmdZm8(^3#E(9(;&LXF$-WA(I|{LS zP|bKEwBByVx7wx($~fhr^dzkmKgWd-b8c6wA^9!DZ=;Yeqbx%Iz zKswhFCdMaKb=nU zwYAr5K7XA|wOY{mpFIstY9Mezh#wu~y4vU&3DjwEKgdjB1^wJ=NJ=6wHa7Mp zxpI4MwBJMYJ-fZ7KLaq`D@dE;{*oM1({FgSJNeu&ld zjPH_7lZ^013zeV+zJLF2Xhqu_~0{SFI*_ukz#3eNf+kjFjTgdea0 z_p&P8Xa>{Gj3DP!LTN+SvK?t3!1k_&UVBfBZ^!=p>w4nz&BQ;OH}QJiz7egK5R2+y zMD1pDYfU2^DAQ`5WU;JN-L`Agq_0<{MeXL`i)ySq+&uO23M6J_uxiK?2KnwFO3zCH>P z5)uXm3_wUos7$kQa(p}@G4UM%0W~_S0*+xNzSaE7OrfH9`f8As6}?;Q#&DuNg=|_C z%GvpO8kb>I!}A~Cc&PPDhPd~r@aEo1Ms;b!bfdpol)hzkzuW5#A8&G*Fv=D?S;(kq zg&l(e`uZ=n0%l=FY<^E?78OjL9|z|VoJS1OGa0@KauZ|!li^4=j4HV#RcQoJ9;azy za@Wko8OA?`&NaHg=N(`3d*K7rO@CrVNeNi#6||zAXQ|eH>)X^ON8~3S@r8d6bjYVQ zI+eEWg-GY~;4G3B78rRC_z+Gm^YaWT*adCZebZ?oCxvRtZb zy&UDH9fC(dsHQdNjo71|Q*SJ?--|%n>Pw*8eSXB)dy8{(x<)~s7q2BPEZlvv@+dhp zs2aA?>b?g4PaF51#ZeeXL?-Q`CwI8S%j;!lH@&{6=Z{0kZvgNmd11Pi(8ioC({4Ar zS0U>qn=+P;9mvs}iP?W0FPJ%;vr?_Sps5~M_7dQKbSkGXHTqguQ!aQu zwy7c2|EomuYm}nS-6)+tM4Wnm^*sRV+rp^lrfed2S~9&}vldF7kMi00j0FY=UG3zQ zz$i8w5>oKAALG^`N|VQA?^z^~o4^wtjyOn$Foz8!9|9SSM)FRlObQUGjs?HLhoY9d z4S!w))h(24grwM{j2Z}dtSdo~wR5FZ`QIkv-8s2+q>2GoJM|AzQi=JwTED6)>LsnP z>Twn%09Me&%?%zNo`}yQVb3*HHea3+=rCcKWH~-P#Q?B^dXhdOK^sp=7yH0G%uG*P zE?!}ep#U9~nsy3LiOj~mT9Mk;9QHRY@$vbAfscvRd*vTI-m{poc8mACl;-8-u@=w% zy>bKoPUbQTMsU|1<8-4Fb8Q};POyR+LK6>T6mJ1wfA-sm!QU{6+;dL+BUa-V)nUaEJL4yfo?s`Yu3DtlB>x1VNDPwE)o zVEMfu#Qn;7Xbf?!U4nKXgqYsC@n$+-(NuQMk0085Zby-Vyj|^dX@QQvTRr68&gVWc zn(bx$9i5#N$$0bD8A%Xxe}A7;qV4HoXkuc*{pKiAoV5MwAyKTUYAzW0~sTtixwaZ!U9!s@N2)f zYJE1s-on-FFNzn4uPzHDhc*U<-x;LY6i~{~#f0pj@uiA~IZ_X8;3EO!My*w43Wtks z+R>AlHSTuNDj5>Aq*qIjUk zbf4B50s-09-kBUwMOn(4O8#vW)+~7X&HXdtV3SOgfu^RWt*x!1;#8gG(zF@tP!+e+ zk&UzS=|nby%v4jcTG)!uCx1nJP7%H~g*uzX3B)+}0eiy=Y8p{q582Lz<8CMQP? zv_|Xb%8MReON-nNcSw^6lItuN&;jjL?eNeK%r5;Msob7Z2Q{mFt7xI@3FPH-hpgxX zkCPf^SIBy~b^r*^l~`)oD&w(j`n|^Z$t`}_P9=3ADk|}!$C}+tRc`I$DcgbeZsB2z zl}Suyrqz{8Y2}um-$lOx20ujs8GCigH4BJsl+6>x|f!kGcz-rnwlyrCrnsC;0Tx{a9wjQghx|p1-0-r@i1aXFRxVF&IHp#7w?Gd^-!(h2RD&h2U}Zs?=Ii0^$3!}EA_CyCnr`bZ68yI zYR|<=L*n&Ce8zgQfsCSJRPi-uI&=VljiL?_hxN^%6|H|l3ER0hYD5N7r0i9LerM;R z$BJxv{Uqs6HiPokC&lvnF5SoxRI^$hqLo~iB59B0!*EJqO*arutc}myPY(MpcHoy^ z<(rE``pZaw@K$C@hE6?XXHn6|8E!=Ux`{lBw1)`%^)BFzSMY=`WB<-YM~46cI3K-w zY{_tfx@xl)Z1yP`P7Q)5lk@X25t#3B2uUKvp`Rdu7{r8cCa#AQFMUbw?(gB@@0d}i zmY4My@rAgZ?d+X-Z8sMk$Y|2=xSUV6_m9=oH8;1nS#~GHSO`X9X*g$;XVTKr(2dSez>22x-q7z5CGF3MgHkOsqw`jPi zx~MuTvvxDKK9b)4eP_smKP8u5$y@mKK!3$)tX&=(i^|;eVo>sWluN{}L|;TO+Sz8j za0LVtHUubu#SP9FEoJ`b9c6@4s+X#vYKg`R+}j+AK#G#pG)H_O#Wq=J z)Pcryt)Y>>u#9+)NEu6Es-0W-53(;|k-_@$BSbG~ZJBNF`b?z`zp@0gUXm)mAzC|n zr>npf=OkBlgtes_d8+zN_j>_N)1PKs!@>jVp7o1gU;kLxjJ4kU_Fb6f8bkeOpu-c{ z*`2=`9}%+(D|cqIO+WaNh==qFM3G8F@r-HE;$~0G&(EtWDCn^eeE9I87YiLC~rmAww?m>vtuQ^@!0(R zjw_g#y>I|9(gH(-6Nax2N5VIT$76kcFfSFqy0voVW;JNJg1&Y!O3TWIO_(TA;uuvY zZgKbveLQ)1d`y-m59$3BS)fFq@<@JSPc2gdnSc>oN-@&;FEl1|HrAeN70tBEqvRZ_ z;u$-<2Em;oQJP$KJ4&1s+241Iqw@Wck8~sGO+QXUF&>)bPBwhOLK}mThywAF&_;1rXBFSpW{0AbKk=n5C*o*07sK}%It6EZ0s~R$Os650RiNpekh+O z`eUtb5O@SQWTn1gEIwor&OHdn{rhAq0A_ZQvDpecf9-0ehGsqh0<%rOyJ3bbiXv#Z zmf~0UhVy3CZvN*~G5j|G(8oq_#fc%khuLR}_COfc+9@3W@7)z&h*rT{@J&V;LGbk= z+KnT)5QPvOe>P9eIf4P5|9?Yb0u!%)#|u8|&i>DlH;jk>#(;T%nDGC60(Q9R-?=}z zp#QT0rqG9tlM+Io@m9Ix>A6nB1nD=D5x*f?$yhES-_lo<>YH;nenCEfP8R_Y{J@HL z@t6ZeJ=0C@u^-91Mrxt5;0S6Vv!U4~70^w&$?ML}NK!|m6Z5(JU59FG<0F-bi&r7! zaWNR0O?7MS3GwgSxV! z=3W~Mnu`9s01mBbyiM(oC&^~DgBENmO5ds8N~)+B0~~f&JyB%DYP2QKP#-o!#`ynE zQyENmV;|AH{I<@&-6=wRoGgx`mH!F*txth&XqKSV9~EYeR#C$l4ywrfUcc~Yc1;@h zM$XLgT}@(cqqU|AIN^T@9g+&0*KP?PN^!e-a`BObDJJ{gv6I$nQKZv-h3M zMCv(ML4!T6JfEXxlK^8&cy+NG-SCb`haM-If3oW1gQ@TUCFSq!*Rz{oX12S#9l6bW zMeTS>bbc-tlB-Y%>0MzyEHp*-e@eFYQmsGHIP^ZsEg8DpZP!*-)B3LM3Aux;<4)~8 z-WmZUWT~-?5%}h>;c-do8?ACzr;se2tIU_3dPn$d#)|ZxBv!@W9YPer<pX zI|RJqwkD^>N>v+g!u?9^8e5$0e6_9bULMnriUb zyB`>^Qt&ddaZrwTRT)o?l|GmWi-?c`9_RN6-@dK%Cio#Z0Uh_3?-*Jd0ISnHtAMNw zIq0c*T94KN{3XR1{=@~Z9z9|QWvy0u?SVj81+%w57#DA27iZb(=jT?1>I+UP&uwpV zfoA8EGD@0ihG##dXbP>t2A6Y>07!4@N5rmyfx@=7)i&sksUaFJ`acW3{K>s7P{q6`R zDQ`+Poryq(X@ZykX+1TZ@8R}@_t7Hk4tuLFbbpzX^m20NA{e=PKsu_Yz5Px_S65)M z@G2&zW_(;WoA})u9GtT*lvkWS7O_ymhl`;xoyL^Q<9dErczEe_zQ(2|qtRu&_XGq` z>!tPDhim1xpa}}8Ord;Uw*G|gPjhbXsH|d6^>MeXTdj2AF=(qQE4Mlx(&HKDdp^4d z20A8wfrJgXto%~7vB^*(=e4`~xnNO8hqut?tuGp$!tZ6JsCYRLogag22r@Xfl@nuQ z?yUUn;{?pt#L?nOk}|v>aBwhJXbyCU+oqN$KSd9s6LYQ>8*g`GU>I|r@V}~2!Siz& zO}Dt(OG*xUJ{~)VicZyA+Zhw8Y`a)ly+mZf7%D8(S#m>V)U-fbJ?2^E zsgRa_!*)kcc%SS)JY-?OaeOEe_kFqH{p>>I*$rtZd8P=>xYr$bb!lyRbFOcT?_!cV zU~_8|9u~^kaO#~9z&5=~vbE2+vGV!Tr{>oN+i0yu(69zQ<3}Y&)6rCRi9GaFWzMf{ z?#{1Pc)Qz23CYp!WXw{_Zv_A#I4dBOFmLWX`Z2e7ltKCY-_Gtpi~D_*<_*v6P8*w7 zL4X8tvb(!fMY^Wf0(XiwS8~8sQ(ioR^$-~ueU~FFi!xVXW_j6Q2_5v5-uxc#9Z=Kc z>wb>c@}kKbBJ=4b%f2ZIC_XkS3EPuyqcuT{K#u9aHu8U*xan_VA*lV@g@YIWv_D)L zuj3*6DYE5k)A#ppX#mV4A1{XFc1MySSMx}zvev{9?9NUf6x^2Ns3apgwdRwhrltoP z6}z65Tq!&B(Y$KgB;fBBgQPJ2Bw};PDa>?=K zsx(C0=jWBx#&UAfzxh3R;7;y*v9Y(rqdfL=*=dT40NuXPl`^X`uVGnD%@nA0x#p0R z6vDmHR5fL_=cCK=>6sZA0f+K}aDMGCUN6h{mpb(Um4w{RH!X3vbO11M*tRrE~j7eq4mM(fqR23wBRul`YGfwkoCHZvW0)K41cX zm~sBq^wq(^>^US-iM+VHnBR6gVV>RS2VJ*VsEv(-iG@iGn!Z|DRg2Iv2vYWTwn(VR z!-Fkf6>>D`$J#u|SV}^K3UPFD*JV2!7dW+=0;p#GML#FL%@K|hqAGnC;!U|I#^2s3 zG`s00AI~?A;8i%EehvD1(Vos00y^)Ubk6jsq__l3Fpw~kSqvlO{g|f-L_|iu zTcI&qn$_AAkC4qD8)Nttd_W&7AtM?+zrG3yBm|R@yMv^~sL3krR`tGQtG{g42LXhv zUzMDj%C!3j_JH;2cTRtz$0>a7H&n2lYwM+`KQ;v@*j!WfW>bu4IB6Y1w>Tbuh#Jf} zM?&(1dVv*;!HLgFlV&171q8k+Nqbo2kEW`|4UG(tWnMpLQBoJFgpX!sDkSD)W-=Wc zm>G)fj7N5d!gp`L1Onb9x=%TM42IfOAHrg2wr!El+g>~Yd1W=n3vgR~=k}c3qOL^- z^2U;ofWvmPXJY`sM+-2gikJK_NtM%3fDa)05;6JJy%$3=&#IG{kz>@J+j&+-+SoY)6| zGC^U)`GE^d;*$$U)hV@evH&~rV7OeDlnkbUv)$dPq)5%KT6G>Rtxy>qMzpBF#RW3Z zG1u2ucQ)G-DX4)Xf=0|%;MLBT%7wtqVbUWcI?0v$0EUl+isUl)eLXM;;qY(|7zFd> zH8mwgO$304M+=nh%pgNq-L#~|_9%l(U)Tx^!nK#t$#CKng|U&AgpABm8>GHdx7{r< zLB!RqaVd7`O?jUah+l4Nb9Qsi>2_yhuP9~VaU|>q z|5S1Qm_oBK0C{9Y)vr`-fp~1&iC8(AkCI+o-cpF$jfH8oblkqbllRbj@@ai^wpf{E z{lrrv6>phbzX-oXz;4^3o;_jeh;Va^005b7ZGuOZ;L>zU)!xeBboyH$O&=mBi_d6r zHcGc-iQ#PH@JsGDLqEf~>9LN0F9~HZG!R9)vb+KfqtS=m+afuaMb|%qf{fJW^6_(y zqK&1DrJ{bcc0TshS1^6j{Sf2$4SMI>etFUPM>q`KcY^D%LVyqnM^qz^QpR>iA)L>i z_a`WE5*HGx3|JEJb#InJ&9V9Vc~Mxw{(%v+Qv%#KZ>)}StH5%Yp<2T+QR*uLNc3NK zHcv3|PRgY(Rctg54jwc#XoI0i1uMzn#oM&Kd>MDUhpPcVUS0j>hMQOwL0Byg@_2lE z98h3pW}Zn$-yl+)%IoeaBxK>C>0qH*-e_+W_Kw$X_UL}`u>A%Z8F|D=%AuS{4=={@ zt>!KC;!I7ba`Eijx3!~vc!?u7bGnEB0N{ZFEO$hYsS)SL4*UXis=U7Fk2Ia&8sp(P z%Y|FETNM_U!g%Y8h4HcdNGVP>mvNLaQH>ATV@AHXfLd3QG?UNQJ&y=@c=(`E7qpa! zM-LB=Kov8X<=fS>P5PSQ-Ey=%s>Ox(_HEDyqwfpxJ!WQ@Q5&0R9zx^}?{9 zO-SYFn)C5D%2-}EQ8Z#!yGyxt8$#!GGyb?bk;kb#VnE=D^ioU?^#kYf!h%{#vK;2u z`hF{3eYdx}Z;ANbj?_a`sFVk(nDf<~rGlmCQ@Akz$y!NFm)6dR_~bRZs%G_UBvkY^ zk9{oQZw*(hq$w}Rv~fu{8*BLjVI!kH%5TDCvg5BZU-m~^c9ejAkqOjK5dX@Z$2nS z+q^*V@p+j;he1R}hxNWFwwH9##(W*2Yf5dQ_>^VtARP{afX<+OQK`DQfBd7=5dUEW zuH2)2f6r3G8Ki$$>ua`mb~U)SC8dU%9riU9H9!w-%-d5WL!YqKu+^ssdWw&Tn~9Bb zq3_wb`1p)oU%Kmj)?IaoNu)ceLe+!#d@=_(l;lTt1y9T`BHt(2VlC7CMAPq z;-ql%+AVU~v$G3IhE^-yH%si2DRsH>)CY%$?~s^H{$hrTLTpYuqc%6q1=&#D{1Q6O zN&eQTchA&H#YpCA8)L#|w$;^DRx*+EeJ}mCx3?o~KI;$B0_*G;3NC{V)&@3$rxyhWlN3V$Sq63-HaEH< z-LfwQrNg4GrPgHs5sdfAdwTnNe`yJ7Yp3Ce?CjvZ9CnZORE+UGUR;i?zHVM(D9WX> zy_2b|Y$YUQNr$uzb3HzS4quEI;?jFDHkQJ>MphHe`r6<>q_&`5YSq=iL=c*^*Wmf= z#;#+D2AG*25WG(YE73>C$CfPbpuG1QvNk8dpi?c5*Y;xC=Q$7o^{C$I*lTKXba1DN zuQfVHA*eiDMMj2h`5aE{_K3+u{#7+cmLs4O-Fr@r0rEO)6?QXCu47}`^;Y-$ipDsSt{xu07c3s0 z)`pIgn|aYfWrpI)UC;fEXG`u+EA^>F{XQ~LjZSrEXH?uRH_o2aguGMR(CX%UJ@PwB z;R(p&f12B?cU6S8uGA>KDz^Tu1JQ3E!~rbsI%_rpXPHJMs>?Ja2jhC}iq$osU-w$Y zyp(iQRJ4>+=eap|=8=d<_?X(t>SQ%_KRsT={yJHa43?^)qZ9o2F}V1&N}a#c^T5}zVC+N}jg@u*MiHs*@Un{HSGnKx7SDKxj9W<=!SfA~b1Q_vA?GlGaRzgXB z9Tb^|e~QD!yFHqzUi0=;sVZZ{k{C{suaeTPKGW|1O1amp^{l@*>!fBJ=u~4(~MG` zEb+8yEpl!YN!g*kFk&Y9IK5iDdv=kENQHuw1|lXbk+)zn5hcT{>Dfz=k6B;=jMH9+@VWmC3gi0n+%okRvfi;#y5yvm zuWoFp<$7|jk}v08C8p$@lm~``d=GbcOB9$Y<@chWVoAx!P~Y*agKLo?9&-KcJV&(K zlnGKPaMmr{&L_YBEtQ!Wug8_wBI=?wFRw^dnpnxAVsp^p^@Txx{wQlzG#=G*cqcX1e|Y+bcEP%-H|^wShaUkPx5ldB6g2aBx5i3|~ui5iL7sFupbwx4w&&B->2|GUg7e z>FdCd3vf#fBb8{(a^yq;!&OGh=fgSZuQA142OebPZ^e9r0Kp73?JjH<3>*Yl228?n zQQta1A3g~I9ySUg2#%CT5CwY7w+9w0ELFdEI4-SAp;!Cr)*rb_`cCh~l*PhwV}Zk{=MN=Sf2ZOCKHubiprL2dE>!riOv7jK z3oJ&A)SS1JY1bNP#`-~{w&X+tC=#lOUD6S_B*W0A(1WxQMm{XeuzkQ7zyK8ol?ERN z%?yPLty*=>k0OZgQ?{qXeur?o_ER*Q9I>m35D!QgF^VGL2x}hcMIYChnzYHuGzx*3 zKAIOS*f}fG8{-*U>zva6_>qXWct-(}hKPboVT?{@F}>dlTm&&Wgg(gwJY$uFGRdT0 zKUlI1z(I)*_C(;mGtDAR`MJ#*XP^d3aG(`PTm;vB)=k!3qH>UCj~oO^UItm>5xP&b z+yl`*+%4pvo-VmpWEH7@jK4mb$yOQbC-z#lc#brkmIaF;$ycuE^xX!*<2NrNW z$u|?YWIop5T*$#Q&yXKw$e*t$uoOGqY$&K=j3j4-jN}ms5{3S|Xm9ul3KXDz^3$80 zu}^Ta7<4s=H9Mm?+c{0{JNS!`E!CayINZS(H^*{K}h&0RByd;a6;YeQOd1Mke4w? z`K-W;fh7jjrQeLy99iacA9r9$U~yq#EcuA$qxNiyI|cowZ8*0z<2_RK6-lW2)i5bD zRM+81BuK=4wh@Eb8@{#pvgv3HVE^~6_-5Fu_nT)>tY{^C0n(x9Jf(DgiN>$E!6Qh6 ztdgOm@&Z*jHaLhlorC;nBrG9R^;t{masBOzJuLeUBw?mUUi z&8FvmhW%9%2ghbk&0;fKIxDou;B1yf_twf^(9q0x9Z{E(L?DB59o82MQ5%^~U8Hc8 zYVCybe+DI+Y@p>EpBQaJi;;qqFf^*$w#fQ+@zdeA(zvQGwLX&y0a9h`s%g!$xy$Wr z5qe|-_@uPm&>sN_#2o(&iOHPH5m;#M|Eg>bYvz6>A?q%Uy+X*)P|y;Hu1V(EaX$x8 zeS;7(1oPSku4u9KzYHWBs-vM0iU1!mq2*zrU|HC?w3#T+kevP*%!dNe%kM49BFQ1N zb_RzKpbf;4WUTJin+I$DBKdG!vR5OiSXcafTBe%#R^72Wh15G+Z5=T<9|_7OD06{f zjIZWH0U=fj4^cE#6CfZe$^U5fZ3~v& zjUsd9wPfk9sV(chu{{O2 z@%4;P#8?cz{f>3hgX(zlZ{b*qcn+!HP;d~TJ1cYA`iryFvg$+sm47kafEt-uIG|a} zU__xJf?Sl*$&w|EYHo$(yh+>pd!**}X2*mbfW_gn4qKSpQLFz4_sxGLU!`P2Ek^~T zko5BQwI+1VI$&B}!Lc9)R#+qS@=iSI5PUYJuUJ6!Vi=yk?A z9CIf#v^6v?<;q(Otw_I#wr~F~l!+juVWcI1$^Xb1DI}$rEn-=R*ik6+wInFmGUKX- z1e1jU6E|(g2i}Lo^$&CoT!(lS?qtki_K@9_dc2Crf8+0{F`b3$z~07qNm9tQvLMT6 zJpYoG>&*;3U?1~4pHASwr#_H^EGi>{9?y7)(+kf|J_rDoz`ih3?+7rsN z2@gPbp2IRAk|N?@2mnt5O<4cNB7_ljGty*%2`z%;b;CUWKZlCgH2$ZOkGS&xmg%9Y z|J!7lVcu$>2JYied9jX?|6kSqD#3LfSa9BNeAk#TGDg~;JT7`Fp)hz2bZk%0FRr*; zH|Gbi`v^Y}9vyN8_Y4eiTK#_A#^_#KC2lie_)lxTC=vMlX&h!~Vd0)QI$ioCb^B!Z z2P1)<{bilSLY3Dm)MJDD%$2F4@rc`E-B-+?VCk}EDqp2kUF-XIBaN9KF_QkG$MWCQ ze&|f(DPav-<%}XKAkXjf|>w zd=8RbJ7V+2pWhHCvVU`>(OGgBHk`;i)Y}V6l3t?a_(xh2sGdo!1M_mz zUykdwZV;A>si|pR?#I}BERjA31qUbbKAMJ#GFvY4^WClM%=OhZN64n)2UIdxezr?5 zvwD8XJRsDGc0HP>d8U0JKW&J7v!$aSgAQa2@#}j5prxio84sI2uYCFh=yuQP5OX=G z(yG;a3QL%(>FRcX5o#GqYrWcV+|d!?;YshXG=ue6WjHVHn;&cM9bP|CEbW+MVrIxrK_mCttyX0q0r!{p|U#w_4@X9%FNNVvC(|CbgaK0(|k?+Zui%l zZvK}$#UQ1N9_;94O|ASfj|00|LqJ?wI)TMhVXlInRAP5~*Xrq}S-&q_V+;`>6&X$8 z)F1vk@g?yJtT?56Jg6xf##(li?mM^v>#C#bb|K9+c6M$Lg^3ap5}@p3W@d)l>FH+W zcCDHg1xWU~Uzi%Y9ARJMvN@b>S-+R&ZuIo?oUe^<$@xfHj(=)$QV);Wbh5{Fj2{626|G+qX{o3xjmHY|(~?qk zOh)5TT97On<9qGOn_1HPLg2`iDovhEIcV^ux&#jB z*|&XNQxxa?ArL#-*##wVoY6N#(Pl;r1pvP%Kb~b$^c*)$kIboj0^!9Hsk)5&;yF)d zR8|($4y48Wd-PmB<(8DJIla_bKB9+h0UbkAhl!2$u!5>rH{6dm#}pEFqbUoo2~XS0 z#=9*K2Zum~>C~&V=u~iQ@!-%9sA_w?JY@idf-F`~4PMWOpAXf1eWAS`E$0Hz*2Ynl zH8l2i_E}HwjiJ`_(>=ATol<+dWYhVs2Pnv0U9=DTp#Xm&zOBOiYI;L5GBVGLbFq&f zJ2y5qz|we4XywK2Ex+q#f>x$%{!+ua`(1UjjlKOL?wg76aYoOsEw0qnwY699>$ctB zXf)4S^#zo-FPvSozuu3v>+5H=zdV>9T0Fd-QstyDRu&Ab^gsz{)i+#F9D(~tzjdMV z`W@o!FN6ADtF>b7`pM>H;s4Nd5sO~NK@3*#?zGBq>kn_6$9=G(X}((AG47l5Mf379 z)t6jpas+QE7@{mwF4pU5upI#8Uhd1=)Gfax>g(&7Cc%4G8;`Zs{yo09umemiES6i1 z*#X$Q$Az?=E`OAecv?D;i=d>mFg1P0VgmA_A|kyyPmlZ~BO~yBp7$4T74xJunw?UQ zzZHKk9c_w%11MxuN(u_VE+%5@C2x(P{(hI;g*}k1-55LR(yIi#g)*go$wf%!_V)I^ zYMzN&TBd6GId8r?B0FQVbC(NBHv;REF)C%{2<dj58STpFi*TZ%8>og3uMDjV3F8 z2zPZgH8eI!=6?B43LUw?*;v=y=Gg|=FkNOH+hiX9%IVi-RILvs#|FlyW!6pxg9qVI z@Ss6vl9*O)b7S9@jJuEP^g!-Z@377?o7mN0AnH~%t~DwZWGj4#y`JuGD72mn|NQy0 z#3NEqKIKiF8e^_ioozFZJ@S${s~*@g++V)R%*~P8RynDT2pA^wW2&b7%LNJ`D_7?D zE98YrdS7Zif4oP1ux?wKuQi{0d43de2mt_SM1Yc>YP3y{-+tMI1*+q6jQ6$&8=Et% z*=9pXebH3MI=$hJqq+Y*mWCCLQHWYf4hBHerhYw|B@!O??)k919Tx|u%W}Clkl54V zY&|oO=wR)0k=R!k7x3vacjj+RSB27sU2l&}MPV|mS?RmKJoyl|&2tk|TX)G&EaasQ z1t7ducZ1)01P_BvtHbbLo=^++7rk%=XK8!xi+Fjx#%p`_TC~k7W+_nu0!INaE_8N~kwCf9mRHX3%P) zKPi8JjI9Q3QT%j7OTK-&WN5qk^0WX3KQdGKvbni=Y0&?igy&u7BL?rZ&ajxP4+0=M z+4K^pL|w~!7SOYQi33>)yu{6~_>(}n`7;?B$VnM59l+nm)SKmi@h+m}_Z*;A6Q%<+ z6CTS}pBI_)TO@R9jr|?*{y8Onj`G9ukl()vnT;GA9LnF)v>mOr>j-#z&7GB9?(q`8 z$3p?~r(|I|eEb4A!odB_r2oJYMG8ei(&4ob{|eU3&;f-)XVkK%)tbrT}Y?X5D>qYY$`#-M)f|zYfHAnzRWD5&(cc-uTS65$F zF}z-GKDlm6*A5m@SH7vNJxC$|@L5bqWTqr@?Itu(G~{3aKt1*QG9o(C_d=yjCw*ZU z;Obz(k5mFf4kIbg8WPO^<=fY$Y7Z$2Yv7Exs&#EBJcvqVVjYR>S#(J>?(}XttM&Ye z5k%Yq%O5&(I?gvo3k>me?l+0XUCjYrXD$~;~cjMF_I3J0Ro zO{~n$&#nN_ll*DDJaGpdBpjT-prFs1*R7+YV`zD~wXkruB{#d$w{I^yRXgC^@!M{0 zh4b@)>VGEe!LgxgjOXGoLe9nI<&p^#(Qwag@Xgk1t%k-%CnU5`&;nc*CWeN}y1D=; zI%w88^5=5+YZG2jusSgCcyp3z3hL^E_gv3?p)zuFb8~8X#^$U+1FY0rx6j8raq&X> zBRYBAZ+IMR_`4bX0#TcFtCs&_0%cOgq z`#WYii~i}Qi7#(xXyD-Hrim@e+RO@l->y-qmpiWWCv z;AdxMEf<>5fQRejC9n@rN==PIF0F?5v^OMCO)Y*!Q|kjEp=#VnOU(U#e){pkWwR=% zvO%J*Jpfl8oWH-`zxV??}J+1bfSNuDqFc;+AbY){I{-n@D9 z@&d*Ga>YDURo@T-&X`{yI*5sGZf;GDjbH;IsN%$+`Cf)KH`-vggDf)z^8X5j+e+$- zUG=L(S2_MnO?5p4l5s<4%l123%cTzZ4EnElG7T?v3fp*Sf1H zD__&e{xH_XKOZ5yuOM}tvR3XXFCC+O&Xuk3X4YHudg^oh)eLSWkK=|)?TR|Z zu;YuNa{sJj%!0$RGW{qF*dOU27db$9972T1(e;ana_ov?`U|k&Xn&*3fJ(!5&X=>OU8ET@b{X6;p(^dapCTz9wz^ixUXR-PCPt)jJyWP86 zht-CMxIsTF%fUWk2X~W4Xosa$&%NU`UiZa#XrSZg`PMVclh|)UaLrraI3m`vK2?As zX$VwxS^G({b=yLfYe+-u-(wLFM<7;KWer1B%e4*e%I%)dV8@4ct5GPVmltdsT?coC zc7shO1g?qs_$senu^RE?l{c6$mDBVyR9Ic|=*Y|4yga!kKe%hj+f+zq*DIeElS>_Lo=Okyl&E|T)lmL`4tr= zJA-d$Pndj3M8n_l>fPCZg_EesN#eFO2QJ>{2jR|v0o$D-Wt25d4&xI-e*V{}sHj+) z?|-izIPLcwkkCp?iZrX;Wd8W0G9{4wngFGlY#t5O)%Lcw=Xo;xIq7_K-~IjlM-n(c zGcr~*HoAh^I1M)Ips=7i^wsObIShdBX}vop2B-2Xi}T5HQj&U7o+vMmWUj(U%DIx3 zmZgLAFt(iZ#r1V*Y4J+)-YwYcdC9}@r@5g43|~vd#)tr$o1?^9^I29lwl2C;hlLBn zq4;ufS=p1d_Vx*r`9}RchW4jV(F0z0YvfG2L$hG-pZ? z@S_sF;IQHGXR+2Clp^;kUL7siAJi1CuC9uR_=|-Rrao;)r+ccasHqjo_vg!|#wEmn zH`<|5nJw2)`L0e*^u^>Xqf)=Gsi7e*KHhS<;oL#}FxhY>UoIWQ-jl^v^FLSbPKb!o zyYRmnJv|dPfmW1{ocNt{TUZeB5HfyRzDEqeV$U` z7kY!E#nvS^3#eEeq+q2Ep-*2{{EgOLfy9^6f4IY3zTqSatZdQ-+(1>zza+a-C{CKD_-PCJWQvF{oK(j}+%6_? zW@81^hf*N=YCVG8ikL=P$Nd8X_dycT-%B;sv=o`0jvC-laCw+--^Ce1tq%;y1vnx- zZb!?#CphTtY8v(0@F$bP)oF~foby$phPcg6(ZrP(Sev+93{bo@u3tYq1X#Df$v7cOg6bnfr6!{zex6>(~9Hj>+)9uCD` zANrSu4iop%N=pm5Z8z^?1{BG8IVU_<5+mP}}WC<;uv&fX4B<3}sLAdT~wL1JOYLOA;7m8wP#-Vt6oJ zIF(nf+LrPo;oX!mA+MXk$YL!tg>(YPM&n+ZMvDs)(BSSu>#`ME&R!xX#hn@v!3at( zZsXg$4yLJmNmkWs3MwjeJAK(X;tr?k$2NOI(%o-3TeXR?u)gU386M+*q~&iXDH59w zVQ9PCL}5@;nVVnH7Z>m6Czn%eb(7K8*H^T^q&w*y&5?*!uUYIpr8Bj#0Kvr5exs|= zem9NR{wCb|fsP&v$dgHri-@>ed-YUPP{4Zo_WG!LY(5=nJz%73gg>ILkBHmtsL^#S za!7*7+1bYCFPOESGQ7w{ZFh-;z=mlw85FK&5VJY_ZRdNkS#7z$zP_#tT#HxO6qT9KV6`W4hbpSL^+T^K%;utz<7D=A*bOwSpl| z>48lKi=81GT&Z*II|9q~o~p_<*wGAb4OLaQ5w@?|yt=;_p`ZWxQ=nXkTlZHhASF0g zMVj(91I^7&%inZ7{!}9%a#V!A2)SyRa{J91R)&Ijpx(}q&wTWTKYoOdpKpDH-^}lN zaZKsN%4iRl%aN3H_tf1Hl!shwd^p!vMC9+GtdM^|D9N{coA03tnk5$TT#=BJFqazG zQ_Z)nuU`u$DL;<8+0z5 z;YG){9~^FulkCl2M{piK+!!r2S}8FQ99Y=gRFacBJ<|4gK=IvTbajq7PAoGVM`YL3 z`Dk-8f!z|}Gn0@|P>3v-^c|^u$q>>}G!M2nj+W@iSjWo=Cxn`guEyi)Lsa?Thl`ud zx>llAI|qON7HXRN=X6X?a?fkE1`(8F9c_&44CnS!m6w&l9{gFIz?bKaxVShPD3ql% zs;~uJ4+#jqUz)ZA*Vev5Mqn++N!&m_@O^{Ti^b(zdTHdaHCpiH%M)z>_3BXrgF4D& zl3MEpu?Cb2@G&AIV`H?TI1mdV^3{6g{L`V~#s(4L<|psR-z}1&<9F9bnUTsz*t?{h zVtwY6l5t{_Wv(aV>)(H+b>pODt;&4|+wA&Oo%${(BjdZ{?HNi+bEx$ATDDwVHCdS9 zow`^=oC6M@p7-Vm2nf&~KkSHp7GV}A1CH+ZrPlZv`_KPlWa?|A|=_(T=OHXfKuq!T28%1rKd#Piu?yMcZ`(@A?d!nYZSQ1rSQQsNWudXmR>Zm(f#5^TMW2&8^XODy_nh~}C3i6Sb`^;l%GK^$Nn7*0iT zGovDL9y~vGtrsDuWhm5yCum_|;n&UD%uI@y4lpk)!Ck^6+(TV$`;d=<8LHxZeL(tmyQ%64cGvsZ5qmZ<=<@Vp|s;^Kh z3kxmy`5QatW0L1yaU_;3b*Dr{M~luT$2^b7mPoSR`Pn>DS-F~*ceG{dG8UOTz>lW6 zvA({)kMS7&21Kne;=N)BA;)bfDqIVCQ2Qv9Mj!cBTx>jESdh|?LM|>Ux?;Cziin7d zCvn*PR_QK=_yrD28+?o^1pIz{r==QFn&dRspt!@&M1K`1H)zeG!%Ip60sC>P0hX=1C~JA8$9dJ;=`B?T+_vHbnW6 z;&J};!FLF@Ks*EP)RbMK+~cXrA`drfkI^Ys&6Z03{MnT9GIo}Sg ztH#B}8A+x1{|lz^xSCvBWOMDy5UqCpw-#DLh`ZZ5W#!V$Mr*6`^4h1TdCADU#4?F_ ztYgV3QGNu*#)?iwm~-0t1%_;yn40SA@6WzLN4}|iZ3!Vs;jvS59u`c|K1^62^eD84 z3%9x1H5YYLy9UrMa?P)@)~lp?L+hjV_7yE5MAHqH9;&LNQ*JJzl8GuDH{0GaRVGrN z;V(o?KgsNGYWlg@mE>~V^OTj9RbIYSGJy>ZNoZ!sC-IMtiY`$c>7z;c&=xuA!raCo z9^`yF-^^JzP!~-!W$8z%P-J=T$~+kO*crV_ujxD zF8k@w3_Ul5Vu0JaD}@VOHBPf9o)jH1^s(Q+m@2EA6Pa&`P6-ajlrSll+)st~mHDQ> zf{mZlRrGfY_E3Iz9wgH0rMaX;{Ft5fzn_wiAJYoVhd(u3s>#UQyMOv|YIf~9p(TGkkdNwi=YfX%WRK6Nr%*e}A zdxYw~nPWv9l3kd}kZ}5;a%H0(E^A&+j^obkr#de;1zVhiX7~FS0u|SO{B3;Q*VpfG zCGYTDCD>5Q*}LZm%P9C5i=2WI#Z$aUD;x*v!+4=4MPLdo4UK=Zu9}L9h@A6FP6IncKQe|-xJDe`*f16q0(38vf+GSo5(GLc_NwE@vmQ&XFEccY-8XY1(f4061feNzVB zRdR8Dew~$)Oy|?n6ixZmZ$kXVRV|dlW)fywQ*{qw|qWYcpM0E>H!e~-v9Dk=&k z1`XkpPk)H0sHoiIe%;*srm?ZHnzI^K98=((Kyn9y8r$46NmQ(mFn)gi2udgE1kS;6 zm)&&XatF0JUA=;@1sU0)%i~2wkUW6&tgt|nh1~NuAtfX0?~}8$<>l%3POKKuV_YW3 zUNM(qD1P@&AGS@Jwy3IQYfL5%nz&|NRWVjtsPt{7cuWaV{ZA-pyyN5J{ivh~2?=4J zAm;wcZC6}umBeM2*1MWF)YSH3vq|6a6>$iS9hI82wDhi8{jXoYg4x(?ohJ=3Fjh@| z%|(@}NVKIT*MFEVPb?jX{MN(ha?^#1DjOCb`)QO{oBq7mq}+O6W}rgW*Z;mT>4jHF zN_PWPW1N(>L(IuRRu*{w=x5p{ZwKau$AqI|VzldRoqA{ARM=Ojjg1u+qP^m7N#t?5 zgFvk>()`}#=3Nu5LFO1U*(lspugE-uFBLf3FZiU>3 zuZW9_3p1RNFD8-SS1A0ftmSV4AHk**7a#vjwOVkRui@L5zon-CqPz@CVf!z$E0dJ$ z)GtkV#>nU=l>Y48F10?nJ~eEnsNs_HeJEYr*IS5|mKN|COziB#0|SI>9MRfs-CbQ- zGSq<{+ANk+&wYe^oU5J5UI~$tlOud4VtMr+wBXTDFYmJ@TBhaxDMVjrY^s}kdQz{ zK`F*Ig2Q2#o0B7>QyLT$v|_8~?(S|Zx)NKgr&itv*SrHpDJ3nfBkGhtZ(?jhHHeR3 zQoiePRv^FBg^wTac6yjJJ4ZGej2_q7ZXAm7>66dFW`9d%xvakaM(EyhPR`e_^xE3m z^771XXl^MqJd1{gF$~(2)(^1fZerlQ(|?zdo67-Uh8x;NwRzh^JiHSIUXPXC1@rOv zcWdvYV_&99r|^VNeEy7n=S|z>WKgHl%0$&(B#m8EludLzHXa_#1QSKt4Z=PH$as_01NK(wXD$w`@1kM8%==`Ylhr2Z4w@1@!LX?|UopZTG=V@M^7@|^d)~uh>re0xo z-(Rq}xEP@8>z-fPRj)Qj;e!Wu8uGZ|SHX;`*J65eP2a#Ugm9tvy9e*Au`uEn5HMBZ z)PHvL5&RNThlhunE8^+BOw7#eU)5uFnXV&++6}T~xY9rvMnx;>@HYZDgYV|5=#yCe zPnMvsZ#G@*mRV#eCbkN5y&*hrZ+}ef@9%3~t;h42$Lo_}Ja?AAc^iT1xTL)O=ue7t zcK__`9J|Ftna!|NTUG|^gqb`>N$N4o8h)Q$l{7UaGyIxo57yWv4iB{se2c=v<#cs- zOBOmP`h_&A9jg@S3Od!<0S8Xv@~HpzMW&ir8Z%6!%&;@wxIe2vy@6kW3?1t(ug9fE z2r;+cgf+2cOE3`+50BPd3l$ZG`N+2&NCXMDTS&|lQdCkB5`L2aD}y$<-fj&%UYolQ z=}ZJ!K??f{IIICYyBJZQMDd~=5fk?iR)J++;Z>7ib+!#7-Lc#$>cwL!hQ7dCZNl08 z!6`2}Ocy0YCBEe4Roe`A0Vlwcc#Okp(4y^ea&p3&D0HB1L&WmUGKJ?CeXe#=Hx5a$ zi0k>W)hN2R46{sbUWq90wb~}Xo+UCmxyVTBJ{m&I>?|R_J#Y3DxSu{goG-8@u)HKF zk8C$j;Pb=Fdb%;T7)kd}DqmUH+U|eUew57PRQ>H6pQ7YoXJd}@*9Q#pw6kQM|$CZcv+jrZSb#Di9!it!+jSLOj4avOSEgsuF-G5VIInFa=v%b~v zTuxE`IUSwb;rdB{0bP82&I8c|W(TkgXf3A;hjWa2AZVJJ&<$KMQ5YToNJsof&VPw?DBk{eNC!6K z>c7|AGr0{P23~Wk{MzULisVp3{r`W>|Ep^gYVe>Z`-%KORPJejKBb>%i4*VR@Jb;S zEzKx0oXFPsvSrbCr;OA%0VPa90;zt@OdL6oe0l5I$JJG_4}4D&d_iw%+EicIEu59p zkGB{Q@HN_x?b1d!JdHH4i;+rxATvV!l$^!tG0vS&jc+5`9$UNN7e8**zvI;iC9T8> zZfiZ;=W3o5CPi(w6{;ulYgen}t;9Y|lFs+tXkUE*Ef6Z>>ZxmhewJCEG+)+`wG^tq zU|R1x{axiX!PMRrayj#m_&@H&`HOdFZVTQ@#r2%4uIKAN8i0}{oZdS-H%Fj(enlz2 z_FL+szrX17M~~#0nIV!4H_+*og@(qt*G()Kne+qMq|Xi)=cpWuRo8$1Ov4};LTlZ8 zWJ&M-^nZN@${U!3w~%l~al@Uf4$#;DA~gI}lP;n6@%8I{p^dN44%gHb6!!l%dVTyU zfb{eaZWtO3cXn-lZtyUZ_`J@Eu}rXhRe$W@!P}4>7ADwUX_XumrT8HREix)QW~fePW^En(m?By;&BhYzx%C`;e$cAnEg2{aGCqjkYbgI zLQSr=G^QvqdcbibS;$b+`&W|nT9SA6ps5Y-JY%4t4xN~s(5iDM!pFaXRA`#}q7$X% z<;5o^?#~-%c)1;cxp1o5$@Fkt6rwt`?C9Op{I70)jI?{tz<%b)c z%eJS1k4PGdfxlT+Z^~KA$K$oAO|DPY?`>*&O&J4&iupd0||0ydF1Nr)ZQp)jk-q=h-B7`_tYwq{L z;sOevnp%JiH6s%fc^U(46sOt9b}$bFzTI=E<>RwoyhyyJJCa?hC{99Io%1?YOA1ubgycWp8`6Esyr&Ap)9Oh zzTfslxj3bvp?f35!)r4Z68myss=g-SYpW(k4;EUcs8Br|H9cDQlp z7hQGkNuv73#>P(4$vuThG`{loOH!>pPrcwuYS;Qb4HGFWyg1w6D1hfDB7)%YJsbi8 zsyay`q9h`ZjhBvp|2FyZHc!5XdgTOA7b~=a6k6znFMBd$I)MA~e}jb;f;luqLKFmD z`}dh9-{~5M=Vy}kKr-%Q?6aFs#A?fY$;zvE--zmNumBVs$O56ke`h<^n=b5Mj)Q}< z*q!i*;89F$>_Cljk?7Qv>Cjg!I@nH>TE!BTg+SU{kpgoIEKapzoA%kO6Qm(?Mr{aHaG;zQ&~7>;3gazT3j+26|x*hPV6?SiDy3m*)o zhzwrWV`}baZp+_Nt%{3ja%r5LwlZu80)m3LJZ5IYDH`agLy^B)t#zKBe4_#g!l=Le zmw?@(P-QW{sEGY2h9hfZ%%HAKt<=6QK7I@Uj~kfQbzs%=C<@tJmm4p9JLuZE zxmzTXcns!zI!ivSj#oPoB=Jwz>#dCquHC&8D42$i$jBd;+3Qdv#)N>I0$@5w!XLAx4|qwg!~3>+N5B4$`JH-C?YfFh!~vodoeGv9nDfnB*! z0|mj1VfMIm#pqNzxqcl@D)objQJb|CDc?Rs> zDcaF;uC9jBGcPZXf!pPiFE1qTzCvCFJJvexY@yvif9bsbxES}7#Avm-y0I}$vE$ic zL1^eb(%IX0vDhspv%DmIr30!5SXpR~7Y4Vc<*9MVUN?!yFx+2Rc(}0e@L@O+O{mB( zt!mp%Gcz*~MBmo^?CPpfyVd>>rUT&lNZ4*8p^1r+F)>`WBR#`$aWgX^nWgxHQ?(b4 zMOJ`P$jQl-mzBQ~s(yrvJ6UNZUO@=sYkSm}eB{TBbyE|`cXmsU)p@*P6%92x=cBI4 z;3Q5qd^`U)vu-X}_mbq4KNdn8?;}Ck;UN^Rt*T0fjm8I*f)huG2q8-=D+~R)Rx7uz z)m8m#m&?iA++0}&1+%yB-oKsZPrGa0om{8?rWs2;&t_g^!|s|VEcBXcOhz^~F-b{Q zM#i4rUZ9sI&sJA|e*D=ILKcfo3EswiU~4U=%2=O0BSJQ(UHH;_g@W*Ci0mHDFG(LJ z)2o8aVt?@8t#q40`y3E(M^UjHEj%wT|8EnuHFN1>9FcdjpA$kAuoItE=Z<4zXaYndw0oJNU@#+ynBkdx)Q)F2u10a}%Ot zV5pkm#z{PN&W;ZeNL7g3L-7= z0YaTO6ueaI-9LI{N`{Oe)RKoM0u{|`bK*9N=fFVO_fbpbiiuk@Y3ZIix)kjo+{%o7diPKD;uD+jg>cTbB(>&ogc}$Ap>NPnz zFS)th5{F0fmAxKBgik9gH^iuke-tFO+nOqjB-7!JqhZcVtUr1BC<;Mp!f6=O2Fg2|M71T5V^CSFS zqz~AGER6R=Z;g!)*p|$%E-yX_@CymuKp5j}F)$7a)GI_D8Rmb>e3W@VWbFl>EfULZ zRACz}qeABNERXjG1R^-iw}ycp0KMM5uB^;H8w{0jC$Ji+p~fQGAmFNqsmb_mt^G?`+Ix+owe8-1q}_A zRPnOiTuS}?sN}9UVi7c`pECH>cq-XQA=g z*+os`HT(lb9cu&D*S`-JX~!^}_o_#~(&9i8clPL_<1w|mLX&vh&T5@6PT@8PdW3wt z{F}E({fx$dX_SStF1%Y>R21M@DKRl61qDKG+l`o&|Iq^2f0m&h80=lxt$gi^g>kz| z7@dpmc~Q>SJQ$us_l1tn@2oUSzuCtO_@c$yOd3{F5^mehzT+PvYF+h;(@RT1fve81 z>}YThR=n2j>b$}%9{Y)9?Zs}@s#&ZRRC?TwOyzD#g6|nwS)$o!o@r^iqQ?H|rRfQ0 z?~5OAPGLBmAKykI_u3T|*50hr(wZIjNd21`(LYz$W45{8!(UxR?&0C7s;b^bPESvf z?OxxXBIJE9rMsEkFN?J6bc+PfTaYgZrR?5%I6WUTjL1dyL@7}%ZA1V%*rnovCGjl{M>EdKK?Re$D0M7O6 z;h9x$6l@h_-uA3>ltw?-X22QJsIga67dC<_>GcLg!UC4lRg#e&CrqQz5tWnx_RSR5 zS8ZoHOXcO)^lm-dhbOJF(yx2TVP)cEs_slMAgnmmCre0aDB<*aL;W2dZF*ehSoB#u zp&MRZhQ;JntgHfvjLg>r;^#C^+j@&z2I(5ZWgJJoJ^gO!tDvkL7hfSR-F+{xnIei2Lpqwe#1#MwhS~hla)Gm6FQ-|xL>6{%*@JSdMqK| zZy%oU*?w}pq_ng+{ogZ)4bNa)e6_QIA_Z(6o<2T8HjBa!1Djb%!;;LE14vcLag9xl z_u8v+b60eWmk*P;k~kes4~9R1k{yT$Ha516vGVb1^EIw2={>8e#L!R|x3fbt@K3h*y*IpP}9@WZqAI%UDn;?+IBdB~VmKX6#EIT+8@8PIdVlC&{_KK5z<@g> zkp~96G)SDWXtJ29%*`(1bslI9z;g4}4HFU1k20j|%xgEHpfD&&BYokb`sVtT^Y+20 zjbZXjD^4n^ma(z+Nw<+H?z2qx{DSm4^RtTAH!*#Ec*3QGp_G-s9XN>kSIPkA-x&XX z!^a0C)W0HcBHJ*0Ox7H|=ScLh-@s=xbDXYsgUV)OV}nIF@|<^`6rSRsjs;K#Ih3vZ zDx&s)?YjK$+fe}$jY>zW#O&-fWO#OVmf7gs<VwFHgC*l;>zwv} z|4jQC(IQ<{gB8=oZ8tk0xaE&_V;=*{B{4Gc&)g21#l)*Fn?EV}$|yfNVxF^GjA+O< z-t+`EJLG_{)Ozqvu}ETMtnGQM*%x|j|I5)jquF1#85jrv8^t2xd8JePC@d-_Asga~ z?I%q*gM>0_!Ckb~aM{+~-Qda*+Px(Gm{&?sl9ipxVQb2(4~r2Mxh)cKC!lkA=wCw- zC!)FG+>v!5A;C3xCKBv1OIk-wqF(N6hRp{w1JLyTFSIZoCFq))a~rfr8gxY8|L!e9 z9$r^>F;cAOPgA2Wel=WaV8{HjHPPE*vc@U+08Bb=`^)@831$qw=@}Ur*+rH{rbZ@X zn<2cHq5G)r94k#SiUoDehY{*kCe4#vPocS2&DJI*hAvWi&>@2Wp(0^DWqaq_#sP5fFk9& z)16)QP!9ADe0-Gh8S?H@>>n+al$C<;xy16Lloah0h15<@4-))T#pfeVo>KnnYfyvN zPS340^#eGQbbx+LpHr^IxwGp0_cA}?iOqjdvUk>tD*LhzA8Mf2!oLq8vC~kVye6#NZO3tsZ@$}AtM2WxyCDc+QrubO4OD{5j|b7M?(@g<--<|M#sz4|~yWP5ReTrRVOhWwzAe}0^&NXKQrO0KyWqwOBYZ5nNRM?f1jy)Y#SxVnAgetr4g;XP5K*p3;Vkbv3f zJ$>e$BVlSBRK8AMlF;LfyB7~xOFQT1YlDIVqR(X-J?zR-evOPcUYs8E(J+-C(*~1p z9WIf06l&EiE{CPR(mDa{<_qWjccPmekufy6xpK|-Pf3r_=ei4n{BK80wz6a|o1SDa z|CxDzcv+*&_Ry`!#Kh#<&#|1f2!?&-fj?{!yG#_YqwDW9aE?j$!Q+ywRaYn&r{?-_ z1rP7`&h1}oJw3uYGdgriU%^QzAtr|E2??`9bU{5ajd1g1#0g%$4-F0uh8y%Wm5*=V zF_q-^o>4c+M8g)(PffNtI$2l-`}+!Va^anPyf@d^PDxks@tF-Jz84n*86-`b4Dvni zZt#hKhGcP*&9Udn!-rAp-z~dOPk2KDNR`4OC|;|p8*SD)5q&KX7ZU@|;sacK%C?e? zvB7@R#IP_~pag>mhP^%(0xu(##PwWt_so0efgtI91Ramf76hUV&o8;3J;Ojg2YtSp zIym}u`vDy-tqHrXQ4Oe~X3GNu?;>VXWhZSBRD$h2I!!)UBwhMUdb(Qc(={m_bJ#1k zoSow?C9knuu3vACp`mFBChl~iJM&_`b0+`>nIsp^{&!_D&$WqPzGq~oy2+#DAbvM+ z=K)oj>2P6M?fKM&3qU-kCMKREbL5t1#F(%>iTe8shY&@q?@ZTSfoH3`x3?IiMsNZ| z#?z*0MiL0IiH@p8=Q|ot=V8%S$t5BDEUa-S%61_xEiPP*!o< z9&dlfV_xb$Ywl~$`}SpfxtB~dAY^&}#H;OdNMM82o?%@O>JOtN9ah#s zaDkplBx|Utfe_YtyC?Nc14I1xw_2dy*!na9Y06j;ZHb)bCLnAx^=3`g(Yb!I#j{?J z3Xdo?noM?=;$cd zOEB2MZZ66ao>izP-&tShbY_*D46HT49&@^LA%q<8IfT~J0IHaZs5{u-FDYRn;l7i^ z=zbnd)C{OoM1SJx!4|vqya)zH=fp%}f3`HQ$6=yK%n;y|H2ql>?96kKQJPiGlAM8p zq*YZIBro5ELYV7+fV6HQ?xJx4c}0sn6Zjkq^z^e)HZ8$;=@OYjLPDF<9$%{S+J_ws z-Z*_azbpAJZzlwwwLMihWctx_Tz^4wY~%4mnA^D_j5RSnel;vz_z}Gdqg^}BU2$Yax!T-LZQ8Ng2{&g`yU3a{P@vsd=kU;-{PBekskA>>ooNy#FMm=32%a0|nTo3V z?Q2_P#Jn6YNVhlIKBV1Om(dYlCAolkH%Zg@gxi{cp`-q#vN1|33XURE+Z zlg)rF^L*ugBUyfrbhxwAymFioKj{7=0%aAIw_AU=w`ZDaYiqNDNiKfi9PE#`LZ855 z`&;xmZY?)M-LY)Lvs1}!t#9y~eX)qgxL(G_m3v&S;~}8XZOroO1dbM_UIh(>S0Mk+ z$oMrot9zn~&4NETIXU_BCn+Q2-;rKDu$S&Fb%!`&H!i$K`2qltc=R(6a6$_FJ7=}{ z?3kGNJQF~GphPaeNKfH)(Na^pgTNlH%M?e5>AOs|)!QVi7)3e-gkCTAi8E0sMI8XLAIVRDd9BztNvsyR$?1!YDtC0zv}C zi#)C_hNq{spWKs3Vv7<=*9137mJs>qu2G`SY~Ri6H$CfiL-pY$Uue6NF$k=O$$*KvsJgQulHUU&75f-%F92m{b-b?ol`LeP$ z@~#vPmkPvv=DbTjJD)4>VCk!(rV8ynxAXojq^zu*-F(dUq%RY$OQ4CMe74kNwE^hk zG7=XDU_^csh`TjjbF`n^Pl#y*RYNB=_2EO;6;3FC7SqMti_L)^_7{8m#`+DH7o2vh zk;&1-1O#^xAS53DU3K36+X#zemyXUOj*AN5|B2dHCyEwjCdgYOqv3qz?)E#Eg_>91 zJm^GBW`1`9iAYGa<>W|l0~4xy`h>?7<^Ywzdu7Y$*KBOO0z#AxP^@cnU1IwSvf$hO z^1DyOWnb&Ou9WGX z_=B)NqMc#E0My&s`hH+JEQ!;O)8%(}_ZE6Y+io(;3a~_|AMV8$y321d@iffBoCYWi zkSg7YJj{Qs`D`U5f@G*Qtyt~K0m3JzpxS~7%*xR_y;Odh#|{1i2GWEv`klonK;eHo z2iG}lX@LRKwwmwB{`O<5vqN*9l;>`0&Ig+YdU`gy1GzRv-G+5-@mvlTuC4;2qAyq` z1&|-3RZCz6b=Wvxf(S+>rDz5QhP$}*)b#X`Hs^ge_xedAa{nn1{&f$)A6<pd}T|`MtK8}(}rmeK97W8 zqS9%7v>(T}=bmd-LT816w0gvJ0~*~iB>CY3zXk)<$$~hYX+9KWnB7&>)l&|5O~*So zp|wQb8W`X`ctBTo0p}Mogs5viyLanacp<$&*LY%*#}@h2xl#A=Gu4Vnk?-Z@ice_Z z-i4~vzP}W3DLp*I#5@uZAYViEv@)H8mP(Lh9PWOh#%*{ecGq~EcIVd`hhfY|kWtGL zxCPVSEw$kgwDQ3J(nb3*uKh1nG$s*`#QGZa4M6W9o+GibIXTp(37ExJR>^VLy8gzqSO#s=Qxd77y1Kf;;4bJl{GR`UAz4l- zD?To6NPWgD9;_?d+Tcp%H?^-`fc)ahJnL{kE!}YaBt~4lICFJV7JMTN;v${yFia%l`rRe%D(g3twxL70HcP;yfwkuE` zI&17-m6eo0oN@!9GRp1|PA&O_Ab)>cqT%7;A@Aq6@AG;zc-_QoZf-&Qu_B*+69a!D zD@asVH!X4X0fF(p=I;folPK3>m4N|%aI7{sU##{tjvbcjT==~9#k5_sI3k~=3F})n zq{#1(RaCrV{pr%t);rF>Uf$QdN#Kx@H<0c8U*orhEE+hKnikxMr}?LoDonRPk-y zCd-maS@W#EO|l002m)dH;}sjhL>{7Wh?iVofEcIkH%uTH1O3ZON~)a~OrU(Uzcj$w zjQkcLd1Mo7J{t22 zLkF55GG>doj-cO2w@Vn}x*U7jMoYs$c{#@pJpi1xm*~O?RaWRzr<|OUlB{gl{Nlob znAV=-0zKPZguZ~@=@s#gzf@~iS9GmKa8}p*3plK{x7Utcr=XxfcWyRgi(kK?(}H$2 ze3K8r=1dBTaBgd-1;Gh4gpJ4y;L96Wg$d{WVwtO}t02o}aVARjH^f5P6;{7|CQOWJ(+(j2$?wJbeAm$0JmxPw0qsyu6y)DttBATQTn5?QlxyXPiexOxG|4 z6(m6&yK8OwJo=v#L46Aq&S7OmCYFQJs%fC;rAl+S=Og zODCI+SFqCTWd70o_FZm+mWe4`K$eM>73kMUS~+PjT4B%5`G*Gbxvrcl0DT3*O#mtU z{rqII@y9c(G!=E@iBc9z$&}ob2Klo>dDDRYTKS=d=7!@8Jv8}XQi`Fl$&Rad% z%K=rmSXy-VI6ZRDhGN%op#%@v7*D9eBpjLgS{Gl<30{jip-JAsEOo!wTUsc8>)`MR zL6)oD|9Kc2t~Tv#2zZS9$ThhqmzS64ghQ6*7a3Lm06(UfuOu3SbL%?tglcV01VMiB z2AA~7tt6I|$l(TJATM6LfaBjFWZ-v}o~zlbn3m4Yw(@V^=;^65n3tbNXp0pc_=6ol zx0smM-Su?9c;&NC?;pS$ucJ01!ziVO-R*z=ys|l!u(d5qtS%ifL*DI2M0M)M3`#$z zduEW&a`8bJqUj@=uOU*ZbJ}4Bf8x2vySfgKi|5XcHh{nyudysj9Q738Lq%>x@K0=9 z+=MR^-=a-!~Wqe&Ywn4{YCm_(5mzS_PD9|2k zhc%JExQ%H2`X#ZOY}TLpTEo>I@wqo>ogrPL!BusU|2+l$I)reOw(%DvrPkft=8>*O0Cid%cChZ=`RLPIqsw3weFh;Lr8vB7G)ABa!z~wvJ;gt z5!$@1w9_*N?J)Ae=ex2Cwbo8y)9g)iJ78omkZ(ZuZPHAhDZGS0X37k zc@4w)!N0rb=XYxb6>K@3g=mW zWHekK`8P%EXuk&S1Nx1K7V``=p^r@qkC)dvvfQJ;ee1DXV-Ic97p4-{?HM0_ z8cIV-eck6r8^hmcCMIt9+z?FBfQ9m70fA(icNE#1uI>iy9XLzLUE%b07?6&yBu5=N zGE&)<#M%4KjyHJVpSoYqi|mPy-aYNOLrf5+2fZI22VE+_{?E+_QGz5e;lE3$5nkCQJe(-=A7TDvF>$ zjTzb!YkXeBQ96E~X<-36M{I0t*Q{facEt08Ld_}|OjtVJ(iS&NU8@_Vw%6+6|F!kN-?yUD1J}4Vt>uCHAiI7MFt*)7sxok~?usFv9;C zjuSj(=XC#PvRd1drH*WFcplHb_2@O(Yi`ZD`^1n3~W z5&fK%kts4|&=uV4;H94jCug}`8RX`d=(lZsK;aLUN`mtkL7W83*)Q^!x1gTh92`l& zeqh)In|WCY3mcn-UZ0Tq5?RtXc&Yqv`-?|2ykKVT?=x9=(WkfgM-{`x4S)*J6BGR?VQ@Z3O!G~{ zq+As@n%O~eRk*UX#S3t&@-PY*3}A>m*Bhh_6FE$vL*|#3Vxl91@wl=S={gxStIrSD zEEg7bPqA?gL_AFOUmH;wSL#=dGkIlbAix7Y^i@-D>uphCs?Ju=Xgp8E9!^0CnXOf+$b$aOWu%UH~;>p08OdcxZ2sRSVB;!B|?mRO<#;q zI#|3cRK6F^DD>9YZ{ja4l!J-whSv^orFN!654x|l<+?!jS3IjJrBp0zTGR|N1lH{H z6IC_{P@BU=(~P6zE`ucXfJ*!Ox50|E>@xZR@T$Yc*aEDUvHcZLCrah)dvJ7-amaqh>18I9Y?lxN)~vo{~wJcJ*Bz-z@KjCh=Z-u{tx`WHo88ZpLfJCbjNd;Hhvr%kH5OCaoEq1g-QuCU(EBH*W8yq{X;?^ zj&Y=3?@UZvJ5D__f!!RRSLY}Afmc`nbs7_!j>0e+@cJEmxS3QlxlscHsvT&4(j-*g zjlc)2n`#z%wedSUK!Q-NQvOT9q?V$ipZMcXab2_Fx3o_FxZKfiZ ziOTKImh5Nm0Rw0G=G%)k%LX1-m9sVcWh(Eve{VE&Xg=QJ9DH$c^d`?_)xaPV7y<2K zvrqHNPkN7hIt>e^%j$)wIbmLCl1<>nve`QXpBy34!7MbKB~G16U2_GynhUH`rWe%* z=#*qvn(nQ2p{55O%s3Yc?=>C;|L3sR`sZL#`H!>2jpzTn&XWJ#%dvb#B&tD1UsTwB z|Bjfxwc2JiyBj*roj0m+La8#pbmIv3+eF9#N&~_b^#dOp7EtiSfc3flhn+6WX+2#e zsaxiKN=ZXovW#xS^hT4j>P?Z$gBHG2naAjCai5*T5dJvII=_0{SH-tdE$^fkc4vziwo|5(g#GyxZ8IrKew{N(iqtLXdlbkF zg$giG_3Z;RnWk?gt9bXYfBKJ`gFP0C#Ngn-+}xahv;D>x9BAG~ddf>?lS-iBaQ(Vm zOkoA);UT0Sg1Mg@vOXSyN|2tOxcUlzq|-4Gj$; zfWzRkhlgV$?*_$emP1qhZ{O}tPxBT?HCwq6D=`%Pi~y9Gx6#qVGd|;Q^j6sjgAOJ> z-2U?X)5JvWDV-ZWm67o92eeC-Dz(oaZVTLUD=f01yVfS7JlxvZ8AWfBlMg9U;tT0p zI1!DBFZ7%p&NfT%B%egk>@EHKfK%TcrUAU%n)bLy@aT+%X&0^ql$FA~yhI-J8&P6W znj3ny2m7Mreb8&`>gq!Cs9(A!ub_~~V?&$7ACm!l;WN!@6-mj1o<=e*I9)cU((%*Q zKXf8Dif6Q@)?uf3NBgmLSX%&{YmAApahu^2u%n@*gH1$QJEVI_9CvVFkdBUbPwIh* zDEf`bzP^C3In)Wg-XI79x%~zJs}!uCM8eQlX`j(>IQ~1gXk)s_WAaXCx?n2e`DJmg zPGf9r%;T~uFUm(~w_^+q@vOf%z2m**urUEyGr;26pAN(=b*KIQjQ6a3iVDPS5aXL> zwn`xQkLYI_9)K>ox`o4gU6G)Lht70g|D zUHg&$>Y?M>P!a~gaIZ1TWjFrCcS}p5?0&Ehgmv8TND6KJJq;27-sa((^HIx zgy*v+m-BuOz<3~7<_K}vPe1$=n^<6@xmk(%g*tq0?g4{FEl69U&+7%HwXqPP5MpNM{n);6>=U$J$U~yV zjSy)1iM&1D)&Y70ShL-|yFOSB^0GSIu8;2^{{H^(HRa{yDQ=jtI*S>wL4brkQye-H z=&pVZPJ1E=2{L9<5`kbONy#WF9mZwnQ)j%DqKpT7yHj8kEoU`lHv?ViN-yZ`!?dU!ZFIpOsr84tgNznlv$XgBkC3AsGjTW zmdXXE4-iiKGQ63Y!u))7HMQNpmrdi}x3G88;;YAg-PT%+#?=jbNlV*4y=7*Qxn3ow ztbDZneru%7MWSuFhb zBTYD7p#XC9C2a%~XI!;-LiO9)=(nQ)-KNs1q=z`)WKcyc`?xaX`lhe~pI-CxY;{@k za8C)3YcuRvUu^rcP9LOn)Xm%75<+mcxH0bSEl7yDw0O8DU6>A&Eu0ulx;dFG7a?!p_2S4bjtkK)=ew#PSsss|5wkGutq*#Jiotc}b zxbDn0KjpH&MKu80NYtqK{rQ~-;0z2YCN3fGyEwa_rcEBe_k{lToxYys2Sh~bii*ZY zro-P;C?lGGT1Q1iD${Fs6&87%9~UPkPUOgviCSj>YfeQ@E|L7I%5u6MBy zh$LiWh9p9EHX+F-D_dsv-lMD#vXf9|viGKpki9oq*?aH%+4pz-j{Clk>-Wd?_vQHf z;c$F<&)4fbpXXzmTS!SsaVFquO?;K{dkgmya0mXkZ5_x}lypzdQl%H)2&5De+9otm5?B%ZLcqxuF%GlbvaW8yi~ zQ8M@`%Djew^Ot(m2N4iO{wyzxW^pE8?8!o`4JeRr$KHnF>I{5VXRlvZ*VMGOv_k#? z7Y~;~fPkTynWDyvXIXvEis?z>XMw#wf)N}+B3gSAc?-(RuOX|i*AP)@r6Qn3({z~1 zM+GWu$Vo}@D0%ilxl&ffEeWxq81yR2%GZ3y$6prhk5|9EcTciN0wLw~UI(QOZ|=;u zj3FE8UPHoH^F0_T71lUPYHGT*kAF-xZTw8R*&>3budn~eVD5O}eh-JHtj*TS9!d8^ z)vF5EufvC%)A#SQRb2g;w$ggS#FTUFNrm8+-g)eC*qJkuPvXfs*sFN7v-4|qM}bNN z!}+$0VL^Ob=W%gy2?50UVY00+Bvc0{AsCtwQG4axUvlJd5=~1@EnJk4tgG@kVnsl! z|GFgqxgRN_D#>{LiJ6m zZ!Fn7PavZl?o>lVdFD0bdB^YbrEb9EZD?axS60l8jRPd1lmM+tTlcv#2LegT*}1jr zqeX-o{>}3{-c)-IF9ykry32n0;T|{dv*us5O;6gR=K6oMyh+&;nZa$qMgjr?Ky|{+!2`E0c!ut< zdoD~=^i@~~KvT2q{_2ng?CuVijTn}9EuEWSbl{`FW4)eLV5)Cucn&FAuMG_os^RkW zu$J$Jf2Sdh4L4G#%yUE(h7i#Y&(nD-p2fz;{*1503~EIYB@9BN+)}nwxZcwI%f52* zYg3?d^tS*E9h@rYeo?!I6h2i^`ph6-Gk?m-$#GeGg&_X|jw<@lWHGg?JmqNc2ZslR z&z2GnXx5z5SW)zB=Z+s(ZqKD0QCr^}drpH-KV#jVU$C@;@sINOD0?k#ncHc%B{W_| zSr|nW!az>;NZ!V={!C>B3`*s`{3|)LuU}gyC@oz!wf9gke6tj;z$==Tm6Zr#Hn3l2 zXIB|_9A^Id1qtEH%cRAEWI#48LRpu~)_7uU>C;k#FHW~-(b5Y7xl$5jfAq`w4*ywL7Y|^?qJHv3pzDW-ni;4Ya zVu^@x12hUQp&MagZi3S_qb?e>@f|G8Y$Q*0fT3>dx#~15D%|?r{`>D92LCw!mPl<+ zM^D8aGeFvWP=0{j1TcK}885%k}KRD8pSo!P50O-``q@>uG*g_g{vmb~GUQjeX zbwpiWL4m?^**pA}j8qX#r{Xdl&x=0bAk+130tV;kuniats;Z59I+!`haU(>xd0X#6 zS~Nl<6@`XGUta9EY7@kU|oZzs7Mjn@utO z(&t!PUG?W>;?R8fP{Ta5TOVtS#DF8gRZw16KKyA`1?$0Wd-|y4`st`IEvROfPbvNw z85?7vKVXfIPfFUD7nuL)CzYZ+@Tr;SFAfDBujOd3!J)X5qa)*WqtsF^jGP~+C`d|& zc{nN`1*FnZAp2W0DeXpvcS-(U)Eo_Dd4Q7Q5oy;OnzuA!CdDQtf#3E$?hRnZFi^$RoQYvn z?zn~3tRvtO;$8T)e$hl)iHrXRQizRVJ&7BCNr3w^9+8B%)~@e382yByBm4XJ^{Emm zuE^A9D89}9{tr|#Vda5&`vIH-+>f#TUA!nitllS1kn;9LFe)<|D#Z(GCja~r+ASuU zHUMgMBO~e2V-}?wH=rs}Zg(Nh{1(68LpDt0O(>cqVb$c{XZe&<)p_A9h8++xCYsfU zA}TE%+-`&lSM3GLm-2i(Bp35WcYAwFxlAMLhFEDeKcc9t%*w__BB!pTmVd@Eyjo)> z#8}9=aJvmDwCVBYPgAY@HNS)M2W-Qeo3AqEj;{ABD#$;tvZJfbmG%5v!slRe^Nx|% z{L|wM8TDnZ@l7ECh{8f*V&aBhjD%Bf?QGA^3%`DS*c2ETG=qv1qb8`0z;kf%BaDn6 zlm-K6p0SB4srNkM;UEsw+%`5-pQw1C=YDqrSO5ynzouwjLTG4yTC)`zmTKnG*+oUc zA>ZxIr5_wRbQ=}JT>%0ix>GTp8aMhlQYZyP^h-wn^_a6J-}qrz(tiEAt6=@q50K5> zCMJe}Tn(;U7A4Z@%HaI9G&a5mCXhw@C>7w*wVFWeqLTfhFCwPYyv=}J^ZIongAQVZ zQJKh1Q1_L`KxMR%G)I5`!%SB$1~R{&=)M*s1?uXm*n|^PoiRI{OuvTNFj9He$ zV(WU$LP=>AKih;NswE?%DLvLbe0aPwui=JWQ;J!nu^u%?@QRSG?^}*uLp@+&e6&`A zGcxK7`-Z(_sBe53nFw4neHZ~_m?oG%7p?8lA7q)c- zj*Lu9f-}*4V`5&&S%pQ14$$`mqoN2x);d2wG4c^NGgUd~k-AbOsvZ;Wzl%N)F8ZC* z1B>}IzcxdE3}SM#dn;v55+#CH)PS$9MfcA`1L#_WR#H= zd8VLHQbEl?M;9Fz7nlK_x>tU!?EKZ$qm%oODZ2Z9D$)E|T3EnxXjw2Zy-ChLI6W=Q z&wuBiZ&ReJz7gDtrFVIF03364cR&k{jJ!1*{v@k!cXu~8H}{!+dTHtYuV2@YZ#g+I zxs41B)%l2^;$)>88*vQ|CEg)N5rr&>yJTc5P3u8pqqrx>J9Nm0XY@H@YT)s0q{%HX z(aVj%MnFV?&@kr=S-I=QCM4X$-{v0T7WPcY*3+p|Xc~FiiY7`3v~7R$UvKGl4i1h? z`K0gP(j{_oY8=kOBMN%Tt+Wzk|Hd-MXUQJCEVtAIv*u_Rc_W_${kn4QV!>LTg_<)+(azdd_Oq#6 z9Sy8zrKX0|QCV_UW@b7sg$ogdJP{XvrJ}O;wWqT)CjNfnM^YRCvW>uTxV%#XS<%JZ zF+-}|KgPuoBl>qqJQH%)a_)k@rV0v01!3r3U43o&O-@a1_}wi!MD$o)_(3p)ajk7_ zT>-ED`}cD%A+MURJz2Kj9_@-@sOV-RPbbaEuHJ$#0ux zFHf?Jt>1}Uz1OI;HR$a(2H;16)?oAzCau`(*6t*2R-hAr!IdQynVmWt^_N)PzI6f@ z6Sxy`uQd%1%RhaoR*LaVOUt4qBo;dudv*1W_{REmoa;q>!%2`blq}*iySn;4JvAmi zIww7KePJ0ymCh^0$8xrYW=>9)JRZkwv#t*)5@aK7F&Po4Ed^z$U2cukXjoz8ab@W~ z*FEEmklp93@g+Vp!VqHiX?67%Tf-5JT|5*bBBTP=lV>i}<5nNnqn3J-1s;zVXjiSL znD#*4e;{62NmVrfNaq$Z{onBp@u^FgoX|o=|>Zg(=TV6!gX+GCn@6kHHM?pzhq|G1Zys$kB6-Y4Y zKbMw{jfwfp@1%VE>}PW`y+Y3ekPz*=al`x%%*kn?A}EGK|=kFL3;Wnk4TU!-bo=NP_R50_1+S0Q~t zH>u&PG&Lo$$Pu$7-UFEFCbbX&+u$__%%dXaF)q%6cVPeg%J;}Cb=$H&%8 z&;(BGHu>L!i_Zcs2Gg~C;Azaw{a)+26Oy$g(KpE)pWm6x>Cs6#Qel${5w*$#{WqzF z`Q3`MvX+|RvOn4qnXY#tM=G^9yFW~E)@Ip^*NXihN%wwU=PD*>_;>F_Dm<_|S;(UK z9VO&wnRX{9aSxDm0Bi`8%2c@*4$n3o0=FXGrCoY$?fK>u7Ehv(MP@`)v9h#X`_6op zq>Vm0Lh&bkTUlsm^BoNq?RDN}>loMZD}rdTE(M%l^sV9eUd&{Y*?gk60=fbr2A;`t zLQ&7>aI#7*;6wPvcpWnz-^pp&+8o(c!Lkzzq4YZ{7YaNPuW>F+sUTQR>SfnH_blM%(d}!L!@@K0@OEr?)` zoQ_m?*+qnTk&vIs1K%hqi4oCSvNpD8ycm^*yHPy6=G)}7V)FBfudk0?cOY}PWDEi+ ziu*al*8@jKamULK*61b_TWR0Y%-jyCu8wFmelA@$nG$gR&2>`Eh2GI|(!#=GLO3WZ z>s=mNP}5!fmkj#i&4O-xnl1uh{-=uP-{@DIlv{!5k_%|ZB zlQv+-tI!=fygO$*=B04K3-Wt{jxP4$ig<81fCgR=RzJBS%x(YsZ$mQsjt3Cx$DDjh zXKzR5O~2n3fJ5^1O7NxN&66XgFO0c56^G{mds0s#-Ys32I_)|(&DugDI-l3+;L1Q2 zV9+fkHFKjouK>fD%hrMOvWAe3(9R3c+Vl^HR8rq%3CqMeAz6v^n=bKg8#uv)8GbVn zLhOdEv|3cO_CBu%3sF8(S8!Rcw3{2#k)pw;-IUtjm{1p-pAmbLT4;IF58AA&!!sMe z*XPEBZbY;O1qK@SU~P%jIB$L8@%rTYTEP~+rVGuoC>x)M-J|bSLrLNOHYJk@y-WB(@zgU6O8W57%c_+FKdD1j$Z(762 zvvRT~s-FosIy!Zy^p5sp{fHQ0T=Sd1QG_O7)4!9FiAAU0YV)G$-;(UE|lZ@T4xe)`$ z-=>JH$E}ts>8N(e>|kJS&lQxU0T=--l6omIyW~43=Mi>x_wIG|kle?|0)BjOaL`g` zb!P6q`iN?dy0MATx!s?Lfh_s?o#VQ*!7bX(412!NR)p2*b|<2Wi9}xaReQ;59lM8OBLs)!3QeV{!^e6B|bcq8%%_D z4gRW1@D^0C9y`Iyga78TH%?ZC@8V+#QYi=?_@KR^X}P$XcJ8}ADb00f12mR%*;7L& z)kf9^x{8W4$)`zWb;v*TV|l`-p?hn#oP0vH&WE4*!Go=_k}Z|z*AdU>(tU~}pgvcS zU)|;2@PTQqy5L*ox1yrH-XApinQ-7Bl7#aPjKyMpxpZ`ND88aJW)>Dcue8*)wDGcn zQg(v&uVY9C)J|&m#lUtHS}*xw>o-It=;{K+do)Le)vp7PF@aZNV!VZ^>HSY{--#)y zAHmmte%^C>dJTF`kVmfDUtk;X_je^HCx5uwKQz=bzf;A>B2qf+cdaX7J}V<5Hs$IB zAbdA)bX0%(^r@gAA}@(0k})Yh-sN)g1X8p?m<7Si1{j5r)HN;};bCDAVM7?&j*1BC z{+{v4@318y2#!fk_Ff%ai5gb;M9yi_8I!Go>TOPj#%^_53a@nwYwS(aER09Xo3me2 zQ zmhWIyI5q5Rlmwa+FsNf!4?u>vdAJ#rqI;4Aw6wH%;n`rLvB8?AInS+#s7Oc{+iJML zL(0ViUC&OJS67DtuyWfyzPu1A`gP*mZ~@B?Z=ye}M6;FY+cBD;IL>bV*y z$jkAUmwSITKPHZpi~($3>v_Tv+Ct#HrC#P?*4forZaKm4QLYE&UuvC={q~R@<#2xf z>Cc%!2tc(BEIbfNoO?AUM{SmH5>fNA;fKoNjKr{l+pI8ygs&mlM1;J-`#H-UP7& z4hh?7Pg3Xx7lN<$vS=iA+x7fKK{nOq=B|-FV8GE?3IUp_I4{)H_#F<{QzaW7YTbcb zQ%O_F?ed&&Jp>(rOZm+kluQ#7!1iFm`owSiJ2alHIW@oh{&5#MuMakE@8Do(_wVaB zgw$TXaOQGM&{ zFTXYgvFg?m*=J+t>$q6ZBDtNoxWqZ2rKGg7uoI2^$Tvk3 z1w5_%2Q<=YX==gsB9_)CC@9EdJqq;@%6-(kA4fm>{?LORe{XNMh{%*=X=2s|(f^y9 z0J#)`Zo5WsL4Wz_H`^kLKbCrVJOeLYL_~zyQf_n_A#u*w=xDBH#bJL- zC>&(@Uyg^OqQaVMkodvhW~SDhGvT4nX=eg=&PYsr?B1~;qNt{XH%3K3<-NAwu42Dy zKTw;Qr+r}2R=$x+9g{mH9vzn~pVqgrZaE`S_cMe~K%hRWz_z|XB3Yfm0XxeN=W2t7b$CMn*VIX__;P> zHCC)ztC-aVVqcpr+kG=Lu+Ns29Yoy=ZISU5qrrdcH=O(KUE6!omdKk;>v$2fI zNI%QL;V>*afRcH3L=8MXJ2$shy`ki@Hi&JA>(hr6F0fG$6US)`f_7XIiUBmW43y%- z^YId7<)x)hR#U6(dMAL`v(8-*dZ@M$8WJ**+)cnL1u37vZ`a-SUR!972DSo+g!P4* zGN1cCqhoD5xW<83fVV&m#>-*9Tu92RvTwOQJHsSMwY9lvB4RoEeJCe@JhU_b0Qsn- zB+fAh4&;w*rI;TkC8&FZSd!?OE%fz2BqmyXDM*QC8Lwl6(3JZ;3u`bbL0=afRv_#f z4N)6mzjf;fbjoICW*ZkMR;|Z)5~X~v}($Wc5w9HPusj5`5kfzhl&5}(`sX| zSHXF`FdJZH>wuY;HP(4olWwCw!Ns-;Z*s@=fpChcg+*xVtU{7yeZi{QaURMA=BU79 z3CX9BNG#-4S34;P_$5Ph!UudCE2}9Arv$BYa9Jad%%e=LbvF|Rx~OMv70-7jsE7xR zwE**|JU%7T-`@|a-ak0#ATcI_dKKVY`^fe|KFnINxUlLx)g2N}oe8T}9bYbK?hNjCv|7%(fpB}8Wba=t=fUzD*OS`F%z~9o=3zfq_ zr5+5dR=hq~{S_FvTfF-pm#e2&=Lg>&Xa^A)rlRoXJnxMZm!K`G`o8UUecDoiG!Jqi_?oxDAy3|?YD=~Z}i5Ek$hbXOEbuN8D)>MMOk0wNEKBg6>y$>d$L!w zoLB}G?~z|l@x>*%8aFn&Q=Bt-Wh9}3&uPfs-y$Y2~q zd`Wszz-k^`u9nR274lOb+U->v`Q9QS)%8l&*483<-tYl~Xp5fU0Sh;TL^1fBa=C8* znVi&yq5s(bZ>Bc_%g62T;wAg4hYVVgPkv6%**-QkHH9S@ruPsc40AFv*y3g}1)`GU zHaF(w(uTawkH40e%e$h!bKm>B0R#ZVbs>M|)^|5A&VB`6Tl*@$;uyHxi3i(T5~nF% zKq{klQ#cGnLc`%^fk)E>bZ>8=3Apj zAFmT(ARsP9P`l%;08v*~q`?Vw@_(9zQ}c&RmLJfn`1pbxi>2}YKK2&fiYXn+p;S1x zWYFIaofdZo1AAnTbpNKY(P*Ioq>`BSB;6*Cj*9wiC6rD8U4^U7!GyrN;RW$l6h4M> zz%{cGluR_`D3ymS55EP!KpydO#@Bwcrr1v&Z|c=8v&ot2i2O`p?z`g4Y1)ky3e+-~ z7H-g6+pDSlWovl&X;x?+Vw*vaVb}nYDW*`gcrLENPzGA7uiw@Zi9A{V?7({JK0G}9 z;6X~^W+PZ)FwYFYg3~uLQgiQQcPB%Dp5{?6_z>p;LLr$YHz!BHd+u@mRC4<8<>i?T zUJ&OmD_K!}QE%_dvYe5*d9AmCoi(EnpNv*^YJNMONTBAiUZpJxLd1C8sVvXr{`Z_5 z3?wAAQ9Ui6H8PH2bF8lB>({#vA3lU*ikjNtdR56|6g*_Q){PT{0=X_f+S^x5j^&Ua z-Ynz!iEp1cxQ?q{CkkMWX9Mq@7+290&S%n}@wORh;?ePm>Por>76JLY+t9%O}w1_$$buWKmT71do7X6vd*5>Mu4R-gV}WQHCP!u#)ij{l%YMk=e1yz)qv z&zjv)hJZp@1{J}AHF}EXZWTqvcj4jn?u*~eXb(;KBcGR*7J454c~cfU1QrXGC>8Gu zr&yS3GXf;xawC2Oex>T+_~Ytvejs=Ih}Z$mhd5$GP}`7TTAz4zsBcYd?Nm#p4KOYg zRIrrEBtKNhDjTRCkbe9F1x)rzs>VZu*(%UunfCQ7yJuxbhD>~OsPhx4cv|e%QWos8 zXOXF+nuaTO900?#~bJMgoRPH$nc7PCNv z6uwDNVwUDRl)umT1 z2%&OvbD!K*H~j+F%eHGRpj%#E7jQkd)LUFmBz^q&XG=s~N=iNC&uD*71E^ zT~mGCSz$9&nHn3LQVaF>vppD8kdaw;4mz*(EE9hHk8h8Ssh_SMlO_tGP3c+dAJRm{ z#w^-fHRFQtVCS3YvCotEjlY+2^$m8e!?+55J+MVK!cYI(=T_Fj=>sdze?L!60hv!a zCw)scB$UH891|DEXTPuw@By{3D~)by8C8Kt2$7^^azH_g$A&(~N1_(%ryft>AE^!v ziBw%KY|KL-1Y|i59^(1h+@1Yce(x)B-!hjKOI}>6PT!`n|I@VWZ{H#CQYKmaP0!N( z`}aXE2}%a|1qF%y#07{6Nsr!#g@r=lW_i}<&s=*wB;pTkYxt`v49aw%<`t-#hcdei zfD^$FT=(VW;%d3tno3Hy2no>;PvI+a(>rG~Z%m{w)L`Q<`D+3vZV;AgJXbPMva;HM zwF-2z5@%lgv}|laO|u~(iDy^ZMb+&t+%+Ix?$j|(A_4ItUHiaNwYJ*M} z>yO+ZIrJWxnl3Fbo2^SgSh1=i4d6Z49gq3>U{rl|LkE}G?_odT*Nmwy99rZ<`@;9% zOa?b&!n*YPCclkD%6E5%%ZG1GH`rKOq9Z&t!U6&l?R`obzrfxH$;VX=oRM|4hsCGdhtkKy2<(}M)T0Hd-b*R+f`K<2OWH|5_0xL*|&kw7PqttVArf!U0X~1^jMZ*4+eTzZBiBM2?o7iO`##6 zPM@^^tW1Sd#c~*eV(#H%`D4D5Wxxp{cBE(FmV**M?FC%0;@xWBiZN(fhb&jXrI${6 z_6~M7el_bGR#LDg_u8{W*~(5tDTab5o&^)H`SRGnq$}BcOFyD6&K9M)wC+7{Qy9Lt z=%lW9e1sH#JTh8OW(hgj(!$bG7*$dk(0*hoJh*o48ZhCoO=}DssFMu!iEU98g>Cdx zS*uIM)4`WRC)8wI8Tv>@Qq^hiubc#}QX@l0d*uE;G~M?s)%jcrGc&7{*mP9iO&Ywg zV1L`*-sNOx_jPFKHgPF#?H2}R=q65x9`G#rY6P$bECBBL9!k*31j^0V|Ddg^BRkcfz@m|gRY>M}s5)z!B+jaOszys>|L zazIBF14H0ow(ck!^*<6mjFkO}+S6;HUMMuQw2eP{^=DHh!CnbA7IGeR@U+(ZjC`N3 zqyqpuMZ_n0flo;=EzBm{1NpN#D{M8+<>Yh#)_A~q<)!6+8QOswE;5<@)hX_6RaMw| zxGhHhNL*ff2WiG2ECpkO&&1h1`*!xO)5ho6|*jq6HFlQsU!zLDb&N zhH~-0-3Umf<4ROe>*xw+CS0n3URjQvdRSRe1~o+%wgXx=S#6b5=T{f<{d-;0BVbhr z6;cW_Zjkm~fnEdD$Bm10lwv~fXP9^ef}YVcc%G<)OFxsfrQ8^IJ~yd3_PhkZi1=c+ z<0E@3oC9uB^F17YL4NzTG4G%?$TnaHYP5dX3ANl_o?avQT9ApJl9*WIW$~N$2n#U9LbM?htjmN&yHHV6GNZ82_UaO?k(@L;I2g_Yx<#H5RkV@sUKlN4)5&5DGCM;lX18*B3-Jq`Kg ze|Ms@j_7FptMr^s7qTT$P=FuuIoZ9({UBIA3j@>UywnY8pYeyx~gDPc;_ znbn>RWLS7RoExPZ!;}mVTVWt=d(brmmhHb?DMwF@iZdE6-M?oH@d7YKm2w1KjhtBu zJ$j``q2$K6kn?0^Uz!e7I{z>kSz27YhKY%DKd&BgcA{H8^H{xvV|)gv$P_+X3IwT0 z0am{?S-&Uqhs}%ojQBS3#(0&=em`5ef`stMgQ%N^OB-M@+WgE1s40EqGg$lu%56xj zJs@ExG*sqyJm0BEMy4{+0}%E%TF-zU;$ZFVQqSH@g$;F3M{rnho$KtArLEhQfCi1I zHcZ!${4{#@+T-$9HHkYHh^;RFeD(Wh8s|SfuZpX4Tv+b=^5x!rc?vdWHUX#e1zc*I z$eyxFkE8CW7BPPJ>bn!=dbPHZU50FW<+AeOpu4DbSjra(dBDF)Ex5l|0hI4N-tF=WML5e+( z)g--V@<)Fhn#`$=wy;9-gZ0xYhl_uxbPkivbY*Z@C5ei%_GIsz{?F!q2;MGO$ z8;J$zbZ$xnc<#KVQ=>qiIXgM>p+YkMV4hY*cQQA4TteluM0^gK$x0ctkoyHW5MdtOss05f0ETXEKDxj||qH3tIB4 zp^IHg)p{SeL;I>FG4p*kb+xC%kl8m{_|-zaLRK||=Ghhq5h6OLx;B#ZZG0uDsj^Kr!R;W@;6D?$7P zzu~8!{m;Bt>F9>0bR<f5NTX)X%Z7tL<{-TAxdSpI-1rXjqq`d-<@@jV z-*>Xz3^?W~Dq!~9Y*s*B;SbJxXdJ2>! z!S|iiWjkw5aEM9C$o?*r3N|RiAtWb7O;+}%gbvbc(eq>_Z8{4ynRD4QVg*)8Y@8L3 z*cgw86Ed*SQvqe&5+U#Y(fdJG314pI=fFQr1lQp3(5p(2taaQ{kR11Gorhd97>Xqc zb8>PZ2Oi3WB?yD~a65WB*w~m!{=UJ2jWV%-Vj|9j z^j(^dC+T~nUkM2k-M)4t`Sa(e;S^)NidW0CX>jtu61nHNeq2Ouz?%xjii*)Mm(a!lg~Q)hQzdcoa&wP8 zy)9+e!O1ha?q0+JmLdpH!DRq$Vuho)7%Ff@+;(GZMPxs5na`zT(?@v{aAy}6H%|YY zg&Lp6jYddc2&drQg=(@!A`cHtgQHacA;0hnotR|;Svk3-u9Pg$vS$rY3%L{7u|4UX zrdxG%I#4@5%}K~M*P-0_=_f*ceuBu5c>FCbF76KqCAnDY&MUB~wd2H&_xw{eeSXi!n5$n!0HBcoISYr-TGy+=t}a#zyOA zuk%r7zFzl509}2qs#u$Jz=`MXewwY41*vjHhED*~1JaF>`*nI$@pCuLG0^(!HF&dh z#>U@g(pdk+2exaI@8>u_S>IbA=BZ zi9=`6B!Eg3lhbY@0l8}*@wRskc{n*OEMLo}GNcH&fJITh6hSEd2Y#AzBpL#BlLS1v zol0|(rg|aB)mIPopWQaXxz26=FWVawSWLz=HNjQk&s{#{R9IFrRccPChLGD-c#Leq zIcRp+YO0=vjsRSeD zP$P)xT3w2^fa(T$#@z|=IpJm&y>icq!wvXo7r*OC689P&+mb+h)`nI^!MW4zFtrMY z5!T`-4nZwo&$0>{g#=t0M#g3D=8HvUHC)nO>W<=~q7&o3M!5Xq9_rffHn+p=E&rKI zy0R0{f!m&}SP;YNdEB;7HjtN>>#vz~iQWVYGv1>U z!d`>WtNJSKl~(-gpam-%NPd0|q&~!Cgrbp9^Er+J_&rv2LB0duncLx!2>dlg#N|IN zs2wu35*EPx0LgXvcw5R!E^p?cNQ0)gdVV>2Bq{8YEEDAn4D^tpeF~9^hfNg3Y`PEC zGId~rUX_whqP{c+c>$L@liqa#=OE_DNX z{l~VEf~3!%{cS)N+R4&c)LW9KQnTlDTkM062rQvZ=%_yvzcE6m3$K#RKl9zhgg_vb z5Gsd~38-n6ei6z8wjH6peX zX8|1qT!-bO2=y7|_uTi&s;YJlE)b)E?we3z0+ACO>~~QRaA1M_dL&Pe8o2uMP8b`6 zyiU=PZ^6NKc6M=UXxY-zjW*{@LP8fJcoaY8(uHbV2(dOsKBq=S5#Coo6}|mJ$P0+1 zW7~&Q08pm*B;SM5;vZuJrl3car!QaCZ3e8z`|{lFV^blBpiq81@(61!8+yLyK-_Hjz-b#Ks4UB8Lxz6 z;|=8Uzll1n(g&qF;Ph3Bw1KM!`O}{v<8$fOU`0N=XA1O|M|d5WE8&bpZpq2Woa5$t z43=b@fEiR@IKF~8CPmm`90cf)_HFVmEWiA)F(4EG3Y0(VV-8+t=5k3Ki=AU7dc>3A zaM=6x>nT;rh~iA|CLT~SYr|!NjVDh>!_vM=?YP#CzAhQF6xKr^TT-QxqWyqA@{{YP zHU#jYq%`=cT+&L!2~0xfv=} z4V5i@)WbKTf9$Ie#h3m4h0R|$85h*jyc*z&v6?=2tFW2M1REd^mm`sL3~|MzLaY9x z2iIfRw$K4@9?fCE!$kN)CJa+w!BkQBt>dS+hGpjnd3AmDh82v~Bg4aa8bx#P^?@UT z>yBJ)GYObX8%JOtzE6+=nL@%|r}WC{77Gi{Kq&Q@&*AAuh?!&<&t?*PZvVUnYyyDP z46NWjTxhKI+H;1P4nWML>DgfvQTLbC6%oAPTi%%u8XvDN*Kzs?XYCrO3|STt1(ht2 z25_=!gjzz{*)`Z-_DlEQsG$KV2r~i!0U;RBAZKl&Jvq6tVv2!|d2M#n1mcUK6G&G# zS(K(NdXdaA(6wr~{-pw_3?)O*kltlSgCzz(>DKujP{KhaVegcaev!t`&JMtL8iR`q z3q)jO7|8$k%Ucw$m(;b^h4l2ijSm$z)BFMgEw#aaL1gtsRH@SbWEYru37l8zw(Vw4 zju;`R$cNi)vA1I(@qxH&(UuSKy?uO9G1JWYn^NASuZ6q@(rF;?Q9?wd0WuYSwYI`~ zQdBA7xvwBl?TMfgSXR3|LwlxFFH{B-eSn4*fvL|^HuaKA}aM$uHu0r z(dugpNfoebp!lYKqv2aUJ=<#Fqv(cB3grz7oE(Ag42>YUDGVtZw@SGn({H^yPr!K5 z7-ld6Yuxlca}8Mqr7weJ)1B4hFGErv;cqd3;x&1y2COImHv>@t#yT)Bgc3Sd3O||p zcHhciVrs()@oj5+Ms1T)SojFsQ7W7z!1y7-SMF~aRY9VCYwNj z6i?22bjWL+Px=?<0{8u+7%fpIVQV8|dSWI_42p-tS+qDK>5&3b{5vfHN@rdL(*@fcRu*8}$1;pEt{O@k5A zNFPl1d&2M$sZk_o=Py1!|@Pnv5+H86t=@DA~|Ub?V3m%2}lIzvqP>(?Q5-CbQ?ga?^vRa|9Gj{XG@Z~J?e z%is---_I9{X6E~=Kp1=8K(twJvfaD)iMH8f#5CQs64!xG_Pgwy)>?894s4T%=o@9CX} z!vw*pVm^vpPfp?H~zKoe6^ASKdl=j#EufxU&Lbmizx@ZNq|p)kDm|QW;O;iD8Tkxvt5U&XW@F*!LhA|5UhOO{$@^$Mu44dDz zt1#qbXTu78ae1}{ExVRwlLgWzG3)HzI&c(~le5~1YH6G?p^dC`@A{r0;|H|J>A?oC z+wTRqbcd-guA?aEzcyiFmLb^2Ai@ZFfq4s?0W5mkiFez>!DU+ zbhJ4Aa}RgK&}m=R#;e9TwVig zKR`f#s0@o@|DO@^_yYZ^_VyG@E2})r8}Q(*;RnYnS#fPmBO@-LV}dUR?sU#tQ%KVY zpcNl`WWKU8|0`mkf0%=dYjS_Wq{wbd zAAwHFPtyVY9`%LssUCKgvy{`&Sj!0rI3Y1*zKW$Kj7uiyvxn)G!Pgj`*e?S3Qj+( zV+RgPr|>wE7TU%QK4kj;Oufp!C zkRp8VTwl`7ZSMgoO?2Ek^ zDI~un!iQd&W7va~qoer01LtQziO?`H{kHx#1p+TnWcs7wNimyKn&bnY@CmUR?;Nf%jU)D;O!o4HtI-80igPM3BG$$Ye zrxbYNS?lHQrdT-I`5A0GZ4J@?%SLtZh<1ijT1{;|t^!<+<St?;zC0DyxESD8`-sf*8a+-RyIqwW67wTW0&-k3l z2_6J(`>aoyY7uJ=5K~-S%-M z>bZ;>+m_$-)7~-mOSnf)-;Iqr^wz+=i~5#kYo+U|HE6xlGEvU$V?xqBW+ou=MkYFN z{`GhKv%sv4$?uQTM;#N8{ z{?XBd2t5M>PtCh?3#AxFl{&$BLIPc>y0|V2yf>oSwPukaA-{KCwSs5#-bz7Q8nuWU zhsEE{v4*a(8W#oFY6qX|4XcO~1~mav!Nn|~#k0i;!+*(AxAF_+m~=+QHb@jpE&jf{ zpIrhJ0~+QH?F#RM^1K=l#ib8hq=4n4`B=iPb^I+BdKLJ?K)haGTKaFC6ns88bI?XC~BzK5>e#6DDYbrNt;gX2T`32Xb8{O`Yit7Iue zx2LB_T~1a$nP?*nMt+c7H{ui?fX*av6gjJ_%E_C*KY99|;wQ-qJAe4=Ha=&+#a4ZLDX)f+oc}Pc{WW(PX2(234jX- z_yb<%E|HVd9XS@c^>9uQav@MB3p$4=NelvPEl4`ZJ%5Or_{gB~!(H)_TupFPqv%5v zc4nqmIJHRJr%%@qFf-fQIRo1+177f8341%cvPWB4qO``wrc>2+B@*9Do+?S31Jev+ zUl}*|ty_@_PpCpA@|>`h9=te`-ppjk1Qw8%7InsPP#(trUlA@t0|O+X64=xn41_Gs)KfD1HO*FRjUKY!UxH#FMBl{ZFV}c>Rv>?3Q!rL27WR$E=`ZN;8UWxe zEd3C#{{L|I7G7CyYuon)D5#VoB_$%Dv?47f-67o#A|(jYr6N+&UDDl%bVx}n-H3EI z(tIcTd7gK?;~UTS4}6X_#$J2xwT%0o*SzLD&g1wUKfBJI{!O0;b91W?7f#hA@p=eR zg+lZd3LR)@>KmL--(d%1AaR;;3~Kq%sg(dJo17dv-=2D?aDDw6Jc^rOXt%`q*K~gR zbjz@K1H9!IyG+T1fC04>*9S zSpHSens_lcr47-Q;JOM&V!KDno8>FhucwCWO3V5Z51OZaA&KpabNS79XG@0m?nG_pymPPPhVXPLdnAE2?S$A-@t&sX{rFUM$rEHlIxt#xUK~dtzez7 zZROCq%d}G2%E$y>4q33XmpZiSKY{)QM#dgL5kugNUk(m62@^Bp0R%-)cWrxDE%-M$)f=SM$fyV({+lXr>)?9zRWq0Z1BWc4oWNP` zAkdxY^iPvJl%25H6hTrE*vuHX_u(Zq*fp_cwAsG9Ylplc-{Nr_-OkP)Id-l(?`HBi zwHm%~Ki$K;RU9-rhHY9h`aoW&+@Tx;heTadl>`rOce!IOX>}FCRq%+EvOZgTy;kyr z>G!}uPgkGO(^zA?vFT~R+_k5q$fJPd-3R%<+*~{)H!l}OP*kG|)X5C`$gy=uv%#Fp7atimLVatnc%5G8rd%eL3(K0lW3chkDe%(nMFd)U}m z{EHrwr4G*%<*tl212MlHFRLEa;CMlEP(a>Z&RsJu?JvIV5fBC3oGj6*P*8$dfr|3W z$?u+h!#xwyU7ey-50L$5Tbx#LBMS^_Zv_?)mQxm2R=k_Rz<2}QYo$RhFRy4~w_VlQ z`55lQU_QYYXb51PfPN!h-!AUr%iH0tf(touOA#oUv2NZ%O-Y;JCzpB!4%4N*UQs01y~D=VPo>~BtKz3|?j6)pvXG4f}J zOGl>;rea9(>WrS2)+r=o01Ade`SgJ;U_fCK2WI=xckpZ5OjTb;;DDR_%dG^m*Dkg0#0g{TYr=pI&WF^#*gFZ0Fh%d7beX6f`y8ZU1_aqHqJrSjb@RWro?FtOSI4 zKD!){VA5b>`nxTidUPK<@%`bsLQpYjmP^bCHq_Q?gY@#-KwU({SYIC+aq9iGOAHsX z71MeX1%jQCalP*Z$SWZz)i*x#by5Y8mzdjnMWvsCMF9I{3c@7 z^b*z4tSgtJ5b$A7A?LQsA8UGN*2kf(fZV|G;DyAM)y6bX12mxqX>}D;+2~HViFQn6_vU#{&66))V>$uR8e7#O-PEu zLExx?iXy2HK6Y8z)8)05_D3zyEf;cOu8NA> zz{PE_o%@vJ?XB=QVlp4H|Nhca)QW(}u$Ai-XO0foV0gwSOpW)if0f{zmgo7S_S)oQ zIFcb>VUkTp*8zkgdmbIxg44A2;Lp3*dooZx4LhB(Pa)N_Oehrj*tgVP@9N70awl~Q z4Hg;O?cjEU^AyomQFX}r-I=`us}9~kVcB_=*|Quz{++jwpDeFFOBZ|F`x}cQPuN(0 z%-P=D*%@s@6MU_i>r=&jNP^hy@f%`)y!wiBUxwyWcf5X171}sw2X=SKn+TUBPLv!~ z_^i@kM&YzDBcXwfS5&St?dmFJepi?T_3#k8dEUd$yA}d#DIYQm3@vf8s;f2a(x`m9 zTHicC6AuM)HtYp+<;x%9BGU0YDTh`fYA%r#U4F|kie zgL<3Ob;*HBPi z(hp zEr!I$x7kGOdX91@Rx7oWr~nYmNQ-&pkcJQnLQD$0E)@rML|y$5I8{G-^CIatM7mdB zBMOQsUEOz^z7FIG1bsW99T$j<#*0KnTrVfv4LNU)d=!GX{d32r4)eizx^Tin1!);4 z&kapMTiX)*r$`LHdt63F>o*G&B4%yFr?EAclYTvqRn?W1H53=tc^9YaFs5o8%E;NC zP*UDT9xLA}tL~_eks(ABa`5pT9^B}^t}r;n4SQ3uWYrTuYQT@=B~xQ#GHyGOFlHtg z!VP98J2;fXya^2rwTjnT(IC{@`+;zzuCn^a^MtotlX2s516gv?ML*}*16#sU^7Dar zz>JH$R8lf7L#gDY@R0(NLJzvGB?l)9%O%7e`u)`7WwqVTKHj|B-O~dT!|SO0gocLV zM_@oZI&xZ3xg}un6Q)Jb@X=p-9qD8d+X6&FO2%ahB1}$ls_kGm0B-COgyu0AHuz<~ZCIuVX=~C|$?3*UP?lm~c zK`Z6OCkIanqOUJ=zBH4)+cIU;pg2swUxvL-)FgWB=>-VMY<=|lTh=;j7FEWUE4WX2` zT#mNX`Oo#|ZlsEG%+L_0&E$Tp52ICwv_wY1heH{y(QD^zpwEsiZb zEfn{DJfsp2JX=vQj-dWU#_poLUUS48!|}cgxc`PU{&??j(_o_s8vg~bIiVB;*|x(L z#sUj^Sy|}@`6t6G*qo2hJsx|BH`@!9bE*|;UYI|_hHEStj+*k| zFC;Ss+IX@Ji%zu;it_WvtLEfMf;6?X)bwWFtPJ$`M?^e^ZzxPoEZ&>L+1WWe1@lEn zp?7Lw?}J!ep~xo03%;kgI1*^Z8ocIJG5MbP_6<0T!bJ<90N&W&B<1BD?d`829SyrT zZ|*=OkDs6a&Ye3jVa5QYg=n>&H?c&BM6WA231%;Ck(hpa$*mZ zdU~wL^=?K2bob9wArbTQm_#Nv@57t&3m12saGJ@s?YM6F40n=$MWECa$Rj+%=7E>& zmobh{JLo#-?IZicSiQX(Y__IB{#H>{9&|sc^Ht4@Xf=dZJKMQby$16NgHjzKVIho( zF`#1|NlLr|YkQ+N$MCS~Lczwsk9L?x3a=|fJWfhdDn7kQUA3@4@J@Ig z+$x2I({s(f^Q7LK(M4x<#l??-AFQI%rpzKw7=DlJ?6V*xEU?pA_uUV^O+wyp)+?Xp zyc}pW?Ck7{9g-Q@VQ#|x03n4(@^L@U2R9iA{a~p(u1yzIWXkF4dQ(2~6;04)-fB9> zsNY-Ct$b4jfJqy@XPlhTM&fc3HWj`ufK)$B;&-NHlsP=qf;S*%rJ-E)pIfx%T#B?3wY753u6fR1m2au>%972k*QP}rKNwtQUKyrnC0wQ z)t>PBIyl?_ZGIC62SoKy>b1A1-hUwca(C*Hy2ioM$X=Rm1EK#%$eXA$2XyedEyGcC z4Je4PnE^^4O5wdBJbIfb`p@Db^X=^!=^*{n?FWFsQ8I$}MPj0l5F7&95}&^qb+=;C zy0Qm27yQxZW;~noUz>&= zs@peL%vS(9xAJq&YkXmSZ10(w9L!?OfMF-vAPw3;e^oTIHW07I0oG3SqkQOrJ#`#fp|aWqDd&`>-yCc;Ru~C{LV}uosmT^e-Tk_iAa% zKD+$};R#5RMY>h=@jITaxz-61LMPR8Z;t4~AK_MzCPYROP~1^``LcU%JL0hv4-XG? zdgT-q3B38^mHq}p^8N>ZDPc8|{(R`7@Zr4~bYkzx94et*>-6GHD1j#2D2$@Y@K8lDgojWb$h-)C+(97$sQ!fYvtx)_9+U`wQ>0O zc&{y0f`}?KzFVekX^&gm8sq*{5J>g5pra!R#4V-gxBdKNT^NiBL$~k>SBehIM)>Gg z9h3(xcoKW{pKLqv7JX8Csoj4Pk+LzoPyD9x-4x?d^4)vKulwiy*m-s3teGWs4ZM1w zb)u_4tDd(M+X1c>yRwkUUAuXmjJvAsv@GO)q{6THFCU`{T>%%f)Hl1r$lBhxw0HAk z+s)D2_i(o`MurCEPE5iy9lwoNk9Y3}LgAFbzdPUMcHb~v9ZgX{#6JPp9F?$QhX@D3 z@$7ab9jeD1)1AxcHRiWikiwG83C~g!MZCI9QW`N|Twr?)+Uf-LEeelIHx|v?Dt#2H zu*AjRHEQ(p5vq`>MALDb(&9qrPwrbNy@txadS?xvi|ocVH+;JXH+BnBDB5uDY%`d6 z;Dlbj_iT1p+B<$MA_(sH`mYuLWoQp6^h*6-|88EdXX|}3MBpq}8*WC5>3nk;s@hw^ zfdEVVh|jWp`$V%PD(0DDk@5%WJQs3SBqd$PF0QrQke3B_A3C@r#VWG9PAy8 z%Npc$R9!bf&gA3el}P`DA^h$4ZzFL)5Sb{U{K?~ZWn<&HKdLyUJl}8&&hE|K-Hgj} zAz4{jmgu{ez(@cb$a}u$XIk;g{ED$+z#SQY56O7%BGNM73jQ)ydM@A207YH~!}%I8 ztZtxq)zjPVaP3f@-YvEA$h-*Xa4UNtfB?Gf5j2trucP1RdZXXk842VP^O>QnllHH9 zS>h?|u`JVDXU;3FHok(CH++P~1~0?-P2KI@zRghLd3mgSE3#`AsM*oYrf7owr@$de zB4)K;2Fczy;9bg>gx^ayu5mXxUL>s)4(kY~Wm&%N;`xr+p!qZW_E@^-_r8X)kS+4p zYpd7L(6O?Y_5gbfW_GfR`vuE4cF{nuq@bYq=J3t+V)6sL0pPa@zwW)<{Y;YvT(-E~ zYyWPR=19b++!FEN%`V8h%f-b5&Y%F1)Z%CZ=y>FIJWP-n+z!sKt2_T0`mj*1hJVB{ z3AiFqffo8u95^oEBv|18?LIx(b_X5!J{+v9<0bCC)Yth7E}k6C$|T3W1}TT@&7ami zBGAqGpP9uStSB|l)j97S!WBWAnTSE28V{1$zSwV-Qx#bI2hPu!GqX6^YBg|)|2!Qk zgVU=@qzXg=D&H>MFHy%r?5jKd!XH6>#FIWpU3fo2Wu+XMK<~oN;&JaY4pw?*I(}#e zGZZqck-ED6l5TN$z$6wVY3D0f*-1l?UsLH9isvrtqdVYH!Xo9!uc%nOs5|}72p-V~ zVU$9N&n4FaWFp;KC&@67BWT-_C@8?c;c(z^bH@*qYQUVsBR5@afCj#Du2S%o11%)2 z{fq8i10f+H`e;+2qXWsaPfhysi;E|xdI8R-gjyLHa9DP#O$hG8f`~6D9bsX1yYo3e zGI4kJw&BM2H_%h~_!-9rj0mmzni` zA?E(kW~vWu4f{ji*JnJ3awDg8r@@niv{iBSU2J>@2Xtmu7Mj;Pn+-Hy9GSf&=%OVc z=orZABepaPXzzkGoFw2VSL%)|PieWXY&FEk#VD&i+nPRK+7xi4(O^x`(bm?{p~IKb z)?W8BdZcbi7&YCw6MW`85AkLdl^OstYlFEAr~=R2+$Ja6&>G+SU=lKFBZ#h2U8Q!R z{la&TDsWJ5@l5;3s7ig7hk?^Ka^54Tzg0gs+IHe*Vk!W+T>9mBYGm1~g&xWv3F<

f()UV=LM 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 literal 0 HcmV?d00001 diff --git a/docs/assets/user-guide/sentiment-search.png b/docs/assets/user-guide/sentiment-search.png new file mode 100644 index 0000000000000000000000000000000000000000..9e686fd9d93815f0ef78f94ab97ffc1700cbcbd8 GIT binary patch literal 101814 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 literal 0 HcmV?d00001 diff --git a/docs/assets/user-guide/topics.png b/docs/assets/user-guide/topics.png new file mode 100644 index 0000000000000000000000000000000000000000..14299542f44ebd98fb61d2cab6802c0d46ca1b2e GIT binary patch literal 41624 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

mGHRn{j?@XR_gA0> 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 literal 0 HcmV?d00001 diff --git a/docs/assets/user-guide/user-sentiment.png b/docs/assets/user-guide/user-sentiment.png new file mode 100644 index 0000000000000000000000000000000000000000..309ada425d9edfd44187d316da00474c9592238a GIT binary patch literal 152337 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 z%0D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 CejRrJ literal 0 HcmV?d00001 diff --git a/docs/contributor-guide/architecture-design.md b/docs/contributor-guide/architecture-design.md index 1ce058c..f35444e 100644 --- a/docs/contributor-guide/architecture-design.md +++ b/docs/contributor-guide/architecture-design.md @@ -78,29 +78,44 @@ extensions. The main entity of the data model is the `Message` object, which represents a message sent by a user. Messages are structured as follows: -| Field | Type | Description | -| -------------------- | ------ | ---------------------------------------------------------------- | -| `message_id` | Tag | The unique identifier of the message. | -| `user_id` | Tag | The unique identifier of the user who sent the message. | -| `channel_id` | Tag | The unique identifier of the channel where the message was sent. | -| `guild_id` | Tag | The unique identifier of the guild where the message was sent. | -| `content` | String | The text content of the message after preprocessing. | -| `timestamp` | Number | The timestamp when the message was sent. | -| `sentiment_neg` | Number | The negative sentiment score of the message. | -| `sentiment_neu` | Number | The neutral sentiment score of the message. | -| `sentiment_pos` | Number | The positive sentiment score of the message. | -| `sentiment_compound` | Number | The compound sentiment score of the message. | -| `embedding` | Vector | The vector representation of the message. | - -Messages are keyed by a namespace prefix combined with the `guild_id` and the `message_id`. This combination ensures -that messages can always be uniquely identified within the context of a guild. - -The fields with type `Tag` can be efficiently used for keyword search and filtering. This way we can quickly retrieve, -filter and aggregate messages based on the context of an interaction, such as a user, channel, or guild. - -The `embedding` field is used to store the vector representation of the message. This field is used for -similarity search and other analysis tasks that require a numerical representation of the text. For the similarity -search, we use the [cosine similarity](https://en.wikipedia.org/wiki/Cosine_similarity) metric to compare the vectors. +| Name | Data Type | Index Type | Description | +| -------------------- | --------- | ---------- | -------------------------------------------------------------------------------------------- | +| `user_id` | `string` | `Tag` | The discord ID of the user who sent the message. | +| `message_id` | `string` | `Tag` | The discord message ID. | +| `channel_id` | `string` | `Tag` | The ID of the discord channel where the message was sent. | +| `guild_id` | `string` | `Tag` | The ID of the guild where the message was sent. | +| `timestamp` | `float` | `Numeric` | The UNIX timestamp of when the message was sent. | +| `sentiment_neg` | `float` | `Numeric` | The negative score of the sentiment analysis of the message. | +| `sentiment_pos` | `float` | `Numeric` | The positive score of the sentiment analysis of the message. | +| `sentiment_neu` | `float` | `Numeric` | The neutral score of the sentiment analysis of the message. | +| `sentiment_compound` | `float` | `Numeric` | The compound score of the sentiment analysis of the message. | +| `embedding` | `bytes` | `Vector` | The embedding vector of the message content | +| `tokens` | `string` | N/A | JSON object mapping each token in the message to number of times it appeared in the message. | + +#### Design Decisions + +While the fields ending with `_id` are integers on Discord, they are stored as strings on Redis and indexed as +[Tag fields](https://redis.io/docs/latest/develop/interact/search-and-query/advanced-concepts/tags/) as opposed +to [Numeric](https://redis.io/docs/latest/develop/interact/search-and-query/basic-constructs/field-and-type-options/#numeric-fields) +because we want to make exact-match queries on these fields and also because they are more memory-efficient and +fast. + +Rather than store the message as a JSON document with the sentiment-related values stored in a nested mapping, +they are stored on the same hash with a prefix of `sentiment_`. This is because JSON documents generally have +a larger memory footprint compared to the Hash when searching over documents. Also, JSON documents take up more +space than the Hash. For context, the JSON document representation of the message takes at least 14Kb while the +hash takes at most 4Kb. + +The [Cosine Similarity](https://en.wikipedia.org/wiki/Cosine_similarity) was chosen over the Euclidean distance +and Internal product as the distance metric for searching the vector embedding because we want to consider the +angle formed by two vectors (messages) and not their magnitude. + +The dimensions of the vector were chosen to match the dimension of the embedding generated by the [all-MiniLM-L6-v2](https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2) +transformer. Ensure to set the correct dimension as the transformer model being used during index creation on Redis. + +The `tokens` field on the hash was not indexed as there was no need to search over it. Including it in the index +would increase the size of the index structure and add unnecessary overhead. However, we can still return it from +search-based queries. ## Packages & Modules @@ -111,7 +126,7 @@ This section describes the packages included with the project and the underlying The application is fully contained within the `courageous_comets` package. The package is structured as follows: | Module | Description | -| ------------------ | -----------------------------------------------------------------------------------------| +| ------------------ | ---------------------------------------------------------------------------------------- | | `cogs` | Contains the bot controllers (cogs) that handle user input. | | `nltk` | Contains helpers for using the Natural Language Toolkit (NLTK) library. | | `redis` | Contains the data access layer for interacting with Redis. | @@ -120,7 +135,7 @@ The application is fully contained within the `courageous_comets` package. The p | `__init__.py` | Entrypoint for the package. Exports the application client instance. | | `__main__.py` | Entrypoint for the application. Responsible for setup, teardown and root error handling. | | `enums.py` | Shared enumerations used across the application. | -| `exceptions.py` | Includes the base exception class and custom exceptions used in the application. | +| `exceptions.py` | Includes the base exception class and custom exceptions used in the application. | | `models.py` | Defines the entities used by the application using Pydantic models. | | `preprocessing.py` | Contains the preprocessing logic for cleaning and normalizing text. | | `sentiment.py` | Implements the sentiment analysis logic using the NLTK library. | @@ -133,7 +148,7 @@ The application is fully contained within the `courageous_comets` package. The p The `tests` package organizes the test suite for the application. The package is structured as follows: | Module | Description | -| ------------------- | ----------------------------------------------------------------------------------| +| ------------------- | --------------------------------------------------------------------------------- | | `conftest.py` | Contains shared fixtures and ensures NLTK and Huggingface data is loaded in CI. | | `courageous_comets` | Includes tests that validate the behavior of each application module. | | `integrations` | Provides tests that validate how the app interacts with Discord and the database. | @@ -145,7 +160,7 @@ processes of the application. This achieves the following goals: 1. The process ensures that code changes are consistently tested, maintaining high code quality and reliability 2. It also enables full traceability and reproducibility of any -artifact released to production. + artifact released to production. This section provides a comprehensive overview of each stage in the pipeline, its purpose, and its components. diff --git a/docs/contributor-guide/development-environment.md b/docs/contributor-guide/development-environment.md index 4b54643..c6c2a2a 100644 --- a/docs/contributor-guide/development-environment.md +++ b/docs/contributor-guide/development-environment.md @@ -3,6 +3,7 @@ Follow the steps below to set up your development environment. !!! NOTE "Prerequisites" + You need to have [Git](https://git-scm.com) installed on your system. ## Environment Setup @@ -16,6 +17,7 @@ The project includes a [development container](https://containers.dev) to automa environment, including the all tools and dependencies required to develop the application locally. !!! NOTE "Prerequisites" + [Docker](https://www.docker.com) must be installed on your system to use the development container. #### Quick Start @@ -57,6 +59,7 @@ The development container includes the following services for local development: If you prefer to set up the development environment manually, follow the steps below. !!! NOTE "Prerequisites" + Please ensure [Python 3.12](https://www.python.org) and [Poetry](https://python-poetry.org) are installed on your system. @@ -120,6 +123,7 @@ The repository includes an encrypted `.env.lock` file with the shared Discord bo [Secrets Management](./secrets-management.md) guide to decrypt the file and start using the token. ??? QUESTION "Can I use my own Discord bot token?" + Yes, you can use your own Discord bot token. If you do so, there's need to decrypt the `.env.lock` file. ### Redis Configuration @@ -146,6 +150,7 @@ The application should now be online and ready to respond to input from your Dis ## Building the Docker Image !!! INFO "Production Builds" + The release process is fully automated and does not require you to build the docker image locally. See the [GitHub Actions](./version-control.md#github-actions) section of the version control guide for more information. diff --git a/docs/contributor-guide/documentation.md b/docs/contributor-guide/documentation.md index 31c97c9..0b49af4 100644 --- a/docs/contributor-guide/documentation.md +++ b/docs/contributor-guide/documentation.md @@ -21,6 +21,7 @@ for the full specification and detailed examples. Here are some examples of how to write good documentation for functions and classes: ??? EXAMPLE "Function Documentation" + ```python def example_function(param1: int, param2: str): """ @@ -55,6 +56,7 @@ Here are some examples of how to write good documentation for functions and clas ``` ??? EXAMPLE "Class Documentation" + ```python class Example: """ @@ -74,6 +76,7 @@ Python type annotations are strongly encouraged to improve code readability and for all parameters and return values, as well as class attributes. ??? QUESTION "What are type annotations?" + Type annotations are a way to specify the expected types of variables, function parameters, and return values in Python code. They are used to improve code readability and catch type-related errors early. Refer to the [official documentation](https://docs.python.org/3/library/typing.html) for more information. diff --git a/docs/contributor-guide/secrets-management.md b/docs/contributor-guide/secrets-management.md index 54a1579..ad5b271 100644 --- a/docs/contributor-guide/secrets-management.md +++ b/docs/contributor-guide/secrets-management.md @@ -61,6 +61,7 @@ on your system. Follow the instructions for your operating system below. ## Generate Keys ??? TIP "Using the development container" + The development container automatically generates a key pair for you on initial setup. You public key will be shown in the terminal output. You can also find it later in the `secrets/keys.txt` file. @@ -75,15 +76,18 @@ This will create a new key pair and save it to the `secrets/keys.txt` file. Shar so it can be registered. !!! DANGER "Security Warning" + Only your public key can be safely shared. Do not share the private key with anyone! ??? QUESTION "Where can I find my public key?" + You can find your public key in the `secrets/keys.txt` file or in the terminal output after generating the key pair. ## Registering a new Public Key !!! NOTE "Prerequisite" + This step needs to be performed by a team member who already has access to the `.env` file. To register a new public key, first extend the `.sops.yaml` file in the project root directory. @@ -109,6 +113,7 @@ git pull ``` !!! NOTE "Prerequisite" + `SOPS` requires the `SOPS_AGE_KEY_FILE` environment variable to be set to the path of your private key file. This is automatically set up in the development container. @@ -122,6 +127,7 @@ This will decrypt the file and save the contents to a new `.env` file in the pro access the Discord bot token. !!! DANGER "Security Warning" + Do not commit your decrypted `.env` file to version control or share the contents with anyone! ## Encrypting Secrets diff --git a/docs/contributor-guide/testing.md b/docs/contributor-guide/testing.md index df82f92..b5b8e97 100644 --- a/docs/contributor-guide/testing.md +++ b/docs/contributor-guide/testing.md @@ -10,6 +10,7 @@ divided into subdirectories for unit tests and integration tests. Each unit test module in the `courageous_comets` package. ??? EXAMPLE "Test Module Structure" + ```plaintext project_root/ ├── courageous_comets/ @@ -38,6 +39,7 @@ poetry run pytest This command will discover and run all the tests modules that match the pattern `test__*.py`. ??? TIP "Running Tests in your IDE" + Most modern IDEs have built-in support for running tests. You can run tests directly from your IDE, which can be more convenient than running them from the command line. @@ -55,6 +57,7 @@ Unit tests should cover the following aspects of your code: - Error handling ??? TIP "Consider Edge Cases" + When writing tests, consider edge cases such as invalid inputs and unexpected behavior. These are often the areas where bugs are most likely to occur. @@ -76,6 +79,7 @@ When writing tests, follow these guidelines: - Use [fixtures](https://docs.pytest.org/en/latest/explanation/fixtures.html) to set up common data or resources. ??? EXAMPLE "Example Tests" + The `examples` folder includes sample tests that you can use as a base for your own test. ## Unit Testing and Type Annotations @@ -87,6 +91,7 @@ need to write, particularly those related to input validation. For instance, consider the following function without type annotations: ???+ EXAMPLE "Function Without Type Annotations" + ```python def add(a, b): return a + b @@ -96,6 +101,7 @@ Without type annotations, you might write multiple tests to ensure that the func types of input, like strings, integers, or floats. But with type annotations: ???+ EXAMPLE "Function With Type Annotations" + ```python def add(a: int, b: int) -> int: return a + b diff --git a/docs/contributor-guide/version-control.md b/docs/contributor-guide/version-control.md index 5f39335..e4d11eb 100644 --- a/docs/contributor-guide/version-control.md +++ b/docs/contributor-guide/version-control.md @@ -29,6 +29,7 @@ Try to keep your commits focused on a single task. If you need to make multiple for each change. ??? EXAMPLE "Conventional Commit Format" + Here's an example of a good commit message: ```plaintext @@ -38,6 +39,7 @@ for each change. ``` ??? TIP "Use Commitizen" + The workspace includes [Commitizen](https://commitizen-tools.github.io/commitizen/) to help you write conventional commit messages. Run the following command to create a commit message interactively: @@ -56,10 +58,12 @@ The pre-commit hooks include: - Commit message validation with [Commitizen](https://commitizen-tools.github.io/commitizen/) ??? QUESTION "What if the pre-commit hooks fail?" + If the pre-commit hooks fail, you will need to address the issues before committing your changes. Follow the instructions provided by the pre-commit hooks to identify and fix the issues. ??? QUESTION "How do I run the pre-commit hooks manually?" + Pre-commit hooks can also be run manually using the following command: ```bash @@ -92,6 +96,7 @@ your branch as the source and the `main` branch as the base. In the pull request description, provide a brief overview of the changes and any relevant information for reviewers. ??? EXAMPLE "Pull Request Description" + Here's an example of a good pull request description: ```plaintext @@ -119,6 +124,7 @@ The project includes automated checks to ensure the code meets the quality stand - Running all tests with [pytest](https://docs.pytest.org/en/stable/) ??? QUESTION "What if the automated checks fail?" + If any of the automated checks fail, please address the issues before requesting a review. Feedback from the automated checks should be available in the pull request checks tab. @@ -130,6 +136,7 @@ feedback and suggestions for improvement. Once the reviewer approves the pull request, you can merge it into the `main` branch. ??? QUESTION "How do I request a review?" + Request a review from a team member by [assigning them as a reviewer](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/requesting-a-pull-request-review) to your pull request. @@ -145,6 +152,7 @@ A code review should focus on the following aspects: - Adherence to the project guidelines ??? EXAMPLE "Good Code Review Feedback" + Here are some examples of good code review feedback: ```plaintext @@ -158,6 +166,7 @@ Always be respectful and considerate when giving feedback. Remember that the goa the author grow as a developer. !!! SUCCESS "Be Positive" + Don't forget to acknowledge the positive aspects of the contribution as well! ## Release @@ -180,6 +189,7 @@ The release will trigger a [GitHub actions workflow](#github-actions) to build a Docker image and update the documentation. ??? TIP "Dry Run" + You can perform a dry run to see the changes that will be made without actually committing them: ```bash @@ -187,6 +197,7 @@ Docker image and update the documentation. ``` ??? TIP "Commitizen and Conventional Commits" + Commitizen uses the commit messages to determine the type of changes and generate the release notes. Make sure to follow the [commit message guidelines](#commits) to ensure accurate release notes. @@ -197,15 +208,17 @@ Semantic version numbers consist of three parts: `major.minor.patch`. For exampl To calculate the next version number, follow these guidelines: -- For *bug fixes* or *minor improvements*, increment the patch version. -- For *new features* or *significant improvements*, increment the minor version. +- For _bug fixes_ or _minor improvements_, increment the patch version. +- For _new features_ or _significant improvements_, increment the minor version. - For **breaking changes**, increment the major version. ??? QUESTION "What is a breaking change?" + A breaking change requires users to change the way they use the software. Examples include removal of features or backwards-incompatible API changes. ??? EXAMPLE "Semantic Versioning" + Here are some examples of version increments: - Bug fixes: `1.0.0` -> `1.0.1` diff --git a/docs/user-guide/getting-started.md b/docs/user-guide/getting-started.md index 36b8f73..4681526 100644 --- a/docs/user-guide/getting-started.md +++ b/docs/user-guide/getting-started.md @@ -1,12 +1,14 @@ # Getting Started -This guide will help you get started using the Courageous Comets Discord bot. You'll learn how to use the bot -to explore a new server and connect with its members. +Joining a new Discord server can sometimes feel overwhelming. With Courageous Comets, you can quickly get a sense +of what a server is all about and decide if it's the right place for you. -We've all experienced the feeling of joining a new server and not knowing where to start. With Courageous Comets, -you can quickly get an overview of what a server is about and whether or not it's a good fit for you. +Whether you're exploring a new community or trying to find where you fit in, Courageous Comets makes it easy. -## Finding your way around +The following sections will walk you through common interactions with the bot. For a complete list of available +interactions, refer to the [Interactions Overview](#interactions-overview) at the end of this guide. + +## Finding Your Way Around Let's say you've just joined the Python Discord server and you are interested in web development with Django. Let's use the search feature to find the most recent messages related to those topics. @@ -20,12 +22,19 @@ Type the following command in any channel: The bot will return a list of messages that are relevant to your query. The search results include references to the channels and message authors, so you can get an idea of where the conversation is happening. -The search feature is also available as a context menu option when you right-click on a message. This allows you -to search for similar messages to the one you selected. +

+ [![Search Results](../assets/user-guide/semantics-search.png)](../assets/user-guide/semantics-search.png) +
Search Results
+
+ +??? TIP "Message Context" + + The search feature is also available as a context menu option when you right-click on a message. Use this when + you see an interesting message and want to find more like it. -## Discovering new communities +## Discovering Like-Minded People -If you're looking for a new community to join, you can ask the bot to find channels that match your interests. +If you're looking for a new community to join, you can ask the bot to find channels and users that share your mindset. Let's say you're excited to learn more about programming and are looking for people who feel the same way. Try the following command: @@ -37,5 +46,109 @@ Try the following command: The bot will return a list of messages that share your sentiment. Again, the results include the channels and authors of the messages to help you explore further. -The sentiment search feature is also available as a context menu option when you right-click on a message. This -allows to find messages with similar sentiments to the one you selected. +
+ [![Sentiment Search Results](../assets/user-guide/sentiment-search.png)](../assets/user-guide/sentiment-search.png) +
Sentiment Search Results
+
+ +??? TIP "Message Context" + + The sentiment search feature is also available as a context menu option when you right-click on a message. + Use this when you find a message that resonates with you and want to find more like it. + +## Getting to Know the Community + +Once you've found a community you're interested in, you might want to learn more about the people who are active +there. + +### Popular Topics + +You can use the `topics` interaction to see a summary of what's being discussed on a server, channel, or +by a specific user. + +For example, to see the most popular topics in the Python Discord server, type: + +```plaintext +/topics scope:GUILD +``` + +The bot will return an overview of the most popular topics in the server. + +
+ [![Popular Topics](../assets/user-guide/topics.png)](../assets/user-guide/topics.png) +
Popular Topics
+
+ +You can also use the `Show user interests` context menu option to see what a particular user likes to talk about. + +### Most Active Times + +If you're curious about when the server is most active, you can use the `frequency` interaction to see a graph +of message frequency over a given time period. + +For example, to see the daily message frequency for the last 7 days, type: + +```plaintext +/frequency duration:DAILY +``` + +
+ [![Message Frequency](../assets/user-guide/frequency.png)](../assets/user-guide/frequency.png) +
Most Active Times
+
+ +Alternatively you can specify `HOURLY` to get the hourly message frequency for the last 24 hours, or `MINUTE` +for the last 60 minutes. + +## Contributing to a Safe Environment + +Courageous Comets can also help moderators maintain a positive and welcoming environment in their servers. +The sentiment analysis used to power the search features can also be used to identify toxic behavior and spam. + +Use the sentiment analysis interaction on a particular message or user to get an overview of their attitude. + +
+ [![User Sentiment](../assets/user-guide/user-sentiment.png)](../assets/user-guide/user-sentiment.png) +
User Sentiment
+
+ +If a user's attitude is consistently positive, the bot provides a mechanism to praise them for their contributions. +When you praise a user, the bot will send them a message to let them know they're appreciated! + +
+ [![Praise](../assets/user-guide/praise.png)](../assets/user-guide/praise.png) +
Praise Message
+
+ +Likewise, if the sentiment analysis indicates negative or toxic behavior, this can be addressed by the moderation +team. + +## About the Bot + +If you ever need a quick overview of the bot and its features, you can use the `about` interaction. + +```plaintext +/about +``` + +The bot will provide a brief description of its capabilities and how to use them. The message also includes a link +to this documentation for more detailed information! + +
+ [![About](../assets/user-guide/about.png)](../assets/user-guide/about.png) +
About the Bot
+
+ +## Interactions overview + +The table below lists all available interactions and in which context they can be used. + +| Interaction | Description | Available As | +| ------------------ | ----------------------------------------------------------------- | ------------------------------------ | +| Search | Find channels, users messages that match your interests. | `/search`, Message context | +| Popular Topics | Show the most popular topics for a given server, channel or user. | `/topics`, User context | +| Sentiment Search | Find channels, users and messages with similar attitudes. | `/sentiment_search`, Message context | +| Sentiment Analysis | Analyze the overall attitude of a message or a user. | User context, Message context | +| Frequency | Show the message frequency on a server over a given time period. | `/frequency` | +| About | Get a brief overview of the bot and its features. | `/about` | +| Ping | Check if the bot is online and responsive. | `/ping` | diff --git a/docs/user-guide/installing-the-bot.md b/docs/user-guide/installing-the-bot.md index f10a0df..c0e8a99 100644 --- a/docs/user-guide/installing-the-bot.md +++ b/docs/user-guide/installing-the-bot.md @@ -1,3 +1,5 @@ + + # Installing the Bot This guide will help you get started with the Courageous Comets Discord bot. You'll learn how to invite the bot @@ -7,7 +9,6 @@ to your server and use its basic features. Adding the bot to your Discord server is easy - just click the button below! - [Add to Discord :fontawesome-brands-discord:](https://discord.com/oauth2/authorize?client_id=1262672493978714174){ .md-button .md-button--primary } ## Initial Setup @@ -22,10 +23,12 @@ can do so by mentioning the bot and using the `sync` command as shown below: After running the `sync` command, the bot will confirm the number of interactions that are now available. ??? TIP "Run the `sync` command regularly" + It's a good idea to run the `sync` command regularly to stay up-to-date with the latest features and interactions. You're all set! The bot is now ready to use on your server. !!! SUCCESS "Results may vary initially" + The quality of responses will improve over time as the bot collects more data. The more active your server is, the faster the bot will learn. diff --git a/mkdocs.yaml b/mkdocs.yaml index e8efa55..d2e702b 100644 --- a/mkdocs.yaml +++ b/mkdocs.yaml @@ -38,27 +38,28 @@ theme: nav: - Users: - - user-guide/index.md - - Getting Started: user-guide/getting-started.md - - Installing the Bot: user-guide/installing-the-bot.md - - Data Privacy: user-guide/data-privacy.md + - user-guide/index.md + - Getting Started: user-guide/getting-started.md + - Installing the Bot: user-guide/installing-the-bot.md + - Data Privacy: user-guide/data-privacy.md - Administrators: - - admin-guide/index.md - - Deployment: admin-guide/deployment.md - - Configuration: admin-guide/configuration.md - - Changelog: CHANGELOG.md + - admin-guide/index.md + - Deployment: admin-guide/deployment.md + - Configuration: admin-guide/configuration.md + - Changelog: CHANGELOG.md - Contributors: - - contributor-guide/index.md - - Architecture & Design: contributor-guide/architecture-design.md - - Development Environment: contributor-guide/development-environment.md - - Secrets Management: contributor-guide/secrets-management.md - - Version Control: contributor-guide/version-control.md - - Documentation: contributor-guide/documentation.md - - Testing: contributor-guide/testing.md + - contributor-guide/index.md + - Architecture & Design: contributor-guide/architecture-design.md + - Development Environment: contributor-guide/development-environment.md + - Secrets Management: contributor-guide/secrets-management.md + - Version Control: contributor-guide/version-control.md + - Documentation: contributor-guide/documentation.md + - Testing: contributor-guide/testing.md markdown_extensions: - admonition - attr_list + - md_in_html - pymdownx.details - pymdownx.snippets - pymdownx.superfences: From 4febb96c5604a400a46a583f90d4ad39d3d99327 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Mon, 29 Jul 2024 19:20:20 +0000 Subject: [PATCH 151/168] docs: improve phrasing --- docs/user-guide/getting-started.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/user-guide/getting-started.md b/docs/user-guide/getting-started.md index 4681526..323f0f6 100644 --- a/docs/user-guide/getting-started.md +++ b/docs/user-guide/getting-started.md @@ -10,8 +10,8 @@ interactions, refer to the [Interactions Overview](#interactions-overview) at th ## Finding Your Way Around -Let's say you've just joined the Python Discord server and you are interested in web development with Django. -Let's use the search feature to find the most recent messages related to those topics. +Let's say you've just joined a new server and you are interested in web development with Django. We'll use the +search feature to find the most recent messages related to those topics. Type the following command in any channel: From 94596fe4e4a9478720e808c10d79c4937988e95f Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Mon, 29 Jul 2024 19:21:28 +0000 Subject: [PATCH 152/168] docs: emphasize context menu features for search --- docs/user-guide/getting-started.md | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/docs/user-guide/getting-started.md b/docs/user-guide/getting-started.md index 323f0f6..9d70009 100644 --- a/docs/user-guide/getting-started.md +++ b/docs/user-guide/getting-started.md @@ -27,10 +27,8 @@ the channels and message authors, so you can get an idea of where the conversati
Search Results
-??? TIP "Message Context" - - The search feature is also available as a context menu option when you right-click on a message. Use this when - you see an interesting message and want to find more like it. +The search feature is also available as a context menu option when you right-click on a message. Use this when +you see an interesting message and want to find more like it. ## Discovering Like-Minded People @@ -51,10 +49,8 @@ of the messages to help you explore further.
Sentiment Search Results
-??? TIP "Message Context" - - The sentiment search feature is also available as a context menu option when you right-click on a message. - Use this when you find a message that resonates with you and want to find more like it. +The sentiment search feature is also available as a context menu option when you right-click on a message. +Use this when you find a message that resonates with you and want to find more like it. ## Getting to Know the Community From 87d39828933af3082e1a27ea818686dd5fab296d Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Mon, 29 Jul 2024 19:24:43 +0000 Subject: [PATCH 153/168] docs: remove space after query --- docs/user-guide/getting-started.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/user-guide/getting-started.md b/docs/user-guide/getting-started.md index 9d70009..1d8d9fd 100644 --- a/docs/user-guide/getting-started.md +++ b/docs/user-guide/getting-started.md @@ -38,7 +38,7 @@ Let's say you're excited to learn more about programming and are looking for peo Try the following command: ```plaintext -/sentiment_search query: "excited to learn about programming" +/sentiment_search query:"excited to learn about programming" ``` The bot will return a list of messages that share your sentiment. Again, the results include the channels and authors From c3483c8f0cb854d1fb85a34e8b4451950f0b8883 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Tue, 30 Jul 2024 09:58:48 +0000 Subject: [PATCH 154/168] docs: reorganize home page using cards --- .markdownlint.json | 1 + docs/README.md | 47 +++++++++++++++++++++++++++++++--------------- 2 files changed, 33 insertions(+), 15 deletions(-) diff --git a/.markdownlint.json b/.markdownlint.json index 4448dcb..4f0486d 100644 --- a/.markdownlint.json +++ b/.markdownlint.json @@ -9,6 +9,7 @@ }, "MD033": { "allowed_elements": [ + "div", "img", "figcaption", "figure", diff --git a/docs/README.md b/docs/README.md index 6d19a7f..c9666e6 100644 --- a/docs/README.md +++ b/docs/README.md @@ -5,35 +5,52 @@ the Discord bot that helps you feel at home in any server! ## Features -Our bot is designed to help you: +
-### Discover and Connect Effortlessly 🚀 +- :fontawesome-solid-people-group:{ .lg .middle } **Discover new communities** -**Channel and Message Search**: Easily find channels and messages that match your interests. Courageous Comets -helps you quickly locate the communities you care about. + --- -**Similar Message Finder**: Found a message you like? Use the bot to find related messages across different channels. -Stay informed and engaged without endless scrolling. + Quickly evaluate whether a new server is the right fit for you. -### Foster Positive Communities 🤗 + [:octicons-arrow-right-24: Get to know a community](./user-guide/getting-started.md#getting-to-know-the-community) -**Sentiment Analysis**: Identify the most welcoming and positive communities on Discord. Our bot uses advanced -sentiment analysis to highlight the friendliest spaces and people. +- :octicons-search-16:{ .lg .middle } **Find your niche** -**Community Member Insights**: Curious about someone’s activity and attitude? Ask the bot for a summary and get -a quick overview of their contributions and demeanor. + --- -### Moderate with Ease 🛡️ + Easily locate the channels and users that match your interests and values. -**Toxic Behavior Detection**: Moderators can use Courageous Comets to spot toxic behavior and spam to maintain -a positive environment. + [:octicons-arrow-right-24: Find your way around](./user-guide/getting-started.md#finding-your-way-around) -**Reward Positive Contributions**: Recognize and reward community members who make positive or relevant contributions. + [:octicons-arrow-right-24: Discover like-minded people](./user-guide/getting-started.md#discovering-like-minded-people) + +- :material-human-greeting-variant:{ .lg .middle } **Foster positivity** + + --- + + Identify the most welcoming and positive communities on Discord and help them grow. + + [:octicons-arrow-right-24: Build safe environments](./user-guide/getting-started.md#contributing-to-a-safe-environment) + +- :material-scale-balance:{ .lg .middle } **Free and open-source** + + --- + + Courageous Comets is fully [open source](https://github.com/thijsfranck/courageous-comets/blob//) + and free to use for anyone! + + [:octicons-arrow-right-24: Install on your server](./user-guide/installing-the-bot.md) + + [:octicons-arrow-right-24: Deploy your own instance](./admin-guide//deployment.md) + +
## Contents This documentation is divided into the following sections: +- [User Guide](./user-guide/index.md): How to interact with the bot and make the most of its features. - [Administrator Guide](./admin-guide/index.md): How to deploy, configure and manage the bot in production. - [Contributor Guide](./contributor-guide/index.md): How to set up a development environment and contribute to the project. From 10ebc5ef9e1f04211d4d6544d373fc8329e7ff85 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Tue, 30 Jul 2024 13:55:13 +0000 Subject: [PATCH 155/168] docs: adjust introduction --- docs/user-guide/installing-the-bot.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/user-guide/installing-the-bot.md b/docs/user-guide/installing-the-bot.md index c0e8a99..235418e 100644 --- a/docs/user-guide/installing-the-bot.md +++ b/docs/user-guide/installing-the-bot.md @@ -3,7 +3,7 @@ # Installing the Bot This guide will help you get started with the Courageous Comets Discord bot. You'll learn how to invite the bot -to your server and use its basic features. +to your server and use its admin features. ## Installation From 4ef22295603740633e9a54a9430d870f59ab5440 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Tue, 30 Jul 2024 13:59:04 +0000 Subject: [PATCH 156/168] docs: update user guide screenshots --- docs/assets/user-guide/semantics-search.png | Bin 124553 -> 142746 bytes docs/assets/user-guide/sentiment-search.png | Bin 101814 -> 114163 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/assets/user-guide/semantics-search.png b/docs/assets/user-guide/semantics-search.png index 11f533fbc5de665742b34c7db3583b6ecd6e6417..ab89fd4c138b6e24ec44cf84c646e89b7eafb047 100644 GIT binary patch literal 142746 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
$y3mAxl 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 W@ZVd5+WV~^<)p6?lP=$W^8Wy}_+KCZ literal 124553 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

f()UV=LM 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 diff --git a/docs/assets/user-guide/sentiment-search.png b/docs/assets/user-guide/sentiment-search.png index 9e686fd9d93815f0ef78f94ab97ffc1700cbcbd8..0ce957d83b6c73fbf185e39f5d43f4b52e8c72fe 100644 GIT binary patch literal 114163 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 From 22ad6075d8730153bf8760637d10d1af07fcf170 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Tue, 30 Jul 2024 15:31:32 +0000 Subject: [PATCH 157/168] docs: extend configuration guide --- docs/admin-guide/configuration.md | 81 ++++++++++++++++++++++--------- 1 file changed, 59 insertions(+), 22 deletions(-) diff --git a/docs/admin-guide/configuration.md b/docs/admin-guide/configuration.md index ec9a5e7..608cdf8 100644 --- a/docs/admin-guide/configuration.md +++ b/docs/admin-guide/configuration.md @@ -11,6 +11,7 @@ The following environment variables are available to configure the application: | [`HF_DOWNLOAD_CONCURRENCY`](#hf_download_concurrency) | The maximum number of concurrent downloads when installing transformers. | No | `3` | | [`HF_HOME`](#hf_home) | The directory containing Huggingface Transformers data files. | No | `hf_data` | | [`LOG_LEVEL`](#log_level) | The minimum log level. | No | `INFO` | +| [`MPLCONFIGDIR`](#mplconfigdir) | The directory containing Matplotlib configuration files. | No | `/app/matplotlib` | | [`NLTK_DATA`](#nltk_data) | The directory containing NLTK data files. | No | `nltk_data` | | [`NLTK_DOWNLOAD_CONCURRENCY`](#nltk_download_concurrency) | The maximum number of concurrent downloads when installing NLTK data. | No | `3` | | [`PREPROCESSING_MAX_WORD_LENGTH`](#preprocessing_max_word_length) | The maximum word length. Longer words are dropped. | No | `35` | @@ -40,29 +41,10 @@ The following settings are optional or have default values that can be overridde ### `BOT_CONFIG_PATH` -This specifies the location of the bot's configuration file, which is a YAML file containing the following information: +This specifies the location of the bot's configuration file. By default, the application searches for a file named +`application.yaml` in the directory from which it is launched. In the Docker image, this file is located at `/app/application.yaml`. -```yaml -# List of cogs to load when the bot starts, identified by their package name. -cogs: - - - - -# List of cogs to load in development mode only, identified by their package name. -dev-cogs: - - - - -# List of NLTK datasets to download on startup. -nltk: - - - - -# List of Huggingface Transformers models to download on startup. -transformers: - - - - -``` - -By default, the application searches for a file named `application.yaml` in the directory from which it is launched. -In the Docker image, this file is located at `/app/application.yaml`. +[Read more](#applicationyaml) about the `application.yaml` file. ### `DISCORD_API_CONCURRENCY` @@ -95,6 +77,11 @@ The minimum log level to display. The following levels are available: The default log level is `INFO`. +### `MPLCONFIGDIR` + +The directory containing `matplotlib` configuration files. Uses the default `matplotlib` configuration directory +unless otherwise configured. In the Docker image, the `matplotlib` configuration files are located at `/app/matplotlib`. + ### `NLTK_DATA` The directory containing NLTK data files. By default, this is set to `nltk_data` in the directory from which the @@ -129,3 +116,53 @@ is set by default. !!! DANGER "Security Warning" Do not share your Redis password with anyone! + +## `application.yaml` + +The `application.yaml` file is a configuration file that specifies the cogs to load, the NLTK datasets to download, +and the Huggingface Transformers models to install on startup. It has the following structure: + +```yaml +# List of cogs to load when the bot starts, identified by their package name. +cogs: + - + - +# List of cogs to load in development mode only, identified by their package name. +dev-cogs: + - + - +# List of NLTK datasets to download on startup. +nltk: + - + - +# List of Huggingface Transformers models to download on startup. +transformers: + - + - +``` + +!!! WARNING "Advanced Users Only" + + For standard use, the `application.yaml` file does not need to be changed. We recommend against modifying this + file unless you know what you're doing. + +One possible use case is to add additional feature or disable existing features by modifying the list of cogs. +To add a new cog, add the package name to the `cogs` list. To disable a feature, remove the package name from +the list. + +Below is a list of all cogs that are loaded by default: + +| Package Name | Description | +| ------------------------------------------------------- | -------------------------------------------------------------------------------- | +| `courageous_comets.cogs.about` | Provides information about the bot. | +| `courageous_comets.cogs.keywords.search_command` | Searches for keywords using a slash command. | +| `courageous_comets.cogs.keywords.search_context_menu` | Searches for keywords using a context menu item. | +| `courageous_comets.cogs.keywords.topics_command` | Lists the most popular keywords for a given context using a slash command. | +| `courageous_comets.cogs.keywords.user_context_menu` | Lists the most popular keywords for a particular user using a context menu item. | +| `courageous_comets.cogs.messages` | Listens for messages and passes them on for internal processing. | +| `courageous_comets.cogs.ping` | Responds to ping requests. | +| `courageous_comets.cogs.sentiment.message_context_menu` | Analyzes the sentiment of a message using a context menu item. | +| `courageous_comets.cogs.sentiment.search_command` | Searches for messages with similar sentiment using a slash command. | +| `courageous_comets.cogs.sentiment.search_context_menu` | Searches for messages with similar sentiment using a context menu item. | +| `courageous_comets.cogs.sentiment.user_context_menu` | Analyzes the sentiment of a user's messages using a context menu item. | +| `courageous_comets.cogs.frequency` | Provides an overview of the amount of recent messages for a given timespan. | From 668e62ecc98307c04d61a75f8eec61e09171e916 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Tue, 30 Jul 2024 15:36:34 +0000 Subject: [PATCH 158/168] docs: extend design documentation --- docs/contributor-guide/architecture-design.md | 88 +++++++++++++++---- 1 file changed, 72 insertions(+), 16 deletions(-) diff --git a/docs/contributor-guide/architecture-design.md b/docs/contributor-guide/architecture-design.md index f35444e..a334149 100644 --- a/docs/contributor-guide/architecture-design.md +++ b/docs/contributor-guide/architecture-design.md @@ -17,15 +17,18 @@ graph LR subgraph Bot[Bot] subgraph Controllers[Controllers] MessagesCog["Messages"] + Interactions["Interactions"] end subgraph ApplicationLogic[Application Logic] Preprocessing{{"Preprocessing"}} - WordCount["Word Count"] - Sentiment["Sentiment Analysis"] - Vectorization["Vectorization"] + subgraph Processing + WordCount["Word Count"] + Sentiment["Sentiment Analysis"] + Vectorization["Vectorization"] + end end end - subgraph Storage[Storage] + subgraph Storage[Database] Redis[("Redis")] end end @@ -35,9 +38,11 @@ graph LR Preprocessing --->|"Text"| WordCount Preprocessing --->|"Text"| Sentiment Preprocessing --->|"Text"| Vectorization - WordCount -->|"Cache"| Redis - Sentiment -->|"Cache"| Redis - Vectorization -->|"Cache"| Redis + WordCount -->|"Token Count"| Redis + Sentiment -->|"Sentiment Scores"| Redis + Vectorization -->|"Vector Embedding"| Redis + User <-->|"Interactions"| Interactions + Interactions -->|"Queries"| Redis ``` ### Bot @@ -45,13 +50,41 @@ graph LR The bot is the main component of the Courageous Comets application. It is responsible for processing messages and performing analysis on them. -The bot is built using the [discord.py](https://discordpy.readthedocs.io/en/stable/) library. It is designed to -be modular and extensible, with the core features implemented on separate layers. +The bot is built using the [discord.py](https://discordpy.readthedocs.io/en/stable/) library. We chose this library +since it's the most mature library for building Discord bots in Python and did not expect to need any special +features provided by other libraries. + +The application is designed to be modular and extensible, with the core features implemented on separate layers. #### Controllers -Controllers are responsible for handling user input and invoking the appropriate application logic. Each controller -is a separate cog in the bot, allowing for easy extension and maintenance. +Controllers are responsible for handling user input and invoking the appropriate application logic. All controllers +are implemented as Discord cogs, which are modular components that can be enabled or disabled based on the +application configuration. + +##### Messages + +The messages cog is highlighted in the diagram as it is the primary controller for processing messages. It listens +for messages sent by users, and passes them on for internal processing. + +##### Interactions + +Other cogs included with the bot are responsible for handling interactions with users, such as slash commands, +context menus, and buttons. Typically, the response to an interaction will be a UI element like an embed. Embeds +may include charts, tables, or other visualizations. + +##### Design Decisions + +Responses from the bot should typically be sent as ephemeral messages, meaning they are only visible to the user +who triggered the interaction. The exception to this is when an interaction is supposed to be visible to all users. + +When an interaction is triggered, the bot should respond as soon as possible to acknowledge the interaction. In +case of loading times, the bot should provide a loading indicator to the user. In case of errors, useful feedback +should be provided to the user in the form of an error message. + +We decided to have a dedicated cog per interaction. For example, there is a separate cog for searching for keywords +using a slash command and a separate cog for searching for keywords using a context menu item. This split makes +sure that each cog does not grow too large and remains maintainable. #### Application Logic @@ -63,11 +96,30 @@ into several components: - **Sentiment Analysis**: Analyzes the sentiment of the input text. - **Vectorization**: Generates a vector representation to support similarity search. -### Storage +## Database The bot uses Redis as a database layer to store the results of the analysis. Redis is a fast and efficient key-value store that offers search and query features needed to enable the application logic. +### Design decisions + +We chose Redis over other databases like PostgreSQL or MongoDB because of its speed and simplicity. + +We knew that we'd be writing messages at a high rate and needed a database that could keep up with the volume +of data. Redis only writes to memory and periodically persists to disk, making it ideal for our workload of writing +a lot of small messages quickly. + +Further, we expected to need efficient full-text search capabilities and metric-based search capabilities. Redis +provides these features out of the box, making it a good fit for our use case. Further, we expected not to require +relational queries or complex joins, which are better suited for a relational database like PostgreSQL. + +Redis is also easy to set up and configure, making it a good choice for a small-scale application like Courageous +Comets. + +There is a small risk of data loss in case of a crash. In case of an unexpected shutdown, the application may +lose data that Redis has not yet persisted to disk. However, we are willing to accept this risk given that +missing a few messages will not affect the overall user experience. + ## Data Model The data model is designed to support a variety of analysis tasks and provide a flexible foundation for future @@ -92,7 +144,7 @@ are structured as follows: | `embedding` | `bytes` | `Vector` | The embedding vector of the message content | | `tokens` | `string` | N/A | JSON object mapping each token in the message to number of times it appeared in the message. | -#### Design Decisions +### Design Decisions While the fields ending with `_id` are integers on Discord, they are stored as strings on Redis and indexed as [Tag fields](https://redis.io/docs/latest/develop/interact/search-and-query/advanced-concepts/tags/) as opposed @@ -127,19 +179,23 @@ The application is fully contained within the `courageous_comets` package. The p | Module | Description | | ------------------ | ---------------------------------------------------------------------------------------- | -| `cogs` | Contains the bot controllers (cogs) that handle user input. | +| `cogs` | Provides the bot controllers (cogs) that handle user input. | +| `discord` | Implements functions for interacting with the Discord API. | | `nltk` | Contains helpers for using the Natural Language Toolkit (NLTK) library. | -| `redis` | Contains the data access layer for interacting with Redis. | +| `redis` | Provides the data access layer for interacting with Redis. | | `transformers` | Contains helpers for working with Huggingface Transformers. | +| `ui` | Includes all UI elements for the bot (`charts`, `components`, `embeds`, and `views`). | | `client.py` | Contains the main application client class. | | `__init__.py` | Entrypoint for the package. Exports the application client instance. | | `__main__.py` | Entrypoint for the application. Responsible for setup, teardown and root error handling. | | `enums.py` | Shared enumerations used across the application. | | `exceptions.py` | Includes the base exception class and custom exceptions used in the application. | -| `models.py` | Defines the entities used by the application using Pydantic models. | +| `models.py` | Defines the entities used by the application using `pydantic` models. | | `preprocessing.py` | Contains the preprocessing logic for cleaning and normalizing text. | +| `processing.py` | Implements the main processing logic for analyzing messages. | | `sentiment.py` | Implements the sentiment analysis logic using the NLTK library. | | `settings.py` | Provides input validation, default values and type hints for the app settings. | +| `utils.py` | Contains utility functions used across the application. | | `vectorizer.py` | Implements the vectorization logic using the Huggingface Transformers library. | | `words.py` | Contains the word count logic for counting the number of words in a text. | From 45da31fba42a546aafe07d753b187c338b09e55d Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Tue, 30 Jul 2024 15:52:56 +0000 Subject: [PATCH 159/168] docs: clarify choice for redis stack --- docs/contributor-guide/architecture-design.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/contributor-guide/architecture-design.md b/docs/contributor-guide/architecture-design.md index a334149..546dbfd 100644 --- a/docs/contributor-guide/architecture-design.md +++ b/docs/contributor-guide/architecture-design.md @@ -99,7 +99,8 @@ into several components: ## Database The bot uses Redis as a database layer to store the results of the analysis. Redis is a fast and efficient key-value -store that offers search and query features needed to enable the application logic. +store that offers search and query features needed to enable the application logic. Courageous Comets uses the +Redis Stack distribution, which includes plugins for full-text search and metric-based search. ### Design decisions @@ -109,9 +110,10 @@ We knew that we'd be writing messages at a high rate and needed a database that of data. Redis only writes to memory and periodically persists to disk, making it ideal for our workload of writing a lot of small messages quickly. -Further, we expected to need efficient full-text search capabilities and metric-based search capabilities. Redis -provides these features out of the box, making it a good fit for our use case. Further, we expected not to require -relational queries or complex joins, which are better suited for a relational database like PostgreSQL. +Further, we expected to need efficient full-text search capabilities and metric-based search capabilities. There +are plugins for Redis that provide these features. The Redis Stack distribution includes these plugins by default, +making it a good fit for our use case. Further, we expected not to require relational queries or complex joins, +which are better suited for a relational database like PostgreSQL. Redis is also easy to set up and configure, making it a good choice for a small-scale application like Courageous Comets. From 47a6130d09fc13366c171a0d098586de90e1bb67 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Tue, 30 Jul 2024 16:02:23 +0000 Subject: [PATCH 160/168] docs: improve phrasing --- docs/contributor-guide/architecture-design.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/contributor-guide/architecture-design.md b/docs/contributor-guide/architecture-design.md index 546dbfd..8fb3b71 100644 --- a/docs/contributor-guide/architecture-design.md +++ b/docs/contributor-guide/architecture-design.md @@ -112,15 +112,17 @@ a lot of small messages quickly. Further, we expected to need efficient full-text search capabilities and metric-based search capabilities. There are plugins for Redis that provide these features. The Redis Stack distribution includes these plugins by default, -making it a good fit for our use case. Further, we expected not to require relational queries or complex joins, -which are better suited for a relational database like PostgreSQL. +making it a good fit for our use case. + +Additionally, we expected not to require relational queries or complex joins, which are better suited for a relational +database like PostgreSQL. Redis is also easy to set up and configure, making it a good choice for a small-scale application like Courageous Comets. -There is a small risk of data loss in case of a crash. In case of an unexpected shutdown, the application may -lose data that Redis has not yet persisted to disk. However, we are willing to accept this risk given that -missing a few messages will not affect the overall user experience. +There is a small risk of data loss in case of a crash. The application may lose data that Redis has not yet persisted +to disk. However, we are willing to accept this risk given that missing a few messages will not affect the overall +user experience. ## Data Model From a4bd7626d42902450fba9050896789095793c0a0 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Tue, 30 Jul 2024 16:17:32 +0000 Subject: [PATCH 161/168] docs: capitalize headings --- docs/contributor-guide/architecture-design.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/contributor-guide/architecture-design.md b/docs/contributor-guide/architecture-design.md index 8fb3b71..4f7b020 100644 --- a/docs/contributor-guide/architecture-design.md +++ b/docs/contributor-guide/architecture-design.md @@ -102,7 +102,7 @@ The bot uses Redis as a database layer to store the results of the analysis. Red store that offers search and query features needed to enable the application logic. Courageous Comets uses the Redis Stack distribution, which includes plugins for full-text search and metric-based search. -### Design decisions +### Design Decisions We chose Redis over other databases like PostgreSQL or MongoDB because of its speed and simplicity. From 558240312a581cf5716ea5d58528cb38c7ea4091 Mon Sep 17 00:00:00 2001 From: Uchechukwu Orji Date: Tue, 30 Jul 2024 18:16:36 +0100 Subject: [PATCH 162/168] docs: reword redis data design decisions (#65) --- docs/contributor-guide/architecture-design.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/contributor-guide/architecture-design.md b/docs/contributor-guide/architecture-design.md index 4f7b020..09e5b08 100644 --- a/docs/contributor-guide/architecture-design.md +++ b/docs/contributor-guide/architecture-design.md @@ -151,15 +151,15 @@ are structured as follows: ### Design Decisions While the fields ending with `_id` are integers on Discord, they are stored as strings on Redis and indexed as -[Tag fields](https://redis.io/docs/latest/develop/interact/search-and-query/advanced-concepts/tags/) as opposed -to [Numeric](https://redis.io/docs/latest/develop/interact/search-and-query/basic-constructs/field-and-type-options/#numeric-fields) -because we want to make exact-match queries on these fields and also because they are more memory-efficient and -fast. +[Tags](https://redis.io/docs/latest/develop/interact/search-and-query/advanced-concepts/tags/) rather than +[Numeric](https://redis.io/docs/latest/develop/interact/search-and-query/basic-constructs/field-and-type-options/#numeric-fields) +because we want to make exact-match queries against these fields. Also, `Tags` are more memory-efficient and +faster to query. Rather than store the message as a JSON document with the sentiment-related values stored in a nested mapping, they are stored on the same hash with a prefix of `sentiment_`. This is because JSON documents generally have -a larger memory footprint compared to the Hash when searching over documents. Also, JSON documents take up more -space than the Hash. For context, the JSON document representation of the message takes at least 14Kb while the +a larger memory footprint compared to the hash when searching over documents. Also, JSON documents take up more +space than the hash. For context, the JSON document representation of the message takes at least 14Kb while the hash takes at most 4Kb. The [Cosine Similarity](https://en.wikipedia.org/wiki/Cosine_similarity) was chosen over the Euclidean distance From a95fa547785e2010b901b050578e1baa08daeff2 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Wed, 31 Jul 2024 08:13:02 +0000 Subject: [PATCH 163/168] docs: improve introduction of moderation features --- docs/user-guide/getting-started.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/user-guide/getting-started.md b/docs/user-guide/getting-started.md index 1d8d9fd..dc36335 100644 --- a/docs/user-guide/getting-started.md +++ b/docs/user-guide/getting-started.md @@ -98,10 +98,13 @@ for the last 60 minutes. ## Contributing to a Safe Environment -Courageous Comets can also help moderators maintain a positive and welcoming environment in their servers. -The sentiment analysis used to power the search features can also be used to identify toxic behavior and spam. +What's better than finding a server that aligns with your interests? When that server is filled +with friendly and welcoming people! -Use the sentiment analysis interaction on a particular message or user to get an overview of their attitude. +Courageous Comets helps moderators maintain a positive and safe environment in their servers. Use the bot to identify +the most helpful members of the community, or to detect toxic behavior and spam. + +Right-click on any user or message and use the sentiment analysis interaction to get an overview of their attitude.

[![User Sentiment](../assets/user-guide/user-sentiment.png)](../assets/user-guide/user-sentiment.png) From b3cb07fe5fb36422d464bd1f9dd77d9425472917 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Wed, 31 Jul 2024 08:13:19 +0000 Subject: [PATCH 164/168] docs: add design notes for analysis steps --- docs/contributor-guide/architecture-design.md | 75 +++++++++++++++++-- 1 file changed, 69 insertions(+), 6 deletions(-) diff --git a/docs/contributor-guide/architecture-design.md b/docs/contributor-guide/architecture-design.md index 09e5b08..ea482ee 100644 --- a/docs/contributor-guide/architecture-design.md +++ b/docs/contributor-guide/architecture-design.md @@ -16,15 +16,15 @@ graph LR subgraph Courageous Comets subgraph Bot[Bot] subgraph Controllers[Controllers] - MessagesCog["Messages"] - Interactions["Interactions"] + MessagesCog[messages] + Interactions[interactions] end subgraph ApplicationLogic[Application Logic] - Preprocessing{{"Preprocessing"}} + Preprocessing{{Preprocessing}} subgraph Processing - WordCount["Word Count"] - Sentiment["Sentiment Analysis"] - Vectorization["Vectorization"] + WordCount[Word Count] + Sentiment[Sentiment Analysis] + Vectorization[Vectorization] end end end @@ -96,6 +96,69 @@ into several components: - **Sentiment Analysis**: Analyzes the sentiment of the input text. - **Vectorization**: Generates a vector representation to support similarity search. +##### Preprocessing + +The preprocessing step is responsible for cleaning and normalizing the input text. This includes the following +steps: + +- Drop code blocks +- Drop links +- Replace special characters and diacritics with standard ASCII characters +- Expand contractions +- Drop punctuation +- Drop very long words +- Drop extra whitespace +- Truncate the text to a maximum length + +Preprocessing is essential to ensure that the input text is in a consistent format before further analysis is +performed and to avoid overloading the downstream components with irrelevant information. + +##### Word Count + +The word count analysis counts the number of keywords in the input message. This analysis is used to generate +the most popular topics in a channel or server. + +The first step in the word count analysis is to tokenize the input text. The text is split into individual words +using the NLTK library, which is a popular library for natural language processing in Python that provides a robust +tokenizer for English text out of the box. + +The tokenized text is then stemmed using NLTK's Snowball Stemmer. Stemming reduces words to their root form, which +helps to group similar words together. For example, the words "running" and "runs" would both be stemmed to "run". + +Next, we remove stopwords from the tokenized text. Stopwords are common words like "the", "and", and "is" that +do not carry much meaning and are typically removed from text before analysis. We also remove any words that are +single characters long, as these are unlikely to be meaningful. + +Finally, the keywords are counted and the results are stored in the database. + +##### Sentiment Analysis + +The sentiment analysis component is responsible for analyzing the attitude of an input message. This enables the +sentiment search feature and supports the moderation features of the bot. + +The sentiment analysisis performed using the NLTK library, which provides a pre-trained sentiment analysis model. +The model assigns polarity scores to the input text, which indicate the positive, negative, and neutral sentiment +of the text. Scores range from -1 (most negative) to 1 (most positive). The model also provides a compound score, +which is a normalized combination of the positive, negative, and neutral scores. + +The polarity scores for every message are stored in the database for later retrieval and analysis. + +##### Vectorization + +The vectorization component is responsible for generating a vector representation of the input text. The vector +representation is used to support similarity search, which allows users to find messages with similar content. + +Vectorization is done using the Sentence Transformers library, which provides pre-trained models for generating +embeddings of text. The embeddings are high-dimensional vectors that capture the semantic meaning of the input +text. + +First, the input text is tokenized using Sentence Transformers and passed into the transformer model to generate +the embedding for each token. These embeddings are then averaged to generate a single embedding for the entire +text. + +Finally, the embedding is passed into Torch to generate the final vector representation. This vector is stored +in the database for later retrieval and analysis. + ## Database The bot uses Redis as a database layer to store the results of the analysis. Redis is a fast and efficient key-value From e00502687e52a76ef5f1ac6e080bd1ba811a0383 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Wed, 31 Jul 2024 08:20:49 +0000 Subject: [PATCH 165/168] docs: break up sentiment analysis scores paragraph --- docs/contributor-guide/architecture-design.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/contributor-guide/architecture-design.md b/docs/contributor-guide/architecture-design.md index ea482ee..8557687 100644 --- a/docs/contributor-guide/architecture-design.md +++ b/docs/contributor-guide/architecture-design.md @@ -138,8 +138,10 @@ sentiment search feature and supports the moderation features of the bot. The sentiment analysisis performed using the NLTK library, which provides a pre-trained sentiment analysis model. The model assigns polarity scores to the input text, which indicate the positive, negative, and neutral sentiment -of the text. Scores range from -1 (most negative) to 1 (most positive). The model also provides a compound score, -which is a normalized combination of the positive, negative, and neutral scores. +of the text. + +Scores range from -1 (most negative) to 1 (most positive). The model also provides a compound score, which is a +normalized combination of the positive, negative, and neutral scores. The polarity scores for every message are stored in the database for later retrieval and analysis. From 91ad82878c6205bcac0f6ab1c2436f5feb13656a Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Wed, 31 Jul 2024 09:09:37 +0000 Subject: [PATCH 166/168] docs: clarify sentiment scoring method --- docs/contributor-guide/architecture-design.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/contributor-guide/architecture-design.md b/docs/contributor-guide/architecture-design.md index 8557687..1f7bde6 100644 --- a/docs/contributor-guide/architecture-design.md +++ b/docs/contributor-guide/architecture-design.md @@ -140,10 +140,13 @@ The sentiment analysisis performed using the NLTK library, which provides a pre- The model assigns polarity scores to the input text, which indicate the positive, negative, and neutral sentiment of the text. -Scores range from -1 (most negative) to 1 (most positive). The model also provides a compound score, which is a -normalized combination of the positive, negative, and neutral scores. +Each polarity score ranges from 0 to 1, with 0 indicating the absence of the sentiment. All three scores sum up +to 1. -The polarity scores for every message are stored in the database for later retrieval and analysis. +The model also provides a compound score, which is a normalized combination of the positive, negative, and neutral +scores. Compound scores range from -1 (most negative) to 1 (most positive). + +The polarity scores and compound score for every message are stored in the database for later retrieval and analysis. ##### Vectorization From 86e4939ed59fefa4363dbd357047548431447465 Mon Sep 17 00:00:00 2001 From: Uchechukwu Orji Date: Wed, 31 Jul 2024 12:20:44 +0100 Subject: [PATCH 167/168] docs: reword vectorization operation (#66) --- docs/contributor-guide/architecture-design.md | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/contributor-guide/architecture-design.md b/docs/contributor-guide/architecture-design.md index 1f7bde6..0365f5b 100644 --- a/docs/contributor-guide/architecture-design.md +++ b/docs/contributor-guide/architecture-design.md @@ -150,19 +150,19 @@ The polarity scores and compound score for every message are stored in the datab ##### Vectorization -The vectorization component is responsible for generating a vector representation of the input text. The vector -representation is used to support similarity search, which allows users to find messages with similar content. +The vectorization component is responsible for generating a vector embedding of the input text. The vector +embeddings support similarity search, which allows users to find messages with similar meaning. -Vectorization is done using the Sentence Transformers library, which provides pre-trained models for generating -embeddings of text. The embeddings are high-dimensional vectors that capture the semantic meaning of the input -text. +Vectorization is done using the [all-MiniLM-L6-v2](https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2) +transformer which maps sentences and paragraphs to a 384-dimensional dense vector space that captures the semantic +meaning of the input text. -First, the input text is tokenized using Sentence Transformers and passed into the transformer model to generate -the embedding for each token. These embeddings are then averaged to generate a single embedding for the entire -text. +First, a tokenizer is trained using the model and used to tokenize the text. The token embeddings are computed +using Torch and then a pooling operation is applied on top of the contextualized word embeddings. +These embeddings are then normalized to generate a single embedding for the entire text. -Finally, the embedding is passed into Torch to generate the final vector representation. This vector is stored -in the database for later retrieval and analysis. +Finally, the embedding vector is converted to bytes and this bytes representation is stored in the database +for later retrieval and analysis. ## Database From a567d6e78c6fc624fbbd79300191347cbf2c2a37 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Wed, 31 Jul 2024 14:56:40 +0000 Subject: [PATCH 168/168] docs: fix typo --- docs/admin-guide/deployment.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/admin-guide/deployment.md b/docs/admin-guide/deployment.md index 2761882..4bebc50 100644 --- a/docs/admin-guide/deployment.md +++ b/docs/admin-guide/deployment.md @@ -5,7 +5,7 @@ This section provides instructions on how to deploy the Courageous Comets application in a production environment. Follow the steps below to set up the application. -The application is deployed using [Docker](https://www.docker.com/) [Docker Compose](https://docs.docker.com/compose/). +The application is deployed using [Docker](https://www.docker.com/) and [Docker Compose](https://docs.docker.com/compose/). It consists of the following services: - [**courageous-comets**](https://github.com/thijsfranck/courageous-comets/pkgs/container/courageous-comets):

3x;6v=|!7UjA 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�$=E$p`S!g}=FKgs8Be!E}8!;x`_iez=( zR2XCR^z}R7!Dpo%#7RVc7W0VfzCG;N`ndIijHT;nXOmSJou7h9YD&SY=Z&(XSlgm* zaT{b^w%TYayx1LXm-l;%crP7Ka89U%$VZzqEOHrLx^_GP5}9TB(!3$&O+vE3ztD#lKsU($$+kdBAslyZs=+M+WtSo0dq-pT}K%)BY2J~xF|bvr~e^<&z5W@a zmIh*|5 zpGt4vdrdAXeb3nZa4gKQ>8#Q3w$(Lni-rdc;a+s-LsBHyo_-{KUso(MqlW(`s=?or zO`}9*M(xm`1N%1@1O*)jRJ7unTP!$G^g2{sHrB83uClC6!?vWjU*2R!js2#=Hzo5@ zEsRBdj^1O*1bMS=C)XUmf`7q!qy@cHA@)hVe#!eK4{0pUM`FsQcE>ks-+tI0Y_nUE z(cy7A>CcW_YDAM{{`B2Zmc5}h?y+T{QRc9zrRi8nI@dGW#0-@YTLZETksT275Mb&sJd0Cqm_5*kkO5Vu0^Wp&vy}A z(P&HRvd?+CF+~#uh1-P)v7WxKXk~5(f?)1pMMuZ7*m5I{l*@-i)6ewuX_ObXUAtJ7 zpWlP}s5GuOuLSIHsaxCIt|Rq5TZd?8ov8pM z5$iSiPp6kLy+sbRaEgS#tPOWf{L9l-%{Shdj-}BQx9d{9r)7k3@vkBEP+Qv4HIx(; z5y6`Ik#1>AhKC^Gy{veY4~lIZj_02|HZs_2&Y{K~a{HqB z;WmmQT}NQ;^Vodd(rzt2w{(5G?cu;K>{|xqc7%CTvmVxm%-jM>yCTti?@j8aQ4v29 zy}!+88l37cT2PRmoh2rxAv=G33v>5J+G)!Pcx@hfv-GGIXlYG%t{Sh673Q+A(oSvc z0sn3LtJ068d{5y&|N3g9d}awI7lN<^rj0ndy49DGj)zH-mMEJu=obAwX1#5kzw2s@ zHAkSnLtlK<@$oGIQpM{gDH#;XSDlhX6X6v5D==BhW_eP|PPK@ge zZ|xpB-X(A`tRGp+uD9=6^R(`MxJr~is!U<@Q23>hTWG_@51pfd{izuQ!TZ-?vAAI5 zfRStadC)>3v>fZ{>7($M#?z5D2YG*{{c-uN9dt)%Gj`^p{;0@0iXZBv`237zjCWp3 zTWMc%(>^1AjU9JL#$GFR!+lN0dFs|Tj3 zg{?4ZgNKH;?yjS6=4<(Y?Lk4y`;44kVsgFu@cNv%+Dkk$hs_Fv2P_C2pU&Riw zjc#lAT<3Y}Pvd_d>O{J7BlnCA=YOt^7siZk?xxJaGs_HZ&0P+2ra0;0yy*07leS+v zR**70r$C#TQ74akBi*3soc~1%q0{`lVOLkTUYM{BVRn3RW-J7DVn4i55TejM&EFb& zPdJpmQ~s{+T*XOHFy^xXQSm17Wt5+~MGT#cUXh(D78(vN!l|CF{cJ2X-Qp_BGKSmD z?wJD)kVtJzBpfQEJQW|wKJ$gzD}+>Jc9(4W tqIHVbFa?JDyCMj3$3RuWP+A(Hf#0LUKUZJ<3XxaeBt&II@`c~L|8MYgDzX3o literal 101814 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