diff --git a/.env-dev-example b/.env-dev-example deleted file mode 100644 index 8fedcb5b..00000000 --- a/.env-dev-example +++ /dev/null @@ -1,46 +0,0 @@ -################################################################## -# Kumiko's Dev Example ENV # -# Replace the values with the appropriate values # -################################################################## - -# More than likely you will not need to use the prod token for testing -# In some cases where Kumiko is near prod, this will get used instead -Kumiko_Token = "Prod-Bot-Token" - -# This is where your main bot token for testing should go. -Dev_Bot_Token = "Dev-Bot-Token" - -# Rin's API Keys -# These are used with Rin's services, and if you do plan on using these, then get the keys for them -# THESE WILL BREAK THE COMMANDS IF YOU DO NOT HAVE THE KEYS HERE -GitHub_API_Access_Token = "key" -Reddit_ID = "id" -Reddit_Secret = "secret" -Kumiko_Tenor_API_Key = "tenor_api_key" -Twitter_Bearer_Token = "twitter_bearer_token" -YouTube_API_Key = "yt_api_key" - -# IPC Secret Key -# This is used to communicate with the IPC server -IPC_Secret_Key = "key" - -# Dev Database Credentials - For PostgreSQL -# Set Postgres_Username and Postgres_Kumiko_Database to the same values -# WITHOUT THESE SET, THE ECONOMY SYSTEM WILL NOT WORK AND WILL BREAK -Postgres_Password = "password" -Postgres_Username = "bot" -Postgres_Server_IP = "host" -Postgres_Port = "5432" -Postgres_Kumiko_Database = "bot" - -# Dev Database Credentials - For MongoDB -# WITHOUT THESE SET, THE ECONOMY SYSTEM WILL NOT WORK AND WILL BREAK -MongoDB_Password = "password" -MongoDB_Username = "bot" -MongoDB_Server_IP = "host" -MongoDB_Server_Port = "27017" - -# Dev Database Credentials - For Redis -# WITHOUT THESE SET, THE ECONOMY SYSTEM WILL NOT WORK AND WILL BREAK -Redis_Server_IP = "host" -Redis_Port = "6379" \ No newline at end of file diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 4f78d576..e5e8247c 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -26,7 +26,7 @@ jobs: uses: actions/checkout@v3 - name: Set up Python 3.11 id: setup-python - uses: actions/setup-python@v4.5.0 + uses: actions/setup-python@v4.6.1 with: python-version: '3.11' - name: Set up Poetry diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 626909ad..bf15f713 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -24,16 +24,16 @@ jobs: images: | ghcr.io/no767/kumiko tags: | - type=semver,pattern={{raw}},suffix=-bullseye - type=semver,pattern={{version}},suffix=-bullseye - type=semver,pattern={{major}}.{{minor}},suffix=-bullseye - type=semver,pattern={{major}},suffix=-bullseye + type=semver,pattern={{raw}},suffix=-bookworm + type=semver,pattern={{version}},suffix=-bookworm + type=semver,pattern={{major}}.{{minor}},suffix=-bookworm + type=semver,pattern={{major}},suffix=-bookworm type=semver,pattern={{raw}} type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} type=semver,pattern={{major}} type=edge,branch=dev - type=edge,branch=dev,suffix=-bullseye + type=edge,branch=dev,suffix=-bookworm - name: Setup Docker Buildx id: buildx @@ -56,7 +56,7 @@ jobs: restore-keys: | ${{ runner.os }}-buildx-debian-ghcr- - name: Build and push - uses: docker/build-push-action@v4.0.0 + uses: docker/build-push-action@v4.1.1 with: context: . file: ./Docker/Dockerfile @@ -84,16 +84,16 @@ jobs: images: | no767/kumiko tags: | - type=semver,pattern={{raw}},suffix=-bullseye - type=semver,pattern={{version}},suffix=-bullseye - type=semver,pattern={{major}}.{{minor}},suffix=-bullseye - type=semver,pattern={{major}},suffix=-bullseye + type=semver,pattern={{raw}},suffix=-bookworm + type=semver,pattern={{version}},suffix=-bookworm + type=semver,pattern={{major}}.{{minor}},suffix=-bookworm + type=semver,pattern={{major}},suffix=-bookworm type=semver,pattern={{raw}} type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} type=semver,pattern={{major}} type=edge,branch=dev - type=edge,branch=dev,suffix=-bullseye + type=edge,branch=dev,suffix=-bookworm - name: Setup Docker Buildx id: buildx @@ -116,7 +116,7 @@ jobs: ${{ runner.os }}-buildx-debian-hub- - name: Build and push - uses: docker/build-push-action@v4.0.0 + uses: docker/build-push-action@v4.1.1 with: context: . file: ./Docker/Dockerfile diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 53b355ee..f0b8eae8 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -8,37 +8,42 @@ on: branches: - dev -env: - DATABASE_URL: postgresql://postgres:postgres@localhost:5432/postgres +# env: +# DATABASE_URL: postgresql://postgres:postgres@localhost:5432/postgres jobs: Analyze: runs-on: ubuntu-latest - services: - postgres: - image: postgres:15 - env: - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - POSTGRES_DB: postgres - ports: - - 5432:5432 - options: >- - --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 + # services: + # postgres: + # image: postgres:15 + # env: + # POSTGRES_USER: postgres + # POSTGRES_PASSWORD: postgres + # POSTGRES_DB: postgres + # ports: + # - 5432:5432 + # options: >- + # --health-cmd pg_isready + # --health-interval 10s + # --health-timeout 5s + # --health-retries 5 + + strategy: + fail-fast: false + matrix: + version: [3.8, 3.9, '3.10', '3.11'] steps: - name: Checkout Repository uses: actions/checkout@v3 - - name: Set up Python 3.11 + - name: Set up Python ${{ matrix.version }} id: setup-python - uses: actions/setup-python@v4.5.0 + uses: actions/setup-python@v4.6.1 with: - python-version: '3.11' + python-version: ${{ matrix.version }} - name: Set up Poetry uses: Gr1N/setup-poetry@v8 @@ -48,17 +53,17 @@ jobs: uses: actions/cache@v3.3.1 with: path: ~/.cache/pypoetry/virtualenvs - key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }} + key: ${{ runner.os }}-poetry-lint-${{ matrix.version }}-${{ hashFiles('**/poetry.lock') }} - name: Install Poetry Dependencies if: steps.cache-poetry.outputs.cache-hit != 'true' run: | poetry install --with dev - - name: Generate Prisma Client - run: | - poetry run prisma db push - - name: Run Pyright run: | poetry run pyright Bot + + - name: Run Ruff + run: | + poetry run ruff Bot diff --git a/.github/workflows/snyk.yml b/.github/workflows/snyk.yml index b1c0a75b..3bc7958c 100644 --- a/.github/workflows/snyk.yml +++ b/.github/workflows/snyk.yml @@ -14,13 +14,13 @@ jobs: uses: actions/checkout@v3 - name: Set up Python 3.11 id: setup-python - uses: actions/setup-python@v4.5.0 + uses: actions/setup-python@v4.6.1 with: python-version: '3.11' - name: Set up Node.js 18 - uses: actions/setup-node@v3.6.0 + uses: actions/setup-node@v3.7.0 with: - node-version: '18' + node-version: '20' - name: Install Snyk CLI run: | npm install --location=global snyk@latest diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ed4a8039..de016572 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -10,10 +10,8 @@ on: - dev env: - DATABASE_URL: postgresql://postgres:postgres@localhost:5432/postgres - REDIS_HOST: localhost - REDIS_PORT: 6379 - REDIS_PASSWORD: kumiko + POSTGRES_URI: postgresql://postgres:postgres@localhost:5432/postgres + REDIS_URI: redis://localhost:6379/0 jobs: @@ -58,7 +56,7 @@ jobs: - name: Set up Python id: setup-python - uses: actions/setup-python@v4.5.0 + uses: actions/setup-python@v4.6.1 with: python-version: ${{ matrix.version }} diff --git a/.gitignore b/.gitignore index 33bb74b8..b632800a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,83 +1,178 @@ -*.env -Bot/.env -.vscode -*.vscode -*.idea -.idea -__pycache__ -reddit-api-oauth.py -reddit -*reddit.db -*responses.json -Kana -.pytest_cache -disquest -.dccache -*.dccache -.metadata -*.metadata -json_parser.py -deviantarttest.py -chat/responses.json -chat -*.un~ # Vim's temp files -*.py~ # Also Vim's temp files -.py~ -.un~ -.jisho.py.un~ -jisho.py~ -.gitignore~ -..gitignore.un~ -kanji_jmdict.py -tester.py -twitterv2_cred.yaml -qrcode -changelog.md.un~ -*changelog.md~ -jikan-tester.py -spiget-tester.py -instagram-tester.py -modrinth-tester.py -top-gg-tester.py -pinterest-tester.py -spotify-tester.py -Cog-Testers/ -.cache -DeviantArt_API_Keys.txt -deviantart_links -Cogs-Misc/ -deviantart-tokens/ -test.db -text.txt -tokens.db -daTokens/ -button-test.py -reddit-links.txt -test-script.py -pages.py -Bot/database-testing.py -spotify-links.txt -Bot/Cogs/test.py -marketplace_filters.yml -Bot/Cog/economy/config/* -Bot/Cog/economy/config -config -gws-items.py -docker-compose.yml -docker-compose -docker-compose2.yml -redis-cache.py -eco_user_tester.py -quests-test.py -test.sh -user-inv-test.py -ah-test.py -gws-test.py -.env-docker -test-scripts -image-testspy -.vagrant -migrations -.env-old -database.db -json-tests \ No newline at end of file +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# 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 + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# 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/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# 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/ + +# Vagrant project cache files and others +.vagrant/ +inventory.yml +inv.yml + + +# VSC +.vscode/ + +# Cache files +.dccache + +# Testing scripts +test-scripts/ + +# Ruff cache +.ruff_cache/ \ No newline at end of file diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5c0db1a5..b61cebed 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,5 +1,5 @@ default_language_version: - python: python3.10 + python: python3.11 files: '.py' exclude: ".env,.yml,.gitignore,.git,.md,.txt" default_stages: [push, commit] @@ -23,7 +23,7 @@ repos: # description: "Format with AutoPEP8" - repo: https://github.com/psf/black - rev: 22.3.0 + rev: 22.6.0 hooks: - id: black name: Black @@ -39,7 +39,7 @@ repos: stages: [commit] - repo: https://github.com/PyCQA/isort - rev: 5.10.1 + rev: 5.12.0 hooks: - id: isort name: ISort diff --git a/.python-version b/.python-version deleted file mode 100644 index f4687fd4..00000000 --- a/.python-version +++ /dev/null @@ -1 +0,0 @@ -3.11.2 \ No newline at end of file diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 00000000..1979109d --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,13 @@ +version: 2 + +build: + os: ubuntu-22.04 + tools: + python: "3.11" + +sphinx: + configuration: Kumiko-Docs/source/conf.py + +python: + install: + - requirements: Kumiko-Docs/requirements.txt diff --git a/Anible-Playbooks/playbook.yml b/Anible-Playbooks/playbook.yml new file mode 100644 index 00000000..4de99c4f --- /dev/null +++ b/Anible-Playbooks/playbook.yml @@ -0,0 +1,105 @@ +- name: Setup PostgreSQL and Redis + hosts: dev + become: yes + become_method: sudo + roles: + - role: geerlingguy.postgresql + - role: geerlingguy.redis + +- name: Setup Python and Poetry + hosts: dev + roles: + - role: staticdev.pyenv + +- name: Install any dependecies + hosts: dev + become: yes + become_method: sudo + tasks: + - name: Install dependencies + ansible.builtin.apt: + update_cache: yes + pkg: + - curl + - libffi-dev + - git + - libnacl-dev + - libopus-dev + - python3-dev + +- name: Compile Python and install Poetry + hosts: dev + vars: + virtualenvs_inproject: true + virtualenvs_prefer_active_python: true + python_version: 3.11.4 + environment: + PATH: "{{ ansible_env.HOME }}/pyenv/bin:{{ ansible_env.PATH }}" + tasks: + - name: Install Python + ansible.builtin.shell: pyenv install "{{ python_version }}" -v + - name: Set global Python version + ansible.builtin.shell: pyenv global "{{ python_version }}" + - name: Rehash pyenv + ansible.builtin.shell: pyenv rehash + +- name: Setup Poetry + hosts: dev + vars: + virtualenvs_inproject: true + virtualenvs_prefer_active_python: true + tasks: + - name: Install Poetry # noqa command-instead-of-module + ansible.builtin.shell: curl -sSL https://install.python-poetry.org | python3 - + args: + creates: "{{ ansible_env.HOME }}/.local/bin/poetry" + + - name: Check Poetry virtualenvs.in-project config + ansible.builtin.command: ~/.local/bin/poetry config virtualenvs.in-project + register: poetry_in_project + changed_when: false + when: virtualenvs_inproject + + - name: Configure Poetry virtualenvs.in-project + ansible.builtin.command: ~/.local/bin/poetry config virtualenvs.in-project true + when: virtualenvs_inproject and poetry_in_project.stdout != "true" + + - name: Check Poetry virtualenvs.prefer-active-python config + ansible.builtin.command: ~/.local/bin/poetry config virtualenvs.prefer-active-python + register: poetry_prefer_active_python + changed_when: false + when: virtualenvs_prefer_active_python + + - name: Configure Poetry virtualenvs.prefer-active-python + ansible.builtin.command: ~/.local/bin/poetry config virtualenvs.prefer-active-python true + when: virtualenvs_prefer_active_python and poetry_prefer_active_python.stdout != "true" + +- name: Setup Kumiko repo + hosts: dev + vars: + kumiko_home: "{{ lookup('env', 'HOME') }}/Kumiko" + local_bin: "$PATH:{{ ansible_env.HOME }}/.local/bin:{{ ansible_env.HOME }}/pyenv/bin" + python_version: 3.11.4 + tasks: + - name: Ensure that local bin is in path + ansible.builtin.shell: | + export PATH={{ local_bin }} + echo export PATH={{ local_bin }} >> {{ ansible_env.HOME }}/.bashrc + - name: Clone Kumiko repo + ansible.builtin.git: + repo: "https://github.com/No767/Kumiko.git" + dest: "{{ kumiko_home }}" + version: dev + - name: Use local python version + ansible.builtin.shell: pyenv local {{ python_version }} + - name: Install Poetry dependencies + ansible.builtin.shell: | + poetry env use {{ python_version }} + poetry install + args: + chdir: "{{ kumiko_home }}" + - name: Setup Dev ENV file + ansible.builtin.shell: cp Envs/dev.env Bot/.env + args: + chdir: "{{ kumiko_home }}" + \ No newline at end of file diff --git a/Anible-Playbooks/postgres.yml b/Anible-Playbooks/postgres.yml new file mode 100644 index 00000000..b18c8d6b --- /dev/null +++ b/Anible-Playbooks/postgres.yml @@ -0,0 +1,5 @@ +postgresql_databases: + - name: kumiko +postgresql_users: + - name: kumiko + password: somepass # SET THE PASSWORD HERE! \ No newline at end of file diff --git a/Bot/Cogs/actions.py b/Bot/Cogs/actions.py index f152afaa..3c9bb28f 100644 --- a/Bot/Cogs/actions.py +++ b/Bot/Cogs/actions.py @@ -1,94 +1,109 @@ -import aiohttp import discord import orjson from discord import app_commands from discord.ext import commands -from Libs.utils import Embed +from discord.ext.commands import Greedy +from kumikocore import KumikoCore +from Libs.utils import Embed, formatGreedy class Actions(commands.Cog): - """Hug, pet, or kiss someone on Discord!""" + """Hug, pet, or kiss someone on Discord""" - def __init__(self, bot: commands.Bot) -> None: + def __init__(self, bot: KumikoCore) -> None: self.bot = bot + self.session = self.bot.session + + @property + def display_emoji(self) -> discord.PartialEmoji: + return discord.PartialEmoji.from_str("<:headpat:1020641548645437491>") @commands.hybrid_command(name="hug") @app_commands.describe(user="The user to hug") - async def hug(self, ctx: commands.Context, user: discord.Member) -> None: + async def hug(self, ctx: commands.Context, user: Greedy[discord.Member]) -> None: """Hug someone on Discord!""" - async with aiohttp.ClientSession() as session: - async with session.get("https://nekos.life/api/v2/img/hug") as r: - data = await r.json(loads=orjson.loads) - embed = Embed(title=f"{ctx.author.name} hugs {user.name}!") - embed.set_image(url=data["url"]) - await ctx.send(embed=embed) + async with self.session.get("https://nekos.life/api/v2/img/hug") as r: + data = await r.json(loads=orjson.loads) + embed = Embed( + title=f"{ctx.author.name} hugs {formatGreedy([items.name for items in user])}!" + ) + embed.set_image(url=data["url"]) + await ctx.send(embed=embed) @commands.hybrid_command(name="pat") @app_commands.describe(user="The user to pat") - async def pat(self, ctx: commands.Context, user: discord.Member) -> None: + async def pat(self, ctx: commands.Context, user: Greedy[discord.Member]) -> None: """Give someone a headpat!""" - async with aiohttp.ClientSession() as session: - async with session.get("https://nekos.life/api/v2/img/pat") as r: - data = await r.json(loads=orjson.loads) - embed = Embed(title=f"{ctx.author.name} pats {user.name}!") - embed.set_image(url=data["url"]) - await ctx.send(embed=embed) + async with self.session.get("https://nekos.life/api/v2/img/pat") as r: + data = await r.json(loads=orjson.loads) + embed = Embed( + title=f"{ctx.author.name} pats {formatGreedy([items.name for items in user])}!" + ) + embed.set_image(url=data["url"]) + await ctx.send(embed=embed) @commands.hybrid_command(name="kiss") @app_commands.describe(user="The user to kiss") - async def kiss(self, ctx: commands.Context, user: discord.Member) -> None: + async def kiss(self, ctx: commands.Context, user: Greedy[discord.Member]) -> None: """Give someone a kiss!""" - async with aiohttp.ClientSession() as session: - async with session.get("https://nekos.life/api/v2/img/kiss") as r: - data = await r.json(loads=orjson.loads) - embed = Embed(title=f"{ctx.author.name} kisses {user.name}!") - embed.set_image(url=data["url"]) - await ctx.send(embed=embed) + async with self.session.get("https://nekos.life/api/v2/img/kiss") as r: + data = await r.json(loads=orjson.loads) + embed = Embed( + title=f"{ctx.author.name} kisses {formatGreedy([items.name for items in user])}!" + ) + embed.set_image(url=data["url"]) + await ctx.send(embed=embed) @commands.hybrid_command(name="cuddle") @app_commands.describe(user="The user to cuddle") - async def cuddle(self, ctx: commands.Context, user: discord.Member) -> None: + async def cuddle(self, ctx: commands.Context, user: Greedy[discord.Member]) -> None: """Cuddle someone on Discord!""" - async with aiohttp.ClientSession() as session: - async with session.get("https://nekos.life/api/v2/img/cuddle") as r: - data = await r.json(loads=orjson.loads) - embed = Embed(title=f"{ctx.author.name} cuddles {user.name}!") - embed.set_image(url=data["url"]) - await ctx.send(embed=embed) + async with self.session.get("https://nekos.life/api/v2/img/cuddle") as r: + data = await r.json(loads=orjson.loads) + embed = Embed( + title=f"{ctx.author.name} cuddles {formatGreedy([items.name for items in user])}!" + ) + embed.set_image(url=data["url"]) + await ctx.send(embed=embed) @commands.hybrid_command(name="slap") @app_commands.describe(user="The user to slap") - async def slap(self, ctx: commands.Context, user: discord.Member) -> None: + async def slap(self, ctx: commands.Context, user: Greedy[discord.Member]) -> None: """Slaps someone on Discord!""" - async with aiohttp.ClientSession() as session: - async with session.get("https://nekos.life/api/v2/img/slap") as r: - data = await r.json(loads=orjson.loads) - embed = Embed(title=f"{ctx.author.name} slaps {user.name}!") - embed.set_image(url=data["url"]) - await ctx.send(embed=embed) + async with self.session.get("https://nekos.life/api/v2/img/slap") as r: + data = await r.json(loads=orjson.loads) + embed = Embed( + title=f"{ctx.author.name} slaps {formatGreedy([items.name for items in user])}!" + ) + embed.set_image(url=data["url"]) + await ctx.send(embed=embed) @commands.hybrid_command(name="tickle") @app_commands.describe(user="The user to tickle") - async def tickles(self, ctx: commands.Context, user: discord.Member) -> None: + async def tickles( + self, ctx: commands.Context, user: Greedy[discord.Member] + ) -> None: """Tickle someone on Discord!""" - async with aiohttp.ClientSession() as session: - async with session.get("https://nekos.life/api/v2/img/tickle") as r: - data = await r.json(loads=orjson.loads) - embed = Embed(title=f"{ctx.author.name} tickles {user.name}!") - embed.set_image(url=data["url"]) - await ctx.send(embed=embed) + async with self.session.get("https://nekos.life/api/v2/img/tickle") as r: + data = await r.json(loads=orjson.loads) + embed = Embed( + title=f"{ctx.author.name} tickles {formatGreedy([items.name for items in user])}!" + ) + embed.set_image(url=data["url"]) + await ctx.send(embed=embed) @commands.hybrid_command(name="poke") @app_commands.describe(user="The user to poke") - async def poke(self, ctx: commands.Context, user: discord.Member) -> None: + async def poke(self, ctx: commands.Context, user: Greedy[discord.Member]) -> None: """Poke someone on Discord!""" - async with aiohttp.ClientSession() as session: - async with session.get("https://nekos.life/api/v2/img/poke") as r: - data = await r.json(loads=orjson.loads) - embed = Embed(title=f"{ctx.author.name} pokes {user.name}!") - embed.set_image(url=data["url"]) - await ctx.send(embed=embed) + async with self.session.get("https://nekos.life/api/v2/img/poke") as r: + data = await r.json(loads=orjson.loads) + embed = Embed( + title=f"{ctx.author.name} pokes {formatGreedy([items.name for items in user])}!" + ) + embed.set_image(url=data["url"]) + await ctx.send(embed=embed) -async def setup(bot: commands.Bot) -> None: +async def setup(bot: KumikoCore) -> None: await bot.add_cog(Actions(bot)) diff --git a/Bot/Cogs/db.py b/Bot/Cogs/db.py deleted file mode 100644 index cbc92568..00000000 --- a/Bot/Cogs/db.py +++ /dev/null @@ -1,48 +0,0 @@ -import asyncio -import logging - -from discord.ext import commands, tasks -from Libs.utils import backoff -from prisma import Prisma # type: ignore -from prisma.engine.errors import EngineConnectionError # type: ignore - - -class PrismaClientSession(commands.Cog): - def __init__(self, bot: commands.Bot): - self.bot = bot - self.backoffSec = 10 - self.backoffSecIndex = 0 - self.db = Prisma(auto_register=True) - self.logger = logging.getLogger("discord") - - async def cog_load(self) -> None: - self.connect.start() - - async def cog_unload(self) -> None: - try: - await self.db.disconnect() - except EngineConnectionError as e: - self.logger.error( - f"Failed to disconnect from PostgreSQL database - {str(e)}" - ) - - @tasks.loop(count=1) - async def connect(self) -> None: - await self.db.connect() - self.logger.info("Successfully connected to PostgreSQL database") - - @connect.error # type: ignore - async def connError(self, exc: Exception) -> None: - backoffTime = backoff( - backoff_sec=self.backoffSec, backoff_sec_index=self.backoffSecIndex - ) - self.backoffSecIndex += 1 - self.logger.error( - f"({str(exc.__class__.__name__)}) Failed to connect to PostgreSQL database - Reconnecting in {int(backoffTime)} seconds" - ) - await asyncio.sleep(backoffTime) - self.connect.restart() - - -async def setup(bot: commands.Bot): - await bot.add_cog(PrismaClientSession(bot)) diff --git a/Bot/Cogs/dev-tools.py b/Bot/Cogs/dev-tools.py index 334dc1dc..fc1d1076 100644 --- a/Bot/Cogs/dev-tools.py +++ b/Bot/Cogs/dev-tools.py @@ -1,14 +1,22 @@ from typing import Literal, Optional import discord +from discord import app_commands from discord.ext import commands from discord.ext.commands import Context, Greedy +from kumikocore import KumikoCore -class DevTools(commands.Cog): - def __init__(self, bot: commands.Bot): +class DevTools(commands.Cog, command_attrs=dict(hidden=True)): + """Tools for developing Kumiko""" + + def __init__(self, bot: KumikoCore): self.bot = bot + @property + def display_emoji(self) -> discord.PartialEmoji: + return discord.PartialEmoji(name="\U0001f6e0") + @commands.hybrid_command(name="sync") @commands.guild_only() @commands.is_owner() @@ -55,6 +63,19 @@ async def sync( await ctx.send(f"Synced the tree to {ret}/{len(guilds)}.") + @commands.hybrid_command(name="dispatch") + @commands.guild_only() + @commands.is_owner() + @app_commands.describe(event="The event to dispatch") + async def dispatch_event(self, ctx: commands.Context, event: str) -> None: + """Dispatches an custom event + + Args: + ctx (commands.Context): _description_ + """ + self.bot.dispatch(event, ctx.guild, ctx.author) + await ctx.send("Dispatched event") + -async def setup(bot: commands.Bot): +async def setup(bot: KumikoCore): await bot.add_cog(DevTools(bot)) diff --git a/Bot/Cogs/error-handler.py b/Bot/Cogs/error-handler.py index 40fbc36a..38fd0993 100644 --- a/Bot/Cogs/error-handler.py +++ b/Bot/Cogs/error-handler.py @@ -2,14 +2,21 @@ from discord.app_commands.errors import CommandInvokeError from discord.ext import commands -from Libs.errors import HTTPError, KumikoException, NoItemsError, NotFoundError +from kumikocore import KumikoCore +from Libs.errors import ( + HTTPError, + KumikoException, + NoItemsError, + NotFoundError, + ValidationError, +) from Libs.utils import Embed, ErrorEmbed class ErrorHandler(commands.Cog): """Cog to handle errors""" - def __init__(self, bot: commands.Bot) -> None: + def __init__(self, bot: KumikoCore) -> None: self.bot = bot def fullException(self, obj): @@ -134,6 +141,11 @@ async def on_command_error( f"You are missing the following argument(s): {error.param.name}" ) await ctx.send(embed=errorEmbed) + elif isinstance(error, ValidationError): + errorEmbed = ErrorEmbed() + errorEmbed.title = "Validation Error" + errorEmbed.description = str(error) + await ctx.send(embed=errorEmbed) else: errorEmbed = ErrorEmbed() errorEmbed.add_field(name="Error", value=str(error), inline=False) @@ -145,5 +157,5 @@ async def on_command_error( await ctx.send(embed=errorEmbed) -async def setup(bot: commands.Bot) -> None: +async def setup(bot: KumikoCore) -> None: await bot.add_cog(ErrorHandler(bot)) diff --git a/Bot/Cogs/events-handler.py b/Bot/Cogs/events-handler.py new file mode 100644 index 00000000..44ca3aa4 --- /dev/null +++ b/Bot/Cogs/events-handler.py @@ -0,0 +1,133 @@ +from typing import Any + +import discord +from discord.ext import commands +from discord.utils import format_dt, utcnow +from kumikocore import KumikoCore +from Libs.cog_utils.events_log import get_or_fetch_config +from Libs.utils import CancelledActionEmbed, Embed, SuccessActionEmbed + + +class EventsHandler(commands.Cog): + """Cog for handling discord api events""" + + def __init__(self, bot: KumikoCore) -> None: + self.bot = bot + self.pool = self.bot.pool + self.redis_pool = self.bot.redis_pool + + def ensureEnabled(self, config: Any) -> bool: + if (config is not None) and ( + config["logs"] and config["member_events"] is True + ): + return True + else: + return False + + @commands.Cog.listener() + async def on_guild_join(self, guild: discord.Guild) -> None: + existsQuery = "SELECT EXISTS(SELECT 1 FROM guild WHERE id = $1);" + insertQuery = """ + INSERT INTO guild (id) VALUES ($1); + INSERT INTO logging_config (guild_id) VALUES ($1); + """ + async with self.pool.acquire() as conn: + async with conn.transaction(): + exists = await conn.fetchval(existsQuery, guild.id) + if not exists: + await conn.execute(insertQuery, guild.id) + self.bot.prefixes[guild.id] = [self.bot.default_prefix] + + @commands.Cog.listener() + async def on_guild_remove(self, guild: discord.Guild) -> None: + async with self.pool.acquire() as conn: + async with conn.transaction(): + await conn.execute("DELETE FROM guild WHERE id = $1", guild.id) + self.bot.prefixes[guild.id] = self.bot.default_prefix + + @commands.Cog.listener() + async def on_member_join(self, member: discord.Member) -> None: + guild = member.guild + getConfig = await get_or_fetch_config( + id=member.guild.id, redis_pool=self.redis_pool, pool=self.pool + ) + if self.ensureEnabled(getConfig): + channel = guild.get_channel(getConfig["channel_id"]) # type: ignore + if isinstance(channel, discord.TextChannel): + embed = SuccessActionEmbed() + embed.title = "Member Joined" + embed.set_thumbnail(url=member.display_avatar.url) + embed.description = f"{member.mention} {member.global_name}" + embed.timestamp = utcnow() + embed.add_field( + name="Account Age", value=format_dt(member.created_at, "R") + ) + await channel.send(embed=embed) + + @commands.Cog.listener() + async def on_member_remove(self, member: discord.Member) -> None: + guild = member.guild + getConfig = await get_or_fetch_config( + id=guild.id, redis_pool=self.redis_pool, pool=self.pool + ) + if self.ensureEnabled(getConfig): + channel = guild.get_channel(getConfig["channel_id"]) # type: ignore + if isinstance(channel, discord.TextChannel): + embed = CancelledActionEmbed() + embed.title = "Member Left" + embed.set_thumbnail(url=member.display_avatar.url) + embed.description = f"{member.mention} {member.global_name}" + embed.timestamp = utcnow() + embed.add_field( + name="Account Age", value=format_dt(member.created_at, "R") + ) + await channel.send(embed=embed) + + @commands.Cog.listener() + async def on_member_ban(self, guild: discord.Guild, user: discord.User) -> None: + getConfig = await get_or_fetch_config( + id=guild.id, redis_pool=self.redis_pool, pool=self.pool + ) + if self.ensureEnabled(getConfig): + channel = guild.get_channel(getConfig["channel_id"]) # type: ignore + if isinstance(channel, discord.TextChannel): + embed = CancelledActionEmbed() + embed.title = "Member Banned" + embed.set_thumbnail(url=user.display_avatar.url) + embed.description = f"{user.mention} {user.global_name}" + embed.timestamp = utcnow() + await channel.send(embed=embed) + + @commands.Cog.listener() + async def on_member_unban(self, guild: discord.Guild, user: discord.User) -> None: + getConfig = await get_or_fetch_config( + id=guild.id, redis_pool=self.redis_pool, pool=self.pool + ) + if self.ensureEnabled(getConfig): + channel = guild.get_channel(getConfig["channel_id"]) # type: ignore + if isinstance(channel, discord.TextChannel): + embed = Embed(color=discord.Color.from_rgb(255, 143, 143)) + embed.title = "Member Unbanned" + embed.set_thumbnail(url=user.display_avatar.url) + embed.description = f"{user.mention} {user.global_name}" + embed.timestamp = utcnow() + await channel.send(embed=embed) + + @commands.Cog.listener() + async def on_member_kick(self, guild: discord.Guild, user: discord.User) -> None: + getConfig = await get_or_fetch_config( + id=guild.id, redis_pool=self.redis_pool, pool=self.pool + ) + if self.ensureEnabled(getConfig): + channel = guild.get_channel(getConfig["channel_id"]) # type: ignore + if isinstance(channel, discord.TextChannel): + embed = CancelledActionEmbed() + embed.title = "Member Kicked" + embed.set_thumbnail(url=user.display_avatar.url) + embed.description = f"{user.mention} {user.global_name}" + embed.timestamp = utcnow() + await channel.send(embed=embed) + + +async def setup(bot: KumikoCore) -> None: + await bot.add_cog(EventsHandler(bot)) diff --git a/Bot/Cogs/events-log.py b/Bot/Cogs/events-log.py new file mode 100644 index 00000000..ec1d7224 --- /dev/null +++ b/Bot/Cogs/events-log.py @@ -0,0 +1,105 @@ +from discord import PartialEmoji +from discord.ext import commands +from kumikocore import KumikoCore +from Libs.ui.events_log import RegisterView, UnregisterView +from Libs.utils import ConfirmEmbed, Embed + + +class EventsLog(commands.Cog): + """Logging module to track joins, and economy events""" + + def __init__(self, bot: KumikoCore) -> None: + self.bot = bot + self.pool = self.bot.pool + self.redis_pool = self.bot.redis_pool + + @property + def display_emoji(self) -> PartialEmoji: + return PartialEmoji(name="\U0001f4f0") + + @commands.hybrid_group(name="logs") + async def logs(self, ctx: commands.Context) -> None: + """Logs events and actions on your server""" + if ctx.invoked_subcommand is None: + await ctx.send_help(ctx.command) + + @commands.has_guild_permissions(manage_guild=True) + @commands.guild_only() + @logs.command(name="enable") + async def enableLogs(self, ctx: commands.Context) -> None: + """Registers and enables events logging on the server""" + registerInfo = "In order to get started, **only** select one of the options within the dropdown menu in order to set it.\nOnce you are done, click the finish button." + embed = Embed(title="Registration Info") + embed.description = registerInfo + view = RegisterView(pool=self.pool, redis_pool=self.redis_pool) + await ctx.send(embed=embed, view=view) + + @commands.has_guild_permissions(manage_guild=True) + @commands.guild_only() + @logs.command(name="disable") + async def disableLogs(self, ctx: commands.Context) -> None: + """Disables and unregisters the events logging on the server""" + view = UnregisterView(pool=self.pool, redis_pool=self.redis_pool) + embed = ConfirmEmbed() + embed.description = "You are about to disable and unregister the events logging feature on Kumiko. Press Confirm to confirm your action." + await ctx.send(embed=embed, view=view) + + @commands.has_guild_permissions(manage_guild=True) + @commands.guild_only() + @logs.command(name="info") + async def logInfo(self, ctx: commands.Context) -> None: + """Displays info about the events logging module""" + query = """ + SELECT guild.id, guild.logs, logging_config.channel_id, logging_config.member_events, logging_config.mod_events, logging_config.eco_events + FROM guild + INNER JOIN logging_config + ON guild.id = logging_config.guild_id + WHERE guild.id = $1; + """ + guild_id = ctx.guild.id # type: ignore + async with self.pool.acquire() as conn: + result = await conn.fetchrow(query, guild_id) + embed = Embed() + embed.set_author(name="Events Logging Info", icon_url=ctx.guild.icon.url) # type: ignore + embed.description = f"**Enabled?:** {result['logs']}\n**Events Logging Channel:** <#{result['channel_id']}>" + embed.add_field( + name="Member Events", value=f"Enabled?: **{result['member_events']}**" + ) + embed.add_field( + name="Mod Events", value=f"Enabled?: **{result['mod_events']}**" + ) + embed.add_field( + name="Eco Events", value=f"Enabled?: **{result['eco_events']}**" + ) + await ctx.send(embed=embed) + + # TODO - Provide an interactive UI to set which one is enabled or not + # Also im well aware that this has a ton of issues that need to be ironed out + # But i will be fixing them before releasing v0.9.0 + # I just want to get this merged and done with + # an autocomplete is probably needed but im too lazy to figure out how to get it working + # @commands.has_guild_permissions(manage_guild=True) + # @commands.guild_only() + # @logs.command(name="configure") + # @app_commands.describe( + # event="The event to enable", status="Whether the event is enabled or disabled" + # ) + # async def logConfig(self, ctx: commands.Context, event: str, status: bool) -> None: + # """Configures which events are enabled""" + # guild_id = ctx.guild.id # type: ignore + # cache = KumikoCache(connection_pool=self.redis_pool) + # config = await get_or_fetch_config( + # id=guild_id, redis_pool=self.redis_pool, pool=self.pool + # ) + # if config is None or isinstance(config, str): + # await ctx.send("The config was not set up. Please enable the logs module") + # return + # if event in config and config[event] is True: + # key = f"cache:kumiko:{guild_id}:logging_config" + # await cache.setJSONCache(key=key, value=status, path=f".{event}") + # await ctx.send("Config updated. The event has been enabled/disabled") + # return + + +async def setup(bot: KumikoCore) -> None: + await bot.add_cog(EventsLog(bot)) diff --git a/Bot/Cogs/github.py b/Bot/Cogs/github.py index 48a2360d..6b806c6a 100644 --- a/Bot/Cogs/github.py +++ b/Bot/Cogs/github.py @@ -1,12 +1,12 @@ import os -import aiohttp import ciso8601 import orjson -from discord import app_commands +from discord import PartialEmoji, app_commands from discord.ext import commands from discord.utils import format_dt from dotenv import load_dotenv +from kumikocore import KumikoCore from Libs.errors import NotFoundError from Libs.utils import Embed from Libs.utils.pages import EmbedListSource, KumikoPages @@ -19,11 +19,17 @@ class Github(commands.Cog): """Search for releases and repos on GitHub""" - def __init__(self, bot: commands.Bot) -> None: + def __init__(self, bot: KumikoCore) -> None: self.bot = bot + self.session = self.bot.session + + @property + def display_emoji(self) -> PartialEmoji: + return PartialEmoji.from_str("<:githubmarkwhite:1127906278509912185>") @commands.hybrid_group(name="github") async def github(self, ctx: commands.Context) -> None: + """Github search and utility commands""" if ctx.invoked_subcommand is None: await ctx.send_help(ctx.command) @@ -34,115 +40,109 @@ async def githubReleasesList( self, ctx: commands.Context, owner: str, repo: str ) -> None: """Get up to 25 releases for a repo""" - async with aiohttp.ClientSession() as session: - headers = { - "Authorization": f"token {GITHUB_API_KEY}", - "accept": "application/vnd.github.v3+json", - } - params = {"per_page": 25} - async with session.get( - f"https://api.github.com/repos/{owner}/{repo}/releases", - headers=headers, - params=params, - ) as r: - data = await r.json(loads=orjson.loads) - if r.status == 404: - raise NotFoundError - else: - mainData = [ - { - "title": item["name"], - "description": item["body"], - "thumbnail": item["author"]["avatar_url"], - "fields": [ - {"name": "Author", "value": item["author"]["login"]}, - {"name": "URL", "value": item["html_url"]}, - { - "name": "Created At", - "value": format_dt( - ciso8601.parse_datetime(item["created_at"]) - ), - }, - { - "name": "Published At", - "value": format_dt( - ciso8601.parse_datetime(item["published_at"]) - ), - }, - {"name": "Tarball URL", "value": item["tarball_url"]}, - {"name": "Zip URL", "value": item["zipball_url"]}, - { - "name": "Download URL", - "value": [ - subItems["browser_download_url"] + headers = { + "Authorization": f"token {GITHUB_API_KEY}", + "accept": "application/vnd.github.v3+json", + } + params = {"per_page": 25} + async with self.session.get( + f"https://api.github.com/repos/{owner}/{repo}/releases", + headers=headers, + params=params, + ) as r: + data = await r.json(loads=orjson.loads) + if r.status == 404: + raise NotFoundError + else: + mainData = [ + { + "title": item["name"], + "description": item["body"], + "thumbnail": item["author"]["avatar_url"], + "fields": [ + {"name": "Author", "value": item["author"]["login"]}, + {"name": "URL", "value": item["html_url"]}, + { + "name": "Created At", + "value": format_dt( + ciso8601.parse_datetime(item["created_at"]) + ), + }, + { + "name": "Published At", + "value": format_dt( + ciso8601.parse_datetime(item["published_at"]) + ), + }, + {"name": "Tarball URL", "value": item["tarball_url"]}, + {"name": "Zip URL", "value": item["zipball_url"]}, + { + "name": "Download URL", + "value": [ + subItems["browser_download_url"] + for subItems in item["assets"] + ], + }, + { + "name": "Download Count", + "value": str( + [ + subItems["download_count"] for subItems in item["assets"] - ], - }, - { - "name": "Download Count", - "value": str( - [ - subItems["download_count"] - for subItems in item["assets"] - ] - ).replace("'", ""), - }, - ], - } - for item in data - ] - embedSource = EmbedListSource(mainData, per_page=1) - pages = KumikoPages(source=embedSource, ctx=ctx) - await pages.start() + ] + ).replace("'", ""), + }, + ], + } + for item in data + ] + embedSource = EmbedListSource(mainData, per_page=1) + pages = KumikoPages(source=embedSource, ctx=ctx) + await pages.start() @github.command(name="repo") @app_commands.describe(owner="The owner of the repo", repo="The repo to search") async def searchGitHub(self, ctx: commands.Context, owner: str, repo: str) -> None: """Searches for one repo on GitHub""" - async with aiohttp.ClientSession() as session: - headers = { - "Authorization": f"token {GITHUB_API_KEY}", - "accept": "application/vnd.github.v3+json", - } - async with session.get( - f"https://api.github.com/repos/{owner}/{repo}", headers=headers - ) as r: - data = await r.json(loads=orjson.loads) - if r.status == 404: - raise NotFoundError - else: - embed = Embed(title=data["name"], description=data["description"]) - embed.set_thumbnail(url=data["owner"]["avatar_url"]) - embed.add_field(name="Fork?", value=data["fork"]) - embed.add_field(name="Private", value=data["private"]) - embed.add_field(name="Stars", value=data["stargazers_count"]) - embed.add_field( - name="Language", - value=data["language"] - if data["language"] is not None - else "None", - ) - embed.add_field(name="URL", value=data["html_url"]) - embed.add_field( - name="Homepage", - value=data["homepage"] - if data["homepage"] is not None - else "None", - ) - embed.add_field( - name="Created At", - value=format_dt(ciso8601.parse_datetime(data["created_at"])), - ) - embed.add_field( - name="Updated At", - value=format_dt(ciso8601.parse_datetime(data["updated_at"])), - ) - embed.add_field( - name="Pushed At", - value=format_dt(ciso8601.parse_datetime(data["pushed_at"])), - ) - await ctx.send(embed=embed) + headers = { + "Authorization": f"token {GITHUB_API_KEY}", + "accept": "application/vnd.github.v3+json", + } + async with self.session.get( + f"https://api.github.com/repos/{owner}/{repo}", headers=headers + ) as r: + data = await r.json(loads=orjson.loads) + if r.status == 404: + raise NotFoundError + else: + embed = Embed(title=data["name"], description=data["description"]) + embed.set_thumbnail(url=data["owner"]["avatar_url"]) + embed.add_field(name="Fork?", value=data["fork"]) + embed.add_field(name="Private", value=data["private"]) + embed.add_field(name="Stars", value=data["stargazers_count"]) + embed.add_field( + name="Language", + value=data["language"] if data["language"] is not None else "None", + ) + embed.add_field(name="URL", value=data["html_url"]) + embed.add_field( + name="Homepage", + value=data["homepage"] if data["homepage"] is not None else "None", + ) + embed.add_field( + name="Created At", + value=format_dt(ciso8601.parse_datetime(data["created_at"])), + ) + embed.add_field( + name="Updated At", + value=format_dt(ciso8601.parse_datetime(data["updated_at"])), + ) + embed.add_field( + name="Pushed At", + value=format_dt(ciso8601.parse_datetime(data["pushed_at"])), + ) + await ctx.send(embed=embed) -async def setup(bot: commands.Bot) -> None: +async def setup(bot: KumikoCore) -> None: await bot.add_cog(Github(bot)) diff --git a/Bot/Cogs/ipc.py b/Bot/Cogs/ipc.py index 7c6661a9..a11372fa 100644 --- a/Bot/Cogs/ipc.py +++ b/Bot/Cogs/ipc.py @@ -1,35 +1,36 @@ -import os -from typing import Dict - -from discord.ext import commands, ipc -from discord.ext.ipc.objects import ClientPayload -from discord.ext.ipc.server import Server -from dotenv import load_dotenv - -load_dotenv() - -IPC_SECRET_KEY = os.getenv("IPC_SECRET_KEY") -IPC_HOST = os.environ["IPC_HOST"] - - -class IPCServer(commands.Cog): - def __init__(self, bot: commands.Bot): - self.bot = bot - self.ipc = ipc.Server( # type: ignore - self.bot, secret_key=IPC_SECRET_KEY, host=IPC_HOST - ) - - async def cog_load(self) -> None: - await self.ipc.start() - - async def cog_unload(self) -> None: - await self.ipc.stop() - - @Server.route() - async def get_user_data(self, data: ClientPayload) -> Dict: - user = self.bot.get_user(data.user_id) - return user._to_minimal_user_json() # type: ignore - - -async def setup(bot: commands.Bot): - await bot.add_cog(IPCServer(bot)) +import os +from typing import Dict + +from discord.ext import commands, ipc +from discord.ext.ipc.objects import ClientPayload +from discord.ext.ipc.server import Server +from dotenv import load_dotenv +from kumikocore import KumikoCore + +load_dotenv() + +IPC_SECRET_KEY = os.getenv("IPC_SECRET_KEY") +IPC_HOST = os.environ["IPC_HOST"] + + +class IPCServer(commands.Cog): + def __init__(self, bot: KumikoCore): + self.bot = bot + self.ipc = ipc.Server( # type: ignore + self.bot, secret_key=IPC_SECRET_KEY, host=IPC_HOST + ) + + async def cog_load(self) -> None: + await self.ipc.start() + + async def cog_unload(self) -> None: + await self.ipc.stop() + + @Server.route() + async def get_user_data(self, data: ClientPayload) -> Dict: + user = self.bot.get_user(data.user_id) + return user.to_minimal_user_json() # type: ignore + + +async def setup(bot: KumikoCore): + await bot.add_cog(IPCServer(bot)) diff --git a/Bot/Cogs/kumiko.py b/Bot/Cogs/kumiko.py deleted file mode 100644 index e61cdbac..00000000 --- a/Bot/Cogs/kumiko.py +++ /dev/null @@ -1,70 +0,0 @@ -import datetime -import platform -import time - -import discord -from discord.ext import commands -from Libs.utils import Embed - -VERSION = "v0.7.0" - - -class Kumiko(commands.Cog): - """Commands to get basic info about Kumiko""" - - def __init__(self, bot: commands.Bot) -> None: - self.bot = bot - - @commands.Cog.listener() - async def on_ready(self): - global startTime - startTime = time.time() - - @commands.hybrid_group(name="kumiko") - async def kumiko(self, ctx: commands.Context) -> None: - """Base parent command for Kumiko - See the subcommands for more info""" - if ctx.invoked_subcommand is None: - await ctx.send_help(ctx.command) - - @kumiko.command(name="uptime") - async def botUptime(self, ctx: commands.Context) -> None: - """Returns uptime for Kumiko""" - uptime = datetime.timedelta(seconds=int(round(time.time() - startTime))) - embed = Embed() - embed.description = f"Kumiko's Uptime: `{uptime.days} Days, {uptime.seconds//3600} Hours, {(uptime.seconds//60)%60} Minutes, {(uptime.seconds%60)} Seconds`" - await ctx.send(embed=embed) - - @kumiko.command(name="info") - async def kumikoInfo(self, ctx: commands.Context) -> None: - """Shows some basic info about Kumiko""" - embed = Embed() - embed.title = f"{self.bot.user.name} Info" # type: ignore - embed.set_thumbnail(url=self.bot.user.display_avatar.url) # type: ignore - embed.add_field(name="Server Count", value=len(self.bot.guilds), inline=True) - embed.add_field(name="User Count", value=len(self.bot.users), inline=True) - embed.add_field( - name="Python Version", value=platform.python_version(), inline=True - ) - embed.add_field( - name="Discord.py Version", value=discord.__version__, inline=True - ) - embed.add_field(name="Kumiko Build Version", value=VERSION, inline=True) - await ctx.send(embed=embed) - - @kumiko.command(name="version") - async def version(self, ctx: commands.Context) -> None: - """Returns the current version of Kumiko""" - embed = Embed() - embed.description = f"Build Version: {VERSION}" - await ctx.send(embed=embed) - - @kumiko.command(name="ping") - async def ping(self, ctx: commands.Context) -> None: - """Returns the current latency of Kumiko""" - embed = Embed() - embed.description = f"Pong! {round(self.bot.latency * 1000)}ms" - await ctx.send(embed=embed) - - -async def setup(bot: commands.Bot) -> None: - await bot.add_cog(Kumiko(bot)) diff --git a/Bot/Cogs/meta.py b/Bot/Cogs/meta.py new file mode 100644 index 00000000..03612684 --- /dev/null +++ b/Bot/Cogs/meta.py @@ -0,0 +1,100 @@ +import datetime +import platform +import time + +import discord +import psutil +from discord.ext import commands +from kumikocore import KumikoCore +from Libs.utils import Embed +from psutil._common import bytes2human + +VERSION = "v0.9.0" + + +class Meta(commands.Cog): + """Commands to obtain info about Kumiko or others""" + + def __init__(self, bot: KumikoCore) -> None: + self.bot = bot + + @property + def display_emoji(self) -> discord.PartialEmoji: + return discord.PartialEmoji(name="\U00002754") + + @commands.Cog.listener() + async def on_ready(self): + global startTime + startTime = time.time() + + @commands.hybrid_command(name="uptime") + async def botUptime(self, ctx: commands.Context) -> None: + """Returns uptime for Kumiko""" + uptime = datetime.timedelta(seconds=int(round(time.time() - startTime))) + embed = Embed() + embed.description = f"Kumiko's Uptime: `{uptime.days} Days, {uptime.seconds//3600} Hours, {(uptime.seconds//60)%60} Minutes, {(uptime.seconds%60)} Seconds`" + await ctx.send(embed=embed) + + @commands.hybrid_command(name="info") + async def kumikoInfo(self, ctx: commands.Context) -> None: + """Shows some basic info about Kumiko""" + embed = Embed() + embed.title = f"{self.bot.user.name} Info" # type: ignore + embed.set_thumbnail(url=self.bot.user.display_avatar.url) # type: ignore + embed.add_field(name="Server Count", value=len(self.bot.guilds), inline=True) + embed.add_field(name="User Count", value=len(self.bot.users), inline=True) + embed.add_field( + name="Python Version", value=platform.python_version(), inline=True + ) + embed.add_field( + name="Discord.py Version", value=discord.__version__, inline=True + ) + embed.add_field(name="Kumiko Build Version", value=VERSION, inline=True) + await ctx.send(embed=embed) + + @commands.hybrid_command(name="version") + async def version(self, ctx: commands.Context) -> None: + """Returns the current version of Kumiko""" + embed = Embed() + embed.description = f"Build Version: {VERSION}" + await ctx.send(embed=embed) + + @commands.hybrid_command(name="ping") + async def ping(self, ctx: commands.Context) -> None: + """Returns the current latency of Kumiko""" + embed = Embed() + embed.description = f"Pong! {round(self.bot.latency * 1000)}ms" + await ctx.send(embed=embed) + + @commands.is_owner() + @commands.hybrid_command(name="sys-metrics", aliases=["sysmetrics"]) + async def sysMetrics(self, ctx: commands.Context) -> None: + """Tells you the current system metrics along with other information""" + await ctx.defer() + currMem = psutil.virtual_memory() + proc = psutil.Process() + with proc.oneshot(): + procMem = bytes2human(proc.memory_info().rss) + diskUsage = psutil.disk_usage("/") + embed = Embed() + embed.title = "System Metrics + Info" + embed.description = ( + f"**CPU:** {psutil.cpu_percent()}% (Proc - {proc.cpu_percent()}%)\n" + f"**Mem:** {procMem} ({procMem}/{bytes2human(currMem.total)})\n" + f"**Disk (System):** {diskUsage.percent}% ({bytes2human(diskUsage.used)}/{bytes2human(diskUsage.total)})\n" + f"**Proc Status:** {proc.status()}\n" + ) + embed.add_field(name="Kernel Version", value=platform.release()) + embed.add_field(name="Python Compiler", value=platform.python_compiler()) + embed.add_field( + name="Python Version", value=platform.python_version(), inline=True + ) + embed.add_field( + name="Discord.py Version", value=discord.__version__, inline=True + ) + embed.add_field(name="Kumiko Build Version", value=VERSION) + await ctx.send(embed=embed) + + +async def setup(bot: KumikoCore) -> None: + await bot.add_cog(Meta(bot)) diff --git a/Bot/Cogs/moderation.py b/Bot/Cogs/moderation.py index 8e466920..20eaa85b 100644 --- a/Bot/Cogs/moderation.py +++ b/Bot/Cogs/moderation.py @@ -2,17 +2,22 @@ from typing import Optional import discord -from discord import app_commands +from discord import PartialEmoji, app_commands from discord.ext import commands +from kumikocore import KumikoCore from Libs.utils import Embed, parseTimeStr class Moderation(commands.Cog): """A set of fine-tuned moderation commands""" - def __init__(self, bot: commands.Bot) -> None: + def __init__(self, bot: KumikoCore) -> None: self.bot = bot + @property + def display_emoji(self) -> PartialEmoji: + return PartialEmoji.from_str("<:blobban:759935431847968788>") + @commands.hybrid_group(name="mod") async def mod(self, ctx: commands.Context): """A set of fine-tuned moderation commands""" @@ -205,5 +210,5 @@ async def unmute( await ctx.send(embed=embed) -async def setup(bot: commands.Bot) -> None: +async def setup(bot: KumikoCore) -> None: await bot.add_cog(Moderation(bot)) diff --git a/Bot/Cogs/prefix.py b/Bot/Cogs/prefix.py new file mode 100644 index 00000000..4a2db456 --- /dev/null +++ b/Bot/Cogs/prefix.py @@ -0,0 +1,106 @@ +from discord import PartialEmoji, app_commands +from discord.ext import commands +from discord.utils import utcnow +from kumikocore import KumikoCore +from Libs.errors import ValidationError +from Libs.ui.prefix import DeletePrefixView +from Libs.utils import ConfirmEmbed, Embed, PrefixConverter, get_prefix + + +class Prefix(commands.Cog): + """Manages custom prefixes for your server""" + + def __init__(self, bot: KumikoCore) -> None: + self.bot = bot + self.pool = self.bot.pool + + @property + def display_emoji(self) -> PartialEmoji: + return PartialEmoji(name="\U000025b6") + + @commands.hybrid_group(name="prefix") + async def prefix(self, ctx: commands.Context) -> None: + """Utilities to manage and view your server prefixes""" + if ctx.invoked_subcommand is None: + await ctx.send_help(ctx.command) + + @commands.has_guild_permissions(manage_guild=True) + @commands.guild_only() + @prefix.command(name="update") + @app_commands.describe( + old_prefix="The old prefix to replace", new_prefix="The new prefix to use" + ) + async def updatePrefixes( + self, ctx: commands.Context, old_prefix: str, new_prefix: PrefixConverter + ) -> None: + """Updates the prefix for your server""" + query = """ + UPDATE guild + SET prefix = ARRAY_REPLACE(prefix, $1, $2) + WHERE id = $3; + """ + guild_id = ctx.guild.id # type: ignore + if old_prefix in self.bot.prefixes[guild_id]: + async with self.pool.acquire() as conn: + await conn.execute(query, old_prefix, new_prefix, guild_id) + prefixes = self.bot.prefixes[guild_id][ + : + ] # Shallow copy the list so we can safely perform operations on it + idxSearch = [ + idx for idx, item in enumerate(prefixes) if item == old_prefix + ] + prefixes[idxSearch[0]] = new_prefix + self.bot.prefixes[guild_id] = prefixes + await ctx.send(f"Prefix updated to `{new_prefix}`") + else: + await ctx.send("The prefix is not in the list of prefixes for your server") + + @commands.has_guild_permissions(manage_guild=True) + @commands.guild_only() + @prefix.command(name="add") + @app_commands.describe(prefix="The new prefix to add") + async def addPrefixes(self, ctx: commands.Context, prefix: PrefixConverter) -> None: + """Adds new prefixes into your server""" + prefixes = await get_prefix(self.bot, ctx.message) + # validatePrefix(self.bot.prefixes, prefix) is False + if len(prefixes) > 10: + desc = "There was an validation issue. This is because of two reasons:\n- You have more than 10 prefixes for your server\n- Your prefix fails the validation rules" + raise ValidationError(desc) + + query = """ + UPDATE guild + SET prefix = ARRAY_APPEND(prefix, $1) + WHERE id=$2; + """ + async with self.pool.acquire() as conn: + guildId = ctx.guild.id # type: ignore # These are all done in an guild + await conn.execute(query, prefix, guildId) + self.bot.prefixes[guildId].append(prefix) + await ctx.send(f"Added prefix: {prefix}") + + @commands.guild_only() + @prefix.command(name="info") + async def infoPrefixes(self, ctx: commands.Context) -> None: + """Displays infos about the current prefix set on your server""" + prefixes = await get_prefix(self.bot, ctx.message) + cleanedPrefixes = ", ".join([f"`{item}`" for item in prefixes]).rstrip(",") + embed = Embed() + embed.description = f"**Current prefixes**\n{cleanedPrefixes}" + embed.timestamp = utcnow() + embed.set_author(name=ctx.guild.name, icon_url=ctx.guild.icon.url) # type: ignore # LIES, LIES, AND LIES!!! + await ctx.send(embed=embed) + + @commands.has_guild_permissions(manage_guild=True) + @commands.guild_only() + @prefix.command(name="delete") + @app_commands.describe(prefix="The prefix to delete") + async def deletePrefixes(self, ctx: commands.Context, prefix: str) -> None: + """Deletes a prefix from your server""" + view = DeletePrefixView(bot=self.bot, prefix=prefix) + embed = ConfirmEmbed() + embed.description = f"Do you want to delete the following prefix: {prefix}" + await ctx.send(embed=embed, view=view) + + +async def setup(bot: KumikoCore) -> None: + await bot.add_cog(Prefix(bot)) diff --git a/Bot/Cogs/reddit.py b/Bot/Cogs/reddit.py index 2e143b92..51f72b0d 100644 --- a/Bot/Cogs/reddit.py +++ b/Bot/Cogs/reddit.py @@ -2,18 +2,19 @@ from datetime import datetime from typing import Literal, Optional -import aiohttp import asyncpraw import orjson -from discord import app_commands +from discord import PartialEmoji, app_commands from discord.ext import commands from discord.utils import format_dt from dotenv import load_dotenv +from kumikocore import KumikoCore from Libs.utils import parseSubreddit from Libs.utils.pages import EmbedListSource, KumikoPages load_dotenv() + REDDIT_ID = os.environ["REDDIT_ID"] REDDIT_SECRET = os.environ["REDDIT_SECRET"] @@ -21,12 +22,17 @@ class Reddit(commands.Cog): """Search, and view posts and memes from Reddit""" - def __init__(self, bot: commands.Bot) -> None: + def __init__(self, bot: KumikoCore) -> None: self.bot = bot + self.session = self.bot.session + + @property + def display_emoji(self) -> PartialEmoji: + return PartialEmoji.from_str("<:reddit:314349923103670272>") @commands.hybrid_group(name="reddit") async def reddit(self, ctx: commands.Context) -> None: - """Base command for Reddit""" + """Reddit search and utility commands""" if ctx.invoked_subcommand is None: await ctx.send_help(ctx.command) @@ -43,6 +49,7 @@ async def redditSearch( client_id=REDDIT_ID, client_secret=REDDIT_SECRET, user_agent="Kumiko (by /u/No767)", + requestor_kwargs={"session": self.bot.session}, ) as reddit: sub = await reddit.subreddit(parseSubreddit(subreddit)) data = [ @@ -86,14 +93,15 @@ async def redditEggIRL( client_id=REDDIT_ID, client_secret=REDDIT_SECRET, user_agent="Kumiko (by /u/No767)", + requestor_kwargs={"session": self.bot.session}, ) as reddit: sub = await reddit.subreddit(parseSubreddit("egg_irl")) subGen = ( - sub.new(limit=25) + sub.new(limit=10) if filter == "New" - else sub.hot(limit=25) + else sub.hot(limit=10) if filter == "Hot" - else sub.rising(limit=25) + else sub.rising(limit=10) ) data = [ { @@ -139,14 +147,15 @@ async def redditFeed( client_id=REDDIT_ID, client_secret=REDDIT_SECRET, user_agent="Kumiko (by /u/No767)", + requestor_kwargs={"session": self.bot.session}, ) as reddit: sub = await reddit.subreddit(parseSubreddit(subreddit)) subGen = ( - sub.new(limit=25) + sub.new(limit=10) if filter == "New" - else sub.hot(limit=25) + else sub.hot(limit=10) if filter == "Hot" - else sub.rising(limit=25) + else sub.rising(limit=10) ) data = [ { @@ -186,30 +195,29 @@ async def searchMemes( self, ctx: commands.Context, subreddit: str, amount: Optional[int] = 5 ) -> None: """Searches for memes on Reddit""" - async with aiohttp.ClientSession() as session: - async with session.get( - f"https://meme-api.com/gimme/{parseSubreddit(subreddit)}/{amount}" - ) as r: - data = await r.json(loads=orjson.loads) - mainData = [ - { - "title": item["title"], - "image": item["url"], - "fields": [ - {"name": "Author", "value": item["author"]}, - {"name": "Subreddit", "value": item["subreddit"]}, - {"name": "Upvotes", "value": item["ups"]}, - {"name": "NSFW", "value": item["nsfw"]}, - {"name": "Spoiler", "value": item["spoiler"]}, - {"name": "Reddit URL", "value": item["postLink"]}, - ], - } - for item in data["memes"] - ] - embedSource = EmbedListSource(mainData, per_page=1) - pages = KumikoPages(source=embedSource, ctx=ctx) - await pages.start() + async with self.bot.session.get( + f"https://meme-api.com/gimme/{parseSubreddit(subreddit)}/{amount}" + ) as r: + data = await r.json(loads=orjson.loads) + mainData = [ + { + "title": item["title"], + "image": item["url"], + "fields": [ + {"name": "Author", "value": item["author"]}, + {"name": "Subreddit", "value": item["subreddit"]}, + {"name": "Upvotes", "value": item["ups"]}, + {"name": "NSFW", "value": item["nsfw"]}, + {"name": "Spoiler", "value": item["spoiler"]}, + {"name": "Reddit URL", "value": item["postLink"]}, + ], + } + for item in data["memes"] + ] + embedSource = EmbedListSource(mainData, per_page=1) + pages = KumikoPages(source=embedSource, ctx=ctx) + await pages.start() -async def setup(bot: commands.Bot) -> None: +async def setup(bot: KumikoCore) -> None: await bot.add_cog(Reddit(bot)) diff --git a/Bot/Cogs/search.py b/Bot/Cogs/search.py index 6e736e8d..c9c67f94 100644 --- a/Bot/Cogs/search.py +++ b/Bot/Cogs/search.py @@ -1,15 +1,15 @@ import os from typing import Literal, Optional -import aiohttp import ciso8601 import orjson -from discord import app_commands +from discord import PartialEmoji, app_commands from discord.ext import commands from discord.utils import format_dt from dotenv import load_dotenv from gql import Client, gql from gql.transport.aiohttp import AIOHTTPTransport +from kumikocore import KumikoCore from Libs.errors import NoItemsError from Libs.utils.pages import EmbedListSource, KumikoPages @@ -19,14 +19,19 @@ class Searches(commands.Cog): - """Search for anime, manga, gifs, memes, and much more!""" + """Search for anime, manga, gifs, memes, and much more""" - def __init__(self, bot: commands.Bot) -> None: + def __init__(self, bot: KumikoCore) -> None: self.bot = bot + self.session = self.bot.session + + @property + def display_emoji(self) -> PartialEmoji: + return PartialEmoji.from_str("") @commands.hybrid_group(name="search") async def search(self, ctx: commands.Context) -> None: - """Base parent command for searches - See the subcommands for more info""" + """Search for anime, manga, gifs, memes, and much more""" if ctx.invoked_subcommand is None: await ctx.send_help(ctx.command) @@ -34,11 +39,10 @@ async def search(self, ctx: commands.Context) -> None: @app_commands.describe(name="The name of the anime to search") async def searchAnime(self, ctx: commands.Context, *, name: str) -> None: """Searches up animes""" - transport = AIOHTTPTransport(url="https://graphql.anilist.co/") async with Client( - transport=transport, + transport=AIOHTTPTransport(url="https://graphql.anilist.co/"), fetch_schema_from_transport=True, - ) as session: + ) as gql_session: query = gql( """ query ($animeName: String!, $perPage: Int, $isAdult: Boolean!) { @@ -72,15 +76,15 @@ async def searchAnime(self, ctx: commands.Context, *, name: str) -> None: } synonyms id - + } } } - """ + """ ) params = {"animeName": name, "perPage": 25, "isAdult": False} - data = await session.execute(query, variable_values=params) + data = await gql_session.execute(query, variable_values=params) if len(data["Page"]["media"]) == 0: raise NoItemsError @@ -126,11 +130,10 @@ async def searchAnime(self, ctx: commands.Context, *, name: str) -> None: @app_commands.describe(name="The name of the manga to search") async def searchManga(self, ctx: commands.Context, *, name: str): """Searches for manga on AniList""" - transport = AIOHTTPTransport(url="https://graphql.anilist.co/") async with Client( - transport=transport, + transport=AIOHTTPTransport(url="https://graphql.anilist.co/"), fetch_schema_from_transport=True, - ) as session: + ) as gql_session: query = gql( """ query ($mangaName: String!, $perPage: Int, $isAdult: Boolean!) { @@ -163,15 +166,15 @@ async def searchManga(self, ctx: commands.Context, *, name: str): } synonyms id - + } } } - """ + """ ) params = {"mangaName": name, "perPage": 25, "isAdult": False} - data = await session.execute(query, variable_values=params) + data = await gql_session.execute(query, variable_values=params) if len(data["Page"]["media"]) == 0: raise NoItemsError else: @@ -215,28 +218,27 @@ async def searchManga(self, ctx: commands.Context, *, name: str): @app_commands.describe(search="The search term to use") async def searchGifs(self, ctx: commands.Context, *, search: str) -> None: """Searches for gifs on Tenor""" - async with aiohttp.ClientSession() as session: - params = { - "q": search, - "key": TENOR_API_KEY, - "contentfilter": "medium", - "limit": 25, - "media_filter": "minimal", - } - async with session.get( - "https://tenor.googleapis.com/v2/search", params=params - ) as r: - data = await r.json(loads=orjson.loads) - if len(data["results"]) == 0 or r.status == 404: - raise NoItemsError - else: - mainData = [ - {"image": item["media_formats"]["gif"]["url"]} - for item in data["results"] - ] - embedSource = EmbedListSource(mainData, per_page=1) - pages = KumikoPages(source=embedSource, ctx=ctx) - await pages.start() + params = { + "q": search, + "key": TENOR_API_KEY, + "contentfilter": "medium", + "limit": 25, + "media_filter": "minimal", + } + async with self.session.get( + "https://tenor.googleapis.com/v2/search", params=params + ) as r: + data = await r.json(loads=orjson.loads) + if len(data["results"]) == 0 or r.status == 404: + raise NoItemsError + else: + mainData = [ + {"image": item["media_formats"]["gif"]["url"]} + for item in data["results"] + ] + embedSource = EmbedListSource(mainData, per_page=1) + pages = KumikoPages(source=embedSource, ctx=ctx) + await pages.start() @search.command(name="mc-mods") @app_commands.describe( @@ -251,59 +253,58 @@ async def searchMods( modloader: Optional[Literal["Forge", "Fabric"]] = "Forge", ) -> None: """Search for Minecraft mods and plugins on Modrinth""" - async with aiohttp.ClientSession() as session: - params = { - "query": mod_name, - "index": "relevance", - "limit": 25, - "facets": f'[["categories:{str(modloader).lower()}"]]', - } - async with session.get( - "https://api.modrinth.com/v2/search", params=params - ) as r: - data = await r.json(loads=orjson.loads) - if len(data["hits"]) == 0: - raise NoItemsError - else: - mainData = [ - { - "title": item["title"], - "description": item["description"], - "thumbnail": item["icon_url"], - "fields": [ - {"name": "Author", "value": item["author"]}, - {"name": "Categories", "value": item["categories"]}, - {"name": "Versions", "value": item["versions"]}, - { - "name": "Latest Version", - "value": item["latest_version"], - }, - { - "name": "Date Created", - "value": format_dt( - ciso8601.parse_datetime(item["date_created"]) - ), - }, - { - "name": "Date Modified", - "value": format_dt( - ciso8601.parse_datetime(item["date_modified"]) - ), - }, - {"name": "Downloads", "value": item["downloads"]}, - {"name": "License", "value": item["license"]}, - { - "name": "Modrinth URL", - "value": f"https://modrinth.com/{item['project_type']}/{item['slug']}", - }, - ], - } - for item in data["hits"] - ] - embedSource = EmbedListSource(mainData, per_page=1) - pages = KumikoPages(source=embedSource, ctx=ctx) - await pages.start() + params = { + "query": mod_name, + "index": "relevance", + "limit": 25, + "facets": f'[["categories:{str(modloader).lower()}"]]', + } + async with self.session.get( + "https://api.modrinth.com/v2/search", params=params + ) as r: + data = await r.json(loads=orjson.loads) + if len(data["hits"]) == 0: + raise NoItemsError + else: + mainData = [ + { + "title": item["title"], + "description": item["description"], + "thumbnail": item["icon_url"], + "fields": [ + {"name": "Author", "value": item["author"]}, + {"name": "Categories", "value": item["categories"]}, + {"name": "Versions", "value": item["versions"]}, + { + "name": "Latest Version", + "value": item["latest_version"], + }, + { + "name": "Date Created", + "value": format_dt( + ciso8601.parse_datetime(item["date_created"]) + ), + }, + { + "name": "Date Modified", + "value": format_dt( + ciso8601.parse_datetime(item["date_modified"]) + ), + }, + {"name": "Downloads", "value": item["downloads"]}, + {"name": "License", "value": item["license"]}, + { + "name": "Modrinth URL", + "value": f"https://modrinth.com/{item['project_type']}/{item['slug']}", + }, + ], + } + for item in data["hits"] + ] + embedSource = EmbedListSource(mainData, per_page=1) + pages = KumikoPages(source=embedSource, ctx=ctx) + await pages.start() -async def setup(bot: commands.Bot) -> None: +async def setup(bot: KumikoCore) -> None: await bot.add_cog(Searches(bot)) diff --git a/Bot/Cogs/waifus.py b/Bot/Cogs/waifus.py index 71956e3f..23066d48 100644 --- a/Bot/Cogs/waifus.py +++ b/Bot/Cogs/waifus.py @@ -1,21 +1,27 @@ import random -import aiohttp import orjson +from discord import PartialEmoji from discord.ext import commands +from kumikocore import KumikoCore from Libs.utils import Embed from Libs.utils.pages import EmbedListSource, KumikoPages class Waifu(commands.Cog): - """Commands for getting some waifu pics""" + """Gives you random waifu pics""" - def __init__(self, bot: commands.Bot) -> None: + def __init__(self, bot: KumikoCore) -> None: self.bot = bot + self.session = self.bot.session + + @property + def display_emoji(self) -> PartialEmoji: + return PartialEmoji.from_str("<:UwU:1013221555003719772>") @commands.hybrid_group(name="waifu") async def waifu(self, ctx: commands.Context) -> None: - """Base parent command for waifu - See the subcommands for more info""" + """Waifu waifu waifus Mai Sakurajima is the best""" if ctx.invoked_subcommand is None: await ctx.send_help(ctx.command) @@ -31,16 +37,15 @@ async def randomWaifu(self, ctx: commands.Context) -> None: "raiden-shogun", "selfies", ] - async with aiohttp.ClientSession() as session: - params = { - "included_tags": random.choice(waifuTagList), - "is_nsfw": "false", - "excluded_tags": "oppai", - } - async with session.get("https://api.waifu.im/search/", params=params) as r: - data = await r.json(loads=orjson.loads) - embed = Embed().set_image(url=data["images"][0]["url"]) - await ctx.send(embed=embed) + params = { + "included_tags": random.choice(waifuTagList), + "is_nsfw": "false", + "excluded_tags": "oppai", + } + async with self.session.get("https://api.waifu.im/search/", params=params) as r: + data = await r.json(loads=orjson.loads) + embed = Embed().set_image(url=data["images"][0]["url"]) + await ctx.send(embed=embed) @waifu.command(name="many") async def randomWaifuMany(self, ctx: commands.Context) -> None: @@ -54,20 +59,19 @@ async def randomWaifuMany(self, ctx: commands.Context) -> None: "raiden-shogun", "selfies", ] - async with aiohttp.ClientSession() as session: - params = { - "included_tags": random.choice(waifuTagList), - "is_nsfw": "False", - "excluded_tags": "oppai", - "many": "true", - } - async with session.get("https://api.waifu.im/search/", params=params) as r: - data = await r.json(loads=orjson.loads) - mainData = [{"image": item["url"]} for item in data["images"]] - embedSource = EmbedListSource(mainData, per_page=1) - menu = KumikoPages(source=embedSource, ctx=ctx, compact=False) - await menu.start() + params = { + "included_tags": random.choice(waifuTagList), + "is_nsfw": "False", + "excluded_tags": "oppai", + "many": "true", + } + async with self.session.get("https://api.waifu.im/search/", params=params) as r: + data = await r.json(loads=orjson.loads) + mainData = [{"image": item["url"]} for item in data["images"]] + embedSource = EmbedListSource(mainData, per_page=1) + menu = KumikoPages(source=embedSource, ctx=ctx, compact=False) + await menu.start() -async def setup(bot: commands.Bot) -> None: +async def setup(bot: KumikoCore) -> None: await bot.add_cog(Waifu(bot)) diff --git a/Bot/Libs/cache/__init__.py b/Bot/Libs/cache/__init__.py index 071c22b2..301f3c19 100644 --- a/Bot/Libs/cache/__init__.py +++ b/Bot/Libs/cache/__init__.py @@ -1,16 +1,12 @@ -from .cp_manager import KumikoCPManager -from .decorators import cached, cachedJson -from .global_cp import kumikoCP -from .key_builder import CommandKeyBuilder -from .mem_cache import MemoryCache -from .redis_cache import KumikoCache - -__all__ = [ - "MemoryCache", - "CommandKeyBuilder", - "KumikoCache", - "cached", - "cachedJson", - "KumikoCPManager", - "kumikoCP", -] +from .cp_manager import KumikoCPManager +from .decorators import cache, cacheJson +from .key_builder import CommandKeyBuilder +from .redis_cache import KumikoCache + +__all__ = [ + "CommandKeyBuilder", + "KumikoCache", + "KumikoCPManager", + "cache", + "cacheJson", +] diff --git a/Bot/Libs/cache/cp_manager.py b/Bot/Libs/cache/cp_manager.py index 663380ad..086209fd 100644 --- a/Bot/Libs/cache/cp_manager.py +++ b/Bot/Libs/cache/cp_manager.py @@ -1,26 +1,38 @@ -from typing import Optional - -from redis.asyncio.connection import ConnectionPool - - -class KumikoCPManager: - """Redis connection pool manager""" - - def __init__( - self, host: str = "127.0.0.1", port: int = 6379, password: Optional[str] = None - ) -> None: - self.host = host - self.port = port - self.password = password - self.connPool = None - - def createConnPool(self) -> ConnectionPool: - self.connPool = ConnectionPool( - host=self.host, port=self.port, password=self.password, db=0 - ) - return self.connPool - - def getConnPool(self) -> ConnectionPool: - if not self.connPool: - return self.createConnPool() - return self.connPool +from types import TracebackType +from typing import Optional, Type, TypeVar + +from redis.asyncio.connection import ConnectionPool +from yarl import URL + +BE = TypeVar("BE", bound=BaseException) + + +class KumikoCPManager: + def __init__(self, uri: str, max_size: int = 20) -> None: + self.uri = uri + self.max_size = max_size + self.connPool = None + + async def __aenter__(self) -> ConnectionPool: + return self.createPool() + + async def __aexit__( + self, + exc_type: Optional[Type[BE]], + exc: Optional[BE], + traceback: Optional[TracebackType], + ) -> None: + if self.connPool is not None: + await self.connPool.disconnect() + + def createPool(self) -> ConnectionPool: + completeURI = URL(self.uri) % {"decode_responses": "True"} + self.connPool = ConnectionPool(max_connections=self.max_size).from_url( + str(completeURI) + ) + return self.connPool + + def getConnPool(self) -> ConnectionPool: + if not self.connPool: + return self.createPool() + return self.connPool diff --git a/Bot/Libs/cache/decorators.py b/Bot/Libs/cache/decorators.py index 95bd56b6..a3ac346d 100644 --- a/Bot/Libs/cache/decorators.py +++ b/Bot/Libs/cache/decorators.py @@ -1,87 +1,117 @@ -import uuid -from functools import wraps -from typing import Any, Callable, Optional - -from redis.asyncio.connection import ConnectionPool - -from .redis_cache import CommandKeyBuilder, KumikoCache - - -def cached( - connection_pool: ConnectionPool, - command_key: Optional[str], - ttl: int = 30, -) -> Callable[..., Any]: - """A decorator to cache the result of a function that returns a `str` to Redis. - - **Note**: The return type of the corountine used has to be `str` or `bytes` - - Args: - connection_pool (ConnectionPool): Redis connection pool to use - command_key (Optional[str]): Command key to use - ttl (int, optional): TTL (Time-To-Live). Defaults to 30. - - Returns: - Callable[..., Any]: The wrapper function - """ - - def wrapper(func: Callable[..., Any]) -> Any: - @wraps(func) - async def wrapped(*args: Any, **kwargs: Any) -> Any: - currFunc = await func(*args, **kwargs) - cache = KumikoCache(connection_pool=connection_pool) - key = ( - CommandKeyBuilder(id=uuid.uuid4(), command=cached.__name__) - if command_key is None - else command_key - ) - if await cache.cacheExists(key=key) is False: - await cache.setBasicCache(key=key, value=currFunc, ttl=ttl) - else: - return await cache.getBasicCache(key=key) - return currFunc - - return wrapped - - return wrapper - - -def cachedJson( - connection_pool: ConnectionPool, - command_key: Optional[str], - ttl: int = 30, -) -> Callable[..., Any]: - """A decorator to cache the result of a function that returns a `dict` to Redis. - - **Note**: The return type of the corountine used has to be `dict` - - Args: - connection_pool (ConnectionPool): Redis connection pool to use - command_key (Optional[str]): Command key to use - ttl (int, optional): TTL (Time-To-Live). Defaults to 30. - - Returns: - Callable[..., Any]: The wrapper function - """ - - def wrapper(func: Callable[..., Any]) -> Any: - @wraps(func) - async def wrapped(*args: Any, **kwargs: Any) -> Any: - currFunc = await func(*args, **kwargs) - if currFunc is None: - return None - cache = KumikoCache(connection_pool=connection_pool) - key = ( - CommandKeyBuilder(id=uuid.uuid4(), command=cachedJson.__name__) - if command_key is None - else command_key - ) - if await cache.cacheExists(key=key) is False: - await cache.setJSONCache(key=key, value=currFunc, ttl=ttl) - else: - return await cache.getJSONCache(key=key) - return currFunc - - return wrapped - - return wrapper +import uuid +from functools import wraps +from typing import Any, Callable, Optional, Union + +from redis.asyncio.connection import ConnectionPool + +from .redis_cache import CommandKeyBuilder, KumikoCache + + +class cache: + """A decorator to cache the result of a function that returns a `str` to Redis. + + **Note**: The return type of the coroutine used has to be `str` or `bytes` + + Args: + connection_pool (ConnectionPool): Redis connection pool to use + ttl (int, optional): TTL (Time-To-Live). Defaults to 30. + """ + + def __init__( + self, key: Optional[str] = None, ttl: int = 30, name: Optional[str] = None + ): + self.key = key + self.ttl = ttl + self.name = name + + def __call__(self, func: Callable, *args: Any, **kwargs: Any): + @wraps(func) + async def wrapper( + id: int, redis_pool: ConnectionPool, *args: Any, **kwargs: Any + ): + return await self.deco(func, id, redis_pool, *args, **kwargs) + + return wrapper + + async def deco( + self, + func: Callable, + id: Union[int, None], + redis_pool: ConnectionPool, + *args, + **kwargs + ): + res = await func(id, redis_pool, *args, **kwargs) + if isinstance(res, str) is False: + return res + cache = KumikoCache(connection_pool=redis_pool) + key = self.key + if key is None: + key = CommandKeyBuilder( + prefix="cache", + namespace="kumiko", + id=id or uuid.uuid4(), + command=self.name or func.__name__, + ) + + if await cache.cacheExists(key=key) is False: + await cache.setBasicCache(key=key, value=res, ttl=self.ttl) + return res + return await cache.getBasicCache(key=key) + + +class cacheJson: + """ + A decorator to cache the result of a function that returns a `dict` to Redis. + + **Note**: The return type of the coroutine used has to be `dict` + + Args: + connection_pool (ConnectionPool): Redis connection pool to use + ttl (int, optional): TTL (Time-To-Live). If None, then the TTL will not be set. Defaults to 30. + """ + + def __init__( + self, + key: Optional[str] = None, + ttl: Union[int, None] = 30, + name: Optional[str] = None, + ): + self.key = key + self.ttl = ttl + self.name = name + + def __call__(self, func: Callable, *args: Any, **kwargs: Any): + @wraps(func) + async def wrapper( + id: int, redis_pool: ConnectionPool, *args: Any, **kwargs: Any + ): + return await self.deco(func, id, redis_pool, *args, **kwargs) + + return wrapper + + async def deco( + self, + func: Callable, + id: Union[int, None], + redis_pool: ConnectionPool, + *args, + **kwargs + ): + res = await func(id, redis_pool, *args, **kwargs) + if isinstance(res, dict) is False: + return res + cache = KumikoCache(connection_pool=redis_pool) + key = self.key + if key is None: + key = CommandKeyBuilder( + prefix="cache", + namespace="kumiko", + id=id or uuid.uuid4(), + command=self.name or func.__name__, + ) + + if await cache.cacheExists(key=key) is False: + await cache.setJSONCache(key=key, value=res, ttl=self.ttl) + return res + return await cache.getJSONCache(key=key) diff --git a/Bot/Libs/cache/global_cp.py b/Bot/Libs/cache/global_cp.py deleted file mode 100644 index 0236414f..00000000 --- a/Bot/Libs/cache/global_cp.py +++ /dev/null @@ -1,17 +0,0 @@ -import os -from pathlib import Path - -from dotenv import load_dotenv -from Libs.cache import KumikoCPManager - -path = Path(__file__).parents[2].joinpath(".env") - -load_dotenv(dotenv_path=path) - -REDIS_HOST = os.environ["REDIS_HOST"] -REDIS_PORT = os.environ["REDIS_PORT"] -REDIS_PASSWORD = os.getenv("REDIS_PASSWORD") - -kumikoCP: KumikoCPManager = KumikoCPManager( - host=REDIS_HOST, port=int(REDIS_PORT), password=REDIS_PASSWORD -) diff --git a/Bot/Libs/cache/mem_cache.py b/Bot/Libs/cache/mem_cache.py deleted file mode 100644 index 57cd9ac6..00000000 --- a/Bot/Libs/cache/mem_cache.py +++ /dev/null @@ -1,68 +0,0 @@ -from typing import Any, Dict, List - - -class MemoryCache: - """Synchronous memory cache implementation based off of aiocache""" - - def __init__(self): - self._cache: Dict[str, Any] = {} - - def get(self, key: str) -> Any: - """Gets a value from the cache - - Args: - key (str): The key to use - - Returns: - Any: The value from the cache - """ - return self._cache.get(key) - - def getAll(self) -> List[Any]: - """Gets all values from the cache - - Returns: - List[Any]: A list of all of the values - """ - return list(self._cache.values()) - - def set(self, key: str, value: Any) -> Any: - """Sets a value in the cache - - Args: - key (str): The key to use - value (Any): The value to set - - Returns: - Any: The set value from the cache - """ - self._cache[key] = value - return self._cache[key] - - def add(self, key: str, value: Any) -> Any: - """Adds a value to the cache if it doesn't exist - - Args: - key (str): The key to use - value (Any): The value to set - - Raises: - ValueError: If the key already exists - - Returns: - Any: The set value from the cache - """ - if key in self._cache: - raise ValueError(f"Key {key} already exists. Please use .set to update it") - return self.set(key=key, value=value) - - def delete(self, key: str) -> Dict[str, Any]: - """Deletes a key from the cache - - Args: - key (str): The key to delete - - Returns: - Dict[str, Any]: The deleted key-value pair - """ - return self._cache.pop(key, None) diff --git a/Bot/Libs/cache/redis_cache.py b/Bot/Libs/cache/redis_cache.py index f35679b4..9a16806c 100644 --- a/Bot/Libs/cache/redis_cache.py +++ b/Bot/Libs/cache/redis_cache.py @@ -30,7 +30,6 @@ async def setBasicCache( ) conn: redis.Redis = redis.Redis(connection_pool=self.connection_pool) await conn.set(name=key if key is not None else defaultKey, value=value, ex=ttl) - await conn.close() async def getBasicCache(self, key: str) -> Union[str, None]: """Gets the command cache from Redis @@ -40,23 +39,34 @@ async def getBasicCache(self, key: str) -> Union[str, None]: """ conn: redis.Redis = redis.Redis(connection_pool=self.connection_pool) res = await conn.get(key) - await conn.close() return res - async def setJSONCache(self, key: str, value: Dict[str, Any], ttl: int = 5) -> None: + async def setJSONCache( + self, + key: str, + value: Union[Dict[str, Any], Any], + path: str = "$", + ttl: Union[int, None] = 5, + ) -> None: """Sets the JSON cache on Redis Args: key (str): The key to use for Redis - value (Dict[str, Any]): The value of the key-pair value - ttl (Optional[int], optional): TTL of the key-value pair. Defaults to 5. + value (Union[Dict[str, Any], Any]): The value of the key-pair value + path (str): The path to look for or set. Defautls to "$" + ttl (Union[int, None], optional): TTL of the key-value pair. If None, then the TTL will not be set. Defaults to 5. """ client: redis.Redis = redis.Redis(connection_pool=self.connection_pool) - await client.json().set(name=key, path="$", obj=encodeDatetime(value)) - await client.expire(name=key, time=ttl) - await client.close() + await client.json().set( + name=key, + path=path, + obj=encodeDatetime(value) if isinstance(value, dict) else value, + ) + if isinstance(ttl, int): + await client.expire(name=key, time=ttl) - async def getJSONCache(self, key: str) -> Union[str, None]: + # The output type comes from here: https://github.com/redis/redis-py/blob/9f503578d1ffed20d63e8023bcd8a7dccd15ecc5/redis/commands/json/_util.py#L3C1-L3C73 + async def getJSONCache(self, key: str) -> Union[None, Dict[str, Any]]: """Gets the JSON cache on Redis Args: @@ -67,11 +77,20 @@ async def getJSONCache(self, key: str) -> Union[str, None]: """ client: redis.Redis = redis.Redis(connection_pool=self.connection_pool) value = await client.json().get(name=key) - await client.close() if value is None: return None return value + async def deleteJSONCache(self, key: str, path: str = "$") -> None: + """Deletes the JSON cache at key `key` and under `path` + + Args: + key (str): The key to use in Redis + path (str): The path to look for. Defaults to "$" (root) + """ + client: redis.Redis = redis.Redis(connection_pool=self.connection_pool) + await client.json().delete(key=key, path=path) + async def cacheExists(self, key: str) -> bool: """Checks to make sure if the cache exists @@ -83,5 +102,4 @@ async def cacheExists(self, key: str) -> bool: """ client: redis.Redis = redis.Redis(connection_pool=self.connection_pool) keyExists = await client.exists(key) >= 1 - await client.close() return True if keyExists else False diff --git a/Bot/Libs/cog_utils/events_log/__init__.py b/Bot/Libs/cog_utils/events_log/__init__.py new file mode 100644 index 00000000..f54449a7 --- /dev/null +++ b/Bot/Libs/cog_utils/events_log/__init__.py @@ -0,0 +1,3 @@ +from .cache_utils import delete_cache, get_or_fetch_config, set_or_update_cache + +__all__ = ["get_or_fetch_config", "set_or_update_cache", "delete_cache"] diff --git a/Bot/Libs/cog_utils/events_log/cache_utils.py b/Bot/Libs/cog_utils/events_log/cache_utils.py new file mode 100644 index 00000000..9b6378b1 --- /dev/null +++ b/Bot/Libs/cog_utils/events_log/cache_utils.py @@ -0,0 +1,41 @@ +from typing import Any, Dict, Union + +import asyncpg +from Libs.cache import KumikoCache, cacheJson +from redis.asyncio.connection import ConnectionPool + + +# idk how to test this one +# Someone remind Noelle to test this once she figures out how to +@cacheJson(ttl=None, name="logging_config") +async def get_or_fetch_config( + id: int, redis_pool: ConnectionPool, pool: asyncpg.Pool +) -> Union[Dict[str, Union[int, bool]], None]: + query = """ + SELECT guild.id, guild.logs, logging_config.channel_id, logging_config.member_events + FROM guild + INNER JOIN logging_config + ON guild.id = logging_config.guild_id + WHERE guild.id = $1; + """ + async with pool.acquire() as conn: + res = await conn.fetchrow(query, id) + return dict(res) + + +async def set_or_update_cache( + key: str, redis_pool: ConnectionPool, data: Dict[str, Any] +) -> None: + cache = KumikoCache(connection_pool=redis_pool) + if not await cache.cacheExists(key=key): + await cache.setJSONCache(key=key, value=data, ttl=None) + else: + await cache.setJSONCache( + key=key, value=data["channel_id"], path="$.channel_id", ttl=None + ) + + +async def delete_cache(key: str, redis_pool: ConnectionPool) -> None: + cache = KumikoCache(connection_pool=redis_pool) + if await cache.cacheExists(key=key): + await cache.deleteJSONCache(key=key) diff --git a/Bot/Libs/economy/__init__.py b/Bot/Libs/economy/__init__.py deleted file mode 100644 index 88b6eedb..00000000 --- a/Bot/Libs/economy/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .cache_utils import getUser - -__all__ = ["getUser"] diff --git a/Bot/Libs/economy/cache_utils.py b/Bot/Libs/economy/cache_utils.py deleted file mode 100644 index ee450110..00000000 --- a/Bot/Libs/economy/cache_utils.py +++ /dev/null @@ -1,31 +0,0 @@ -import uuid -from typing import Dict, Union - -from prisma.models import User -from prisma.types import UserInclude - -from ..cache import CommandKeyBuilder, cachedJson, kumikoCP - - -@cachedJson( - connection_pool=kumikoCP.getConnPool(), - command_key=CommandKeyBuilder( - prefix="cache", namespace="kumiko", id=uuid.uuid4(), command="internal_get_user" - ), -) -async def getUser( - user_id: int, includes: UserInclude = {"inv": False, "marketplace": False} -) -> Union[Dict, None]: - """[Coroutine] Helper coroutine to obtain a user's profile from the database - - For reducing the latency for accessing the data, this helper coroutine is cached on Redis (w/ RedisJSON). Also note that this coroutine expects that the Prisma query engine and database are already connected. - - Args: - user_id (int): User ID to use to search up the user - includes (Dict[str, bool], optional): Which schemas to include (for 1-n relations) Note that it must be a dict containing the column, and to include it or not. Defaults to {"inv": False, "marketplace": False}. - - Returns: - Union[Dict, None]: The user's profile, or None if the user is not found - """ - user = await User.prisma().find_unique(where={"id": user_id}, include=includes) - return user.dict() if user is not None else None diff --git a/Bot/Libs/errors/__init__.py b/Bot/Libs/errors/__init__.py index 5f2d72a7..b21140be 100644 --- a/Bot/Libs/errors/__init__.py +++ b/Bot/Libs/errors/__init__.py @@ -4,12 +4,14 @@ KumikoException, NoItemsError, NotFoundError, + ValidationError, ) __all__ = [ "KumikoException", "NoItemsError", "ItemNotFoundError", + "ValidationError", "HTTPError", "NotFoundError", ] diff --git a/Bot/Libs/errors/exceptions.py b/Bot/Libs/errors/exceptions.py index b33091ef..96f2e887 100644 --- a/Bot/Libs/errors/exceptions.py +++ b/Bot/Libs/errors/exceptions.py @@ -19,6 +19,10 @@ class ItemNotFoundError(KumikoException): """Generally used if any item of the economy system is not found""" +class ValidationError(KumikoException): + """Raised when a validation of any function fails""" + + class HTTPError(KumikoException): """Raised when an HTTP request fails. diff --git a/Bot/Libs/ui/economy/__init__.py b/Bot/Libs/ui/economy/__init__.py new file mode 100644 index 00000000..80a66e3a --- /dev/null +++ b/Bot/Libs/ui/economy/__init__.py @@ -0,0 +1,3 @@ +from .views import RegisterView + +__all__ = ["RegisterView"] diff --git a/Bot/Libs/ui/economy/views.py b/Bot/Libs/ui/economy/views.py new file mode 100644 index 00000000..ae04e3cc --- /dev/null +++ b/Bot/Libs/ui/economy/views.py @@ -0,0 +1,44 @@ +import discord +from Libs.utils import Embed +from prisma.models import User # type: ignore + + +class RegisterView(discord.ui.View): + def __init__(self) -> None: + super().__init__() + + @discord.ui.button(label="Confirm", style=discord.ButtonStyle.green) + async def confirm( + self, interaction: discord.Interaction, button: discord.ui.Button + ) -> None: + button.disabled = True + doesUserExist = ( + await User.prisma().count(where={"id": interaction.user.id}, take=1) == 1 + ) + if doesUserExist: + return await interaction.response.edit_message( + embed=Embed( + title="Already Registered", + description="You already have an account!", + ), + view=self, + ) + await User.prisma().create( + data={"id": interaction.user.id, "name": interaction.user.name} + ) + await interaction.edit_original_response( + embed=Embed( + title="Registered", description="You have successfully registered!" + ) + ) + self.stop() + + @discord.ui.button(label="Cancel", style=discord.ButtonStyle.red) + async def cancel( + self, interaction: discord.Interaction, button: discord.ui.Button + ) -> None: + button.disabled = True + await interaction.response.edit_message( + embed=Embed(title="Cancelled"), view=self + ) + self.stop() diff --git a/Bot/Libs/ui/events_log/__init__.py b/Bot/Libs/ui/events_log/__init__.py new file mode 100644 index 00000000..ebca55ab --- /dev/null +++ b/Bot/Libs/ui/events_log/__init__.py @@ -0,0 +1,3 @@ +from .views import RegisterView, UnregisterView + +__all__ = ["RegisterView", "UnregisterView"] diff --git a/Bot/Libs/ui/events_log/views.py b/Bot/Libs/ui/events_log/views.py new file mode 100644 index 00000000..7d75eb5c --- /dev/null +++ b/Bot/Libs/ui/events_log/views.py @@ -0,0 +1,140 @@ +import asyncpg +import discord +from Libs.cog_utils.events_log import delete_cache, set_or_update_cache +from Libs.utils import ErrorEmbed, SuccessActionEmbed +from redis.asyncio.connection import ConnectionPool + + +class RegisterView(discord.ui.View): + def __init__(self, pool: asyncpg.Pool, redis_pool: ConnectionPool) -> None: + super().__init__() + self.pool = pool + self.redis_pool = redis_pool + + @discord.ui.select( + cls=discord.ui.ChannelSelect, channel_types=[discord.ChannelType.text] + ) + async def select_channels( + self, interaction: discord.Interaction, select: discord.ui.ChannelSelect + ) -> None: + query = """ + WITH guild_update AS ( + UPDATE guild + SET logs = $3 + WHERE id = $1 + RETURNING id + ) + INSERT INTO logging_config (channel_id, guild_id) + VALUES ($2, (SELECT id FROM guild_update)) + ON CONFLICT (guild_id) DO + UPDATE SET channel_id = excluded.channel_id; + """ + async with self.pool.acquire() as conn: + guildId = interaction.guild.id # type: ignore + tr = conn.transaction() + await tr.start() + + try: + await conn.execute(query, guildId, select.values[0].id, True) + data = { + "id": guildId, + "logs": True, + "channel_id": select.values[0].id, + "member_events": True, + "mod_events": True, + "eco_events": False, + } + await set_or_update_cache( + key=f"cache:kumiko:{guildId}:logging_config", + redis_pool=self.redis_pool, + data=data, + ) + except asyncpg.UniqueViolationError: + await tr.rollback() + await interaction.response.send_message("There are duplicate records") + except Exception: + await tr.rollback() + await interaction.response.send_message("Could not create records.") + else: + await tr.commit() + await interaction.response.send_message( + f"Successfully set the logging channel to {select.values[0].mention}" + ) + + @discord.ui.button(label="Finish", style=discord.ButtonStyle.green) + async def button_quit( + self, interaction: discord.Interaction, button: discord.ui.Button + ) -> None: + await interaction.response.defer() + await interaction.delete_original_response() + self.stop() + + +class UnregisterView(discord.ui.View): + def __init__(self, pool: asyncpg.Pool, redis_pool: ConnectionPool) -> None: + super().__init__() + self.pool = pool + self.redis_pool = redis_pool + + @discord.ui.button( + label="Confirm", + style=discord.ButtonStyle.green, + emoji="<:greenTick:596576670815879169>", + ) + async def confirm( + self, interaction: discord.Interaction, button: discord.ui.Button + ) -> None: + query = """ + WITH guild_update AS ( + UPDATE guild + SET logs = $2 + WHERE id = $1 + RETURNING id + ) + DELETE FROM logging_config WHERE guild_id = (SELECT id FROM guild_update); + """ + async with self.pool.acquire() as conn: + guildId = interaction.guild.id # type: ignore + key = f"cache:kumiko:{guildId}:logging_config" + + tr = conn.transaction() + await tr.start() + + try: + await conn.execute(query, guildId, False) + await delete_cache(key=key, redis_pool=self.redis_pool) + except asyncpg.UniqueViolationError: + await tr.rollback() + self.clear_items() + uniqueViolationEmbed = ErrorEmbed( + description="There are duplicate records" + ) + await interaction.response.edit_message( + embed=uniqueViolationEmbed, view=self + ) + except Exception: + await tr.rollback() + self.clear_items() + failedEmbed = ErrorEmbed( + description="Could not update or delete records" + ) + await interaction.response.edit_message(embed=failedEmbed, view=self) + else: + await tr.commit() + self.clear_items() + successEmbed = SuccessActionEmbed() + successEmbed.description = "Disabled and cleared all logging configs" + + await interaction.response.edit_message(embed=successEmbed, view=self) + + @discord.ui.button( + label="Cancel", + style=discord.ButtonStyle.red, + emoji="<:redTick:596576672149667840>", + ) + async def cancel( + self, interaction: discord.Interaction, button: discord.ui.Button + ) -> None: + await interaction.response.defer() + await interaction.delete_original_response() + self.stop() diff --git a/Bot/Libs/ui/prefix/__init__.py b/Bot/Libs/ui/prefix/__init__.py new file mode 100644 index 00000000..fc7a6690 --- /dev/null +++ b/Bot/Libs/ui/prefix/__init__.py @@ -0,0 +1,3 @@ +from .views import DeletePrefixView + +__all__ = ["DeletePrefixView"] diff --git a/Bot/Libs/ui/prefix/views.py b/Bot/Libs/ui/prefix/views.py new file mode 100644 index 00000000..9af8a3eb --- /dev/null +++ b/Bot/Libs/ui/prefix/views.py @@ -0,0 +1,48 @@ +import discord +from kumikocore import KumikoCore +from Libs.utils import CancelledActionEmbed, SuccessActionEmbed + + +class DeletePrefixView(discord.ui.View): + def __init__(self, bot: KumikoCore, prefix: str) -> None: + super().__init__() + self.bot = bot + self.prefix = prefix + self.pool = self.bot.pool + + @discord.ui.button( + label="Confirm", + style=discord.ButtonStyle.green, + emoji="<:greenTick:596576670815879169>", + ) + async def confirm( + self, interaction: discord.Interaction, button: discord.ui.Button + ) -> None: + query = """ + UPDATE guild + SET prefix = ARRAY_REMOVE(prefix, $1) + WHERE id=$2; + """ + async with self.pool.acquire() as conn: + guild_id = interaction.guild.id # type: ignore # lying again + await conn.execute(query, self.prefix, guild_id) + self.bot.prefixes[guild_id].remove( + self.prefix + ) # This makes the assumption that the guild is already in the LRU cache. This is not the best - Noelle + self.clear_items() + embed = SuccessActionEmbed( + description=f"The prefix `{self.prefix}` was successfully removed" + ) + await interaction.response.edit_message(embed=embed, view=self) + + @discord.ui.button( + label="Cancel", + style=discord.ButtonStyle.red, + emoji="<:redTick:596576672149667840>", + ) + async def cancel( + self, interaction: discord.Interaction, button: discord.ui.Button + ) -> None: + self.clear_items() + embed = CancelledActionEmbed() + await interaction.response.edit_message(embed=embed, view=self) diff --git a/Bot/Libs/utils/__init__.py b/Bot/Libs/utils/__init__.py index eb42808c..574df561 100644 --- a/Bot/Libs/utils/__init__.py +++ b/Bot/Libs/utils/__init__.py @@ -1,13 +1,33 @@ -from .backoff import backoff -from .embeds import Embed, ErrorEmbed -from .utils import encodeDatetime, parseDatetime, parseSubreddit, parseTimeStr - -__all__ = [ - "backoff", - "parseDatetime", - "encodeDatetime", - "Embed", - "ErrorEmbed", - "parseSubreddit", - "parseTimeStr", -] +from .converters import PrefixConverter +from .embeds import ( + CancelledActionEmbed, + ConfirmEmbed, + Embed, + ErrorEmbed, + JoinEmbed, + LeaveEmbed, + SuccessActionEmbed, +) +from .greedy_formatter import formatGreedy +from .kumiko_logger import KumikoLogger +from .prefix import get_prefix, validatePrefix +from .utils import encodeDatetime, parseDatetime, parseSubreddit, parseTimeStr + +__all__ = [ + "PrefixConverter", + "parseDatetime", + "encodeDatetime", + "Embed", + "ErrorEmbed", + "parseSubreddit", + "parseTimeStr", + "formatGreedy", + "KumikoLogger", + "get_prefix", + "validatePrefix", + "ConfirmEmbed", + "SuccessActionEmbed", + "CancelledActionEmbed", + "JoinEmbed", + "LeaveEmbed", +] diff --git a/Bot/Libs/utils/backoff.py b/Bot/Libs/utils/backoff.py deleted file mode 100644 index 9047697e..00000000 --- a/Bot/Libs/utils/backoff.py +++ /dev/null @@ -1,19 +0,0 @@ -import random - - -def backoff(backoff_sec: int = 5, backoff_sec_index: int = 0) -> float: - """Helper function to calculate backoff time (exponential) - - Args: - backoff_sec (int, optional): The init second to use. Defaults to 5. - backoff_sec_index (int, optional): The index to use. Defaults to 0. - - Returns: - float: Backoff time (seconds) - """ - sleepAmt = backoff_sec * 2**backoff_sec_index + random.uniform( # nosec - 0, 1 - ) # nosec - if sleepAmt > 60: - return float(60) - return sleepAmt diff --git a/Bot/Libs/utils/converters.py b/Bot/Libs/utils/converters.py new file mode 100644 index 00000000..0867baca --- /dev/null +++ b/Bot/Libs/utils/converters.py @@ -0,0 +1,11 @@ +from discord.ext import commands + + +class PrefixConverter(commands.Converter): + async def convert(self, ctx: commands.Context, argument: str): + user_id = ctx.bot.user.id + if argument.startswith((f"<@{user_id}>", f"<@!{user_id}>")): + raise commands.BadArgument("That is a reserved prefix already in use.") + if len(argument) > 100: + raise commands.BadArgument("That prefix is too long.") + return argument diff --git a/Bot/Libs/utils/embeds.py b/Bot/Libs/utils/embeds.py index e1574c97..63f80262 100644 --- a/Bot/Libs/utils/embeds.py +++ b/Bot/Libs/utils/embeds.py @@ -1,4 +1,5 @@ import discord +from discord.utils import utcnow class Embed(discord.Embed): @@ -9,6 +10,26 @@ def __init__(self, **kwargs): super().__init__(**kwargs) +class SuccessActionEmbed(discord.Embed): + """Kumiko's custom success action embed""" + + def __init__(self, **kwargs): + kwargs.setdefault("color", discord.Color.from_rgb(75, 181, 67)) + kwargs.setdefault("title", "Action successful") + kwargs.setdefault("description", "The action requested was successful") + super().__init__(**kwargs) + + +class CancelledActionEmbed(discord.Embed): + """Kumiko's custom confirm action embed""" + + def __init__(self, **kwargs): + kwargs.setdefault("color", discord.Color.from_rgb(255, 0, 51)) + kwargs.setdefault("title", "Action cancelled") + kwargs.setdefault("description", "The action requested was cancelled") + super().__init__(**kwargs) + + class ErrorEmbed(discord.Embed): """Kumiko's custom error embed""" @@ -20,3 +41,30 @@ def __init__(self, **kwargs): "Uh oh! It seems like the command ran into an issue! For support, please visit Kumiko's Support Server to get help!", ) super().__init__(**kwargs) + + +class ConfirmEmbed(discord.Embed): + """Kumiko's custom confirm embed""" + + def __init__(self, **kwargs): + kwargs.setdefault("color", discord.Color.from_rgb(255, 191, 0)) + kwargs.setdefault("title", "Are you sure?") + super().__init__(**kwargs) + + +class JoinEmbed(discord.Embed): + """Kumiko's custom join embed""" + + def __init__(self, **kwargs): + kwargs.setdefault("color", discord.Color.from_rgb(127, 255, 0)) + kwargs.setdefault("timestamp", utcnow()) + super().__init__(**kwargs) + + +class LeaveEmbed(discord.Embed): + """Kumiko's custom leave embed""" + + def __init__(self, **kwargs): + kwargs.setdefault("color", discord.Color.from_rgb(255, 0, 51)) + kwargs.setdefault("timestamp", utcnow()) + super().__init__(**kwargs) diff --git a/Bot/Libs/utils/greedy_formatter.py b/Bot/Libs/utils/greedy_formatter.py new file mode 100644 index 00000000..c0601614 --- /dev/null +++ b/Bot/Libs/utils/greedy_formatter.py @@ -0,0 +1,22 @@ +from typing import List + + +def formatGreedy(list: List[str]) -> str: + """Formats a Greedy list into a human-readable string + + For example, if we had a list of ["a", "b", "c"], it would return "a, b, and c". + If we had a list of ["a", "b"], it would return "a and b". + If we had a list of ["a"], it would return "a". + If we had a list of [], it would return "". + + Args: + list: The list of strings to format + + Returns: + str: The formatted string + """ + if len(list) >= 3: + return f"{', '.join(list[:-1])}, and {list[-1]}" + elif len(list) == 2: + return " and ".join(list) + return "".join(list) diff --git a/Bot/Libs/utils/help/__init__.py b/Bot/Libs/utils/help/__init__.py index 7550d69d..4423cad1 100644 --- a/Bot/Libs/utils/help/__init__.py +++ b/Bot/Libs/utils/help/__init__.py @@ -1,105 +1,4 @@ -import contextlib +from .kumiko_help import KumikoHelp +from .kumiko_help_paginated import KumikoHelpPaginated -from discord.ext import commands -from Libs.utils import Embed - - -class KumikoHelp(commands.HelpCommand): - def __init__(self): - super().__init__( # create our class with some aliases and cooldown - command_attrs={ - "help": "The help command for the bot", - "cooldown": commands.CooldownMapping.from_cooldown( - 1, 3.0, commands.BucketType.user - ), - "aliases": ["commands"], - } - ) - - async def send(self, **kwargs): - """a shortcut to sending to get_destination""" - await self.get_destination().send(**kwargs) - - async def send_bot_help(self, mapping): - """triggers when a `help` is called""" - ctx = self.context - embed = Embed(title=f"{ctx.me.display_name} Help") - embed.set_thumbnail(url=ctx.me.display_avatar) - usable = 0 - - for ( - cog, - commands, - ) in mapping.items(): # iterating through our mapping of cog: commands - if filtered_commands := await self.filter_commands(commands): - # if no commands are usable in this category, we don't want to display it - amount_commands = len(filtered_commands) - usable += amount_commands - if cog: # getting attributes dependent on if a cog exists or not - name = cog.qualified_name - description = cog.description or "No description" - else: - name = "No" - description = "Commands with no category" - - embed.add_field( - name=f"{name} Category [{amount_commands}]", value=description - ) - - # embed.description = f"{len(bot.commands)} commands | {usable} usable" - - await self.send(embed=embed) - - async def send_command_help(self, command): - """triggers when a `help ` is called""" - signature = self.get_command_signature( - command - ) # get_command_signature gets the signature of a command in [optional] - embed = Embed(title=signature, description=command.help or "No help found...") - - if cog := command.cog: - embed.add_field(name="Category", value=cog.qualified_name) - - can_run = "No" - # command.can_run to test if the cog is usable - with contextlib.suppress(commands.CommandError): - if await command.can_run(self.context): - can_run = "Yes" - - embed.add_field(name="Usable", value=can_run) - - if command._buckets and ( - cooldown := command._buckets._cooldown - ): # use of internals to get the cooldown of the command - embed.add_field( - name="Cooldown", - value=f"{cooldown.rate} per {cooldown.per:.0f} seconds", - ) - - await self.send(embed=embed) - - async def send_help_embed( - self, title, description, commands - ): # a helper function to add commands to an embed - embed = Embed(title=title, description=description or "No help found...") - - if filtered_commands := await self.filter_commands(commands): - for command in filtered_commands: - embed.add_field( - name=self.get_command_signature(command), - value=command.help or "No help found...", - ) - - await self.send(embed=embed) - - async def send_group_help(self, group): - """triggers when a `help ` is called""" - title = self.get_command_signature(group) - await self.send_help_embed(title, group.help, group.commands) - - async def send_cog_help(self, cog): - """triggers when a `help ` is called""" - title = cog.qualified_name or "No" - await self.send_help_embed( - f"{title} Category", cog.description, cog.get_commands() - ) +__all__ = ["KumikoHelpPaginated", "KumikoHelp"] diff --git a/Bot/Libs/utils/help/kumiko_help.py b/Bot/Libs/utils/help/kumiko_help.py index 4cd74f52..b111c3f6 100644 --- a/Bot/Libs/utils/help/kumiko_help.py +++ b/Bot/Libs/utils/help/kumiko_help.py @@ -1,5 +1,4 @@ import contextlib -from typing import List, Mapping, Optional from discord.ext import commands from Libs.utils import Embed @@ -7,7 +6,7 @@ class KumikoHelp(commands.HelpCommand): def __init__(self): - super().__init__( + super().__init__( # create our class with some aliases and cooldown command_attrs={ "help": "The help command for the bot", "cooldown": commands.CooldownMapping.from_cooldown( @@ -17,29 +16,22 @@ def __init__(self): } ) - async def send(self, **kwargs) -> None: + async def send(self, **kwargs): """a shortcut to sending to get_destination""" await self.get_destination().send(**kwargs) - async def send_bot_help( - self, mapping: Mapping[Optional[commands.Cog], List[commands.Command]] - ) -> None: - """Generates the help embed when the default help command is called - - Args: - mapping (Mapping[Optional[commands.Cog], List[commands.Command]]): Mapping of cogs to commands - """ + async def send_bot_help(self, mapping): + """triggers when a `help` is called""" ctx = self.context embed = Embed(title=f"{ctx.me.display_name} Help") embed.set_thumbnail(url=ctx.me.display_avatar) - embed.description = f"{ctx.me.display_name} is a multipurpose bot built with freedom and choice in mind." usable = 0 for ( cog, - commands, + cmds, ) in mapping.items(): # iterating through our mapping of cog: commands - if filtered_commands := await self.filter_commands(commands): + if filtered_commands := await self.filter_commands(cmds): # if no commands are usable in this category, we don't want to display it amount_commands = len(filtered_commands) usable += amount_commands @@ -54,7 +46,7 @@ async def send_bot_help( name=f"{name} Category [{amount_commands}]", value=description ) - # embed.description = f"{len(ctx.commands)} commands | {usable} usable" + # embed.description = f"{len(bot.commands)} commands | {usable} usable" await self.send(embed=embed) @@ -63,9 +55,7 @@ async def send_command_help(self, command): signature = self.get_command_signature( command ) # get_command_signature gets the signature of a command in [optional] - embed = Embed( - title=signature, description=command.short_doc or "No help found..." - ) + embed = Embed(title=signature, description=command.help or "No help found...") if cog := command.cog: embed.add_field(name="Category", value=cog.qualified_name) @@ -97,18 +87,10 @@ async def send_help_embed( for command in filtered_commands: embed.add_field( name=self.get_command_signature(command), - value=command.short_doc or "No help found...", + value=command.help or "No help found...", ) await self.send(embed=embed) - # cmdList = [] - # if filtered_commands := await self.filter_commands(commands): - # for command in filtered_commands: - # cmdList.append((self.get_command_signature(command), command.help or "No help found...")) - - # source = FieldPageSource(cmdList, per_page=3) - # pages = KumikoPages(source, ctx=self.context) - # await pages.start_help(channel=self.get_destination()) async def send_group_help(self, group): """triggers when a `help ` is called""" diff --git a/Bot/Libs/utils/help/kumiko_help_paginated.py b/Bot/Libs/utils/help/kumiko_help_paginated.py new file mode 100644 index 00000000..9ee0206f --- /dev/null +++ b/Bot/Libs/utils/help/kumiko_help_paginated.py @@ -0,0 +1,446 @@ +import inspect +import itertools +from typing import Any, Dict, List, Optional, Union + +import discord +from discord.ext import commands, menus +from Libs.utils.pages import KumikoPages + +# class BotCategories(discord.ui.Select): +# def __init__(self, cogs: List[commands.Cog]) -> None: +# options = [ +# discord.SelectOption(label=cog.qualified_name or "No", description=cog.description) +# for cog in cogs if cog.qualified_name not in ["DevTools", "ErrorHandler", "IPCServer"] +# ] +# super().__init__(placeholder="Select a category...", options=options) + +# async def callback(self, interaction: discord.Interaction): + +# RGB Colors: +# Pink (255, 161, 231) - Used for the main bot page +# Lavender (197, 184, 255) - Used for cog and group pages +# Light Orange (255, 199, 184) - Used for command pages + + +class GroupHelpPageSource(menus.ListPageSource): + def __init__( + self, + group: Union[commands.Group, commands.Cog], + entries: List[commands.Command], + *, + prefix: str, + ): + super().__init__(entries=entries, per_page=6) + self.group: Union[commands.Group, commands.Cog] = group + self.prefix: str = prefix + self.title: str = f"{self.group.qualified_name} Commands" + self.description: str = self.group.description + + async def format_page(self, menu: KumikoPages, commands: List[commands.Command]): + embed = discord.Embed( + title=self.title, + description=self.description, + colour=discord.Colour.from_rgb(197, 184, 255), + ) + + for command in commands: + signature = f"{command.qualified_name} {command.signature}" + embed.add_field( + name=signature, + value=command.short_doc or "No help given...", + inline=False, + ) + + maximum = self.get_max_pages() + if maximum > 1: + embed.set_author( + name=f"Page {menu.current_page + 1}/{maximum} ({len(self.entries)} commands)" + ) + + embed.set_footer( + text=f'Use "{self.prefix}help command" for more info on a command.' + ) + return embed + + +class HelpSelectMenu(discord.ui.Select["HelpMenu"]): + def __init__(self, entries: Dict[commands.Cog, List[commands.Command]], bot): + super().__init__( + placeholder="Select a category...", + min_values=1, + max_values=1, + row=0, + ) + self.cmds: dict[commands.Cog, List[commands.Command]] = entries + self.bot = bot + self.__fill_options() + + def __fill_options(self) -> None: + self.add_option( + label="Index", + emoji="\N{WAVING HAND SIGN}", + value="__index", + description="The help page showing how to use the bot.", + ) + for cog, cmds in self.cmds.items(): + if not cmds: + continue + description = cog.description.split("\n", 1)[0] or None + emoji = getattr(cog, "display_emoji", None) + self.add_option( + label=cog.qualified_name, + value=cog.qualified_name, + description=description, + emoji=emoji, + ) + + async def callback(self, interaction: discord.Interaction): + assert self.view is not None + value = self.values[0] + if value == "__index": + await self.view.rebind(FrontPageSource(), interaction) + else: + cog = self.bot.get_cog(value) + if cog is None: + await interaction.response.send_message( + "Somehow this category does not exist?", ephemeral=True + ) + return + + commands = self.cmds[cog] + if not commands: + await interaction.response.send_message( + "This category has no commands for you", ephemeral=True + ) + return + + source = GroupHelpPageSource( + cog, commands, prefix=self.view.ctx.clean_prefix + ) + await self.view.rebind(source, interaction) + + +class HelpMenu(KumikoPages): + def __init__(self, source: menus.PageSource, ctx: commands.Context): + super().__init__(source, ctx=ctx, compact=True) + + def add_categories( + self, commands: Dict[commands.Cog, List[commands.Command]] + ) -> None: + self.clear_items() + self.add_item(HelpSelectMenu(commands, self.ctx.bot)) + self.fill_items() + + async def rebind( + self, source: menus.PageSource, interaction: discord.Interaction + ) -> None: + self.source = source + self.current_page = 0 + + await self.source._prepare_once() + page = await self.source.get_page(0) + kwargs = await self._get_kwargs_from_page(page) + self._update_labels(0) + await interaction.response.edit_message(**kwargs, view=self) + + +class FrontPageSource(menus.PageSource): + def is_paginating(self) -> bool: + # This forces the buttons to appear even in the front page + return True + + def get_max_pages(self) -> Optional[int]: + # There's only one actual page in the front page + # However we need at least 2 to show all the buttons + return 2 + + async def get_page(self, page_number: int) -> Any: + # The front page is a dummy + self.index = page_number + return self + + def format_page(self, menu: HelpMenu, page: Any): + embed = discord.Embed( + title="Bot Help", colour=discord.Colour.from_rgb(255, 161, 231) + ) + # embed.description = "help" + embed.description = inspect.cleandoc( + f""" + Hello! Welcome to the help page. + + Use "{menu.ctx.clean_prefix}help command" for more info on a command. + Use "{menu.ctx.clean_prefix}help category" for more info on a category. + Use the dropdown menu below to select a category. + """ + ) + + embed.add_field( + name="Support Server", + value="For more help, consider joining the official server over at https://discord.gg/sYP7z2sUda", + inline=False, + ) + + # created_at = time.format_dt(menu.ctx.bot.user.created_at, 'F') + if self.index == 0: + embed.add_field( + name="About Kumiko", + value=( + "Kumiko is an multipurpose bot that takes an unique and alternative approach to " + "what an multipurpose bot is. Kumiko offers features such as moderation, economy, and many more. You can get more " + "information on the commands offered by using the dropdown below.\n\n" + "Kumiko is also open source. You can see the code on [GitHub](https://github.com/No767/Kumiko)" + ), + inline=False, + ) + elif self.index == 1: + entries = ( + ("", "This means the argument is __**required**__."), + ("[argument]", "This means the argument is __**optional**__."), + ("[A|B]", "This means that it can be __**either A or B**__."), + ( + "[argument...]", + "This means you can have multiple arguments.\n" + "Now that you know the basics, it should be noted that...\n" + "__**You do not type in the brackets!**__", + ), + ) + + embed.add_field( + name="How do I use this bot?", + value="Reading the bot signature is pretty simple.", + ) + + for name, value in entries: + embed.add_field(name=name, value=value, inline=False) + + return embed + + +class KumikoHelpPaginated(commands.HelpCommand): + context: commands.Context + + def __init__(self): + super().__init__( + command_attrs={ + "cooldown": commands.CooldownMapping.from_cooldown( + 1, 3.0, commands.BucketType.member + ), + "help": "Shows help about the bot, a command, or a category", + } + ) + + async def on_help_command_error( + self, ctx: commands.Context, error: commands.CommandError + ): + if isinstance(error, commands.CommandInvokeError): + # Ignore missing permission errors + if ( + isinstance(error.original, discord.HTTPException) + and error.original.code == 50013 + ): + return + + await ctx.send(str(error.original)) + + def get_command_signature(self, command: commands.Command) -> str: + parent = command.full_parent_name + if len(command.aliases) > 0: + aliases = "|".join(command.aliases) + fmt = f"[{command.name}|{aliases}]" + if parent: + fmt = f"{parent} {fmt}" + alias = fmt + else: + alias = command.name if not parent else f"{parent} {command.name}" + return f"{alias} {command.signature}" + + async def send_bot_help(self, mapping): + bot = self.context.bot + + def key(command) -> str: + cog = command.cog + return cog.qualified_name if cog else "\U0010ffff" + + entries: list[commands.Command] = await self.filter_commands( + bot.commands, sort=True, key=key + ) + + all_commands: dict[commands.Cog, List[commands.Command]] = {} + for name, children in itertools.groupby(entries, key=key): + if name == "\U0010ffff": + continue + + cog = bot.get_cog(name) + assert cog is not None + all_commands[cog] = sorted(children, key=lambda c: c.qualified_name) + + menu = HelpMenu(FrontPageSource(), ctx=self.context) + menu.add_categories(all_commands) + await menu.start() + + async def send_cog_help(self, cog): + entries = await self.filter_commands(cog.get_commands(), sort=True) + menu = HelpMenu( + GroupHelpPageSource(cog, entries, prefix=self.context.clean_prefix), + ctx=self.context, + ) + await menu.start() + + def common_command_formatting(self, embed_like, command): + embed_like.title = self.get_command_signature(command) + if command.description: + embed_like.description = f"{command.description}\n\n{command.help}" + else: + embed_like.description = command.help or "No help found..." + + async def send_command_help(self, command): + # No pagination necessary for a single command. + embed = discord.Embed(colour=discord.Colour.from_rgb(255, 199, 184)) + self.common_command_formatting(embed, command) + await self.context.send(embed=embed) + + async def send_group_help(self, group): + subcommands = group.commands + if len(subcommands) == 0: + return await self.send_command_help(group) + + entries = await self.filter_commands(subcommands, sort=True) + if len(entries) == 0: + return await self.send_command_help(group) + + source = GroupHelpPageSource(group, entries, prefix=self.context.clean_prefix) + self.common_command_formatting(source, group) + menu = HelpMenu(source, ctx=self.context) + await menu.start() + + # def __init__(self) -> None: + # super().__init__( + # command_attrs={ + # "help": "The help command for the bot", + # "cooldown": commands.CooldownMapping.from_cooldown( + # 1, 3.0, commands.BucketType.user + # ), + # "aliases": ["commands"], + # } + # ) + + # async def send(self, **kwargs) -> None: + # """a shortcut to sending to get_destination""" + # await self.get_destination().send(**kwargs) + + # async def help_embed( + # self, title: str, description: str, commands: List[commands.Command] + # ) -> None: + # """The default help embed builder + + # Mainly used so we don't repeat ourselves when building help embeds + + # Args: + # title (str): The title of the embed. Usually the name of the cog, group, etc + # description (str): The description of the embed. Usually the desc of the cog or group + # commands (List[commands.Command]): List of commands + # """ + # filteredCommands = await self.filter_commands(commands) + # fieldSource = [ + # (self.get_command_signature(command), command.help or "No help found...") + # for command in filteredCommands + # ] + # sources = FieldPageSource( + # entries=fieldSource, + # per_page=5, + # inline=False, + # clear_description=False, + # title=title or "No", + # description=description or "No help found...", + # ) + # pages = KumikoPages(source=sources, ctx=self.context) + # await pages.start() + + # async def send_bot_help( + # self, mapping: Mapping[Optional[commands.Cog], List[commands.Command]] + # ) -> None: + # """Generates the help embed when the default help command is called + + # Args: + # mapping (Mapping[Optional[commands.Cog], List[commands.Command]]): Mapping of cogs to commands + # """ + # ctx = self.context + # embed = Embed(title=f"{ctx.me.display_name} Help") + # embed.set_thumbnail(url=ctx.me.display_avatar) + # embed.description = f"{ctx.me.display_name} is a multipurpose bot built with freedom and choice in mind." + # usable = 0 + + # for ( + # cog, + # cmds, + # ) in mapping.items(): # iterating through our mapping of cog: commands + # if filtered_commands := await self.filter_commands(cmds): + # # if no commands are usable in this category, we don't want to display it + # amount_commands = len(filtered_commands) + # usable += amount_commands + # if cog: # getting attributes dependent on if a cog exists or not + # name = cog.qualified_name + # description = cog.description or "No description" + # else: + # name = "No" + # description = "Commands with no category" + + # embed.add_field( + # name=f"{name} Category [{amount_commands}]", value=description + # ) + + # # embed.description = f"{len(ctx.commands)} commands | {usable} usable" + + # await self.send(embed=embed) + + # async def send_command_help(self, command: commands.Command) -> None: + # """Triggers when a `help ` is called + + # Args: + # command (commands.Command): The command to get help for + # """ + # signature = self.get_command_signature( + # command + # ) # get_command_signature gets the signature of a command in [optional] + # embed = Embed(title=signature, description=command.help or "No help found...") + + # if cog := command.cog: + # embed.add_field(name="Category", value=cog.qualified_name) + + # can_run = "No" + # # command.can_run to test if the cog is usable + # with contextlib.suppress(commands.CommandError): + # if await command.can_run(self.context): + # can_run = "Yes" + + # embed.add_field(name="Usable", value=can_run) + + # if command._buckets and ( + # cooldown := command._buckets._cooldown + # ): # use of internals to get the cooldown of the command + # embed.add_field( + # name="Cooldown", + # value=f"{cooldown.rate} per {cooldown.per:.0f} seconds", + # ) + + # await self.send(embed=embed) + + # async def send_cog_help(self, cog: commands.Cog) -> None: + # """Send the help command when a `help ` is called + + # Args: + # cog (commands.Cog): The cog requested + # """ + # title = cog.qualified_name or "No" + # await self.help_embed( + # title=f"{title} Category", + # description=cog.description, + # commands=cog.get_commands(), + # ) + + # async def send_group_help(self, group): + # """triggers when a `help ` is called""" + # title = self.get_command_signature(group) + # await self.help_embed( + # title=title, description=group.help, commands=group.commands + # ) diff --git a/Bot/Libs/utils/kumiko_logger.py b/Bot/Libs/utils/kumiko_logger.py new file mode 100644 index 00000000..819945aa --- /dev/null +++ b/Bot/Libs/utils/kumiko_logger.py @@ -0,0 +1,42 @@ +import logging +import re +from types import TracebackType +from typing import Optional, Type, TypeVar + +import discord + +BE = TypeVar("BE", bound=BaseException) + + +class RemoveIPCNoise(logging.Filter): + def __init__(self) -> None: + self.self = self + + def filter(self, record: logging.LogRecord) -> bool: + matchRegex = r"(connection\s[open|closed])" + if bool(re.search(matchRegex, record.msg)): + return False + return True + + +class KumikoLogger: + def __init__(self) -> None: + self.self = self + self.log = logging.getLogger("discord") + + def __enter__(self) -> None: + logging.getLogger("discord.ext.ipc.server").addFilter(RemoveIPCNoise()) + logging.getLogger("gql").setLevel(logging.WARNING) + fmt = logging.Formatter( + fmt="%(asctime)s %(levelname)s %(message)s", + datefmt="[%Y-%m-%d %H:%M:%S]", + ) + discord.utils.setup_logging(formatter=fmt) + + def __exit__( + self, + exc_type: Optional[Type[BE]], + exc: Optional[BE], + traceback: Optional[TracebackType], + ) -> None: + self.log.info("Shutting down Kumiko...") \ No newline at end of file diff --git a/Bot/Libs/utils/pages/sources.py b/Bot/Libs/utils/pages/sources.py index 62ef1c77..f1dfae4d 100644 --- a/Bot/Libs/utils/pages/sources.py +++ b/Bot/Libs/utils/pages/sources.py @@ -39,9 +39,11 @@ def __init__( per_page: int = 5, inline: bool = False, clear_description: bool = True, + title: str = "", + description: str = "", ) -> None: super().__init__(entries, per_page=per_page) - self.embed: Embed = Embed() + self.embed: Embed = Embed(title=title, description=description) self.clear_description: bool = clear_description self.inline: bool = inline diff --git a/Bot/Libs/utils/postgresql/__init__.py b/Bot/Libs/utils/postgresql/__init__.py index 37600a57..9ef1b553 100644 --- a/Bot/Libs/utils/postgresql/__init__.py +++ b/Bot/Libs/utils/postgresql/__init__.py @@ -1,3 +1,3 @@ -from .ctx import PrismaClientSession - -__all__ = ["PrismaClientSession"] +# from .ctx import PrismaSessionManager +from .ensure_open_conns import ensureOpenPostgresConn +__all__ = ["ensureOpenPostgresConn"] diff --git a/Bot/Libs/utils/postgresql/ctx.py b/Bot/Libs/utils/postgresql/ctx.py deleted file mode 100644 index fcde37a4..00000000 --- a/Bot/Libs/utils/postgresql/ctx.py +++ /dev/null @@ -1,29 +0,0 @@ -import logging -from contextlib import asynccontextmanager - -from prisma import Prisma # type: ignore -from prisma.engine.errors import EngineConnectionError # type: ignore - -logger = logging.getLogger("discord") or logging.getLogger(__name__) - -# This will only really be kept around for either test scripts or for other purposes -@asynccontextmanager -async def PrismaClientSession(): - """Implements an asynchronous context manager for Prisma client sessions - - Raises: - EngineConnectionError: Raised when the PostgreSQL server or DB server is unreachable - - Returns: - AsyncIterator[None]: Returns an asynchronous context manager for Prisma client sessions - """ - try: - db = Prisma(auto_register=True) - conn = await db.connect() - logger.info("Successfully connected to PostgreSQL database") - yield conn - except EngineConnectionError: - logger.error("Failed to connect to PostgreSQL database") - raise EngineConnectionError - finally: - await db.disconnect() diff --git a/Bot/Libs/utils/postgresql/ensure_open_conns.py b/Bot/Libs/utils/postgresql/ensure_open_conns.py new file mode 100644 index 00000000..cb05380b --- /dev/null +++ b/Bot/Libs/utils/postgresql/ensure_open_conns.py @@ -0,0 +1,21 @@ +import logging +from typing import Literal + +import asyncpg + + +async def ensureOpenPostgresConn(conn_pool: asyncpg.Pool) -> Literal[True]: + """Ensures that the current connection pulled from the pool can be run. + + Args: + conn_pool (asyncpg.Pool): The connection pool to get connections from. + + Returns: + Literal[True]: If successful, the coroutine will return True, otherwise it will raise an exception + """ + logger = logging.getLogger("discord") + async with conn_pool.acquire() as conn: + connStatus = conn.is_closed() + if connStatus is False: + logger.info("PostgreSQL server is up") + return True diff --git a/Bot/Libs/utils/prefix.py b/Bot/Libs/utils/prefix.py new file mode 100644 index 00000000..993bcd3c --- /dev/null +++ b/Bot/Libs/utils/prefix.py @@ -0,0 +1,58 @@ +from typing import List + +import discord + +# TODO - Prevent people from setting up `/` prefixes. Doesn't help +# removed the type hinting bc of circular imports +# if there is a way to solve it, then it would be back + + +async def get_prefix(bot, msg: discord.Message) -> List[str]: + if msg.guild is None: + return bot.default_prefix + # return commands.when_mentioned_or(bot.default_prefix)(bot, msg) + cachedPrefix = bot.prefixes.get(msg.guild.id) + if cachedPrefix is None: + async with bot.pool.acquire() as conn: + query = """ + SELECT prefix FROM guild WHERE id = $1; + """ + updateQuery = """ + UPDATE guild + SET prefix = $1 + WHERE id = $2; + """ + fetchPrefix = await conn.fetchval(query, msg.guild.id) + if fetchPrefix: + bot.prefixes[msg.guild.id] = fetchPrefix + # return commands.when_mentioned_or(bot.prefixes[msg.guild.id])(bot, msg) + return bot.prefixes[msg.guild.id] + else: + await conn.execute(updateQuery, [bot.default_prefix], msg.guild.id) + bot.prefixes[msg.guild.id] = bot.default_prefix + return bot.prefixes[msg.guild.id] + # return commands.when_mentioned_or(bot.prefixes[msg.guild.id])(bot, msg) + else: + return bot.prefixes[msg.guild.id] + # return commands.when_mentioned_or(bot.prefixes[msg.guild.id])(bot, msg) + + +def validatePrefix(prefixes: List[str], new_prefix: str) -> bool: + """Validates whether the prefix given is valid or not. + + The rules for the prefix being valid goes as follows: + + 1. The new prefix must not be in the prefixes list + 2. The new prefix cannot be "/". + a. If a prefix containing "/" is followed with another character (eg "/e"), this is considered valid + + Args: + prefixes (List[str]): The list of prefixes associated with the guild. This is usually found within the prefix cache + new_prefix (str): The new prefix to validate + + Returns: + bool: Whether the prefix is valid or not + """ + return new_prefix in prefixes or ( + new_prefix.startswith("/") and len(new_prefix) == 1 + ) diff --git a/Bot/Libs/utils/redis/__init__.py b/Bot/Libs/utils/redis/__init__.py index 454a8cb0..d6d53d20 100644 --- a/Bot/Libs/utils/redis/__init__.py +++ b/Bot/Libs/utils/redis/__init__.py @@ -1,3 +1,3 @@ -from .connections import pingRedis, redisCheck - -__all__ = ["pingRedis", "redisCheck"] +from .connections import ensureOpenRedisConn + +__all__ = ["ensureOpenRedisConn"] diff --git a/Bot/Libs/utils/redis/connections.py b/Bot/Libs/utils/redis/connections.py index d81669ab..1b9767f2 100644 --- a/Bot/Libs/utils/redis/connections.py +++ b/Bot/Libs/utils/redis/connections.py @@ -1,61 +1,23 @@ -import asyncio -import logging -from typing import Union - -import redis.asyncio as redis -from Libs.cache import kumikoCP -from redis.asyncio.connection import ConnectionPool -from redis.exceptions import ConnectionError, TimeoutError - -from ..backoff import backoff - -logger = logging.getLogger("discord") - - -async def pingRedis(connection_pool: ConnectionPool) -> bool: - """Pings the redis server to ensure that it is alive - - Args: - connection_pool (ConnectionPool): ConnectionPool object to use - - Returns: - bool: Whether the Redis server is alive or not - """ - r: redis.Redis = redis.Redis(connection_pool=connection_pool, socket_timeout=10.0) - return await r.ping() - - -async def redisCheck( - backoff_sec: int = 15, - backoff_index: int = 0, -) -> Union[bool, None]: - """Integration method to check if the Redis server is alive - - Also sets up the conn pool cache. This is handled recursively actually. - There is a base case of 5 so the recursion only goes 5 calls deep on the stack. This is to prevent infinite calls on the stack from piling up. - - Args: - backoff_sec (int, optional): Backoff time in seconds. Defaults to 15. - backoff_index (int, optional): Backoff index. This is used privately Defaults to 0. - - Returns: - Union[Literal[True], None]: Returns True if the Redis server is alive. Returns None if the coroutine is in a recursive loop. - """ - try: - connPool = kumikoCP.getConnPool() - res = await pingRedis(connection_pool=connPool) - if backoff_index == 5: - logger.error("Unable to connect to Redis server") - return False - if res is True: - logger.info("Successfully connected to Redis server") - return True - except (ConnectionError, TimeoutError): - backoffTime = backoff(backoff_sec=backoff_sec, backoff_sec_index=backoff_index) - logger.error( - f"Failed to connect to Redis server - Restarting connection in {int(backoffTime)} seconds" - ) - await asyncio.sleep(backoffTime) - await redisCheck( - backoff_index=backoff_index + 1, - ) +import logging +from typing import Literal + +import redis.asyncio as redis +from redis.asyncio.connection import ConnectionPool + +logger = logging.getLogger("discord") + + +async def ensureOpenRedisConn(redis_pool: ConnectionPool) -> Literal[True]: + """Pings the Redis server to check if it's open or not + + Args: + connection_pool (Union[ConnectionPool, None]): The supplied connection pool. If none, it will be created automatically + + Returns: + Literal[True]: If successful, the coroutine will return True, otherwise it will raise an exception + """ + r: redis.Redis = redis.Redis(connection_pool=redis_pool) + resultPing = await r.ping() + if resultPing: + logger.info("Sucessfully connected to the Redis server") + return True diff --git a/Bot/kumikobot.py b/Bot/kumikobot.py index 98a63312..aaacd4fd 100644 --- a/Bot/kumikobot.py +++ b/Bot/kumikobot.py @@ -1,34 +1,60 @@ -import logging +import asyncio import os +import asyncpg import discord -from anyio import run +from aiohttp import ClientSession from dotenv import load_dotenv from kumikocore import KumikoCore +from Libs.cache import KumikoCPManager +from Libs.utils import KumikoLogger + +# Only used for Windows development +if os.name == "nt": + import winloop + + asyncio.set_event_loop_policy(winloop.WinLoopPolicy()) +else: + try: + import uvloop + + asyncio.set_event_loop_policy(uvloop.EventLoopPolicy()) + except ImportError: + pass load_dotenv() KUMIKO_TOKEN = os.environ["KUMIKO_TOKEN"] DEV_MODE = os.getenv("DEV_MODE") in ("True", "TRUE") +POSTGRES_URI = os.environ["POSTGRES_URI"] +REDIS_URI = os.environ["REDIS_URI"] + intents = discord.Intents.default() intents.message_content = True +intents.members = True -FORMATTER = logging.Formatter( - fmt="%(asctime)s %(levelname)s %(message)s", datefmt="[%Y-%m-%d %H:%M:%S]" -) -discord.utils.setup_logging(formatter=FORMATTER) -logger = logging.getLogger("discord") -logging.getLogger("gql").setLevel(logging.WARNING) +async def main() -> None: + async with ClientSession() as session, asyncpg.create_pool( + dsn=POSTGRES_URI, command_timeout=60, max_size=20, min_size=20 + ) as pool, KumikoCPManager(uri=REDIS_URI, max_size=25) as redis_pool: + async with KumikoCore( + intents=intents, + session=session, + pool=pool, + redis_pool=redis_pool, + dev_mode=DEV_MODE, + ) as bot: + await bot.start(KUMIKO_TOKEN) -async def main() -> None: - async with KumikoCore(intents=intents, dev_mode=DEV_MODE) as bot: - await bot.start(KUMIKO_TOKEN) +def launch() -> None: + with KumikoLogger(): + asyncio.run(main()) if __name__ == "__main__": try: - run(main, backend_options={"use_uvloop": True}) + launch() except KeyboardInterrupt: - logger.info("Shutting down...") + pass diff --git a/Bot/kumikocore.py b/Bot/kumikocore.py index cfca18fc..61f2ef96 100644 --- a/Bot/kumikocore.py +++ b/Bot/kumikocore.py @@ -1,11 +1,17 @@ import logging from pathlib import Path as SyncPath +import asyncpg import discord +from aiohttp import ClientSession from anyio import Path from discord.ext import commands -from Libs.utils.help import KumikoHelp -from Libs.utils.redis import redisCheck +from Libs.utils import get_prefix +from Libs.utils.help import KumikoHelpPaginated +from Libs.utils.postgresql import ensureOpenPostgresConn +from Libs.utils.redis import ensureOpenRedisConn +from lru import LRU +from redis.asyncio.connection import ConnectionPool # Some weird import logic to ensure that watchfiles is there _fsw = True @@ -21,21 +27,81 @@ class KumikoCore(commands.Bot): def __init__( self, intents: discord.Intents, + session: ClientSession, + pool: asyncpg.Pool, + redis_pool: ConnectionPool, + lru_size: int = 50, dev_mode: bool = False, *args, **kwargs, ): super().__init__( intents=intents, - command_prefix=commands.when_mentioned_or(">"), - help_command=KumikoHelp(), - activity=discord.Activity(type=discord.ActivityType.watching, name="/help"), + command_prefix=get_prefix, + help_command=KumikoHelpPaginated(), + activity=discord.Activity(type=discord.ActivityType.watching, name=">help"), *args, **kwargs, ) self.dev_mode = dev_mode + self.lru_size = lru_size + self._session = session + self._pool = pool + self._redis_pool = redis_pool + self._prefixes: LRU = LRU(self.lru_size) + self.default_prefix = ">" self.logger: logging.Logger = logging.getLogger("kumikobot") + @property + def session(self) -> ClientSession: + """A global web session used throughout the lifetime of the bot + + Returns: + ClientSession: AIOHTTP's ClientSession + """ + return self._session + + @property + def pool(self) -> asyncpg.Pool: + """A global object managed throughout the lifetime of Kumiko + + Holds the asyncpg pool for connections + + Returns: + asyncpg.Pool: asyncpg connection pool + """ + return self._pool + + @property + def redis_pool(self) -> ConnectionPool: + """A global object managed throughout the lifetime of Kumiko + + Returns: + ConnectionPool: Redis connection pool + """ + return self._redis_pool + + # It is preffered in this case to keep an LRU cache instead of a regular Dict cache + # For example, if an running instance keeps 100 entries ({guild_id: prefix}) + # then this would take up too much memory. + # + # By instead using an LRU cache, if we reach the max, then we evict the prefix from the guild that hasn't used it in a while + # The limit for the LRU cache is set to 100 + # + # The primary goal of Kumiko is to keep the footprint of the RAM usage as low as possible + # We don't need to have the bot consuming 250-300MB of RAM like when Prisma was used. + @property + def prefixes(self) -> LRU: + """A LRU cache of the guild prefixes + + The LRU cache's implementation is built in C, + so we natively can keep the performance the same as if it was a regular dict + + Returns: + LRU: LRU cache of the guild prefixes + """ + return self._prefixes + async def fsWatcher(self) -> None: cogsPath = SyncPath(__file__).parent.joinpath("Cogs") async for changes in awatch(cogsPath): @@ -48,18 +114,11 @@ async def fsWatcher(self) -> None: async def setup_hook(self) -> None: cogsPath = Path(__file__).parent.joinpath("Cogs") async for cog in cogsPath.rglob("**/*.py"): - if cog.parent.name == "Cogs": - self.logger.debug( - f"Loaded extension: {cog.parent.name}.{cog.name[:-3]}" - ) - await self.load_extension(f"{cog.parent.name}.{cog.name[:-3]}") - else: - self.logger.debug( - f"Loaded extension: Cogs.{cog.parent.name}.{cog.name[:-3]}" - ) - await self.load_extension(f"Cogs.{cog.parent.name}.{cog.name[:-3]}") - - self.loop.create_task(redisCheck()) + self.logger.debug(f"Loaded extension: {cog.parent.name}.{cog.name[:-3]}") + await self.load_extension(f"{cog.parent.name}.{cog.name[:-3]}") + + self.loop.create_task(ensureOpenPostgresConn(self._pool)) + self.loop.create_task(ensureOpenRedisConn(self._redis_pool)) if self.dev_mode is True and _fsw is True: self.logger.info("Dev mode is enabled. Loading Jishaku and FSWatcher") diff --git a/Docker/Dockerfile b/Docker/Dockerfile index 91fc2fcc..d200095d 100644 --- a/Docker/Dockerfile +++ b/Docker/Dockerfile @@ -1,7 +1,7 @@ #################################################################################################### ## Builder image #################################################################################################### -FROM python:3.11-slim-bullseye AS builder +FROM python:3.11-slim-bookworm AS builder ENV DEBIAN_FRONTEND=noninteractive @@ -29,14 +29,14 @@ RUN poetry export -f requirements.txt --output requirements.txt --without-hashes #################################################################################################### ## Final image #################################################################################################### -FROM python:3.11-slim-bullseye +FROM python:3.11-slim-bookworm RUN apt update \ && apt install -y --no-install-recommends \ tini \ ca-certificates \ bash \ - netcat \ + netcat-traditional \ libopus-dev \ libffi-dev \ libnacl-dev \ @@ -45,9 +45,10 @@ RUN apt update \ WORKDIR /Kumiko COPY /Bot/kumikobot.py /Kumiko/Bot/ COPY /Bot/kumikocore.py /Kumiko/Bot/ -COPY /schema.prisma /Kumiko/Bot +COPY /Migrations /Kumiko/Migrations COPY /Bot/Cogs /Kumiko/Bot/Cogs COPY /Bot/Libs /Kumiko/Bot/Libs/ +COPY /migrations-runner.py /Kumiko/migrations-runner.py COPY /Docker/start.sh /Kumiko/start.sh COPY /Docker/wait-for /Kumiko/wait-for @@ -71,9 +72,12 @@ ENTRYPOINT ["/usr/bin/tini", "--"] CMD ["/Kumiko/start.sh"] -STOPSIGNAL SIGTERM +# We want to use SIGINT instead since that is the same signal used when hitting ctrl+c +# By default, this is the expected behavior of Kumiko, so we need to keep it that way +# Will result in a clean exit without any dead conns +STOPSIGNAL SIGINT LABEL org.opencontainers.image.title="Kumiko" LABEL org.opencontainers.image.description="A multipurpose Discord bot built with freedom and choice in mind" LABEL org.opencontainers.image.licenses="Apache-2.0" -LABEL org.opencontainers.image.source="https://github.com/No767/Kumiko" +LABEL org.opencontainers.image.source="https://github.com/No767/Kumiko" \ No newline at end of file diff --git a/Docker/pg/Dockerfile b/Docker/pg/Dockerfile new file mode 100644 index 00000000..b5890c1c --- /dev/null +++ b/Docker/pg/Dockerfile @@ -0,0 +1,2 @@ +FROM postgres:15 +COPY /init-ext.sql /docker-entrypoint-initdb.d/ \ No newline at end of file diff --git a/Docker/pg/init-ext.sql b/Docker/pg/init-ext.sql new file mode 100644 index 00000000..59c53695 --- /dev/null +++ b/Docker/pg/init-ext.sql @@ -0,0 +1 @@ +CREATE EXTENSION IF NOT EXISTS pg_trgm; \ No newline at end of file diff --git a/Docker/start.sh b/Docker/start.sh index 7acdf7de..cf4e4a7b 100644 --- a/Docker/start.sh +++ b/Docker/start.sh @@ -1,65 +1,11 @@ #!/usr/bin/env bash -if [[ -v KUMIKO_TOKEN ]]; then - echo "KUMIKO_TOKEN=${KUMIKO_TOKEN}" >> /Kumiko/Bot/.env -else - echo "Missing Kumiko's bot token! KUMIKO_TOKEN environment variable is not set." - exit 1; -fi - -# Testing bot token -# Not needed in production -if [[ -v DEV_BOT_TOKEN ]]; then - echo "DEV_BOT_TOKEN=${DEV_BOT_TOKEN}" >> /Kumiko/Bot/.env -fi - -# API Keys -# GitHub -# if [[ -v GITHUB_API_ACCESS_TOKEN ]]; then -# echo "GitHub_API_Access_Token=${GITHUB_API_ACCESS_TOKEN}" >> /Kumiko/Bot/.env -# else -# echo "Missing GitHub API token! GITHUB_API_ACCESS_TOKEN environment variable is not set." -# fi -# # Reddit ID -# if [[ -v REDDIT_ID ]]; then -# echo "Reddit_ID=${REDDIT_ID}" >> /Kumiko/Bot/.env -# else -# echo "Missing Reddit ID! REDDIT_ID environment variable is not set." -# fi -# # Reddit Secret -# if [[ -v REDDIT_SECRET ]]; then -# echo "Reddit_Secret=${REDDIT_SECRET}" >> /Kumiko/Bot/.env -# else -# echo "Missing Reddit secret! REDDIT_SECRET environment variable is not set." -# fi -# # Tenor -# if [[ -v TENOR_API_KEY ]]; then -# echo "Kumiko_Tenor_API_Key=${TENOR_API_KEY}" >> /Kumiko/Bot/.env -# else -# echo "Missing Tenor API key! TENOR_API_KEY environment variable is not set." -# fi -# YouTube - -# if [[ -v IPC_SECRET_KEY ]]; then -# echo "IPC_SECRET_KEY=${IPC_SECRET_KEY}" >> /Kumiko/Bot/.env -# else -# echo "Missing IPC_Secret_Key env var! IPC_Secret_Key environment variable is not set." -# exit 1; -# fi - -if [[ -v DATABASE_URL ]]; then - echo "DATABASE_URL=${DATABASE_URL}" >> /Kumiko/Bot/.env -else - echo "Missing DATABASE_URL env var! DATABASE_URL environment variable is not set." - exit 1; -fi - KUMIKO_FIRST_START_CHECK="KUMIKO_FIRST_START" if [ ! -f $KUMIKO_FIRST_START_CHECK ]; then touch $KUMIKO_FIRST_START_CHECK echo 'DO NOT EDIT THIS FILE! THIS IS USED WHEN YOU FIRST RUN KUMIKO USING DOCKER!' >> $KUMIKO_FIRST_START_CHECK - prisma db push --schema /Kumiko/Bot/schema.prisma + python3 /Kumiko/migrations-runner.py fi exec python3 /Kumiko/Bot/kumikobot.py \ No newline at end of file diff --git a/Docs/api-keys.md b/Docs/api-keys.md deleted file mode 100644 index c3d0db32..00000000 --- a/Docs/api-keys.md +++ /dev/null @@ -1,7 +0,0 @@ -## API Keys - -Kumiko is built on top of Rin, and Kumiko requires some API keys. Here's a list of current services that require API keys: - -- [GitHub](https://docs.github.com/en/rest/guides/basics-of-authentication) -- [Reddit](https://www.reddit.com/prefs/apps) (Get both the ID and Secret) -- [Tenor](https://developers.google.com/tenor/guides/quickstart#setup) \ No newline at end of file diff --git a/Docs/database-setup.md b/Docs/database-setup.md deleted file mode 100644 index c6259083..00000000 --- a/Docs/database-setup.md +++ /dev/null @@ -1,15 +0,0 @@ -# Database Setup - -Kumiko requires PostgreSQL, and Redis to get started. - -## Setting up the `.env` files - -There are two `.env` files that should be found in your project. One is in the `Bot` directory, and the other is in the root directory of the repo. But they aren't named `.env`. So when you run the `dev-setup` command in make, this will rename the dev env file to `.env`, and is found under `Bot/.env`. The other one should remain in the root directory of the repo. Rename `.env-docker-example` to `.env`. Once you set up the credentials within `Bot/.env`, make sure to copy the values to `.env` in the root directory of the repo. **Do not directly copy and paste the contents of `Bot/.env` into to the one in the root of the repo.** - -## Migration - -There is one last thing that needs to be done. And that is to migrate the data. Run the following command in order to do so: - -```sh -prisma db push -``` diff --git a/Docs/dev-prerequisites.md b/Docs/dev-prerequisites.md deleted file mode 100644 index 2228345f..00000000 --- a/Docs/dev-prerequisites.md +++ /dev/null @@ -1,59 +0,0 @@ -# Dev Prerequisites - -## Software Prerequisites - -Before you get started, please ensure you have the following installed: - -- [Git](https://git-scm.com/) -- [Python 3](https://www.python.org/) (Python 3.11 is what the codebase uses) -- [Poetry](https://python-poetry.org/) -- [Pyenv](https://github.com/pyenv/pyenv) (Optional, Recommended) -- [WSL2](https://docs.microsoft.com/en-us/windows/wsl/) (If working on Windows) -- [Docker](https://www.docker.com/) (Use [Docker Engine](https://docs.docker.com/engine/) on Linux, [Docker Desktop](https://www.docker.com/products/docker-desktop/) on Windows/WSL2, MacOS and Linux (beta)) -- Discord Account + Discord App - -> **Note** -> Kumiko is natively developed for Linux. If you are using Windows, please use WSL2. - -## Package Prerequisites - -### Debian/Ubuntu - -```sh -sudo apt-get install libffi-dev python3-dev libnacl-dev libopus-dev libopus0 libopusenc-dev build-essentials \ -libssl-dev curl wget git -``` - -### RHEL/CentOS/Fedora 22 or below - -```sh -sudo yum install make gcc libffi-devel python-devel \ -openssl-devel opus-devel opus curl wget git -``` - -### Fedora 23+ - -```sh -sudo dnf install make automake gcc gcc-c++ kernel-devel \ -libffi-devel python3-libnacl python3.11-devel openssl11-devel \ -openssl-devel opus opus-devel curl wget git -``` - -### OpenSUSE - -```sh -sudo zypper install gcc make automake openssl-devel openssl-1_1 \ -libffi-devel python311-devel python311-libnacl opus libopus0 wget git curl -``` - -### Arch - -```sh -sudo pacman -S --needed base-devel openssl openssl-1.1 libffi python python-libnacl opus -``` - -### MacOS - -```sh -brew install openssl openssl@1.1 libffi git curl make opus -``` \ No newline at end of file diff --git a/Docs/dev-setup.md b/Docs/dev-setup.md deleted file mode 100644 index 464d0d71..00000000 --- a/Docs/dev-setup.md +++ /dev/null @@ -1,40 +0,0 @@ -# Development Setup - -## Local - -1. Fork and clone the repo - - ```sh - git clone https://github.com/[username]/Kumiko.git && cd Kumiko - ``` - - Or if you have the `gh` cli tool installed: - - ```sh - gh repo clone [username]/Kumiko - ``` - -2. Install all of the dependencies (including dev dependencies) - - ```sh - poetry install --with=dev,test - ``` - -3. Start the Docker Compose stack - - ```sh - sudo docker compose -f docker-compose-dev.yml up -d - ``` - -4. Run the database migrations - - ```sh - poetry run prisma db push - ``` - -## Vagrant - -Kumiko also supports using Vagrant as a development environment. In order to use Vagrant, you will need Oracle VirtualBox or VMWare Workstation installed on your machine. Once installed and properly configured, you can just run `vagrant up` to provision and start it up, and connect to it by SSH or by VSCode. - -## Environment Variables -Kumiko v0.7+ includes an development mode feature, which will set up jishaku and a custom FS watcher. Later on, there may be more development features that will be included. Make sure you first install the dev dependencies first! And in order to enable it, set an environment variable called `DEV_MODE` to `True`. \ No newline at end of file diff --git a/Docs/getting-dev-discord-bot.md b/Docs/getting-dev-discord-bot.md deleted file mode 100644 index e0864b87..00000000 --- a/Docs/getting-dev-discord-bot.md +++ /dev/null @@ -1,37 +0,0 @@ -# Getting the Discord Bot - -First things first, you'll more than likely need a dev bot to run Kumiko. Luckily you'll find the steps below to help you on that - -1. Create the app that will be needed for the bot. Once done, you should see the page as shown above - - ![images](../assets/getting-started-assets/create-app.png) - - -2. Now head done to the bot section, and click on the button that says "Add Bot". - - ![yesyes](../assets/getting-started-assets/create-bot.png) - -3. You'll see a pop-up that asks you if you want to create the bot. - - ![ewom](../assets/getting-started-assets/allow-bot.png) - -4. Make sure to have all 3 of the buttons enabled. Kumiko will need all 3 of them to work. - - ![intents](../assets/getting-started-assets/allow-intents.png) - - -5. You'll see a page just like the one above. We'll need access the the token for the bot, and the only way to do it is to reset the token. - - ![whyyy](../assets/getting-started-assets/reset-token.png) - -6. Allow for the token to be reset. Note that if your account is hooked up with 2FA, it will ask you to enter your 2FA code. Go to your authenticator app and enter the code from the app. - - ![confirm](../assets/getting-started-assets/allow-reset-token.png) - - -7. Now click on the copy button and copy the token - - ![copytoken](../assets/getting-started-assets/copy-token.png) - - -8. Now save this token in `Bot/.env`. Save it in a variable named `DEV_BOT_TOKEN` diff --git a/Envs/dev.env b/Envs/dev.env new file mode 100644 index 00000000..f6983a84 --- /dev/null +++ b/Envs/dev.env @@ -0,0 +1,32 @@ +################################################################## +# Kumiko's Dev Example ENV # +# Replace the values with the appropriate values # +################################################################## + +# More than likely you will not need to use the prod token for testing +# In some cases where Kumiko is near prod, this will get used instead +KUMIKO_TOKEN=token + +# Dev bot token +DEV_BOT_TOKEN=token +DEV_MODE=True + +# For the migrations system +# DO NOT TOUCH THIS UNLESS IT IS TO UPGRADE +TARGET_REVISION=rev3 + +# Search Cog API Keys +# THESE WILL BREAK THE COMMANDS IF YOU DO NOT HAVE THE KEYS HERE +GITHUB_API_KEY=key +REDDIT_ID=key +REDDIT_SECRET=key +TENOR_API_KEY=key + +# IPC Secret Key +# This is used to communicate with the IPC server +IPC_SECRET_KEY=key +IPC_HOST=0.0.0.0 + +# Dev Database Credentials - For PostgreSQL and Redis +POSTGRES_URI=postgres://user:password@localhost:5432/user +REDIS_URI=redis://localhost:6379/0 \ No newline at end of file diff --git a/.env-docker-example b/Envs/docker.env similarity index 74% rename from .env-docker-example rename to Envs/docker.env index 25fd330e..7b5746ca 100644 --- a/.env-docker-example +++ b/Envs/docker.env @@ -10,18 +10,18 @@ KUMIKO_TOKEN=token # If you are using the development builds, use the token that you wish to use for the dev bot. DEV_BOT_TOKEN=token -# Rin's API Keys + Access Tokens +TARGET_REVISION=rev3 GITHUB_API_ACCESS_TOKEN=apiKey REDDIT_ID=apiKey REDDIT_SECRET=apiKey TENOR_API_KEY=apiKey -YOUTUBE_API_KEY=apiKey -# IPC Server Key IPC_SECRET_KEY=key +IPC_HOST=some_ip_address -DATABASE_URL=postgresql://user:password@host:port/database +POSTGRES_PASSWORD=password +POSTGRES_USER=postgres +POSTGRES_DB=postgres -# Redis Credentials -REDIS_IP=ip -REDIS_PORT=port \ No newline at end of file +POSTGRES_URI=postgresql://user:password@host:port/database +REDIS_URI=redis://localhost:6379/0 \ No newline at end of file diff --git a/Envs/prod.env b/Envs/prod.env new file mode 100644 index 00000000..225cd0c9 --- /dev/null +++ b/Envs/prod.env @@ -0,0 +1,28 @@ +################################################################## +# Kumiko's Prod Example ENV # +# Replace the values with the appropriate values # +################################################################## + +# More than likely you will not need to use the prod token for testing +# In some cases where Kumiko is near prod, this will get used instead +KUMIKO_TOKEN=token + +# For the migrations system +# DO NOT TOUCH THIS UNLESS IT IS TO UPGRADE +TARGET_REVISION=rev3 + +# Search Cog API Keys +# THESE WILL BREAK THE COMMANDS IF YOU DO NOT HAVE THE KEYS HERE +GITHUB_API_KEY=key +REDDIT_ID=key +REDDIT_SECRET=key +TENOR_API_KEY=key + +# IPC Secret Key +# This is used to communicate with the IPC server +IPC_SECRET_KEY=key +IPC_HOST=127.0.0.1 + +# Prod Database Credentials - For PostgreSQL and Redis +POSTGRES_URI=postgres://user:password@localhost:5432/user +REDIS_URI=redis://localhost:6379/0 \ No newline at end of file diff --git a/Kumiko-Docs/Makefile b/Kumiko-Docs/Makefile new file mode 100644 index 00000000..d2efd273 --- /dev/null +++ b/Kumiko-Docs/Makefile @@ -0,0 +1,23 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +autobuild: + sphinx-autobuild source build/html \ No newline at end of file diff --git a/Kumiko-Docs/make.bat b/Kumiko-Docs/make.bat new file mode 100644 index 00000000..747ffb7b --- /dev/null +++ b/Kumiko-Docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "" goto help + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/Kumiko-Docs/requirements.txt b/Kumiko-Docs/requirements.txt new file mode 100644 index 00000000..9538c935 --- /dev/null +++ b/Kumiko-Docs/requirements.txt @@ -0,0 +1,50 @@ +alabaster==0.7.13 ; python_version >= "3.8" and python_version < "4.0" +babel==2.12.1 ; python_version >= "3.8" and python_version < "4.0" +beautifulsoup4==4.12.2 ; python_version >= "3.8" and python_version < "4.0" +certifi==2023.5.7 ; python_version >= "3.8" and python_version < "4.0" +charset-normalizer==3.1.0 ; python_version >= "3.8" and python_version < "4.0" +colorama==0.4.6 ; python_version >= "3.8" and python_version < "4.0" +contourpy==1.1.0 ; python_version >= "3.8" and python_version < "4.0" +cycler==0.11.0 ; python_version >= "3.8" and python_version < "4.0" +docutils==0.20.1 ; python_version >= "3.8" and python_version < "4.0" +fonttools==4.40.0 ; python_version >= "3.8" and python_version < "4.0" +furo==2023.5.20 ; python_version >= "3.8" and python_version < "4.0" +idna==3.4 ; python_version >= "3.8" and python_version < "4.0" +imagesize==1.4.1 ; python_version >= "3.8" and python_version < "4.0" +importlib-metadata==6.7.0 ; python_version >= "3.8" and python_version < "3.10" +importlib-resources==5.12.0 ; python_version >= "3.8" and python_version < "3.10" +jinja2==3.1.2 ; python_version >= "3.8" and python_version < "4.0" +kiwisolver==1.4.4 ; python_version >= "3.8" and python_version < "4.0" +livereload==2.6.3 ; python_version >= "3.8" and python_version < "4.0" +markdown-it-py==3.0.0 ; python_version >= "3.8" and python_version < "4.0" +markupsafe==2.1.3 ; python_version >= "3.8" and python_version < "4.0" +matplotlib==3.7.1 ; python_version >= "3.8" and python_version < "4.0" +mdit-py-plugins==0.4.0 ; python_version >= "3.8" and python_version < "4.0" +mdurl==0.1.2 ; python_version >= "3.8" and python_version < "4.0" +myst-parser==2.0.0 ; python_version >= "3.8" and python_version < "4.0" +numpy==1.24.4 ; python_version >= "3.8" and python_version < "4.0" +packaging==23.1 ; python_version >= "3.8" and python_version < "4.0" +pillow==9.5.0 ; python_version >= "3.8" and python_version < "4.0" +pygments==2.15.1 ; python_version >= "3.8" and python_version < "4.0" +pyparsing==3.1.0 ; python_version >= "3.8" and python_version < "4.0" +python-dateutil==2.8.2 ; python_version >= "3.8" and python_version < "4.0" +pytz==2023.3 ; python_version >= "3.8" and python_version < "3.9" +pyyaml==6.0 ; python_version >= "3.8" and python_version < "4.0" +requests==2.31.0 ; python_version >= "3.8" and python_version < "4.0" +six==1.16.0 ; python_version >= "3.8" and python_version < "4.0" +snowballstemmer==2.2.0 ; python_version >= "3.8" and python_version < "4.0" +soupsieve==2.4.1 ; python_version >= "3.8" and python_version < "4.0" +sphinx-autobuild==2021.3.14 ; python_version >= "3.8" and python_version < "4.0" +sphinx-basic-ng==1.0.0b1 ; python_version >= "3.8" and python_version < "4.0" +sphinx-copybutton==0.5.2 ; python_version >= "3.8" and python_version < "4.0" +sphinx==7.0.1 ; python_version >= "3.8" and python_version < "4.0" +sphinxcontrib-applehelp==1.0.4 ; python_version >= "3.8" and python_version < "4.0" +sphinxcontrib-devhelp==1.0.2 ; python_version >= "3.8" and python_version < "4.0" +sphinxcontrib-htmlhelp==2.0.1 ; python_version >= "3.8" and python_version < "4.0" +sphinxcontrib-jsmath==1.0.1 ; python_version >= "3.8" and python_version < "4.0" +sphinxcontrib-qthelp==1.0.3 ; python_version >= "3.8" and python_version < "4.0" +sphinxcontrib-serializinghtml==1.1.5 ; python_version >= "3.8" and python_version < "4.0" +sphinxext-opengraph==0.8.2 ; python_version >= "3.8" and python_version < "4.0" +tornado==6.3.2 ; python_version >= "3.8" and python_version < "4.0" +urllib3==2.0.3 ; python_version >= "3.8" and python_version < "4.0" +zipp==3.15.0 ; python_version >= "3.8" and python_version < "3.10" diff --git a/Kumiko-Docs/source/_static/1-Getting-Bot.png b/Kumiko-Docs/source/_static/1-Getting-Bot.png new file mode 100644 index 00000000..455e78b8 Binary files /dev/null and b/Kumiko-Docs/source/_static/1-Getting-Bot.png differ diff --git a/Kumiko-Docs/source/_static/2-Create-Bot-Page.png b/Kumiko-Docs/source/_static/2-Create-Bot-Page.png new file mode 100644 index 00000000..6f05def8 Binary files /dev/null and b/Kumiko-Docs/source/_static/2-Create-Bot-Page.png differ diff --git a/Kumiko-Docs/source/_static/3-Auth-Bot-Creation.png b/Kumiko-Docs/source/_static/3-Auth-Bot-Creation.png new file mode 100644 index 00000000..6100c296 Binary files /dev/null and b/Kumiko-Docs/source/_static/3-Auth-Bot-Creation.png differ diff --git a/Kumiko-Docs/source/_static/4-Ensure-Intents-Are-Enabled.png b/Kumiko-Docs/source/_static/4-Ensure-Intents-Are-Enabled.png new file mode 100644 index 00000000..19d6126e Binary files /dev/null and b/Kumiko-Docs/source/_static/4-Ensure-Intents-Are-Enabled.png differ diff --git a/Kumiko-Docs/source/_static/5-Copy-Token.png b/Kumiko-Docs/source/_static/5-Copy-Token.png new file mode 100644 index 00000000..3af31044 Binary files /dev/null and b/Kumiko-Docs/source/_static/5-Copy-Token.png differ diff --git a/Kumiko-Docs/source/_static/kumiko-resized-round.svg b/Kumiko-Docs/source/_static/kumiko-resized-round.svg new file mode 100644 index 00000000..dde13f47 --- /dev/null +++ b/Kumiko-Docs/source/_static/kumiko-resized-round.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/Kumiko-Docs/source/conf.py b/Kumiko-Docs/source/conf.py new file mode 100644 index 00000000..acffb8f9 --- /dev/null +++ b/Kumiko-Docs/source/conf.py @@ -0,0 +1,48 @@ +# Configuration file for the Sphinx documentation builder. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information + +project = "Kumiko" +copyright = "2023, No767" +author = "No767" + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +extensions = ["sphinx.ext.napoleon", "sphinxext.opengraph", "sphinx_copybutton"] + + +templates_path = ["_templates"] +exclude_patterns = [] + +latex_elements = { + "sphinxsetup": "verbatimwithframe=false", +} + +html_title = "Kumiko" + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +html_theme = "furo" +html_static_path = ["_static"] + +html_theme_options = { + "dark_css_variables": { + "color-brand-primary": "#A685E2", + "color-brand-content": "#FFABE1", + }, + "light_css_variables": { + "color-brand-primary": "#6867AC", + "color-brand-content": "#CE7BB0", + }, +} + +ogp_site_url = "https://kumiko.readthedocs.io" +ogp_image = ( + "https://raw.githubusercontent.com/No767/Kumiko/dev/assets/kumiko-resized-round.png" +) diff --git a/Kumiko-Docs/source/guides/dev/dev-contributing.rst b/Kumiko-Docs/source/guides/dev/dev-contributing.rst new file mode 100644 index 00000000..ca4c47ff --- /dev/null +++ b/Kumiko-Docs/source/guides/dev/dev-contributing.rst @@ -0,0 +1,94 @@ +Dev Contributing Guide +====================== + +We are glad that you're willing to contribute to this project. We are usually very lenient and relaxed with the submissions of PRs, and Issues reports. But there are some stuff that you need to know before contributing. + +Note to new contributors +--------------------------- + +When you contribute to this project, you are subject to the `Code of Conduct `_. Any violations of the Code Of Conduct will be handled as stated. Read the contributing guide. **Support is not given if you didn't bother reading the documentation for setting up any of the requirements, or if you didn't bother to read the contributing guide.** + +Before Starting +---------------- + +Make suer to read these guides listed below (read them in order): + +- :doc:`requirements` +- :doc:`setup` + +Coding Style +------------- + +Variables +^^^^^^^^^^ +Most of the code written uses ``camelCasing`` for variables, ``PascalCasing`` for classes, and ``snake_casing`` for args. To sum it up: + +- ``camelCasing`` for variables +- ``PascalCasing`` for classes +- ``snake_casing`` for args +- ``ALL_CAPS`` for constants +- ``kebab-casing`` for files + +Formatting +^^^^^^^^^^^ + +Kumiko uses pre-commit hooks to format all of the code. Make sure run ``git add --all`` before committing to add all of the files. More than likely you'll need to commit twice due to the formatting that pre-commit does afterwards. + +Docstrings +^^^^^^^^^^^ + +Just like how major programs are documented, the libraries that are custom made for Kumiko also have to be documented. The current standard for this project is to use `Google's Docstring Guide `_. A handy VS Code extension that should be used is the `autoDocstring `_ extension. By default it will generate the docstring in the Google format. Docstrings should be used on all coroutines and methods (excluding cogs), and on classes as well. + +Google, Numpy, and Sphinx docstrings are also supported for commands. Kumiko is documented w/ Google docstrings, so please make sure to use that format. + +Example Cog: + +.. code-block:: python + + import discord + from discord.ext import commands + from discord.ext.commands import Context, Bot + + class MyCog(commands.Cog): + """An example cog for demo purposes""" + def __init__(self, bot: Bot): + self.bot = bot + + @commands.hybrid_command(name="hello") + async def myCommand(self, ctx: Context): + """This is an example of a description for a slash command""" + await ctx.send(f"Hello {ctx.user.name}!") + + async def setup(bot: Bot): + await bot.add_cog(MyCog(bot)) + +GitHub Contributing Guidelines +----------------------------------- + +Issue and Feature Requests Reports +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If there is an issue or a feature you want to be added, use the built-in GitHub issue tracker. Though a system like Jira could be used, it would be more efficient to just use the issue tracker that GitHub provides. + +- If submitting a issue report, follow the template. Duplicates will not receive support +- If submitting a feature request, follow the template as well. As with issue reports, duplicate requests will not receive support + +Git Commit Styleguides +^^^^^^^^^^^^^^^^^^^^^^^ + +- If updating any other files that aren't project files or not important (stuff like README.md, contributing.md, etc), add the [skip ci] label in the front +- With each new commit, the message should be more or less describing the changes. Please don't write useless commit messages... +- If releasing tags, have it in this style. ``Release: v[version number]``, ``Update: v[version number]``, and ``Fix: v[version number]``. Release is a major release. This means it bumps from 1.0.0 to 2.0.0. Minor means it bumps up the version from 1.4 to 1.5 for example. And fix just applies a patch, which would be 1.4.1 to 1.4.2. + +Releasing Tags +^^^^^^^^^^^^^^^ + +In order to automate the release system, you have to make sure that in order to use it, the git commit message must be done correctly. Only use this if there is a new update that is ready to be released. Kumiko uses `SemVer `_ as the standard for versioning. Here's a table that should help with explaining this: + + =============================================================== ===================== + Type of Release, Update, or Patch Example + =============================================================== ===================== + Major Release (For updates that are not backwards compatible) ``Release: v2.0.0`` + Minor Release (For updates that are backwards compatible) ``Update: v2.5.0`` + Patch Release (For critical security patches and bug fixes) ``Fix: v2.5.1`` + =============================================================== ===================== diff --git a/Kumiko-Docs/source/guides/dev/index.rst b/Kumiko-Docs/source/guides/dev/index.rst new file mode 100644 index 00000000..3f95831d --- /dev/null +++ b/Kumiko-Docs/source/guides/dev/index.rst @@ -0,0 +1,12 @@ +================ +Developer Guide +================ + +Kumiko offers a developer guide so future developers can easily get started with the project. + +.. toctree:: + :maxdepth: 2 + + requirements + setup + dev-contributing \ No newline at end of file diff --git a/Kumiko-Docs/source/guides/dev/requirements.rst b/Kumiko-Docs/source/guides/dev/requirements.rst new file mode 100644 index 00000000..e14d6efb --- /dev/null +++ b/Kumiko-Docs/source/guides/dev/requirements.rst @@ -0,0 +1,59 @@ +Requirements +================================== + + +Software Requirements +--------------------- +Before you get started, please ensure you have the following installed: + +- `Git `_ +- `Python 3 `_ +- `Poetry `_ +- `Docker `_ +- Discord Account + App + +.. CAUTION:: + Kumiko is natively developed for Linux. Development should work on Windows but it is highly untested. + +Package Prerequisites +---------------------- + +Debian/Ubuntu +^^^^^^^^^^^^^ + +.. code-block:: bash + + sudo apt-get install libffi-dev python3-dev libnacl-dev libopus-dev \ + build-essentials libssl-dev curl wget git + + +Fedora +^^^^^^^^^^ + +.. code-block:: bash + + sudo dnf install make automake gcc gcc-c++ kernel-devel \ + libffi-devel python3-libnacl python3.11-devel openssl-devel \ + opus-devel curl wget git + +OpenSUSE +^^^^^^^^ + +.. code-block:: bash + + sudo zypper install gcc make automake openssl-devel libffi-devel \ + python311-devel python311-libnacl libopus0 wget git curl + +Arch Linux +^^^^^^^^^^ + +.. code-block:: bash + + sudo pacman -S --needed base-devel openssl libffi python python-libnacl opus + +MacOS +^^^^^ + +.. code-block:: bash + + brew install openssl libffi git curl make opus diff --git a/Kumiko-Docs/source/guides/dev/setup.rst b/Kumiko-Docs/source/guides/dev/setup.rst new file mode 100644 index 00000000..5d4c3c88 --- /dev/null +++ b/Kumiko-Docs/source/guides/dev/setup.rst @@ -0,0 +1,91 @@ +Setup +======== + +Local Setup +----------- + +1. Fork and clone the repo + + .. code-block:: bash + + git clone https://github.com/[username]/Kumiko.git && cd Kumiko + + + Or if you have the `gh` cli tool installed: + + .. code-block:: bash + + gh repo clone [username]/Kumiko + + +2. Install all of the dependencies (including dev dependencies) + + .. code-block:: bash + + poetry install --with=dev,test,docs + +3. Copy the ENV files into the correct places + + .. code-block:: bash + + cp Envs/dev.env Bot/.env \ + cp Envs/docker.env .env + +4. Edit the ``.env`` file placed in the root of the repo and in the ``Bot`` folder to include any credentials needed for the bot to run + + .. code-block:: bash + + # THIS IS ONLY AN EXAMPLE + POSTGRES_PASSWORD=... + POSTGRES_USER=... + POSTGRES_URI=postgres://user:somepass@localhost:5432/somedb + + +5. Start the Docker Compose stack + + .. code-block:: bash + + sudo docker compose -f docker-compose-dev.yml up -d + + +6. Enable the PostgreSQL extension ``pg_trgm`` + + .. code-block:: sql + + CREATE EXTENSION pg_trgm; + +7. Run the database migrations + + .. code-block:: bash + + python migrations-runner.py + +Vagrant +------- + +Kumiko also supports using Vagrant as a development environment. + +.. note:: + + The Ansible playbook only sets up the environment which includes everything needed to get started. There is still a layer of manual configuration that needs to be done. The Ansible playbook installs PostgreSQL, Redis, Python and Poetry into the VM, and also sets up the repo for development. There is no need to use Docker since PostgreSQL and Redis are installed natively into the system. + +Requirements +^^^^^^^^^^^^ + +* Vagrant (w/ `VirtualBox WSL2 plugin `_) +* WSL2 +* Ansible (installed on WSL2 (you will need to execute the vagrant commands in WSL2)) + +Ansible roles needed: + +* ``geerlingguy.postgresql`` +* ``geerlingguy.redis`` +* ``staticdev.pyenv`` + + +In order to use Vagrant, you will need Oracle VirtualBox or VMWare Workstation installed on your machine. Once installed and properly configured, you can just run ``vagrant up`` (in your WSL2 or Linux environment) to provision and start it up, and connect to it by SSH or by VSCode. + +Environment Variables +--------------------- + +Kumiko v0.7+ includes an development mode feature, which will set up jishaku and a custom FS watcher. The FS (File System) watcher is just like HMR (Hot Module Replacements). Once you press Ctrl+s in your cog, it will automatically reload it so the code executed is changed. Later on, there may be more development features that will be included. Make sure you first install the dev dependencies first! And in order to enable it, set an environment variable called ``DEV_MODE`` to ``True``. \ No newline at end of file diff --git a/Kumiko-Docs/source/guides/user/bot-setup.rst b/Kumiko-Docs/source/guides/user/bot-setup.rst new file mode 100644 index 00000000..867419a0 --- /dev/null +++ b/Kumiko-Docs/source/guides/user/bot-setup.rst @@ -0,0 +1,36 @@ +Bot Setup +================ + +This guide provides the steps for setting up a testing bot. This testing bot should only be used for development purposes, or for trying out a self hosted version of Kumiko. **DO NOT RUN THESE BOTS IN PRODUCTION** + + +1. Create a new application at https://discord.com/developers/applications + + .. figure:: /_static/1-Getting-Bot.png + :alt: Create a new application + +2. Click on the "Bot" tab and click "Add Bot" + + .. figure:: /_static/2-Create-Bot-Page.png + :alt: Add a bot + +3. Click "Yes, do it!". Optionally if you have 2FA enabled, you'll need to enter in your MFA token. + + .. figure:: /_static/3-Auth-Bot-Creation.png + :alt: Confirm bot creation + +4. Ensure that the intents shown in the pink square are enabled. ``message_content`` intents are required for the bot to function. + + .. figure:: /_static/4-Ensure-Intents-Are-Enabled.png + :alt: Enable intents + +5. Copy the bot token + + .. figure:: /_static/5-Copy-Token.png + :alt: Copy bot token + +6. Now paste this token in the ``.env`` file located in ``Bot/.env``. If you are using the Docker image for Kumiko, put it in the ``.env`` file located in the root of the repo. + + .. code-block:: bash + + DEV_BOT_TOKEN=YOUR_TOKEN_HERE \ No newline at end of file diff --git a/Kumiko-Docs/source/guides/user/configurations/bot-config.rst b/Kumiko-Docs/source/guides/user/configurations/bot-config.rst new file mode 100644 index 00000000..cc1d72f3 --- /dev/null +++ b/Kumiko-Docs/source/guides/user/configurations/bot-config.rst @@ -0,0 +1,4 @@ +Bot Config +================= + +All configs are handled via the ``Bot/.env`` file. Consult the ``Env/prod.env`` file for an example. \ No newline at end of file diff --git a/Kumiko-Docs/source/guides/user/configurations/index.rst b/Kumiko-Docs/source/guides/user/configurations/index.rst new file mode 100644 index 00000000..d34416ff --- /dev/null +++ b/Kumiko-Docs/source/guides/user/configurations/index.rst @@ -0,0 +1,9 @@ +User Configurations +==================== + +These contains any configs for the bot for the end user + +.. toctree:: + :maxdepth: 2 + + bot-config \ No newline at end of file diff --git a/Kumiko-Docs/source/guides/user/index.rst b/Kumiko-Docs/source/guides/user/index.rst new file mode 100644 index 00000000..46fe04ec --- /dev/null +++ b/Kumiko-Docs/source/guides/user/index.rst @@ -0,0 +1,11 @@ +User Guide +============ + +Kumiko offers a user guide where users can find information on commands, features, and many others. This section is incomplete, but will be completed in the future. + +.. toctree:: + :maxdepth: 2 + + bot-setup + trying-out + configurations/index \ No newline at end of file diff --git a/Kumiko-Docs/source/guides/user/trying-out.rst b/Kumiko-Docs/source/guides/user/trying-out.rst new file mode 100644 index 00000000..389f0e38 --- /dev/null +++ b/Kumiko-Docs/source/guides/user/trying-out.rst @@ -0,0 +1,182 @@ +Trying out Kumiko +================== + +Kumiko can be tried out by running the Docker image. For the official versions of Kumiko, please invite the bot into your guild instead. For those who want to try out the latest breaking features, using Docker is recommended. + +Prerequisites +------------- + +1. Make sure you have set up your bot token. Refer to :doc:`bot-setup` for the full guide. +2. Make sure you have these installed: + - `Docker `_ + - curl or wget + - Git + +Standalone Prerequisites +^^^^^^^^^^^^^^^^^^^^^^^^ + +If you are running Kumiko on a standalone machine (w/o Docker Compose or using Systemd), you will need to install the following: + +- `PostgreSQL `_ (w/ ``pg_trgm`` extension loaded) +- `Redis Stack `_ (or Redis w/ RedisJSON and RedisSearch modules loaded) + + +Docker CLI (Standalone) +----------------------- + +1. Pull the image from either GHCR or Docker Hub + + - GHCR + + .. code-block:: bash + + docker pull ghcr.io/no767/kumiko:latest + + - Docker Hub + + .. code-block:: bash + + docker pull no767/kumiko:latest + +2. Set up the docker ENV file + + .. code-block:: bash + + curl -o https://raw.githubusercontent.com/no767/kumiko/master/Envs/docker.env .env + + # Or using wget: + + wget -O .env https://raw.githubusercontent.com/no767/kumiko/master/Envs/docker.env + +3. Edit the ``.env`` file to include any credentials needed for the bot to run + + .. code-block:: bash + + # THIS IS ONLY AN EXAMPLE + POSTGRES_PASSWORD=... + POSTGRES_USER=... + POSTGRES_URI=postgres://user:somepass@localhost:5432/somedb + +4. Run the bot + + .. code-block:: bash + + docker run -d --env-file=.env --name Kumiko no767/kumiko:latest + +Systemd (Standalone) +-------------------- + +**Before you start, ensure that you have PostgreSQL and Redis correctly configured and is running** + +1. Ensure that the PostgreSQL extension ``pg_trgm`` and the RedisJSON module are loaded. Refer to the `Redis docs `_ on how to install and load the JSON module. + + .. code-block:: sql + + CREATE EXTENSION IF NOT EXISTS pg_trgm; + +2. Clone the repo + + .. code-block:: bash + + git clone https://github.com/No767/Kumiko.git && cd Kumiko + + + Or if you have the ``gh`` cli tool installed: + + .. code-block:: bash + + gh repo clone No767/Kumiko + + .. note:: + + By default, this will clone the dev branch. For stable releases, run ``git checkout master`` to checkout into stable releases (or checkout the latest tag) + +3. Set up the prod ENV file. During this step, please also fill your credentials in the ENV file + + .. code-block:: bash + + cp Envs/prod.env Bot/.env + +4. Create an venv so that you can install the dependencies without polluting your system + + .. code-block:: bash + + python3 -m venv ./venv + +5. Activate the venv, install the dependencies, and then deactivate it + + .. code-block:: bash + + source ./venv/bin/activate \ + && pip install -r Requirements/prod.txt \ + && deactivate + +6. Create an systemd service file. This is an example, and you will need to edit it to point to the correct directory and user. + + .. code-block:: ini + + [Unit] + Description=Kumiko + After=network-online.target + Requires=postgresql.service + + [Service] + Type=simple + WorkingDirectory=/your/bots/directory + ExecStart=/your/bots/directory/venv/bin/python3 /your/bots/directory/Bot/kumikobot.py + User=username + Restart=on-failure + EnvironmentFile=/your/bots/directory/Bot/.env + + [Install] + WantedBy=multi-user.target + +7. Test whether you have everything set up. If you have ``make`` installed, you can run ``make prod-run`` in order to run the bot (the ``Makefile`` is found in the root of the repo). Otherwise, just run ``kumikobot.py`` + +8. Run and enable the systemd service. + + .. code-block:: bash + + sudo systemctl enable --now kumiko + +Docker Compose +-------------- + +1. Clone the repo + + .. code-block:: bash + + git clone https://github.com/No767/Kumiko.git && cd Kumiko + + + Or if you have the ``gh`` cli tool installed: + + .. code-block:: bash + + gh repo clone No767/Kumiko + + .. note:: + + By default, this will clone the dev branch. For stable releases, run ``git checkout master`` to checkout into stable releases (or checkout the latest tag) + +2. Copy the ENV files into the correct places + + .. code-block:: bash + + cp Envs/docker.env .env + +3. Edit the ``.env`` file placed in the root of the repo to include any credentials needed for the bot to run + + .. code-block:: bash + + # THIS IS ONLY AN EXAMPLE + POSTGRES_PASSWORD=... + POSTGRES_USER=... + POSTGRES_URI=postgres://user:somepass@localhost:5432/somedb + +4. Once everything is set, literally just fire up the whole entire Docker Compose stack. All of the database creation, and the migrations will be done automatically. + + .. code-block:: bash + + docker-compose up -d + diff --git a/Kumiko-Docs/source/index.rst b/Kumiko-Docs/source/index.rst new file mode 100644 index 00000000..de242e6d --- /dev/null +++ b/Kumiko-Docs/source/index.rst @@ -0,0 +1,80 @@ +.. Kumiko documentation master file, created by + sphinx-quickstart on Sun Apr 16 21:15:08 2023. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + + +Kumiko +================================== + +.. toctree:: + :maxdepth: 2 + :hidden: + :caption: Guides + + guides/user/index + guides/dev/index + +.. toctree:: + :maxdepth: 2 + :hidden: + :caption: Terms of Service + + terms-of-service/tos.rst + terms-of-service/privacy-policy.rst + + +.. figure:: /_static/kumiko-resized-round.svg + :align: right + +.. image:: https://img.shields.io/github/v/release/No767/Kumiko?label=Release&logo=github&sort=semver + :alt: GitHub release (latest SemVer) + +.. image:: https://img.shields.io/github/license/No767/Kumiko?label=License&logo=github + :alt: GitHub + +.. image:: https://img.shields.io/badge/Kumiko-Oumae-white + +Kumiko is a multipurpose Discord bot built with freedom and choice in mind. Kumiko takes a unique approach to what a multipurpose bot is defined as. + +Features +---------- + +.. CAUTION:: + Some of these features are not implemented yet. Kumiko is still under heavy development. + +- Customizable prefix module +- Global or guild economy with jobs and marketplaces +- Custom logging module +- Basic moderation commands +- Search for memes, posts, anime, manga, and others! + +And some ones that are planned: + +- Complete Web Dashboard + Custom Embeds (WIP, pinned for v2) +- Annoucement releaser with support from GitHub (Pinned for v2) + +To see the full list of planned features, please see the roadmap `here `_. + +Guides +---------------- + +Are you interested in contributing to Kumiko as a developer? Or are you interested in learning how to use Kumiko as the end user? This is the place to get started! + +- :doc:`guides/user/index` +- :doc:`guides/dev/index` + +Resources and Links +------------------- + +- `Docker Hub `_ +- `GHCR `_ +- `GitHub `_ +- Discord Server + +Project Links +^^^^^^^^^^^^^ + +- `Contributing `_ +- `Roadmap `_ +- `Project License `_ \ No newline at end of file diff --git a/Kumiko-Docs/source/terms-of-service/privacy-policy.rst b/Kumiko-Docs/source/terms-of-service/privacy-policy.rst new file mode 100644 index 00000000..16832fec --- /dev/null +++ b/Kumiko-Docs/source/terms-of-service/privacy-policy.rst @@ -0,0 +1,31 @@ +Privacy Policy +============== +Kumiko is designed to only use what it needs. This means no other data, unless specified, is used or granted to Kumiko, or any other staff and administration members. By using Kumiko, and in turn any of the services provided, you truthfully agree to follow such terms and conditions in exchange for use of Kumiko and their services. Those shall be stated as: + +Collections of interest +----------------------- +Kumiko is designed to only take what the bot needs. The data will not be viewed by anyone else unless authorized to do so. The information collected as so is of the following: + +- Discord user IDs + +Kumiko and her services will only collect the info listed above. Kumiko does not collect IPs, and any other sensitive info unless needed + +Reasons for Collecting Info +--------------------------- +The info is only used to associate the Discord user with their economy account. The IDs are stored on a database, and are not viewed unless needed. + +Data Retention +-------------- +All data will be retained for as however long it is needed. If an user decides to create an economy account, the Discord ID of the user is stored, and purged when the user deletes his/her/their account. This applies to the web dashboard and other services as well. + +Data Sharing +------------ +Kumiko does not share any information with anyone. Kumiko only shares info between her internal services (the web dashboard), and only between the services. All data can only be accessed by the owner of those bots. + +Request to Access Such Data +--------------------------- +If you feel the need to request for your own data for purposes of security only, please reach out to either the staff, or the owner directly. If you wish to remove such data, please also reach out to either the staff or the owner directly. + +Changes to this Policy +---------------------- +The staff and the owner reserves the right to change this policy at any time. Any changes will be posted on the Discord server, and will be reflected on this page. \ No newline at end of file diff --git a/Kumiko-Docs/source/terms-of-service/tos.rst b/Kumiko-Docs/source/terms-of-service/tos.rst new file mode 100644 index 00000000..983e2283 --- /dev/null +++ b/Kumiko-Docs/source/terms-of-service/tos.rst @@ -0,0 +1,42 @@ +Terms of Service +================= + +By using the services provided by Kumiko, you agree to follow these terms stated below. + +TOS for Kumiko +-------------- + +1. All users who wish to use Kumiko, and any of its features are subject to all terms and conditions mentioned within this page + +2. Upon registration with the approved providers, it means that he/she/they completely aggress with the terms and conditions mentioned, and any others that may be added + +3. Users may not exploit the marketplace or any of the economy based features. This means selling items that are named inappropriately (e.g names that imply sexual behavior, nsfw content, advertisement, etc) can get you banned from using any of Kumiko's services + +4. It is forbidden to sell any of the services and use it for unauthorized commercial use or in use of malicious gains + +5. Bribes for coins are forbidden. This can lead to severe punishments, including perma-bans + +6. Community support is available via GitHub Discussions and Issues, while commercial support is allowed via the discord server. DO NOT spam GitHub issues and/or discussions with spam comments, or reviews. + +7. It is completely possible that these terms of services can change at any time, or add new ones as needed. Please keep up to date with any announcements on the Discord server + +TOS for Kumiko's Dashboard and Website +-------------------------------------- + +1. All info should be ideally truthful. This is needed in order to provide accurate data to the user + +2. Upon registration with the approved providers, it means that he/she/they completely aggress with the terms and conditions mentioned, and any others that may be added + +3. It is forbidden to sell subscriptions to other people, and to sell any part of the website or any of its features to other people. + +4. It is forbidden to sell any of the services and use it for unauthorized commercial use or in use of malicious gains + +5. The owner and its staff claim no responsibilities if such users causes damage or losses to the client as a result of improper treatment of the site. + +6. It is against such terms of service to use the services provided to you against any law, which must legally bind to the sovereign nation or of the case of none, to the international laws, to cause harm or for malicious intents to the site, any providers of the site, and to any other person legally bound to such terms and conditions + +7. The owner, and thus any member of the administration team has the right to suspend any subscription or account of such client if he/she/they violates the terms and conditions of the site + +8. The client truthfully will acknownledge that the features, and services provided within this site cannot be used for purposes of any gain, or to cause physical, mental, or emotional harm of any kind to another user or such persons + +**If any of such rules are violated, such persons is required to expose themselves or such company for liability and prosecution by the administration of the site.** diff --git a/Makefile b/Makefile index efaffd9f..d1c3bcd5 100644 --- a/Makefile +++ b/Makefile @@ -9,3 +9,6 @@ dev-setup: run: poetry run python Bot/kumikobot.py + +prod-run: + ./venv/bin/python3 Bot/kumikobot.py \ No newline at end of file diff --git a/Migrations/20230612_initial_up_rev1.sql b/Migrations/20230612_initial_up_rev1.sql new file mode 100644 index 00000000..f5a3cf56 --- /dev/null +++ b/Migrations/20230612_initial_up_rev1.sql @@ -0,0 +1,28 @@ +-- Initial Migrations +-- Created while I am in Saijo, Ehime + +CREATE TABLE IF NOT EXISTS guild ( + id BIGINT PRIMARY KEY, + prefix VARCHAR(255)[], + logs BOOLEAN DEFAULT FALSE, + birthday BOOLEAN DEFAULT FALSE, + local_economy BOOLEAN DEFAULT FALSE +); + +CREATE TABLE IF NOT EXISTS eco_user ( + id BIGINT PRIMARY KEY, + rank INT DEFAULT 0, + petals INT DEFAULT 0, + created_at timestamp WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL +); + +CREATE TABLE IF NOT EXISTS eco_item ( + id SERIAL PRIMARY KEY, + user_id BIGINT, + guild_id BIGINT, + name VARCHAR(255), + description TEXT, + price INT DEFAULT 0, + amount INT DEFAULT 0, + CONSTRAINT fk_user FOREIGN KEY (user_id) REFERENCES eco_user (id) ON DELETE CASCADE +); \ No newline at end of file diff --git a/Migrations/20230702_rev1_up_rev2.sql b/Migrations/20230702_rev1_up_rev2.sql new file mode 100644 index 00000000..ef39f37b --- /dev/null +++ b/Migrations/20230702_rev1_up_rev2.sql @@ -0,0 +1,23 @@ +ALTER TABLE eco_user ALTER COLUMN created_at SET DEFAULT (NOW() AT TIME ZONE 'utc'); + +ALTER TABLE guild ALTER COLUMN prefix SET DATA TYPE VARCHAR(255)[3]; + +CREATE TABLE IF NOT EXISTS eco_item_lookup ( + id SERIAL PRIMARY KEY, + name TEXT, + guild_id BIGINT, + owner_id BIGINT, + created_at TIMESTAMP DEFAULT (now() AT TIME ZONE 'utc'), + item_id INTEGER REFERENCES eco_item (id) ON DELETE CASCADE ON UPDATE NO ACTION +); + +CREATE INDEX IF NOT EXISTS eco_item_name_idx ON eco_item (name); +CREATE INDEX IF NOT EXISTS eco_item_name_trgm_idx ON eco_item USING GIN (name gin_trgm_ops); +CREATE INDEX IF NOT EXISTS eco_item_name_lower_idx ON eco_item (LOWER(name)); +CREATE UNIQUE INDEX IF NOT EXISTS eco_item_uniq_idx ON eco_item (LOWER(name), guild_id); + +CREATE INDEX IF NOT EXISTS eco_item_lookup_name_idx ON eco_item_lookup (name); +CREATE INDEX IF NOT EXISTS eco_item_lookup_location_id_idx ON eco_item_lookup (guild_id); +CREATE INDEX IF NOT EXISTS eco_item_lookup_name_trgm_idx ON eco_item_lookup USING GIN (name gin_trgm_ops); +CREATE INDEX IF NOT EXISTS eco_item_lookup_name_lower_idx ON eco_item_lookup (LOWER(name)); +CREATE UNIQUE INDEX IF NOT EXISTS eco_item_lookup_uniq_idx ON eco_item_lookup (LOWER(name), guild_id); \ No newline at end of file diff --git a/Migrations/20230702_rev2_up_rev3.sql b/Migrations/20230702_rev2_up_rev3.sql new file mode 100644 index 00000000..7dcec1d4 --- /dev/null +++ b/Migrations/20230702_rev2_up_rev3.sql @@ -0,0 +1,10 @@ +ALTER TABLE guild ADD COLUMN local_economy_name VARCHAR(255) DEFAULT 'Server Economy'; + +CREATE TABLE IF NOT EXISTS logging_config ( + id SERIAL PRIMARY KEY, + channel_id BIGINT, + member_events BOOLEAN DEFAULT TRUE, + mod_events BOOLEAN DEFAULT TRUE, + eco_events BOOLEAN DEFAULT FALSE, + guild_id BIGINT UNIQUE REFERENCES guild (id) ON DELETE CASCADE ON UPDATE NO ACTION +); \ No newline at end of file diff --git a/README.md b/README.md index 88ed560f..54f46c86 100644 --- a/README.md +++ b/README.md @@ -6,40 +6,38 @@ ![Kumiko](./assets/kumiko-resized-round.svg) -[![Required Python Version](https://img.shields.io/badge/Python-3.8%20|%203.9%20|%203.10%20|%203.11-blue?logo=python&logoColor=white)](https://github.com/No767/Kumiko/blob/dev/pyproject.toml) [![CodeQL](https://github.com/No767/Kumiko/actions/workflows/codeql-analysis.yml/badge.svg?branch=dev)](https://github.com/No767/Kumiko/actions/workflows/codeql-analysis.yml) [![Snyk](https://github.com/No767/Kumiko/actions/workflows/snyk.yml/badge.svg?branch=dev)](https://github.com/No767/Kumiko/actions/workflows/snyk.yml) [![Lint](https://github.com/No767/Kumiko/actions/workflows/lint.yml/badge.svg)](https://github.com/No767/Kumiko/actions/workflows/lint.yml) [![Docker Build](https://github.com/No767/Kumiko/actions/workflows/docker-build.yml/badge.svg)](https://github.com/No767/Kumiko/actions/workflows/docker-build.yml) [![Tests](https://github.com/No767/Kumiko/actions/workflows/tests.yml/badge.svg)](https://github.com/No767/Kumiko/actions/workflows/tests.yml) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/950cd812f1e04f0d813bb0298fdaa225)](https://www.codacy.com/gh/No767/Kumiko/dashboard?utm_source=github.com&utm_medium=referral&utm_content=No767/Kumiko&utm_campaign=Badge_Grade) [![codecov](https://codecov.io/gh/No767/Kumiko/branch/dev/graph/badge.svg?token=CwcMp3LIFx)](https://codecov.io/gh/No767/Kumiko) [![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/No767/Kumiko?label=Release&logo=github&sort=semver)](https://github.com/No767/Kumiko/releases) ![Docker Image Version (latest semver)](https://img.shields.io/docker/v/no767/kumiko?label=Docker%20Release&logo=docker&logoColor=white&sort=semver) [![GitHub License](https://img.shields.io/github/license/No767/Rin?label=License&logo=github)](https://github.com/No767/Kumiko/blob/dev/LICENSE) [![Kumiko](https://img.shields.io/badge/Kumiko-Oumae-white)](https://hibike-euphonium.fandom.com/wiki/Kumiko_Oumae) + + +[![Required Python Version](https://img.shields.io/badge/Python-3.8%20|%203.9%20|%203.10%20|%203.11-blue?logo=python&logoColor=white)](https://github.com/No767/Kumiko/blob/dev/pyproject.toml) [![CodeQL](https://github.com/No767/Kumiko/actions/workflows/codeql-analysis.yml/badge.svg?branch=dev)](https://github.com/No767/Kumiko/actions/workflows/codeql-analysis.yml) [![Snyk](https://github.com/No767/Kumiko/actions/workflows/snyk.yml/badge.svg?branch=dev)](https://github.com/No767/Kumiko/actions/workflows/snyk.yml) [![Lint](https://github.com/No767/Kumiko/actions/workflows/lint.yml/badge.svg)](https://github.com/No767/Kumiko/actions/workflows/lint.yml) [![Docker Build](https://github.com/No767/Kumiko/actions/workflows/docker-build.yml/badge.svg)](https://github.com/No767/Kumiko/actions/workflows/docker-build.yml) [![Tests](https://github.com/No767/Kumiko/actions/workflows/tests.yml/badge.svg)](https://github.com/No767/Kumiko/actions/workflows/tests.yml) ![Read the Docs](https://img.shields.io/readthedocs/kumiko?label=Docs&logo=readthedocs&logoColor=white) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/950cd812f1e04f0d813bb0298fdaa225)](https://www.codacy.com/gh/No767/Kumiko/dashboard?utm_source=github.com&utm_medium=referral&utm_content=No767/Kumiko&utm_campaign=Badge_Grade) [![codecov](https://codecov.io/gh/No767/Kumiko/branch/dev/graph/badge.svg?token=CwcMp3LIFx)](https://codecov.io/gh/No767/Kumiko) [![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/No767/Kumiko?label=Release&logo=github&sort=semver)](https://github.com/No767/Kumiko/releases) ![Docker Image Version (latest semver)](https://img.shields.io/docker/v/no767/kumiko?label=Docker%20Release&logo=docker&logoColor=white&sort=semver) [![GitHub License](https://img.shields.io/github/license/No767/Rin?label=License&logo=github)](https://github.com/No767/Kumiko/blob/dev/LICENSE) [![Kumiko](https://img.shields.io/badge/Kumiko-Oumae-white)](https://hibike-euphonium.fandom.com/wiki/Kumiko_Oumae) A multipurpose Discord bot built with freedom and choice in mind
-| :bangbang: | Kumiko is currently undergoing a migration from Pycord to Discord.py (including a full rewrite). At this stage, Kumiko may not be working as intended. | -| :--------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ## Kumiko (久美子) -Kumiko is a multipurpose Discord bot built with freedom and choice in mind. Kumiko allows you to decide on whether you wish to use the services provided. Kumiko includes a opt-in economy system, custom quests, and many more features. Kumiko also includes integration with AniList, Reddit, Twitter, and others. +Kumiko is a multipurpose Discord bot built with freedom and choice in mind. Kumiko provides a wide range of features, including an customizable and local economy, logging, and many more features. Integration with AniList, and others are also included. ## Features -- An **Opt-In** Economy System with jobs system and marketplace (WIP, pinned for v1) - - Includes a marketplace - - Custom currency (Lavender Petals) - - Custom jobs (WIP) - - Rank system (WIP) - - Custom quests -- Basic Administration Commands -- Search memes, posts, anime, manga, and others! +- Customizable prefix module +- Customizable local economy module with jobs, ranks, and more +- Custom logging module (aka EventsLog) +- Basic moderation commands +- Search for memes, posts, anime, manga, and others! And some ones that are planned: + - Complete Web Dashboard + Custom Embeds (WIP, pinned for v2) -- Event Logging (Pinned for v1) - Annoucement releaser with support from GitHub (Pinned for v2) -- AI Driven GAN Anime Waifu Generator (Not implemented yet) -- Selector Roles (pinned for v2) +To see the full list of planned features, please see the roadmap [here](https://github.com/No767/Kumiko/discussions/266) (#266) ## Prefix Kumiko uses both a prefixed command and slash commands. The currently supported prefixes are `>` , and `/`. +`>` is the default prefix, but this can be customized to be any custom prefix that you wish to have. For example, if you wanted to, you could set the prefix to be `?` instead. These do not change the global prefix; only on the current server where you have set it up + ## Inviting the Bot Currently under beta stages. Not ready for release yet. Currently Kumiko is under v0, which means it's beta-level software as of now, and thus should not be used in production. Kumiko is subject to breaking changes within this version. Kumiko will be ready to be invited once it reaches to v1, which is production-ready software. @@ -54,13 +52,13 @@ If you would like to support me with projects like this, please consider starrin If you would like to get started, here are some links to help you with that: -- [Manual Deployment (Self Hosting)](https://kumiko-docs.vercel.app/docs/deployment/manual-deployment) +- [Trying out Kumiko](https://kumiko.readthedocs.io/en/latest/guides/user/trying-out.html) - [Contributing](contributing.md) -- [Documentation](https://kumiko-docs.vercel.app/) +- [Documentation](https://kumiko.readthedocs.io/) ## Resources and Links -- [Documentation](https://kumiko-docs.vercel.app/) +- [Documentation](https://kumiko.readthedocs.io/) - [Docker Hub](https://hub.docker.com/r/no767/kumiko) - [GHCR](https://github.com/No767/Kumiko/pkgs/container/kumiko) @@ -70,4 +68,4 @@ Kumiko and any software that is a part of Kumiko is licensed under [Apache-2.0]( ## Name Inspiration -Kumiko's name comes from the anime character [Kumiko Oumae](https://hibike-euphonium.fandom.com/wiki/Kumiko_Oumae) from the anime [Hibike! Euphonium](https://anilist.co/anime/20912/Hibike-Euphonium/). +Kumiko's name comes from the anime character [Kumiko Oumae](https://hibike-euphonium.fandom.com/wiki/Kumiko_Oumae) from the anime [Hibike! Euphonium](https://anilist.co/anime/20912/Hibike-Euphonium/). \ No newline at end of file diff --git a/Requirements/prod.txt b/Requirements/prod.txt new file mode 100644 index 00000000..010d074c --- /dev/null +++ b/Requirements/prod.txt @@ -0,0 +1,50 @@ +aiodns==3.0.0 ; python_version >= "3.8" and python_version < "4.0" +aiofiles==0.8.0 ; python_version >= "3.8" and python_version < "4.0" +aiohttp==3.8.4 ; python_version >= "3.8" and python_version < "4.0" +aiosignal==1.3.1 ; python_version >= "3.8" and python_version < "4.0" +aiosqlite==0.17.0 ; python_version >= "3.8" and python_version < "4.0" +anyio==3.7.1 ; python_version >= "3.8" and python_version < "4.0" +async-generator==1.10 ; python_version >= "3.8" and python_version < "4.0" +async-timeout==4.0.2 ; python_version >= "3.8" and python_version < "4.0" +asyncio-extras==1.3.2 ; python_version >= "3.8" and python_version < "4.0" +asyncpg-trek==0.3.1 ; python_version >= "3.8" and python_version < "4" +asyncpg==0.28.0 ; python_version >= "3.8" and python_version < "4.0" +asyncpraw==7.7.0 ; python_version >= "3.8" and python_version < "4.0" +asyncprawcore==2.3.0 ; python_version >= "3.8" and python_version < "4.0" +attrs==23.1.0 ; python_version >= "3.8" and python_version < "4.0" +backoff==2.2.1 ; python_version >= "3.8" and python_version < "4.0" +better-ipc==2.0.3 ; python_version >= "3.8" and python_version < "4.0" +brotli==1.0.9 ; python_version >= "3.8" and python_version < "4.0" +certifi==2023.5.7 ; python_version >= "3.8" and python_version < "4.0" +cffi==1.15.1 ; python_version >= "3.8" and python_version < "4.0" +charset-normalizer==3.1.0 ; python_version >= "3.8" and python_version < "4.0" +ciso8601==2.3.0 ; python_version >= "3.8" and python_version < "4.0" +cython==0.29.36 ; python_version >= "3.8" and python_version < "4.0" and sys_platform == "win32" +discord-ext-menus @ git+https://github.com/Rapptz/discord-ext-menus@8686b5d1bbc1d3c862292eb436ab630d6e9c9b53 ; python_version >= "3.8" and python_version < "4.0" +discord-py==2.3.1 ; python_version >= "3.8" and python_version < "4.0" +discord-py[voice]==2.3.1 ; python_version >= "3.8" and python_version < "4.0" +exceptiongroup==1.1.1 ; python_version >= "3.8" and python_version < "3.11" +faust-cchardet==2.1.18 ; python_version >= "3.8" and python_version < "4.0" +frozenlist==1.3.3 ; python_version >= "3.8" and python_version < "4.0" +gql[aiohttp]==3.4.1 ; python_version >= "3.8" and python_version < "4.0" +graphql-core==3.2.3 ; python_version >= "3.8" and python_version < "4" +hiredis==2.2.3 ; python_version >= "3.8" and python_version < "4.0" +idna==3.4 ; python_version >= "3.8" and python_version < "4.0" +lru-dict==1.2.0 ; python_version >= "3.8" and python_version < "4.0" +multidict==6.0.4 ; python_version >= "3.8" and python_version < "4.0" +orjson==3.9.2 ; python_version >= "3.8" and python_version < "4.0" +psutil==5.9.5 ; python_version >= "3.8" and python_version < "4.0" +pycares==4.3.0 ; python_version >= "3.8" and python_version < "4.0" +pycparser==2.21 ; python_version >= "3.8" and python_version < "4.0" +pynacl==1.5.0 ; python_version >= "3.8" and python_version < "4.0" +python-dotenv==1.0.0 ; python_version >= "3.8" and python_version < "4.0" +redis[hiredis]==4.6.0 ; python_version >= "3.8" and python_version < "4.0" +requests==2.31.0 ; python_version >= "3.8" and python_version < "4.0" +sniffio==1.3.0 ; python_version >= "3.8" and python_version < "4.0" +typing-extensions==4.7.0 ; python_version >= "3.8" and python_version < "4.0" +update-checker==0.18.0 ; python_version >= "3.8" and python_version < "4.0" +urllib3==2.0.3 ; python_version >= "3.8" and python_version < "4.0" +uvloop==0.17.0 ; python_version >= "3.8" and python_version < "4.0" and sys_platform != "win32" +websockets==11.0.3 ; python_version >= "3.8" and python_version < "4.0" +winloop==0.0.6 ; python_version >= "3.8" and python_version < "4.0" and sys_platform == "win32" +yarl==1.9.2 ; python_version >= "3.8" and python_version < "4.0" diff --git a/Vagrantfile b/Vagrantfile index 796eb9e5..442c2544 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -5,13 +5,11 @@ Vagrant.configure("2") do |config| config.vm.define "kumiko_dev" config.vm.box = "generic/ubuntu2204" config.vm.synced_folder ".", "/home/vagrant/Kumiko", create: true - # config.vm.provision "ansible" do |ansible| - # ansible.verbose = "v" - # ansible.playbook = "playbook.yml" - # ansible.groups = { - # "dev" => ["kumiko_dev"] - # } - # end - config.vm.provision "shell", path: "scripts/vagrant-dev-provision.sh" + config.vm.network "private_network", ip: "192.168.40.0/21" + config.vm.provision "ansible" do |ansible| + ansible.verbose = "v" + ansible.playbook = "playbook.yml" + ansible.ask_become_pass = true + end -end +end \ No newline at end of file diff --git a/assets/logos/kumiko_possible-transformed (1).png b/assets/logos/kumiko_possible-transformed (1).png new file mode 100644 index 00000000..82485b96 Binary files /dev/null and b/assets/logos/kumiko_possible-transformed (1).png differ diff --git a/assets/logos/kumiko_possible-transformed.png b/assets/logos/kumiko_possible-transformed.png new file mode 100644 index 00000000..733d4cd2 Binary files /dev/null and b/assets/logos/kumiko_possible-transformed.png differ diff --git a/assets/logos/kumiko_possible_final.png b/assets/logos/kumiko_possible_final.png new file mode 100644 index 00000000..a76bd0b7 Binary files /dev/null and b/assets/logos/kumiko_possible_final.png differ diff --git a/assets/logos/kumiko_possible_rounded.png b/assets/logos/kumiko_possible_rounded.png new file mode 100644 index 00000000..04b0c1d3 Binary files /dev/null and b/assets/logos/kumiko_possible_rounded.png differ diff --git a/changelog.md b/changelog.md index 33f8085c..5b7f72af 100644 --- a/changelog.md +++ b/changelog.md @@ -1,30 +1,140 @@ -# 🛠️ Kumiko v0.7.2 🛠️ +# ✨ Kumiko v0.9.0 ✨ -This release just fixes the deployment. Accidently deployed an update, not a patch. +More reworks of literally everything... This release migrates from Prisma to pure SQL (asyncpg), and fully stabilities the repo to use discord.py instead of Pycord. Nearly all of the planned features are implemented in this release, except the economy module. +For the full list of changes, please see them here: [`v0.8.x...v0.9.0`](https://github.com/No767/Kumiko/compare/v0.8.0...v0.9.0) -Full changes can be found below: -- [`v0.6.0...v0.7.2`](https://github.com/No767/Kumiko/compare/v0.6.0...v0.7.2) -- [`v0.7.0...v0.7.2`](https://github.com/No767/Kumiko/compare/v0.7.0...v0.7.2) - -**Note: See changelog for v0.7.1 for full information** +**Note**: The commands to configure which events are turned on are disabled as of now, since it would require a full rework of the schema and code. This will be implemented in a future release. ## :boom: Breaking Changes :boom: -- None +- All of the SQL queries have been rewritten to use SQL w/ asyncpg instead of Prisma +- A ton of cogs, and commands have been either moved or deleted since v0.8.x. Please consider resyncing your commands with the include dev-tool cog (or by activating jishaku) +- All `*-bullseye` tags are now deprecated. Kumiko is now built using Debian 12 (Bookworm) ## ✨ TD;LR -- None +- Migration from Prisma to asyncpg +- Kumiko now supports custom prefixes (max is 10). The default that will be set is `>` +- asyncpg-trek migration system +- Kumiko's EventsLog module has been implemented +- Docs has been merged into one repo (https://kumiko.readthedocs.io/en/latest/index.html) ## 🛠️ Changes -- None +- Allow actions commands to greedily consume users to mention +- Replaced RedisCheck with an simple ping check (`ensureOpenRedisConn`) +- Replace all Prisma related code with asyncpg code +- Reuse AIOHTTP `ClientSession`, `asyncpg.Pool`, `redis.asyncio.connection.ConnectionPool`, and `LRU` objects throughout the lifecycle of the bot +- Hide `.python-version` file from the repo +- Expect `id` and `redis_pool` args to be in an function when using `@cache` or `@cacheJson` decos +- Update docs to add new instructions for hosting, and new requirements +- Don't stack context managers, but rather spawn 3 new ones in one go (this is recommended instead) +- Update Dockerfile to use Debian 12 (Bookworm) +- Use `Embed.timestamp` for some embeds to show timestamps +- Replaced `.gitignore` with a proper one from GitHub +- Replaced `kumiko.py` with `meta.py` to allow for clearer purpose +- Updated the return types of `KumikoCache().getJSONCache()` +- Allow for custom paths to search for `KumikoCache().setJSONCache()` +- Alow for custom keys and names to be used instead in `@cache` and `@cacheJson` decos +- Use WindowsEventLoop for Windows (#375) (@rtk-rnjn) +- Implement WinLoop event policy for Windows +- Ensure that the migration runner always runs first +- Change stop signal from `SIGTERM` to `SIGINT` for Docker +- Build the Docker image locally for Kumiko's Postgres server +- Redirect users to the Dev Contributing Guide (located in the docs) within `contributing.md` -## ✨ Additions -- None +## ✨ Additions +- Migrations system using asyncpg-trek +- SQL migrations +- SQL based code to replace Prisma +- Context manager based logging system +- Custom prefix module (aka Kumiko supports custom prefixes for guilds) +- Ansible playbooks, proper Vagrant config +- Discord API events handler, custom dispatch events +- Prefix utils +- EventsLog module +- Ping checks to ensure that the connections are open for PostgreSQL and Redis +- Docs merged into one repo - this repo +- Added `display_emoji` property to allow for cogs to have emojis when being loaded in the select menus +- The final version of what an help command should be (taken from RDanny directly as usual) +- Increased test coverage +- `sys-metrics` command to obtain system metrics (for Noelle only) ## ➖ Removals -- None +- Global KumikoCPM variable in favor of having it stored during runtime instead +- Old economy packages +- cog-ext module +- Prisma along with other unused libs +- Any old v0.8.x Prisma code +- Old "Docs" # ⬆️ Dependabot Updates -- None +- \[pip](deps-dev)\: Bump pre-commit from 3.2.1 to 3.2.2 (#300) (@dependabot) +- \[pip](deps-dev)\: Bump pyright from 1.1.301 to 1.1.302 (#301) (@dependabot) +- \[pip](deps-dev)\: Bump pytest from 7.2.2 to 7.3.0 (#302) (@dependabot) +- \[pip](deps)\: Bump orjson from 3.8.9 to 3.8.10 (#303) (@dependabot) +- \[pip](deps-dev)\: Bump pytest from 7.3.0 to 7.3.1 (#304) (@dependabot) +- \[pip](deps-dev)\: Bump pyright from 1.1.302 to 1.1.303 (#305) (@dependabot) +- \[pip](deps-dev)\: Bump ruff from 0.0.261 to 0.0.262 (#306) (@dependabot) +- \[Actions](deps)\: Bump actions/setup-python from 4.5.0 to 4.6.0 (#307) (@dependabot) +- \[pip](deps-dev)\: Bump pyright from 1.1.303 to 1.1.304 (#308) (@dependabot) +- \[pip](deps-dev)\: Bump sphinx from 6.1.3 to 6.2.0 (#310) (@dependabot) +- \[pip](deps-dev)\: Bump nox from 2022.11.21 to 2023.4.22 (@dependabot) +- \[pip](deps-dev)\: Bump sphinx from 6.2.0 to 6.2.1 (#312) (@dependabot) +- \[pip](deps-dev)\: Bump ruff from 0.0.262 to 0.0.263 (#313) (@dependabot) +- \[pip](deps-dev)\: Bump pyright from 1.1.304 to 1.1.305 (#314) (@dependabot) +- \[pip](deps)\: Bump orjson from 3.8.10 to 3.8.11 (#315) (@dependabot) +- \[pip](deps)\: Bump discord-py from 2.2.2 to 2.2.3 (#316) (@dependabot) +- \[pip](deps-dev)\: Bump pre-commit from 3.2.2 to 3.3.0 (#317) (@dependabot) +- \[pip](deps-dev)\: Bump pre-commit from 3.3.0 to 3.3.1 (#318) (@dependabot) +- \[pip](deps-dev)\: Bump ruff from 0.0.263 to 0.0.264 (#319) (@dependabot) +- \[pip](deps-dev)\: Bump pyright from 1.1.305 to 1.1.306 (#320) (@dependabot) +- \[pip](deps-dev)\: Bump ruff from 0.0.264 to 0.0.265 (#321) (@dependabot) +- \[pip](deps)\: Bump orjson from 3.8.11 to 3.8.12 (#322) (@dependabot) +- \[pip](deps)\: Bump redis from 4.5.4 to 4.5.5 (#324) (@dependabot) +- \[pip](deps)\: Bump gql from 3.4.0 to 3.4.1 (#323) (@dependabot) +- \[pip](deps-dev)\: Bump pyright from 1.1.306 to 1.1.307 (#325) (@dependabot) +- \[pip](deps-dev)\: Bump pyright from 1.1.307 to 1.1.308 (#326) (@dependabot) +- \[pip](deps-dev)\: Bump ruff from 0.0.265 to 0.0.267 (#327) (@dependabot) +- \[pip](deps-dev)\: Bump pyright from 1.1.308 to 1.1.309 (#328) (@dependabot) +- \[pip](deps-dev)\: Bump pre-commit from 3.3.1 to 3.3.2 (#329) (@dependabot) +- \[pip](deps-dev)\: Bump ruff from 0.0.267 to 0.0.269 (#330) (@dependabot) +- \[pip](deps-dev)\: Bump furo from 2023.3.27 to 2023.5.20 (#331) (@dependabot) +- \[pip](security)\: Bump requests from 2.28.2 to 2.31.0 (#332) (@dependabot) +- \[Actions](deps)\: Bump actions/setup-python from 4.6.0 to 4.6.1 (#333) (@dependabot) +- \[pip](deps)\: Bump orjson from 3.8.12 to 3.8.13 (#334) (@dependabot) +- \[pip](security)\: Bump tornado from 6.2 to 6.3.2 (#336) (@dependabot) +- \[pip](deps-dev)\: Bump pyright from 1.1.309 to 1.1.310 (#337) (@dependabot) +- \[pip](deps-dev)\: Bump pytest-cov from 4.0.0 to 4.1.0 (#335) (@dependabot) +- \[pip](deps-dev)\: Bump ruff from 0.0.269 to 0.0.270 (#338) (@dependabot) +- \[pip](deps)\: Bump orjson from 3.8.13 to 3.8.14 (#339) (@dependabot) +- \[pip](deps-dev)\: Bump pyright from 1.1.310 to 1.1.311 (#340) (@dependabot) +- \[pip](deps)\: Bump orjson from 3.8.14 to 3.9.0 (#341) (@dependabot) +- \[pip](deps-dev)\: Bump pyinstrument from 4.4.0 to 4.5.0 (#342) (@dependabot) +- \[pip](deps-dev)\: Bump ruff from 0.0.270 to 0.0.271 (#343) (@dependabot) +- \[pip](deps-dev)\: Bump pyright from 1.1.311 to 1.1.313 (#344) (@dependabot) +- \[pip](deps-dev)\: Bump ruff from 0.0.271 to 0.0.272 (#345) (@dependabot) +- \[Actions](deps)\: Bump docker/build-push-action from 4.0.0 to 4.1.0 (#346) (@dependabot) +- \[pip](deps)\: Bump orjson from 3.9.0 to 3.9.1 (#347) (@dependabot) +- \[pip](deps-dev)\: Bump pytest from 7.3.1 to 7.3.2 (#348) (@dependabot) +- \[pip](deps)\: Bump prisma from 0.8.2 to 0.9.0 (#349) (@dependabot) +- \[pip](deps)\: Bump discord-py from 2.2.3 to 2.3.0 (#350) (@dependabot) +- \[Actions](deps)\: Bump docker/build-push-action from 4.1.0 to 4.1.1 (#352) (@dependabot) +- \[pip](deps-dev)\: Bump myst-parser from 1.0.0 to 2.0.0 (@dependabot) +- \[pip](deps-dev)\: Bump pre-commit from 3.3.2 to 3.3.3 (#354) (@dependabot) +- \[pip](deps-dev)\: Bump pyright from 1.1.313 to 1.1.314 (#356) (@dependabot) +- \[pip](deps-dev)\: Bump sphinx from 6.2.1 to 7.0.1 (@dependabot) +- \[pip](deps-dev)\: Bump pyright from 1.1.314 to 1.1.315 (#357) (@dependabot) +- \[pip](deps-dev)\: Bump ruff from 0.0.272 to 0.0.274 (#358) (@dependabot) +- \[pip](deps-dev)\: Bump ruff from 0.0.274 to 0.0.275 (#359) (@dependabot) +- \[pip](deps-dev)\: Bump pytest from 7.3.2 to 7.4.0 (#360) (@dependabot) +- \[pip](deps)\: Bump discord-py from 2.3.0 to 2.3.1 (#361) (@dependabot) +- \[pip](deps)\: Bump redis from 4.5.5 to 4.6.0 (#363) (@dependabot) +- \[pip](deps-dev)\: Bump pyright from 1.1.315 to 1.1.316 (#362) (@dependabot) +- \[pip](deps)\: Bump prisma from 0.9.0 to 0.9.1 (#367) (@dependabot) +- \[pip](deps-dev)\: Bump ruff from 0.0.275 to 0.0.276 (#370) (@dependabot) +- \[pip](deps-dev)\: Bump ruff from 0.0.276 to 0.0.277 (#371) (@dependabot) +- \[Actions](deps)\: Bump actions/setup-node from 3.6.0 to 3.7.0 (#372) (@dependabot) +- \[pip](deps)\: Bump asyncpg from 0.27.0 to 0.28.0 (#373) (@dependabot) +- \[pip](deps)\: Bump orjson from 3.9.1 to 3.9.2 (#374) (@dependabot) +- \[pip](deps-dev)\: Bump nox-poetry from 1.0.2 to 1.0.3 (#376) (@dependabot) diff --git a/codecov.yml b/codecov.yml index 8ab53c34..67489276 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,4 +1,7 @@ ignore: - "Bot/Libs/ui/**" - "Bot/Libs/utils/help/**" - - "Bot/Libs/utils/pages/**" \ No newline at end of file + - "Bot/Libs/utils/pages/**" + - "Bot/Libs/utils/kumiko_logger.py" + - "Bot/Libs/utils/embeds.py" + - "Bot/Libs/utils/prefix.py" # This contains an coroutine that we can't even test to begin with \ No newline at end of file diff --git a/contributing.md b/contributing.md index b9cbd191..1fa507cc 100644 --- a/contributing.md +++ b/contributing.md @@ -1,80 +1,3 @@ # Contributing -We are glad that you're willing to contribute to this project. We are usually very lenient and relaxed with the submissions of PRs, and Issues reports. But there are some stuff that you need to know before contributing. - -## Note to new contributors - -When you contribute to this project, you are subject to the [Code of Conduct](./CODE_OF_CONDUCT.md). Any violations of the Code Of Conduct will be handled as stated. Read the contributing guide. **Support is not given if you didn't bother reading the documentation for setting up any of the requirements, or if you didn't bother to read the contributing guide.** - -## Before Starting - -Make sure to read these guides listed below (read them in order): - -- [Dev Prerequisites](./Docs/dev-prerequisites.md) -- [Getting the Dev Discord Bot](./Docs/getting-dev-discord-bot.md) -- [API Keys](./Docs/api-keys.md) -- [Database Setup](./Docs/database-setup.md) - -## Coding Style -### Variables - -Most of the code written uses camelCasing for variables, `PascalCasing` for classes, and `snake_casing` for args. To sum it up: - -- `camelCasing` for variables -- `PascalCasing` for classes -- `snake_casing` for args -- `ALL_CAPS` for constants -- `kebab-casing` for files - -### Formatting - -Kumiko uses pre-commit hooks to format all of the code. Make sure run `git add --all` before committing to add all of the files. More than likely you'll need to commit twice due to the formatting that pre-commit does afterwards. - -### Docstrings - -Just like how major programs are documented, the libraries that are custom made for Kumiko also have to be documented. The current standard for this project is to use [Google's Docstring format](https://google.github.io/styleguide/pyguide.html#s3.8-comments-and-docstrings). A handy VS Code extension that should be used is the [autoDocstring](https://marketplace.visualstudio.com/items?itemName=njpwerner.autodocstring) extension. By default it will generate the docstring in the Google format. Docstrings should be used on all coroutines and methods (excluding cogs), and on classes as well. - -Google, Numpy, and Sphinx docstrings are also supported for commands. Kumiko is documented w/ Google docstrings, so please make sure to use that format. - -Example Cog: - -```py -import discord -from discord.ext import commands -from discord.ext.commands import Context, Bot - -class MyCog(commands.Cog): - """An example cog for demo purposes""" - def __init__(self, bot: Bot): - self.bot = bot - - @commands.hybrid_command(name="hello") - async def myCommand(self, ctx: Context): - """This is an example of a description for a slash command""" - await ctx.send(f"Hello {ctx.user.name}!") - -async def setup(bot: Bot): - await bot.add_cog(MyCog(bot)) -``` -## GitHub Contributing Guidelines -### Issue and Feature Requests Reports - -If there is an issue or a feature you want to be added, use the built-in GitHub issue tracker. Though a system like Jira could be used, it would be more efficient to just use the issue tracker that GitHub provides. - -- If submitting a issue report, follow the template. Duplicates will not receive support -- If submitting a feature request, follow the template as well. As with issue reports, duplicate requests will not receive support - -### Git Commit Styleguides - -- If updating any other files that aren't project files or not important (stuff like README.md, contributing.md, etc), add the [skip ci] label in the front -- With each new commit, the message should be more or less describing the changes. Please don't write useless commit messages... -- If releasing tags, have it in this style. `Release: v[version number]`, `Update: v[version number]`, and `Fix: v[version number]`. Release is a major release. This means it bumps from 1.0.0 to 2.0.0. Minor means it bumps up the version from 1.4 to 1.5 for example. And fix just applies a patch, which would be 1.4.1 to 1.4.2. - -### Releasing Tags -In order to automate the release system, you have to make sure that in order to use it, the git commit message must be done correctly. Only use this if there is a new update that is ready to be released. Kumiko uses [SemVer](https://semver.org/) as the standard for versioning. Here's a table that should help with explaining this: - -| Type of Release, Update, or Patch | Example | -| :--: | :--: | -| Major Release (For updates that are not backwards compatible) | `Release: v2.0.0` | -| Minor Release (For updates that are backwards compatible) | `Update: v2.5.0`| -| Patch Release (For critical security patches and bug fixes) | `Fix: v2.5.1` | \ No newline at end of file +Please see the [Dev Contributing Guide](https://kumiko.readthedocs.io/en/latest/guides/dev/dev-contributing.html) for more info. \ No newline at end of file diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml index 3c7ef949..63f7fd39 100644 --- a/docker-compose-dev.yml +++ b/docker-compose-dev.yml @@ -13,7 +13,7 @@ services: redis: container_name: Kumiko-Redis-Stack-Dev - image: redis/redis-stack:7.0.6-RC6 + image: redis/redis-stack:7.2.0-M01 ports: - 6379:6379 volumes: diff --git a/docker-compose-example.yml b/docker-compose.yml similarity index 70% rename from docker-compose-example.yml rename to docker-compose.yml index 955265ef..a18b06fc 100644 --- a/docker-compose-example.yml +++ b/docker-compose.yml @@ -1,34 +1,34 @@ -version: "3.9" -services: - postgres: - container_name: Kumiko-Postgres - image: postgres:15 - env_file: - - .env - volumes: - - postgres_volume:/var/lib/postgresql/data - ports: - - 5432:5432 - - redis: - container_name: Kumiko-Redis-Stack - image: redis/redis-stack:7.0.6-RC6 - ports: - - 6379:6379 - volumes: - - redis_volume:/data - command: redis-stack-server --protected-mode no - - kumiko: - container_name: Kumiko - image: no767/kumiko:edge - command: sh -c '/Kumiko/wait-for postgres:5432 -- echo "[Wait-for] PostgreSQL is fully up. Waiting for Redis" && /Kumiko/wait-for redis:6379 -- echo "[Wait-for] Both PostgreSQL and Redis are fully ready. Starting up Kumiko" && /Kumiko/start.sh' - env_file: - - .env - depends_on: - - postgres - - redis - -volumes: - postgres_volume: - redis_volume: +version: "3.9" +name: "kumiko-prod" +services: + postgres: + container_name: Kumiko-Postgres + image: kumiko-postgres:prod-latest + build: + context: ./Docker/pg + dockerfile: Dockerfile + env_file: + - .env + volumes: + - postgres_volume:/var/lib/postgresql/data + ports: + - 5432:5432 + + redis: + container_name: Kumiko-Redis + image: redis/redis-stack-server:latest + ports: + - 6379:6379 + + kumiko: + container_name: Kumiko + image: no767/kumiko:edge + command: sh -c '/Kumiko/wait-for postgres:5432 -- echo "[Wait-for] PostgreSQL is fully up. Waiting for Redis" && /Kumiko/wait-for redis:6379 -- echo "[Wait-for] Both PostgreSQL and Redis are fully ready. Starting up Kumiko" && /Kumiko/start.sh' + env_file: + - .env + depends_on: + - postgres + - redis + +volumes: + postgres_volume: \ No newline at end of file diff --git a/migrations-runner.py b/migrations-runner.py new file mode 100644 index 00000000..f6c2b62b --- /dev/null +++ b/migrations-runner.py @@ -0,0 +1,46 @@ +import asyncio +import logging +import os +from pathlib import Path + +import asyncpg +from asyncpg_trek import Direction, execute, plan +from asyncpg_trek.asyncpg import AsyncpgBackend +from dotenv import load_dotenv + +load_dotenv() + +MIGRATIONS_DIR = Path(__file__).parent / "Migrations" + +POSTGRES_URI = os.environ["POSTGRES_URI"] +TARGET_REVISION = os.environ["TARGET_REVISION"] +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s [Migrations] %(levelname)s %(message)s", + datefmt="[%Y-%m-%d %H:%M:%S]", +) + + +async def migrate( + conn: asyncpg.Connection, + target_revision: str, +) -> None: + backend = AsyncpgBackend(conn) + async with backend.connect(): + planned = await plan( + backend, + MIGRATIONS_DIR, + target_revision=target_revision, + direction=Direction.up, + ) + await execute(backend, planned) + + +async def main(): + conn = await asyncpg.connect(dsn=POSTGRES_URI) + await migrate(conn, TARGET_REVISION) + await conn.close() + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/noxfile.py b/noxfile.py index 688c649a..a3620fbb 100644 --- a/noxfile.py +++ b/noxfile.py @@ -4,7 +4,6 @@ @nox.session(python="3.11") def test311(session: nox.Session): session.run_always("poetry", "install", "--with", "test", external=True) - session.run_always("poetry", "run", "prisma", "db", "push", external=True) session.run( "poetry", "run", @@ -19,7 +18,6 @@ def test311(session: nox.Session): @nox.session(python="3.10") def test310(session: nox.Session): session.run_always("poetry", "install", "--with", "test", external=True) - session.run_always("poetry", "run", "prisma", "db", "push", external=True) session.run( "poetry", "run", @@ -34,7 +32,6 @@ def test310(session: nox.Session): @nox.session(python="3.9") def test39(session: nox.Session): session.run_always("poetry", "install", "--with", "test", external=True) - session.run_always("poetry", "run", "prisma", "db", "push", external=True) session.run( "poetry", "run", @@ -49,7 +46,6 @@ def test39(session: nox.Session): @nox.session(python="3.8") def test38(session: nox.Session): session.run_always("poetry", "install", "--with", "test", external=True) - session.run_always("poetry", "run", "prisma", "db", "push", external=True) session.run( "poetry", "run", diff --git a/poetry.lock b/poetry.lock index 6d107d40..67bbb2a8 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,10 +1,9 @@ -# This file is automatically @generated by Poetry 1.4.0 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.5.0 and should not be changed by hand. [[package]] name = "aiodns" version = "3.0.0" description = "Simple DNS resolver for asyncio" -category = "main" optional = false python-versions = "*" files = [ @@ -19,7 +18,6 @@ pycares = ">=4.0.0" name = "aiofiles" version = "0.8.0" description = "File support for asyncio." -category = "main" optional = false python-versions = ">=3.6,<4.0" files = [ @@ -31,7 +29,6 @@ files = [ name = "aiohttp" version = "3.8.4" description = "Async http client/server framework (asyncio)" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -140,7 +137,6 @@ speedups = ["Brotli", "aiodns", "cchardet"] name = "aiosignal" version = "1.3.1" description = "aiosignal: a list of registered asynchronous callbacks" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -155,7 +151,6 @@ frozenlist = ">=1.1.0" name = "aiosqlite" version = "0.17.0" description = "asyncio bridge to the standard sqlite3 module" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -166,47 +161,56 @@ files = [ [package.dependencies] typing_extensions = ">=3.7.2" +[[package]] +name = "alabaster" +version = "0.7.13" +description = "A configurable sidebar-enabled Sphinx theme" +optional = false +python-versions = ">=3.6" +files = [ + {file = "alabaster-0.7.13-py3-none-any.whl", hash = "sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3"}, + {file = "alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2"}, +] + [[package]] name = "anyio" -version = "3.6.2" +version = "3.7.1" description = "High level compatibility layer for multiple asynchronous event loop implementations" -category = "main" optional = false -python-versions = ">=3.6.2" +python-versions = ">=3.7" files = [ - {file = "anyio-3.6.2-py3-none-any.whl", hash = "sha256:fbbe32bd270d2a2ef3ed1c5d45041250284e31fc0a4df4a5a6071842051a51e3"}, - {file = "anyio-3.6.2.tar.gz", hash = "sha256:25ea0d673ae30af41a0c442f81cf3b38c7e79fdc7b60335a4c14e05eb0947421"}, + {file = "anyio-3.7.1-py3-none-any.whl", hash = "sha256:91dee416e570e92c64041bd18b900d1d6fa78dff7048769ce5ac5ddad004fbb5"}, + {file = "anyio-3.7.1.tar.gz", hash = "sha256:44a3c9aba0f5defa43261a8b3efb97891f2bd7d804e0e1f56419befa1adfc780"}, ] [package.dependencies] +exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} idna = ">=2.8" sniffio = ">=1.1" [package.extras] -doc = ["packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] -test = ["contextlib2", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (<0.15)", "uvloop (>=0.15)"] -trio = ["trio (>=0.16,<0.22)"] +doc = ["Sphinx", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme (>=1.2.2)", "sphinxcontrib-jquery"] +test = ["anyio[trio]", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] +trio = ["trio (<0.22)"] [[package]] name = "argcomplete" -version = "2.0.0" +version = "3.1.1" description = "Bash tab completion for argparse" -category = "dev" optional = false python-versions = ">=3.6" files = [ - {file = "argcomplete-2.0.0-py2.py3-none-any.whl", hash = "sha256:cffa11ea77999bb0dd27bb25ff6dc142a6796142f68d45b1a26b11f58724561e"}, - {file = "argcomplete-2.0.0.tar.gz", hash = "sha256:6372ad78c89d662035101418ae253668445b391755cfe94ea52f1b9d22425b20"}, + {file = "argcomplete-3.1.1-py3-none-any.whl", hash = "sha256:35fa893a88deea85ea7b20d241100e64516d6af6d7b0ae2bed1d263d26f70948"}, + {file = "argcomplete-3.1.1.tar.gz", hash = "sha256:6c4c563f14f01440aaffa3eae13441c5db2357b5eec639abe7c0b15334627dff"}, ] [package.extras] -test = ["coverage", "flake8", "pexpect", "wheel"] +test = ["coverage", "mypy", "pexpect", "ruff", "wheel"] [[package]] name = "astunparse" version = "1.6.3" description = "An AST unparser for Python" -category = "dev" optional = false python-versions = "*" files = [ @@ -222,7 +226,6 @@ wheel = ">=0.23.0,<1.0" name = "async-generator" version = "1.10" description = "Async generators and context managers for Python 3.5+" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -234,7 +237,6 @@ files = [ name = "async-timeout" version = "4.0.2" description = "Timeout context manager for asyncio programs" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -246,7 +248,6 @@ files = [ name = "asyncio-extras" version = "1.3.2" description = "Asynchronous generators, context managers and more for asyncio" -category = "main" optional = false python-versions = "*" files = [ @@ -261,11 +262,77 @@ async-generator = ">=1.3" doc = ["sphinx-autodoc-typehints"] test = ["pytest", "pytest-asyncio", "pytest-cov"] +[[package]] +name = "asyncpg" +version = "0.28.0" +description = "An asyncio PostgreSQL driver" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "asyncpg-0.28.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a6d1b954d2b296292ddff4e0060f494bb4270d87fb3655dd23c5c6096d16d83"}, + {file = "asyncpg-0.28.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0740f836985fd2bd73dca42c50c6074d1d61376e134d7ad3ad7566c4f79f8184"}, + {file = "asyncpg-0.28.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e907cf620a819fab1737f2dd90c0f185e2a796f139ac7de6aa3212a8af96c050"}, + {file = "asyncpg-0.28.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86b339984d55e8202e0c4b252e9573e26e5afa05617ed02252544f7b3e6de3e9"}, + {file = "asyncpg-0.28.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0c402745185414e4c204a02daca3d22d732b37359db4d2e705172324e2d94e85"}, + {file = "asyncpg-0.28.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c88eef5e096296626e9688f00ab627231f709d0e7e3fb84bb4413dff81d996d7"}, + {file = "asyncpg-0.28.0-cp310-cp310-win32.whl", hash = "sha256:90a7bae882a9e65a9e448fdad3e090c2609bb4637d2a9c90bfdcebbfc334bf89"}, + {file = "asyncpg-0.28.0-cp310-cp310-win_amd64.whl", hash = "sha256:76aacdcd5e2e9999e83c8fbcb748208b60925cc714a578925adcb446d709016c"}, + {file = "asyncpg-0.28.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a0e08fe2c9b3618459caaef35979d45f4e4f8d4f79490c9fa3367251366af207"}, + {file = "asyncpg-0.28.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b24e521f6060ff5d35f761a623b0042c84b9c9b9fb82786aadca95a9cb4a893b"}, + {file = "asyncpg-0.28.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99417210461a41891c4ff301490a8713d1ca99b694fef05dabd7139f9d64bd6c"}, + {file = "asyncpg-0.28.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f029c5adf08c47b10bcdc857001bbef551ae51c57b3110964844a9d79ca0f267"}, + {file = "asyncpg-0.28.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ad1d6abf6c2f5152f46fff06b0e74f25800ce8ec6c80967f0bc789974de3c652"}, + {file = "asyncpg-0.28.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d7fa81ada2807bc50fea1dc741b26a4e99258825ba55913b0ddbf199a10d69d8"}, + {file = "asyncpg-0.28.0-cp311-cp311-win32.whl", hash = "sha256:f33c5685e97821533df3ada9384e7784bd1e7865d2b22f153f2e4bd4a083e102"}, + {file = "asyncpg-0.28.0-cp311-cp311-win_amd64.whl", hash = "sha256:5e7337c98fb493079d686a4a6965e8bcb059b8e1b8ec42106322fc6c1c889bb0"}, + {file = "asyncpg-0.28.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:1c56092465e718a9fdcc726cc3d9dcf3a692e4834031c9a9f871d92a75d20d48"}, + {file = "asyncpg-0.28.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4acd6830a7da0eb4426249d71353e8895b350daae2380cb26d11e0d4a01c5472"}, + {file = "asyncpg-0.28.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63861bb4a540fa033a56db3bb58b0c128c56fad5d24e6d0a8c37cb29b17c1c7d"}, + {file = "asyncpg-0.28.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:a93a94ae777c70772073d0512f21c74ac82a8a49be3a1d982e3f259ab5f27307"}, + {file = "asyncpg-0.28.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d14681110e51a9bc9c065c4e7944e8139076a778e56d6f6a306a26e740ed86d2"}, + {file = "asyncpg-0.28.0-cp37-cp37m-win32.whl", hash = "sha256:8aec08e7310f9ab322925ae5c768532e1d78cfb6440f63c078b8392a38aa636a"}, + {file = "asyncpg-0.28.0-cp37-cp37m-win_amd64.whl", hash = "sha256:319f5fa1ab0432bc91fb39b3960b0d591e6b5c7844dafc92c79e3f1bff96abef"}, + {file = "asyncpg-0.28.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b337ededaabc91c26bf577bfcd19b5508d879c0ad009722be5bb0a9dd30b85a0"}, + {file = "asyncpg-0.28.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4d32b680a9b16d2957a0a3cc6b7fa39068baba8e6b728f2e0a148a67644578f4"}, + {file = "asyncpg-0.28.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4f62f04cdf38441a70f279505ef3b4eadf64479b17e707c950515846a2df197"}, + {file = "asyncpg-0.28.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f20cac332c2576c79c2e8e6464791c1f1628416d1115935a34ddd7121bfc6a4"}, + {file = "asyncpg-0.28.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:59f9712ce01e146ff71d95d561fb68bd2d588a35a187116ef05028675462d5ed"}, + {file = "asyncpg-0.28.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fc9e9f9ff1aa0eddcc3247a180ac9e9b51a62311e988809ac6152e8fb8097756"}, + {file = "asyncpg-0.28.0-cp38-cp38-win32.whl", hash = "sha256:9e721dccd3838fcff66da98709ed884df1e30a95f6ba19f595a3706b4bc757e3"}, + {file = "asyncpg-0.28.0-cp38-cp38-win_amd64.whl", hash = "sha256:8ba7d06a0bea539e0487234511d4adf81dc8762249858ed2a580534e1720db00"}, + {file = "asyncpg-0.28.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d009b08602b8b18edef3a731f2ce6d3f57d8dac2a0a4140367e194eabd3de457"}, + {file = "asyncpg-0.28.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ec46a58d81446d580fb21b376ec6baecab7288ce5a578943e2fc7ab73bf7eb39"}, + {file = "asyncpg-0.28.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b48ceed606cce9e64fd5480a9b0b9a95cea2b798bb95129687abd8599c8b019"}, + {file = "asyncpg-0.28.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8858f713810f4fe67876728680f42e93b7e7d5c7b61cf2118ef9153ec16b9423"}, + {file = "asyncpg-0.28.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5e18438a0730d1c0c1715016eacda6e9a505fc5aa931b37c97d928d44941b4bf"}, + {file = "asyncpg-0.28.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e9c433f6fcdd61c21a715ee9128a3ca48be8ac16fa07be69262f016bb0f4dbd2"}, + {file = "asyncpg-0.28.0-cp39-cp39-win32.whl", hash = "sha256:41e97248d9076bc8e4849da9e33e051be7ba37cd507cbd51dfe4b2d99c70e3dc"}, + {file = "asyncpg-0.28.0-cp39-cp39-win_amd64.whl", hash = "sha256:3ed77f00c6aacfe9d79e9eff9e21729ce92a4b38e80ea99a58ed382f42ebd55b"}, + {file = "asyncpg-0.28.0.tar.gz", hash = "sha256:7252cdc3acb2f52feaa3664280d3bcd78a46bd6c10bfd681acfffefa1120e278"}, +] + +[package.extras] +docs = ["Sphinx (>=5.3.0,<5.4.0)", "sphinx-rtd-theme (>=1.2.2)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] +test = ["flake8 (>=5.0,<6.0)", "uvloop (>=0.15.3)"] + +[[package]] +name = "asyncpg-trek" +version = "0.3.1" +description = "A simple migrations system for asyncpg" +optional = false +python-versions = ">=3.7,<4" +files = [ + {file = "asyncpg_trek-0.3.1-py3-none-any.whl", hash = "sha256:3dbe0db791c99e5648c3d823142abb8f6b825a2ba0e9d7435e42f6309764e742"}, + {file = "asyncpg_trek-0.3.1.tar.gz", hash = "sha256:22a1f0f49209d135661c258e181c1f0ff1f94e90f02cab8ec15e6bca3420942d"}, +] + +[package.dependencies] +asyncpg = ">=0.26.0" + [[package]] name = "asyncpraw" version = "7.7.0" description = "Async PRAW, an abbreviation for \"Asynchronous Python Reddit API Wrapper\", is a python package that allows for simple access to Reddit's API." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -283,16 +350,15 @@ update-checker = ">=0.18" [package.extras] ci = ["coveralls"] -dev = ["asynctest (>=0.13.0,<0.14.0)", "mock (>=4.0.0,<5.0.0)", "packaging", "pre-commit", "pytest (>=7.0.0,<8.0.0)", "pytest-asyncio (>=0.18.0,<0.19.0)", "pytest-vcr (>=1.0.0,<2.0.0)", "sphinx", "sphinx-rtd-dark-mode", "sphinx-rtd-theme", "sphinxcontrib-trio", "testfixtures (>=6.0.0,<7.0.0)", "vcrpy (>=4.0.0,<5.0.0)"] +dev = ["asynctest (==0.13.*)", "mock (==4.*)", "packaging", "pre-commit", "pytest (==7.*)", "pytest-asyncio (==0.18.*)", "pytest-vcr (==1.*)", "sphinx", "sphinx-rtd-dark-mode", "sphinx-rtd-theme", "sphinxcontrib-trio", "testfixtures (==6.*)", "vcrpy (==4.*)"] lint = ["pre-commit", "sphinx", "sphinx-rtd-dark-mode", "sphinx-rtd-theme", "sphinxcontrib-trio"] readthedocs = ["sphinx", "sphinx-rtd-dark-mode", "sphinx-rtd-theme", "sphinxcontrib-trio"] -test = ["asynctest (>=0.13.0,<0.14.0)", "mock (>=4.0.0,<5.0.0)", "pytest (>=7.0.0,<8.0.0)", "pytest-asyncio (>=0.18.0,<0.19.0)", "pytest-vcr (>=1.0.0,<2.0.0)", "testfixtures (>=6.0.0,<7.0.0)", "vcrpy (>=4.0.0,<5.0.0)"] +test = ["asynctest (==0.13.*)", "mock (==4.*)", "pytest (==7.*)", "pytest-asyncio (==0.18.*)", "pytest-vcr (==1.*)", "testfixtures (==6.*)", "vcrpy (==4.*)"] [[package]] name = "asyncprawcore" version = "2.3.0" description = "Low-level asynchronous communication layer for Async PRAW 7+." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -312,28 +378,40 @@ test = ["asynctest (>=0.13.0)", "mock (>=0.8)", "pytest", "pytest-vcr", "testfix [[package]] name = "attrs" -version = "22.2.0" +version = "23.1.0" description = "Classes Without Boilerplate" -category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "attrs-22.2.0-py3-none-any.whl", hash = "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836"}, - {file = "attrs-22.2.0.tar.gz", hash = "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"}, + {file = "attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"}, + {file = "attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"}, ] [package.extras] -cov = ["attrs[tests]", "coverage-enable-subprocess", "coverage[toml] (>=5.3)"] -dev = ["attrs[docs,tests]"] -docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope.interface"] -tests = ["attrs[tests-no-zope]", "zope.interface"] -tests-no-zope = ["cloudpickle", "cloudpickle", "hypothesis", "hypothesis", "mypy (>=0.971,<0.990)", "mypy (>=0.971,<0.990)", "pympler", "pympler", "pytest (>=4.3.0)", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-mypy-plugins", "pytest-xdist[psutil]", "pytest-xdist[psutil]"] +cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] +dev = ["attrs[docs,tests]", "pre-commit"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] +tests = ["attrs[tests-no-zope]", "zope-interface"] +tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] + +[[package]] +name = "babel" +version = "2.12.1" +description = "Internationalization utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "Babel-2.12.1-py3-none-any.whl", hash = "sha256:b4246fb7677d3b98f501a39d43396d3cafdc8eadb045f4a31be01863f655c610"}, + {file = "Babel-2.12.1.tar.gz", hash = "sha256:cc2d99999cd01d44420ae725a21c9e3711b3aadc7976d6147f622d8581963455"}, +] + +[package.dependencies] +pytz = {version = ">=2015.7", markers = "python_version < \"3.9\""} [[package]] name = "backoff" version = "2.2.1" description = "Function decoration for backoff and retry" -category = "main" optional = false python-versions = ">=3.7,<4.0" files = [ @@ -341,16 +419,33 @@ files = [ {file = "backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba"}, ] +[[package]] +name = "beautifulsoup4" +version = "4.12.2" +description = "Screen-scraping library" +optional = false +python-versions = ">=3.6.0" +files = [ + {file = "beautifulsoup4-4.12.2-py3-none-any.whl", hash = "sha256:bd2520ca0d9d7d12694a53d44ac482d181b4ec1888909b035a3dbf40d0f57d4a"}, + {file = "beautifulsoup4-4.12.2.tar.gz", hash = "sha256:492bbc69dca35d12daac71c4db1bfff0c876c00ef4a2ffacce226d4638eb72da"}, +] + +[package.dependencies] +soupsieve = ">1.2" + +[package.extras] +html5lib = ["html5lib"] +lxml = ["lxml"] + [[package]] name = "better-ipc" -version = "2.0.1" +version = "2.0.3" description = "A high-performance inter-process communication library designed to work with the latest version of discord.py" -category = "main" optional = false python-versions = ">=3.8.0" files = [ - {file = "better-ipc-2.0.1.tar.gz", hash = "sha256:eb7f057c74ac9eae4363da9ef85928805bc1c52c65b024eb445284585c1996e9"}, - {file = "better_ipc-2.0.1-py3-none-any.whl", hash = "sha256:419e37dc30d9a8b7989a3fbd79c150c704b36b32345db1ffd5a23b759c867703"}, + {file = "better-ipc-2.0.3.tar.gz", hash = "sha256:000c19f279760e88361d06318b51f970a8bf916a1e45fd35a812570ee317cfc6"}, + {file = "better_ipc-2.0.3-py3-none-any.whl", hash = "sha256:dfb82c2497591d711f05c07b38e0886d6327d678d5e090c33174706c0ff0e5b1"}, ] [package.dependencies] @@ -360,7 +455,6 @@ websockets = ">=10.4" name = "braceexpand" version = "0.1.7" description = "Bash-style brace expansion for Python" -category = "dev" optional = false python-versions = "*" files = [ @@ -372,7 +466,6 @@ files = [ name = "brotli" version = "1.0.9" description = "Python bindings for the Brotli compression library" -category = "main" optional = false python-versions = "*" files = [ @@ -462,21 +555,19 @@ files = [ [[package]] name = "certifi" -version = "2022.12.7" +version = "2023.5.7" description = "Python package for providing Mozilla's CA Bundle." -category = "main" optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"}, - {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, + {file = "certifi-2023.5.7-py3-none-any.whl", hash = "sha256:c6c2e98f5c7869efca1f8916fed228dd91539f9f1b444c314c06eef02980c716"}, + {file = "certifi-2023.5.7.tar.gz", hash = "sha256:0f0d56dc5a6ad56fd4ba36484d6cc34451e1c6548c61daad8c320169f91eddc7"}, ] [[package]] name = "cffi" version = "1.15.1" description = "Foreign Function Interface for Python calling C code." -category = "main" optional = false python-versions = "*" files = [ @@ -553,7 +644,6 @@ pycparser = "*" name = "cfgv" version = "3.3.1" description = "Validate configuration and produce human readable error messages." -category = "dev" optional = false python-versions = ">=3.6.1" files = [ @@ -565,7 +655,6 @@ files = [ name = "charset-normalizer" version = "3.1.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -650,7 +739,6 @@ files = [ name = "ciso8601" version = "2.3.0" description = "Fast ISO8601 date time parser for Python written in C" -category = "main" optional = false python-versions = "*" files = [ @@ -705,7 +793,6 @@ files = [ name = "click" version = "8.1.3" description = "Composable command line interface toolkit" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -720,7 +807,6 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""} name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ @@ -732,7 +818,6 @@ files = [ name = "colorlog" version = "6.7.0" description = "Add colours to the output of Python's logging module." -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -746,65 +831,131 @@ colorama = {version = "*", markers = "sys_platform == \"win32\""} [package.extras] development = ["black", "flake8", "mypy", "pytest", "types-colorama"] +[[package]] +name = "contourpy" +version = "1.1.0" +description = "Python library for calculating contours of 2D quadrilateral grids" +optional = false +python-versions = ">=3.8" +files = [ + {file = "contourpy-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:89f06eff3ce2f4b3eb24c1055a26981bffe4e7264acd86f15b97e40530b794bc"}, + {file = "contourpy-1.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dffcc2ddec1782dd2f2ce1ef16f070861af4fb78c69862ce0aab801495dda6a3"}, + {file = "contourpy-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25ae46595e22f93592d39a7eac3d638cda552c3e1160255258b695f7b58e5655"}, + {file = "contourpy-1.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:17cfaf5ec9862bc93af1ec1f302457371c34e688fbd381f4035a06cd47324f48"}, + {file = "contourpy-1.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18a64814ae7bce73925131381603fff0116e2df25230dfc80d6d690aa6e20b37"}, + {file = "contourpy-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90c81f22b4f572f8a2110b0b741bb64e5a6427e0a198b2cdc1fbaf85f352a3aa"}, + {file = "contourpy-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:53cc3a40635abedbec7f1bde60f8c189c49e84ac180c665f2cd7c162cc454baa"}, + {file = "contourpy-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:1f795597073b09d631782e7245016a4323cf1cf0b4e06eef7ea6627e06a37ff2"}, + {file = "contourpy-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0b7b04ed0961647691cfe5d82115dd072af7ce8846d31a5fac6c142dcce8b882"}, + {file = "contourpy-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:27bc79200c742f9746d7dd51a734ee326a292d77e7d94c8af6e08d1e6c15d545"}, + {file = "contourpy-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:052cc634bf903c604ef1a00a5aa093c54f81a2612faedaa43295809ffdde885e"}, + {file = "contourpy-1.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9382a1c0bc46230fb881c36229bfa23d8c303b889b788b939365578d762b5c18"}, + {file = "contourpy-1.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5cec36c5090e75a9ac9dbd0ff4a8cf7cecd60f1b6dc23a374c7d980a1cd710e"}, + {file = "contourpy-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f0cbd657e9bde94cd0e33aa7df94fb73c1ab7799378d3b3f902eb8eb2e04a3a"}, + {file = "contourpy-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:181cbace49874f4358e2929aaf7ba84006acb76694102e88dd15af861996c16e"}, + {file = "contourpy-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fb3b7d9e6243bfa1efb93ccfe64ec610d85cfe5aec2c25f97fbbd2e58b531256"}, + {file = "contourpy-1.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bcb41692aa09aeb19c7c213411854402f29f6613845ad2453d30bf421fe68fed"}, + {file = "contourpy-1.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5d123a5bc63cd34c27ff9c7ac1cd978909e9c71da12e05be0231c608048bb2ae"}, + {file = "contourpy-1.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62013a2cf68abc80dadfd2307299bfa8f5aa0dcaec5b2954caeb5fa094171103"}, + {file = "contourpy-1.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0b6616375d7de55797d7a66ee7d087efe27f03d336c27cf1f32c02b8c1a5ac70"}, + {file = "contourpy-1.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:317267d915490d1e84577924bd61ba71bf8681a30e0d6c545f577363157e5e94"}, + {file = "contourpy-1.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d551f3a442655f3dcc1285723f9acd646ca5858834efeab4598d706206b09c9f"}, + {file = "contourpy-1.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e7a117ce7df5a938fe035cad481b0189049e8d92433b4b33aa7fc609344aafa1"}, + {file = "contourpy-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:d4f26b25b4f86087e7d75e63212756c38546e70f2a92d2be44f80114826e1cd4"}, + {file = "contourpy-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc00bb4225d57bff7ebb634646c0ee2a1298402ec10a5fe7af79df9a51c1bfd9"}, + {file = "contourpy-1.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:189ceb1525eb0655ab8487a9a9c41f42a73ba52d6789754788d1883fb06b2d8a"}, + {file = "contourpy-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f2931ed4741f98f74b410b16e5213f71dcccee67518970c42f64153ea9313b9"}, + {file = "contourpy-1.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:30f511c05fab7f12e0b1b7730ebdc2ec8deedcfb505bc27eb570ff47c51a8f15"}, + {file = "contourpy-1.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:143dde50520a9f90e4a2703f367cf8ec96a73042b72e68fcd184e1279962eb6f"}, + {file = "contourpy-1.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e94bef2580e25b5fdb183bf98a2faa2adc5b638736b2c0a4da98691da641316a"}, + {file = "contourpy-1.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ed614aea8462735e7d70141374bd7650afd1c3f3cb0c2dbbcbe44e14331bf002"}, + {file = "contourpy-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:438ba416d02f82b692e371858143970ed2eb6337d9cdbbede0d8ad9f3d7dd17d"}, + {file = "contourpy-1.1.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a698c6a7a432789e587168573a864a7ea374c6be8d4f31f9d87c001d5a843493"}, + {file = "contourpy-1.1.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:397b0ac8a12880412da3551a8cb5a187d3298a72802b45a3bd1805e204ad8439"}, + {file = "contourpy-1.1.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:a67259c2b493b00e5a4d0f7bfae51fb4b3371395e47d079a4446e9b0f4d70e76"}, + {file = "contourpy-1.1.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2b836d22bd2c7bb2700348e4521b25e077255ebb6ab68e351ab5aa91ca27e027"}, + {file = "contourpy-1.1.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:084eaa568400cfaf7179b847ac871582199b1b44d5699198e9602ecbbb5f6104"}, + {file = "contourpy-1.1.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:911ff4fd53e26b019f898f32db0d4956c9d227d51338fb3b03ec72ff0084ee5f"}, + {file = "contourpy-1.1.0.tar.gz", hash = "sha256:e53046c3863828d21d531cc3b53786e6580eb1ba02477e8681009b6aa0870b21"}, +] + +[package.dependencies] +numpy = ">=1.16" + +[package.extras] +bokeh = ["bokeh", "selenium"] +docs = ["furo", "sphinx-copybutton"] +mypy = ["contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.2.0)", "types-Pillow"] +test = ["Pillow", "contourpy[test-no-images]", "matplotlib"] +test-no-images = ["pytest", "pytest-cov", "wurlitzer"] + [[package]] name = "coverage" -version = "7.0.5" +version = "7.2.7" description = "Code coverage measurement for Python" -category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "coverage-7.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2a7f23bbaeb2a87f90f607730b45564076d870f1fb07b9318d0c21f36871932b"}, - {file = "coverage-7.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c18d47f314b950dbf24a41787ced1474e01ca816011925976d90a88b27c22b89"}, - {file = "coverage-7.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef14d75d86f104f03dea66c13188487151760ef25dd6b2dbd541885185f05f40"}, - {file = "coverage-7.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66e50680e888840c0995f2ad766e726ce71ca682e3c5f4eee82272c7671d38a2"}, - {file = "coverage-7.0.5-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9fed35ca8c6e946e877893bbac022e8563b94404a605af1d1e6accc7eb73289"}, - {file = "coverage-7.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d8d04e755934195bdc1db45ba9e040b8d20d046d04d6d77e71b3b34a8cc002d0"}, - {file = "coverage-7.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7e109f1c9a3ece676597831874126555997c48f62bddbcace6ed17be3e372de8"}, - {file = "coverage-7.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0a1890fca2962c4f1ad16551d660b46ea77291fba2cc21c024cd527b9d9c8809"}, - {file = "coverage-7.0.5-cp310-cp310-win32.whl", hash = "sha256:be9fcf32c010da0ba40bf4ee01889d6c737658f4ddff160bd7eb9cac8f094b21"}, - {file = "coverage-7.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:cbfcba14a3225b055a28b3199c3d81cd0ab37d2353ffd7f6fd64844cebab31ad"}, - {file = "coverage-7.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:30b5fec1d34cc932c1bc04017b538ce16bf84e239378b8f75220478645d11fca"}, - {file = "coverage-7.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1caed2367b32cc80a2b7f58a9f46658218a19c6cfe5bc234021966dc3daa01f0"}, - {file = "coverage-7.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d254666d29540a72d17cc0175746cfb03d5123db33e67d1020e42dae611dc196"}, - {file = "coverage-7.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19245c249aa711d954623d94f23cc94c0fd65865661f20b7781210cb97c471c0"}, - {file = "coverage-7.0.5-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b05ed4b35bf6ee790832f68932baf1f00caa32283d66cc4d455c9e9d115aafc"}, - {file = "coverage-7.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:29de916ba1099ba2aab76aca101580006adfac5646de9b7c010a0f13867cba45"}, - {file = "coverage-7.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e057e74e53db78122a3979f908973e171909a58ac20df05c33998d52e6d35757"}, - {file = "coverage-7.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:411d4ff9d041be08fdfc02adf62e89c735b9468f6d8f6427f8a14b6bb0a85095"}, - {file = "coverage-7.0.5-cp311-cp311-win32.whl", hash = "sha256:52ab14b9e09ce052237dfe12d6892dd39b0401690856bcfe75d5baba4bfe2831"}, - {file = "coverage-7.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:1f66862d3a41674ebd8d1a7b6f5387fe5ce353f8719040a986551a545d7d83ea"}, - {file = "coverage-7.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b69522b168a6b64edf0c33ba53eac491c0a8f5cc94fa4337f9c6f4c8f2f5296c"}, - {file = "coverage-7.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:436e103950d05b7d7f55e39beeb4d5be298ca3e119e0589c0227e6d0b01ee8c7"}, - {file = "coverage-7.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8c56bec53d6e3154eaff6ea941226e7bd7cc0d99f9b3756c2520fc7a94e6d96"}, - {file = "coverage-7.0.5-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a38362528a9115a4e276e65eeabf67dcfaf57698e17ae388599568a78dcb029"}, - {file = "coverage-7.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f67472c09a0c7486e27f3275f617c964d25e35727af952869dd496b9b5b7f6a3"}, - {file = "coverage-7.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:220e3fa77d14c8a507b2d951e463b57a1f7810a6443a26f9b7591ef39047b1b2"}, - {file = "coverage-7.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ecb0f73954892f98611e183f50acdc9e21a4653f294dfbe079da73c6378a6f47"}, - {file = "coverage-7.0.5-cp37-cp37m-win32.whl", hash = "sha256:d8f3e2e0a1d6777e58e834fd5a04657f66affa615dae61dd67c35d1568c38882"}, - {file = "coverage-7.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:9e662e6fc4f513b79da5d10a23edd2b87685815b337b1a30cd11307a6679148d"}, - {file = "coverage-7.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:790e4433962c9f454e213b21b0fd4b42310ade9c077e8edcb5113db0818450cb"}, - {file = "coverage-7.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:49640bda9bda35b057b0e65b7c43ba706fa2335c9a9896652aebe0fa399e80e6"}, - {file = "coverage-7.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d66187792bfe56f8c18ba986a0e4ae44856b1c645336bd2c776e3386da91e1dd"}, - {file = "coverage-7.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:276f4cd0001cd83b00817c8db76730938b1ee40f4993b6a905f40a7278103b3a"}, - {file = "coverage-7.0.5-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95304068686545aa368b35dfda1cdfbbdbe2f6fe43de4a2e9baa8ebd71be46e2"}, - {file = "coverage-7.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:17e01dd8666c445025c29684d4aabf5a90dc6ef1ab25328aa52bedaa95b65ad7"}, - {file = "coverage-7.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ea76dbcad0b7b0deb265d8c36e0801abcddf6cc1395940a24e3595288b405ca0"}, - {file = "coverage-7.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:50a6adc2be8edd7ee67d1abc3cd20678987c7b9d79cd265de55941e3d0d56499"}, - {file = "coverage-7.0.5-cp38-cp38-win32.whl", hash = "sha256:e4ce984133b888cc3a46867c8b4372c7dee9cee300335e2925e197bcd45b9e16"}, - {file = "coverage-7.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:4a950f83fd3f9bca23b77442f3a2b2ea4ac900944d8af9993743774c4fdc57af"}, - {file = "coverage-7.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3c2155943896ac78b9b0fd910fb381186d0c345911f5333ee46ac44c8f0e43ab"}, - {file = "coverage-7.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:54f7e9705e14b2c9f6abdeb127c390f679f6dbe64ba732788d3015f7f76ef637"}, - {file = "coverage-7.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ee30375b409d9a7ea0f30c50645d436b6f5dfee254edffd27e45a980ad2c7f4"}, - {file = "coverage-7.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b78729038abea6a5df0d2708dce21e82073463b2d79d10884d7d591e0f385ded"}, - {file = "coverage-7.0.5-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13250b1f0bd023e0c9f11838bdeb60214dd5b6aaf8e8d2f110c7e232a1bff83b"}, - {file = "coverage-7.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2c407b1950b2d2ffa091f4e225ca19a66a9bd81222f27c56bd12658fc5ca1209"}, - {file = "coverage-7.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c76a3075e96b9c9ff00df8b5f7f560f5634dffd1658bafb79eb2682867e94f78"}, - {file = "coverage-7.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f26648e1b3b03b6022b48a9b910d0ae209e2d51f50441db5dce5b530fad6d9b1"}, - {file = "coverage-7.0.5-cp39-cp39-win32.whl", hash = "sha256:ba3027deb7abf02859aca49c865ece538aee56dcb4871b4cced23ba4d5088904"}, - {file = "coverage-7.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:949844af60ee96a376aac1ded2a27e134b8c8d35cc006a52903fc06c24a3296f"}, - {file = "coverage-7.0.5-pp37.pp38.pp39-none-any.whl", hash = "sha256:b9727ac4f5cf2cbf87880a63870b5b9730a8ae3a4a360241a0fdaa2f71240ff0"}, - {file = "coverage-7.0.5.tar.gz", hash = "sha256:051afcbd6d2ac39298d62d340f94dbb6a1f31de06dfaf6fcef7b759dd3860c45"}, + {file = "coverage-7.2.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d39b5b4f2a66ccae8b7263ac3c8170994b65266797fb96cbbfd3fb5b23921db8"}, + {file = "coverage-7.2.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6d040ef7c9859bb11dfeb056ff5b3872436e3b5e401817d87a31e1750b9ae2fb"}, + {file = "coverage-7.2.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba90a9563ba44a72fda2e85302c3abc71c5589cea608ca16c22b9804262aaeb6"}, + {file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7d9405291c6928619403db1d10bd07888888ec1abcbd9748fdaa971d7d661b2"}, + {file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31563e97dae5598556600466ad9beea39fb04e0229e61c12eaa206e0aa202063"}, + {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ebba1cd308ef115925421d3e6a586e655ca5a77b5bf41e02eb0e4562a111f2d1"}, + {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cb017fd1b2603ef59e374ba2063f593abe0fc45f2ad9abdde5b4d83bd922a353"}, + {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62a5c7dad11015c66fbb9d881bc4caa5b12f16292f857842d9d1871595f4495"}, + {file = "coverage-7.2.7-cp310-cp310-win32.whl", hash = "sha256:ee57190f24fba796e36bb6d3aa8a8783c643d8fa9760c89f7a98ab5455fbf818"}, + {file = "coverage-7.2.7-cp310-cp310-win_amd64.whl", hash = "sha256:f75f7168ab25dd93110c8a8117a22450c19976afbc44234cbf71481094c1b850"}, + {file = "coverage-7.2.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06a9a2be0b5b576c3f18f1a241f0473575c4a26021b52b2a85263a00f034d51f"}, + {file = "coverage-7.2.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5baa06420f837184130752b7c5ea0808762083bf3487b5038d68b012e5937dbe"}, + {file = "coverage-7.2.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdec9e8cbf13a5bf63290fc6013d216a4c7232efb51548594ca3631a7f13c3a3"}, + {file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:52edc1a60c0d34afa421c9c37078817b2e67a392cab17d97283b64c5833f427f"}, + {file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63426706118b7f5cf6bb6c895dc215d8a418d5952544042c8a2d9fe87fcf09cb"}, + {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:afb17f84d56068a7c29f5fa37bfd38d5aba69e3304af08ee94da8ed5b0865833"}, + {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:48c19d2159d433ccc99e729ceae7d5293fbffa0bdb94952d3579983d1c8c9d97"}, + {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0e1f928eaf5469c11e886fe0885ad2bf1ec606434e79842a879277895a50942a"}, + {file = "coverage-7.2.7-cp311-cp311-win32.whl", hash = "sha256:33d6d3ea29d5b3a1a632b3c4e4f4ecae24ef170b0b9ee493883f2df10039959a"}, + {file = "coverage-7.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:5b7540161790b2f28143191f5f8ec02fb132660ff175b7747b95dcb77ac26562"}, + {file = "coverage-7.2.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f2f67fe12b22cd130d34d0ef79206061bfb5eda52feb6ce0dba0644e20a03cf4"}, + {file = "coverage-7.2.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a342242fe22407f3c17f4b499276a02b01e80f861f1682ad1d95b04018e0c0d4"}, + {file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:171717c7cb6b453aebac9a2ef603699da237f341b38eebfee9be75d27dc38e01"}, + {file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49969a9f7ffa086d973d91cec8d2e31080436ef0fb4a359cae927e742abfaaa6"}, + {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b46517c02ccd08092f4fa99f24c3b83d8f92f739b4657b0f146246a0ca6a831d"}, + {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:a3d33a6b3eae87ceaefa91ffdc130b5e8536182cd6dfdbfc1aa56b46ff8c86de"}, + {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:976b9c42fb2a43ebf304fa7d4a310e5f16cc99992f33eced91ef6f908bd8f33d"}, + {file = "coverage-7.2.7-cp312-cp312-win32.whl", hash = "sha256:8de8bb0e5ad103888d65abef8bca41ab93721647590a3f740100cd65c3b00511"}, + {file = "coverage-7.2.7-cp312-cp312-win_amd64.whl", hash = "sha256:9e31cb64d7de6b6f09702bb27c02d1904b3aebfca610c12772452c4e6c21a0d3"}, + {file = "coverage-7.2.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:58c2ccc2f00ecb51253cbe5d8d7122a34590fac9646a960d1430d5b15321d95f"}, + {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d22656368f0e6189e24722214ed8d66b8022db19d182927b9a248a2a8a2f67eb"}, + {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a895fcc7b15c3fc72beb43cdcbdf0ddb7d2ebc959edac9cef390b0d14f39f8a9"}, + {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e84606b74eb7de6ff581a7915e2dab7a28a0517fbe1c9239eb227e1354064dcd"}, + {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0a5f9e1dbd7fbe30196578ca36f3fba75376fb99888c395c5880b355e2875f8a"}, + {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:419bfd2caae268623dd469eff96d510a920c90928b60f2073d79f8fe2bbc5959"}, + {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2aee274c46590717f38ae5e4650988d1af340fe06167546cc32fe2f58ed05b02"}, + {file = "coverage-7.2.7-cp37-cp37m-win32.whl", hash = "sha256:61b9a528fb348373c433e8966535074b802c7a5d7f23c4f421e6c6e2f1697a6f"}, + {file = "coverage-7.2.7-cp37-cp37m-win_amd64.whl", hash = "sha256:b1c546aca0ca4d028901d825015dc8e4d56aac4b541877690eb76490f1dc8ed0"}, + {file = "coverage-7.2.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:54b896376ab563bd38453cecb813c295cf347cf5906e8b41d340b0321a5433e5"}, + {file = "coverage-7.2.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3d376df58cc111dc8e21e3b6e24606b5bb5dee6024f46a5abca99124b2229ef5"}, + {file = "coverage-7.2.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e330fc79bd7207e46c7d7fd2bb4af2963f5f635703925543a70b99574b0fea9"}, + {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e9d683426464e4a252bf70c3498756055016f99ddaec3774bf368e76bbe02b6"}, + {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d13c64ee2d33eccf7437961b6ea7ad8673e2be040b4f7fd4fd4d4d28d9ccb1e"}, + {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b7aa5f8a41217360e600da646004f878250a0d6738bcdc11a0a39928d7dc2050"}, + {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8fa03bce9bfbeeef9f3b160a8bed39a221d82308b4152b27d82d8daa7041fee5"}, + {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:245167dd26180ab4c91d5e1496a30be4cd721a5cf2abf52974f965f10f11419f"}, + {file = "coverage-7.2.7-cp38-cp38-win32.whl", hash = "sha256:d2c2db7fd82e9b72937969bceac4d6ca89660db0a0967614ce2481e81a0b771e"}, + {file = "coverage-7.2.7-cp38-cp38-win_amd64.whl", hash = "sha256:2e07b54284e381531c87f785f613b833569c14ecacdcb85d56b25c4622c16c3c"}, + {file = "coverage-7.2.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:537891ae8ce59ef63d0123f7ac9e2ae0fc8b72c7ccbe5296fec45fd68967b6c9"}, + {file = "coverage-7.2.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06fb182e69f33f6cd1d39a6c597294cff3143554b64b9825d1dc69d18cc2fff2"}, + {file = "coverage-7.2.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:201e7389591af40950a6480bd9edfa8ed04346ff80002cec1a66cac4549c1ad7"}, + {file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f6951407391b639504e3b3be51b7ba5f3528adbf1a8ac3302b687ecababf929e"}, + {file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f48351d66575f535669306aa7d6d6f71bc43372473b54a832222803eb956fd1"}, + {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b29019c76039dc3c0fd815c41392a044ce555d9bcdd38b0fb60fb4cd8e475ba9"}, + {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:81c13a1fc7468c40f13420732805a4c38a105d89848b7c10af65a90beff25250"}, + {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:975d70ab7e3c80a3fe86001d8751f6778905ec723f5b110aed1e450da9d4b7f2"}, + {file = "coverage-7.2.7-cp39-cp39-win32.whl", hash = "sha256:7ee7d9d4822c8acc74a5e26c50604dff824710bc8de424904c0982e25c39c6cb"}, + {file = "coverage-7.2.7-cp39-cp39-win_amd64.whl", hash = "sha256:eb393e5ebc85245347950143969b241d08b52b88a3dc39479822e073a1a8eb27"}, + {file = "coverage-7.2.7-pp37.pp38.pp39-none-any.whl", hash = "sha256:b7b4c971f05e6ae490fef852c218b0e79d4e52f79ef0c8475566584a8fb3e01d"}, + {file = "coverage-7.2.7.tar.gz", hash = "sha256:924d94291ca674905fe9481f12294eb11f2d3d3fd1adb20314ba89e94f44ed59"}, ] [package.dependencies] @@ -813,11 +964,71 @@ tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.1 [package.extras] toml = ["tomli"] +[[package]] +name = "cycler" +version = "0.11.0" +description = "Composable style cycles" +optional = false +python-versions = ">=3.6" +files = [ + {file = "cycler-0.11.0-py3-none-any.whl", hash = "sha256:3a27e95f763a428a739d2add979fa7494c912a32c17c4c38c4d5f082cad165a3"}, + {file = "cycler-0.11.0.tar.gz", hash = "sha256:9c87405839a19696e837b3b818fed3f5f69f16f1eec1a1ad77e043dcea9c772f"}, +] + +[[package]] +name = "cython" +version = "0.29.36" +description = "The Cython compiler for writing C extensions for the Python language." +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "Cython-0.29.36-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1ea33c1c57f331f5653baa1313e445fbe80d1da56dd9a42c8611037887897b9d"}, + {file = "Cython-0.29.36-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:2fe34615c13ace29e77bf9d21c26188d23eff7ad8b3e248da70404e5f5436b95"}, + {file = "Cython-0.29.36-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ae75eac4f13cbbcb50b2097470dcea570182446a3ebd0f7e95dd425c2017a2d7"}, + {file = "Cython-0.29.36-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:847d07fc02978c4433d01b4f5ee489b75fd42fd32ccf9cc4b5fd887e8cffe822"}, + {file = "Cython-0.29.36-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:7cb44aeaf6c5c25bd6a7562ece4eadf50d606fc9b5f624fa95bd0281e8bf0a97"}, + {file = "Cython-0.29.36-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:28fb10aabd56a2e4d399273b48e106abe5a0d271728fd5eed3d36e7171000045"}, + {file = "Cython-0.29.36-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:86b7a13c6b23ab6471d40a320f573fbc8a4e39833947eebed96661145dc34771"}, + {file = "Cython-0.29.36-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:19ccf7fc527cf556e2e6a3dfeffcadfbcabd24a59a988289117795dfed8a25ad"}, + {file = "Cython-0.29.36-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:74bddfc7dc8958526b2018d3adc1aa6dc9cf2a24095c972e5ad06758c360b261"}, + {file = "Cython-0.29.36-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:6c4d7e36fe0211e394adffd296382b435ac22762d14f2fe45c506c230f91cf2d"}, + {file = "Cython-0.29.36-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:0bca6a7504e8cfc63a4d3c7c9b9a04e5d05501942a6c8cee177363b61a32c2d4"}, + {file = "Cython-0.29.36-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:17c74f80b06e2fa8ffc8acd41925f4f9922da8a219cd25c6901beab2f7c56cc5"}, + {file = "Cython-0.29.36-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:25ff471a459aad82146973b0b8c177175ab896051080713d3035ad4418739f66"}, + {file = "Cython-0.29.36-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a9738f23d00d99481797b155ad58f8fc1c72096926ea2554b8ccc46e1d356c27"}, + {file = "Cython-0.29.36-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:af2f333f08c4c279f3480532341bf70ec8010bcbc7d8a6daa5ca0bf4513af295"}, + {file = "Cython-0.29.36-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:cd77cedbcc13cb67aef39b8615fd50a67fc42b0c6defea6fc0a21e19d3a062ec"}, + {file = "Cython-0.29.36-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:50d506d73a46c4a522ef9fdafcbf7a827ba13907b18ff58f61a8fa0887d0bd8d"}, + {file = "Cython-0.29.36-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:6a571d7c7b52ee12d73bc65b4855779c069545da3bac26bec06a1389ad17ade5"}, + {file = "Cython-0.29.36-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a216b2801c7d9c3babe0a10cc25da3bc92494d7047d1f732d3c47b0cceaf0941"}, + {file = "Cython-0.29.36-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:68abee3be27f21c9642a07a93f8333d491f4c52bc70068e42f51685df9ac1a57"}, + {file = "Cython-0.29.36-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:1ef90023da8a9bf84cf16f06186db0906d2ce52a09f751e2cb9d3da9d54eae46"}, + {file = "Cython-0.29.36-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:9deef0761e8c798043dbb728a1c6df97b26e5edc65b8d6c7608b3c07af3eb722"}, + {file = "Cython-0.29.36-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:69af2365de2343b4e5a61c567e7611ddf2575ae6f6e5c01968f7d4f2747324eb"}, + {file = "Cython-0.29.36-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:fdf377b0f6e9325b73ad88933136023184afdc795caeeaaf3dca13494cffd15e"}, + {file = "Cython-0.29.36-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1ff2cc5518558c598028ae8d9a43401e0e734b74b6e598156b005328c9da3472"}, + {file = "Cython-0.29.36-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7ca921068242cd8b52544870c807fe285c1f248b12df7b6dfae25cc9957b965e"}, + {file = "Cython-0.29.36-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6058a6d04e04d790cda530e1ff675e9352359eb4b777920df3cac2b62a9a030f"}, + {file = "Cython-0.29.36-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:de2045ceae1857e56a72f08e0acfa48c994277a353b7bdab1f097db9f8803f19"}, + {file = "Cython-0.29.36-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:9f2a4b4587aaef08815410dc20653613ca04a120a2954a92c39e37c6b5fdf6be"}, + {file = "Cython-0.29.36-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:2edd9f8edca69178d74cbbbc180bc3e848433c9b7dc80374a11a0bb0076c926d"}, + {file = "Cython-0.29.36-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c6c0aea8491a70f98b7496b5057c9523740e02cec21cd678eef609d2aa6c1257"}, + {file = "Cython-0.29.36-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:768f65b16d23c630d8829ce1f95520ef1531a9c0489fa872d87c8c3813f65aee"}, + {file = "Cython-0.29.36-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:568625e8274ee7288ad87b0f615ec36ab446ca9b35e77481ed010027d99c7020"}, + {file = "Cython-0.29.36-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:bdc0a4cb99f55e6878d4b67a4bfee23823484915cb6b7e9c9dd01002dd3592ea"}, + {file = "Cython-0.29.36-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:f0df6552be39853b10dfb5a10dbd08f5c49023d6b390d7ce92d4792a8b6e73ee"}, + {file = "Cython-0.29.36-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:8894db6f5b6479a3c164e0454e13083ebffeaa9a0822668bb2319bdf1b783df1"}, + {file = "Cython-0.29.36-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:53f93a8c342e9445a8f0cb7039775294f2dbbe5241936573daeaf0afe30397e4"}, + {file = "Cython-0.29.36-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:ee317f9bcab901a3db39c34ee5a27716f7132e5c0de150125342694d18b30f51"}, + {file = "Cython-0.29.36-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e4b8269e5a5d127a2191b02b9df3636c0dac73f14f1ff8a831f39cb5197c4f38"}, + {file = "Cython-0.29.36-py2.py3-none-any.whl", hash = "sha256:95bb13d8be507425d03ebe051f90d4b2a9fdccc64e4f30b35645fdb7542742eb"}, + {file = "Cython-0.29.36.tar.gz", hash = "sha256:41c0cfd2d754e383c9eeb95effc9aa4ab847d0c9747077ddd7c0dcb68c3bc01f"}, +] + [[package]] name = "discord-ext-menus" version = "1.0.0a32+g8686b5d" description = "An extension module to make reaction based menus with discord.py" -category = "main" optional = false python-versions = ">=3.5.3" files = [] @@ -834,14 +1045,13 @@ resolved_reference = "8686b5d1bbc1d3c862292eb436ab630d6e9c9b53" [[package]] name = "discord-py" -version = "2.2.2" +version = "2.3.1" description = "A Python wrapper for the Discord API" -category = "main" optional = false python-versions = ">=3.8.0" files = [ - {file = "discord.py-2.2.2-py3-none-any.whl", hash = "sha256:38fc52a784727b8e5e5749267089400035b187a009028eddfabeb182abcc6d52"}, - {file = "discord.py-2.2.2.tar.gz", hash = "sha256:b9944056bcb5711b2d04088848fd004466cf117c15c84fa798bf55470f28275f"}, + {file = "discord.py-2.3.1-py3-none-any.whl", hash = "sha256:149652f24da299706270bf8c03c2fcf80cf1caf3a480744c61d5b001688b380d"}, + {file = "discord.py-2.3.1.tar.gz", hash = "sha256:8eb4fe66b5d503da6de3a8425e23012711dc2fbcd7a782107a92beac15ee3459"}, ] [package.dependencies] @@ -858,7 +1068,6 @@ voice = ["PyNaCl (>=1.3.0,<1.6)"] name = "distlib" version = "0.3.6" description = "Distribution utilities" -category = "dev" optional = false python-versions = "*" files = [ @@ -866,16 +1075,47 @@ files = [ {file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"}, ] +[[package]] +name = "docutils" +version = "0.20.1" +description = "Docutils -- Python Documentation Utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "docutils-0.20.1-py3-none-any.whl", hash = "sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6"}, + {file = "docutils-0.20.1.tar.gz", hash = "sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b"}, +] + +[[package]] +name = "dpytest" +version = "0.7.0" +description = "A package that assists in writing tests for discord.py" +optional = false +python-versions = ">=3.8" +files = [ + {file = "dpytest-0.7.0-py3-none-any.whl", hash = "sha256:b9febeb860e6bc05bac3d433882becdba1b965ff4db1667e2d629acbc868b0d2"}, + {file = "dpytest-0.7.0.tar.gz", hash = "sha256:9f16d58347591756a8d662a44c2307864698c8a5656408a584d0bb5d69c83bbb"}, +] + +[package.dependencies] +"discord.py" = ">=2.3,<3.0" +pytest = "*" +pytest-asyncio = "*" + +[package.extras] +dev = ["build", "flake8", "invoke"] +doc = ["sphinx"] +test = ["pytest", "pytest-asyncio"] + [[package]] name = "exceptiongroup" -version = "1.1.0" +version = "1.1.1" description = "Backport of PEP 654 (exception groups)" -category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.1.0-py3-none-any.whl", hash = "sha256:327cbda3da756e2de031a3107b81ab7b3770a602c4d16ca618298c526f4bec1e"}, - {file = "exceptiongroup-1.1.0.tar.gz", hash = "sha256:bcb67d800a4497e1b404c2dd44fca47d3b7a5e5433dbab67f96c1a685cdfdf23"}, + {file = "exceptiongroup-1.1.1-py3-none-any.whl", hash = "sha256:232c37c63e4f682982c8b6459f33a8981039e5fb8756b2074364e5055c498c9e"}, + {file = "exceptiongroup-1.1.1.tar.gz", hash = "sha256:d484c3090ba2889ae2928419117447a14daf3c1231d5e30d0aae34f354f01785"}, ] [package.extras] @@ -885,7 +1125,6 @@ test = ["pytest (>=6)"] name = "faust-cchardet" version = "2.1.18" description = "cChardet is high speed universal character encoding detector." -category = "main" optional = false python-versions = "*" files = [ @@ -946,25 +1185,80 @@ files = [ [[package]] name = "filelock" -version = "3.9.0" +version = "3.12.2" description = "A platform independent file lock." -category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "filelock-3.9.0-py3-none-any.whl", hash = "sha256:f58d535af89bb9ad5cd4df046f741f8553a418c01a7856bf0d173bbc9f6bd16d"}, - {file = "filelock-3.9.0.tar.gz", hash = "sha256:7b319f24340b51f55a2bf7a12ac0755a9b03e718311dac567a0f4f7fabd2f5de"}, + {file = "filelock-3.12.2-py3-none-any.whl", hash = "sha256:cbb791cdea2a72f23da6ac5b5269ab0a0d161e9ef0100e653b69049a7706d1ec"}, + {file = "filelock-3.12.2.tar.gz", hash = "sha256:002740518d8aa59a26b0c76e10fb8c6e15eae825d34b6fdf670333fd7b938d81"}, +] + +[package.extras] +docs = ["furo (>=2023.5.20)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "diff-cover (>=7.5)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)", "pytest-timeout (>=2.1)"] + +[[package]] +name = "fonttools" +version = "4.40.0" +description = "Tools to manipulate font files" +optional = false +python-versions = ">=3.8" +files = [ + {file = "fonttools-4.40.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b802dcbf9bcff74672f292b2466f6589ab8736ce4dcf36f48eb994c2847c4b30"}, + {file = "fonttools-4.40.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7f6e3fa3da923063c286320e728ba2270e49c73386e3a711aa680f4b0747d692"}, + {file = "fonttools-4.40.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fdf60f8a5c6bcce7d024a33f7e4bc7921f5b74e8ea13bccd204f2c8b86f3470"}, + {file = "fonttools-4.40.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91784e21a1a085fac07c6a407564f4a77feb471b5954c9ee55a4f9165151f6c1"}, + {file = "fonttools-4.40.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:05171f3c546f64d78569f10adc0de72561882352cac39ec7439af12304d8d8c0"}, + {file = "fonttools-4.40.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7449e5e306f3a930a8944c85d0cbc8429cba13503372a1a40f23124d6fb09b58"}, + {file = "fonttools-4.40.0-cp310-cp310-win32.whl", hash = "sha256:bae8c13abbc2511e9a855d2142c0ab01178dd66b1a665798f357da0d06253e0d"}, + {file = "fonttools-4.40.0-cp310-cp310-win_amd64.whl", hash = "sha256:425b74a608427499b0e45e433c34ddc350820b6f25b7c8761963a08145157a66"}, + {file = "fonttools-4.40.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:00ab569b2a3e591e00425023ade87e8fef90380c1dde61be7691cb524ca5f743"}, + {file = "fonttools-4.40.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:18ea64ac43e94c9e0c23d7a9475f1026be0e25b10dda8f236fc956188761df97"}, + {file = "fonttools-4.40.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:022c4a16b412293e7f1ce21b8bab7a6f9d12c4ffdf171fdc67122baddb973069"}, + {file = "fonttools-4.40.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:530c5d35109f3e0cea2535742d6a3bc99c0786cf0cbd7bb2dc9212387f0d908c"}, + {file = "fonttools-4.40.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5e00334c66f4e83535384cb5339526d01d02d77f142c23b2f97bd6a4f585497a"}, + {file = "fonttools-4.40.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:eb52c10fda31159c22c7ed85074e05f8b97da8773ea461706c273e31bcbea836"}, + {file = "fonttools-4.40.0-cp311-cp311-win32.whl", hash = "sha256:6a8d71b9a5c884c72741868e845c0e563c5d83dcaf10bb0ceeec3b4b2eb14c67"}, + {file = "fonttools-4.40.0-cp311-cp311-win_amd64.whl", hash = "sha256:15abb3d055c1b2dff9ce376b6c3db10777cb74b37b52b78f61657634fd348a0d"}, + {file = "fonttools-4.40.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:14037c31138fbd21847ad5e5441dfdde003e0a8f3feb5812a1a21fd1c255ffbd"}, + {file = "fonttools-4.40.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:94c915f6716589f78bc00fbc14c5b8de65cfd11ee335d32504f1ef234524cb24"}, + {file = "fonttools-4.40.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37467cee0f32cada2ec08bc16c9c31f9b53ea54b2f5604bf25a1246b5f50593a"}, + {file = "fonttools-4.40.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56d4d85f5374b45b08d2f928517d1e313ea71b4847240398decd0ab3ebbca885"}, + {file = "fonttools-4.40.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8c4305b171b61040b1ee75d18f9baafe58bd3b798d1670078efe2c92436bfb63"}, + {file = "fonttools-4.40.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a954b90d1473c85a22ecf305761d9fd89da93bbd31dae86e7dea436ad2cb5dc9"}, + {file = "fonttools-4.40.0-cp38-cp38-win32.whl", hash = "sha256:1bc4c5b147be8dbc5df9cc8ac5e93ee914ad030fe2a201cc8f02f499db71011d"}, + {file = "fonttools-4.40.0-cp38-cp38-win_amd64.whl", hash = "sha256:8a917828dbfdb1cbe50cf40eeae6fbf9c41aef9e535649ed8f4982b2ef65c091"}, + {file = "fonttools-4.40.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:882983279bf39afe4e945109772c2ffad2be2c90983d6559af8b75c19845a80a"}, + {file = "fonttools-4.40.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c55f1b4109dbc3aeb496677b3e636d55ef46dc078c2a5e3f3db4e90f1c6d2907"}, + {file = "fonttools-4.40.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec468c022d09f1817c691cf884feb1030ef6f1e93e3ea6831b0d8144c06480d1"}, + {file = "fonttools-4.40.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d5adf4ba114f028fc3f5317a221fd8b0f4ef7a2e5524a2b1e0fd891b093791a"}, + {file = "fonttools-4.40.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:aa83b3f151bc63970f39b2b42a06097c5a22fd7ed9f7ba008e618de4503d3895"}, + {file = "fonttools-4.40.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:97d95b8301b62bdece1af943b88bcb3680fd385f88346a4a899ee145913b414a"}, + {file = "fonttools-4.40.0-cp39-cp39-win32.whl", hash = "sha256:1a003608400dd1cca3e089e8c94973c6b51a4fb1ef00ff6d7641617b9242e637"}, + {file = "fonttools-4.40.0-cp39-cp39-win_amd64.whl", hash = "sha256:7961575221e3da0841c75da53833272c520000d76f7f71274dbf43370f8a1065"}, + {file = "fonttools-4.40.0-py3-none-any.whl", hash = "sha256:200729d12461e2038700d31f0d49ad5a7b55855dec7525074979a06b46f88505"}, + {file = "fonttools-4.40.0.tar.gz", hash = "sha256:337b6e83d7ee73c40ea62407f2ce03b07c3459e213b6f332b94a69923b9e1cb9"}, ] [package.extras] -docs = ["furo (>=2022.12.7)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.5)"] -testing = ["covdefaults (>=2.2.2)", "coverage (>=7.0.1)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-timeout (>=2.1)"] +all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0,<5)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.0.0)", "xattr", "zopfli (>=0.1.4)"] +graphite = ["lz4 (>=1.7.4.2)"] +interpolatable = ["munkres", "scipy"] +lxml = ["lxml (>=4.0,<5)"] +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.0.0)"] +woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] [[package]] name = "frozenlist" version = "1.3.3" description = "A list-like structure which implements collections.abc.MutableSequence" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1044,16 +1338,32 @@ files = [ {file = "frozenlist-1.3.3.tar.gz", hash = "sha256:58bcc55721e8a90b88332d6cd441261ebb22342e238296bb330968952fbb3a6a"}, ] +[[package]] +name = "furo" +version = "2023.5.20" +description = "A clean customisable Sphinx documentation theme." +optional = false +python-versions = ">=3.7" +files = [ + {file = "furo-2023.5.20-py3-none-any.whl", hash = "sha256:594a8436ddfe0c071f3a9e9a209c314a219d8341f3f1af33fdf7c69544fab9e6"}, + {file = "furo-2023.5.20.tar.gz", hash = "sha256:40e09fa17c6f4b22419d122e933089226dcdb59747b5b6c79363089827dea16f"}, +] + +[package.dependencies] +beautifulsoup4 = "*" +pygments = ">=2.7" +sphinx = ">=6.0,<8.0" +sphinx-basic-ng = "*" + [[package]] name = "gql" -version = "3.4.0" +version = "3.4.1" description = "GraphQL client for Python" -category = "main" optional = false python-versions = "*" files = [ - {file = "gql-3.4.0-py2.py3-none-any.whl", hash = "sha256:59c8a0b8f0a2f3b0b2ff970c94de86f82f65cb1da3340bfe57143e5f7ea82f71"}, - {file = "gql-3.4.0.tar.gz", hash = "sha256:ca81aa8314fa88a8c57dd1ce34941278e0c352d762eb721edcba0387829ea7c0"}, + {file = "gql-3.4.1-py2.py3-none-any.whl", hash = "sha256:315624ca0f4d571ef149d455033ebd35e45c1a13f18a059596aeddcea99135cf"}, + {file = "gql-3.4.1.tar.gz", hash = "sha256:11dc5d8715a827f2c2899593439a4f36449db4f0eafa5b1ea63948f8a2f8c545"}, ] [package.dependencies] @@ -1064,11 +1374,11 @@ yarl = ">=1.6,<2.0" [package.extras] aiohttp = ["aiohttp (>=3.7.1,<3.9.0)"] -all = ["aiohttp (>=3.7.1,<3.9.0)", "botocore (>=1.21,<2)", "requests (>=2.26,<3)", "requests-toolbelt (>=0.9.1,<1)", "urllib3 (>=1.26)", "websockets (>=10,<11)", "websockets (>=9,<10)"] +all = ["aiohttp (>=3.7.1,<3.9.0)", "botocore (>=1.21,<2)", "requests (>=2.26,<3)", "requests-toolbelt (>=0.9.1,<1)", "urllib3 (>=1.26,<2)", "websockets (>=10,<11)", "websockets (>=9,<10)"] botocore = ["botocore (>=1.21,<2)"] -dev = ["aiofiles", "aiohttp (>=3.7.1,<3.9.0)", "black (==22.3.0)", "botocore (>=1.21,<2)", "check-manifest (>=0.42,<1)", "flake8 (==3.8.1)", "isort (==4.3.21)", "mock (==4.0.2)", "mypy (==0.910)", "parse (==1.15.0)", "pytest (==6.2.5)", "pytest-asyncio (==0.16.0)", "pytest-console-scripts (==1.3.1)", "pytest-cov (==3.0.0)", "requests (>=2.26,<3)", "requests-toolbelt (>=0.9.1,<1)", "sphinx (>=3.0.0,<4)", "sphinx-argparse (==0.2.5)", "sphinx-rtd-theme (>=0.4,<1)", "types-aiofiles", "types-mock", "types-requests", "urllib3 (>=1.26)", "vcrpy (==4.0.2)", "websockets (>=10,<11)", "websockets (>=9,<10)"] -requests = ["requests (>=2.26,<3)", "requests-toolbelt (>=0.9.1,<1)", "urllib3 (>=1.26)"] -test = ["aiofiles", "aiohttp (>=3.7.1,<3.9.0)", "botocore (>=1.21,<2)", "mock (==4.0.2)", "parse (==1.15.0)", "pytest (==6.2.5)", "pytest-asyncio (==0.16.0)", "pytest-console-scripts (==1.3.1)", "pytest-cov (==3.0.0)", "requests (>=2.26,<3)", "requests-toolbelt (>=0.9.1,<1)", "urllib3 (>=1.26)", "vcrpy (==4.0.2)", "websockets (>=10,<11)", "websockets (>=9,<10)"] +dev = ["aiofiles", "aiohttp (>=3.7.1,<3.9.0)", "black (==22.3.0)", "botocore (>=1.21,<2)", "check-manifest (>=0.42,<1)", "flake8 (==3.8.1)", "isort (==4.3.21)", "mock (==4.0.2)", "mypy (==0.910)", "parse (==1.15.0)", "pytest (==6.2.5)", "pytest-asyncio (==0.16.0)", "pytest-console-scripts (==1.3.1)", "pytest-cov (==3.0.0)", "requests (>=2.26,<3)", "requests-toolbelt (>=0.9.1,<1)", "sphinx (>=3.0.0,<4)", "sphinx-argparse (==0.2.5)", "sphinx-rtd-theme (>=0.4,<1)", "types-aiofiles", "types-mock", "types-requests", "urllib3 (>=1.26,<2)", "vcrpy (==4.0.2)", "websockets (>=10,<11)", "websockets (>=9,<10)"] +requests = ["requests (>=2.26,<3)", "requests-toolbelt (>=0.9.1,<1)", "urllib3 (>=1.26,<2)"] +test = ["aiofiles", "aiohttp (>=3.7.1,<3.9.0)", "botocore (>=1.21,<2)", "mock (==4.0.2)", "parse (==1.15.0)", "pytest (==6.2.5)", "pytest-asyncio (==0.16.0)", "pytest-console-scripts (==1.3.1)", "pytest-cov (==3.0.0)", "requests (>=2.26,<3)", "requests-toolbelt (>=0.9.1,<1)", "urllib3 (>=1.26,<2)", "vcrpy (==4.0.2)", "websockets (>=10,<11)", "websockets (>=9,<10)"] test-no-transport = ["aiofiles", "mock (==4.0.2)", "parse (==1.15.0)", "pytest (==6.2.5)", "pytest-asyncio (==0.16.0)", "pytest-console-scripts (==1.3.1)", "pytest-cov (==3.0.0)", "vcrpy (==4.0.2)"] websockets = ["websockets (>=10,<11)", "websockets (>=9,<10)"] @@ -1076,7 +1386,6 @@ websockets = ["websockets (>=10,<11)", "websockets (>=9,<10)"] name = "graphql-core" version = "3.2.3" description = "GraphQL implementation for Python, a port of GraphQL.js, the JavaScript reference implementation for GraphQL." -category = "main" optional = false python-versions = ">=3.6,<4" files = [ @@ -1084,173 +1393,113 @@ files = [ {file = "graphql_core-3.2.3-py3-none-any.whl", hash = "sha256:5766780452bd5ec8ba133f8bf287dc92713e3868ddd83aee4faab9fc3e303dc3"}, ] -[[package]] -name = "h11" -version = "0.14.0" -description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" -category = "main" -optional = false -python-versions = ">=3.7" -files = [ - {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, - {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, -] - [[package]] name = "hiredis" -version = "2.2.2" +version = "2.2.3" description = "Python wrapper for hiredis" -category = "main" -optional = false -python-versions = ">=3.7" -files = [ - {file = "hiredis-2.2.2-cp310-cp310-macosx_10_12_universal2.whl", hash = "sha256:ba6123ff137275e2f4c31fc74b93813fcbb79160d43f5357163e09638c7743de"}, - {file = "hiredis-2.2.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:d995846acc8e3339fb7833cd19bf6f3946ff5157c8488a4df9c51cd119a36870"}, - {file = "hiredis-2.2.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:82f869ca44bcafa37cd71cfa1429648fa354d6021dcd72f03a2f66bcb339c546"}, - {file = "hiredis-2.2.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa90a5ee7a7f30c3d72d3513914b8f51f953a71b8cbd52a241b6db6685e55645"}, - {file = "hiredis-2.2.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:01e2e588392b5fdcc3a6aa0eb62a2eb2a142f829082fa4c3354228029d3aa1ce"}, - {file = "hiredis-2.2.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5dac177a6ab8b4eb4d5e74978c29eef7cc9eef14086f814cb3893f7465578044"}, - {file = "hiredis-2.2.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4cb992e3f9753c5a0c637f333c2010d1ad702aebf2d730ee4d484f32b19bae97"}, - {file = "hiredis-2.2.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e61c22fda5fc25d31bbced24a8322d33c5cb8cad9ba698634c16edb5b3e79a91"}, - {file = "hiredis-2.2.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9873898e26e50cd41415e9d1ea128bfdb60eb26abb4f5be28a4500fd7834dc0c"}, - {file = "hiredis-2.2.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:2c18b00a382546e19bcda8b83dcca5b6e0dbc238d235723434405f48a18e8f77"}, - {file = "hiredis-2.2.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:8c3a6998f6f88d7ca4d082fd26525074df13162b274d7c64034784b6fdc56666"}, - {file = "hiredis-2.2.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:0fc1f9a9791d028b2b8afa318ccff734c7fc8861d37a04ca9b3d27c9b05f9718"}, - {file = "hiredis-2.2.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5f2cfd323f83985f2bed6ed013107873275025af270485b7d04c338bfb47bd14"}, - {file = "hiredis-2.2.2-cp310-cp310-win32.whl", hash = "sha256:55c7e9a9e05f8c0555bfba5c16d98492f8b6db650e56d0c35cc28aeabfc86020"}, - {file = "hiredis-2.2.2-cp310-cp310-win_amd64.whl", hash = "sha256:eaff526c2fed31c971b0fa338a25237ae5513550ef75d0b85b9420ec778cca45"}, - {file = "hiredis-2.2.2-cp311-cp311-macosx_10_12_universal2.whl", hash = "sha256:688b9b7458b4f3f452fea6ed062c04fa1fd9a69d9223d95c6cb052581aba553b"}, - {file = "hiredis-2.2.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:544d52fde3a8dac7854673eac20deca05214758193c01926ffbb0d57c6bf4ffe"}, - {file = "hiredis-2.2.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:990916e8b0b4eedddef787e73549b562f8c9e73a7fea82f9b8ff517806774ad0"}, - {file = "hiredis-2.2.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:10dc34854e9acfb3e7cc4157606e2efcb497b1c6fca07bd6c3be34ae5e413f13"}, - {file = "hiredis-2.2.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c446a2007985ae49c2ecd946dd819dea72b931beb5f647ba08655a1a1e133fa8"}, - {file = "hiredis-2.2.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:02b9f928dc6cd43ed0f0ffc1c75fb209fb180f004b7e2e19994805f998d247aa"}, - {file = "hiredis-2.2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a355aff8dfa02ebfe67f0946dd706e490bddda9ea260afac9cdc43942310c53"}, - {file = "hiredis-2.2.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:831461abe5b63e73719621a5f31d8fc175528a05dc09d5a8aa8ef565d6deefa4"}, - {file = "hiredis-2.2.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:75349f7c8f77eb0fd33ede4575d1e5b0a902a8176a436bf03293d7fec4bd3894"}, - {file = "hiredis-2.2.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1eb39b34d15220095dc49ad1e1082580d35cd3b6d9741def52988b5075e4ff03"}, - {file = "hiredis-2.2.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:a9b306f4e870747eea8b008dcba2e9f1e4acd12b333a684bc1cc120e633a280e"}, - {file = "hiredis-2.2.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:03dfb4ab7a2136ce1be305592553f102e1bd91a96068ab2778e3252aed20d9bc"}, - {file = "hiredis-2.2.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d8bc89c7e33fecb083a199ade0131a34d20365a8c32239e218da57290987ca9a"}, - {file = "hiredis-2.2.2-cp311-cp311-win32.whl", hash = "sha256:ed44b3c711cecde920f238ac35f70ac08744f2079b6369655856e43944464a72"}, - {file = "hiredis-2.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:2e2f0ce3e8ab1314a52f562386220f6714fd24d7968a95528135ad04e88cc741"}, - {file = "hiredis-2.2.2-cp37-cp37m-macosx_10_12_x86_64.whl", hash = "sha256:e7e61ab75b851aac2d6bc634d03738a242a6ef255a44178437b427c5ebac0a87"}, - {file = "hiredis-2.2.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9eb14339e399554bb436cc4628e8aaa3943adf7afcf34aba4cbd1e3e6b9ec7ec"}, - {file = "hiredis-2.2.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4ec57886f20f4298537cb1ab9dbda98594fb8d7c724c5fbf9a4b55329fd4a63"}, - {file = "hiredis-2.2.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a89f5afb9827eab07b9c8c585cd4dc95e5232c727508ae2c935d09531abe9e33"}, - {file = "hiredis-2.2.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3645590b9234cafd21c8ecfbf252ad9aa1d67629f4bdc98ba3627f48f8f7b5aa"}, - {file = "hiredis-2.2.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99350e89f52186146938bdba0b9c6cd68802c20346707d6ca8366f2d69d89b2f"}, - {file = "hiredis-2.2.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b5d290f3d8f7a05c4adbe6c355055b87c7081bfa1eccd1ae5491216307ee5f53"}, - {file = "hiredis-2.2.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c95be6f20377d5995ef41a98314542e194d2dc9c2579d8f130a1aea78d48fd42"}, - {file = "hiredis-2.2.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:e4e2da61a04251121cb551f569c3250e6e27e95f2a80f8351c36822eda1f5d2b"}, - {file = "hiredis-2.2.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:ac7f8d68826f95a3652e44b0c12bfa74d3aa6531d47d5dbe6a2fbfc7979bc20f"}, - {file = "hiredis-2.2.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:359e662324318baadb768d3c4ade8c4bdcfbb313570eb01e15d75dc5db781815"}, - {file = "hiredis-2.2.2-cp37-cp37m-win32.whl", hash = "sha256:fd0ca35e2cf44866137cbb5ae7e439fab18a0b0e0e1cf51d45137622d59ec012"}, - {file = "hiredis-2.2.2-cp37-cp37m-win_amd64.whl", hash = "sha256:c9488ffb10acc6b121c498875278b0a6715d193742dc92d21a281712169ac06d"}, - {file = "hiredis-2.2.2-cp38-cp38-macosx_10_12_universal2.whl", hash = "sha256:1570fe4f93bc1ea487fb566f2b863fd0ed146f643a4ea31e4e07036db9e0c7f8"}, - {file = "hiredis-2.2.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:8753c561b37cccbda7264c9b4486e206a6318c18377cd647beb3aa41a15a6beb"}, - {file = "hiredis-2.2.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a06d0dd84f10be6b15a92edbca2490b64917280f66d8267c63de99b6550308ad"}, - {file = "hiredis-2.2.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40ff3f1ec3a4046732e9e41df08dcb1a559847196755d295d43e32528aae39e6"}, - {file = "hiredis-2.2.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c24d856e13c02bd9d28a189e47be70cbba6f2c2a4bd85a8cc98819db9e7e3e06"}, - {file = "hiredis-2.2.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ee9fe7cef505e8d925c70bebcc16bfab12aa7af922f948346baffd4730f7b00"}, - {file = "hiredis-2.2.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03ab1d545794bb0e09f3b1e2c8b3adcfacd84f6f2d402bfdcd441a98c0e9643c"}, - {file = "hiredis-2.2.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:14dfccf4696d75395c587a5dafafb4f7aa0a5d55309341d10bc2e7f1eaa20771"}, - {file = "hiredis-2.2.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2ddc573809ca4374da1b24b48604f34f3d5f0911fcccfb1c403ff8d8ca31c232"}, - {file = "hiredis-2.2.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:24301ca2bf9b2f843b4c3015c90f161798fa3bbc5b95fd494785751b137dbbe2"}, - {file = "hiredis-2.2.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:b083a69e158138ffa95740ff6984d328259387b5596908021b3ccb946469ff66"}, - {file = "hiredis-2.2.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:8e16dc949cc2e9c5fbcd08de05b5fb61b89ff65738d772863c5c96248628830e"}, - {file = "hiredis-2.2.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:674f296c3c89cb53f97aa9ba2508d3f360ad481b9e0c0e3a59b342a15192adaf"}, - {file = "hiredis-2.2.2-cp38-cp38-win32.whl", hash = "sha256:20ecbf87aac4f0f33f9c55ae15cb73b485d256c57518c590b7d0c9c152150632"}, - {file = "hiredis-2.2.2-cp38-cp38-win_amd64.whl", hash = "sha256:b11960237a3025bf248135e5b497dc4923e83d137eb798fbfe78b40d57c4b156"}, - {file = "hiredis-2.2.2-cp39-cp39-macosx_10_12_universal2.whl", hash = "sha256:18103090b8eda9c529830e26594e88b0b1472055785f3ed29b8adc694d03862a"}, - {file = "hiredis-2.2.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:d1acb7c957e5343303b3862947df3232dc7395da320b3b9ae076dfaa56ad59dc"}, - {file = "hiredis-2.2.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4997f55e1208af95a8fbd0fa187b04c672fcec8f66e49b9ab7fcc45cc1657dc4"}, - {file = "hiredis-2.2.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:449e18506d22af40977abd0f5a8979f57f88d4562fe591478a3438d76a15133d"}, - {file = "hiredis-2.2.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a32a4474f7a4abdea954f3365608edee3f90f1de9fa05b81d214d4cad04c718a"}, - {file = "hiredis-2.2.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e86c800c6941698777fc58419216a66a7f76504f1cea72381d2ee206888e964d"}, - {file = "hiredis-2.2.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c73aa295c5369135247ff63aa1fbb116067485d0506cd787cc0c868e72bbee55"}, - {file = "hiredis-2.2.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e10a66680023bd5c5a3d605dae0844e3dde60eac5b79e39f51395a2aceaf634"}, - {file = "hiredis-2.2.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:03ab760fc96e0c5d36226eb727f30645bf6a53c97f14bfc0a4d0401bfc9b8af7"}, - {file = "hiredis-2.2.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:855d258e7f1aee3d7fbd5b1dc87790b1b5016e23d369a97b934a25ae7bc0171f"}, - {file = "hiredis-2.2.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:ccc33d87866d213f84f857a98f69c13f94fbf99a3304e328869890c9e49c8d65"}, - {file = "hiredis-2.2.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:339af17bb9817f8acb127247c79a99cad63db6738c0fb2aec9fa3d4f35d2a250"}, - {file = "hiredis-2.2.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:57f73aa04d0b70ff436fb35fa7ea2b796aa7addbd7ebb8d1aa1f3d1b3e4439f1"}, - {file = "hiredis-2.2.2-cp39-cp39-win32.whl", hash = "sha256:e97d4e650b8d933a1229f341db92b610fc52b8d752490235977b63b81fbbc2cb"}, - {file = "hiredis-2.2.2-cp39-cp39-win_amd64.whl", hash = "sha256:8d43a7bba66a800279e33229a206861be09c279e261eaa8db4824e59465f4848"}, - {file = "hiredis-2.2.2-pp37-pypy37_pp73-macosx_10_12_x86_64.whl", hash = "sha256:632d79fd02b03e8d9fbaebbe40bfe34b920c5d0a9c0ef6270752e0db85208175"}, - {file = "hiredis-2.2.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a5fefac31c84143782ec1ebc323c04e733a6e4bfebcef9907a34e47a465e648"}, - {file = "hiredis-2.2.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5155bc1710df8e21aa48c9b2f4d4e13e4987e1efff363a1ef9c84fae2cc6c145"}, - {file = "hiredis-2.2.2-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f220b71235d2deab1b4b22681c8aee444720d973b80f1b86a4e2a85f6bcf1e1"}, - {file = "hiredis-2.2.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:f1f1efbe9cc29a3af39cf7eed27225f951aed3f48a1149c7fb74529fb5ab86d4"}, - {file = "hiredis-2.2.2-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:1f1c44242c18b1f02e6d1162f133d65d00e09cc10d9165dccc78662def72abc2"}, - {file = "hiredis-2.2.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9e0f444d9062f7e487ef42bab2fb2e290f1704afcbca48ad3ec23de63eef0fda"}, - {file = "hiredis-2.2.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac15e7e1efca51b4695e540c80c328accb352c9608da7c2df82d1fa1a3c539ef"}, - {file = "hiredis-2.2.2-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:20cfbc469400669a5999aa34ccba3872a1e34490ec3d5c84e8c0752c27977b7c"}, - {file = "hiredis-2.2.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:bae004a0b978bf62e38d0eef5ab9156f8101d01167b3ca7054bd0994b773e917"}, - {file = "hiredis-2.2.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a1ce725542133dbdda9e8704867ef52651886bd1ef568c6fd997a27404381985"}, - {file = "hiredis-2.2.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e6ea7532221c97fa6d79f7d19d452cd9d1141d759c54279cc4774ce24728f13"}, - {file = "hiredis-2.2.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a7114961ed78d708142f6c6eb1d2ed65dc3da4b5ae8a4660ad889dd7fc891971"}, - {file = "hiredis-2.2.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1b084fbc3e69f99865242f8e1ccd4ea2a34bf6a3983d015d61133377526c0ce2"}, - {file = "hiredis-2.2.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2d1ba0799f3487294f72b2157944d5c3a4fb33c99e2d495d63eab98c7ec7234b"}, - {file = "hiredis-2.2.2.tar.gz", hash = "sha256:9c270bd0567a9c60673284e000132f603bb4ecbcd707567647a68f85ef45c4d4"}, -] - -[[package]] -name = "httpcore" -version = "0.16.3" -description = "A minimal low-level HTTP client." -category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "httpcore-0.16.3-py3-none-any.whl", hash = "sha256:da1fb708784a938aa084bde4feb8317056c55037247c787bd7e19eb2c2949dc0"}, - {file = "httpcore-0.16.3.tar.gz", hash = "sha256:c5d6f04e2fc530f39e0c077e6a30caa53f1451096120f1f38b954afd0b17c0cb"}, + {file = "hiredis-2.2.3-cp310-cp310-macosx_10_12_universal2.whl", hash = "sha256:9a1a80a8fa767f2fdc3870316a54b84fe9fc09fa6ab6a2686783de6a228a4604"}, + {file = "hiredis-2.2.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3f006c28c885deb99b670a5a66f367a175ab8955b0374029bad7111f5357dcd4"}, + {file = "hiredis-2.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ffaf841546905d90ff189de7397aa56413b1ce5e54547f17a98f0ebf3a3b0a3b"}, + {file = "hiredis-2.2.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cadb0ac7ba3babfd804e425946bec9717b320564a1390f163a54af9365a720a"}, + {file = "hiredis-2.2.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:33bc4721632ef9708fa44e5df0066053fccc8e65410a2c48573192517a533b48"}, + {file = "hiredis-2.2.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:227c5b4bcb60f89008c275d596e4a7b6625a6b3c827b8a66ae582eace7051f71"}, + {file = "hiredis-2.2.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61995eb826009d99ed8590747bc0da683a5f4fbb4faa8788166bf3810845cd5c"}, + {file = "hiredis-2.2.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f969edc851efe23010e0f53a64269f2629a9364135e9ec81c842e8b2277d0c1"}, + {file = "hiredis-2.2.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d27e560eefb57914d742a837f1da98d3b29cb22eff013c8023b7cf52ae6e051d"}, + {file = "hiredis-2.2.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3759f4789ae1913b7df278dfc9e8749205b7a106f888cd2903d19461e24a7697"}, + {file = "hiredis-2.2.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c6cb613148422c523945cdb8b6bed617856f2602fd8750e33773ede2616e55d5"}, + {file = "hiredis-2.2.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:1d274d5c511dfc03f83f997d3238eaa9b6ee3f982640979f509373cced891e98"}, + {file = "hiredis-2.2.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3b7fe075e91b9d9cff40eba4fb6a8eff74964d3979a39be9a9ef58b1b4cb3604"}, + {file = "hiredis-2.2.3-cp310-cp310-win32.whl", hash = "sha256:77924b0d32fd1f493d3df15d9609ddf9d94c31a364022a6bf6b525ce9da75bea"}, + {file = "hiredis-2.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:dcb0569dd5bfe6004658cd0f229efa699a3169dcb4f77bd72e188adda302063d"}, + {file = "hiredis-2.2.3-cp311-cp311-macosx_10_12_universal2.whl", hash = "sha256:d115790f18daa99b5c11a506e48923b630ef712e9e4b40482af942c3d40638b8"}, + {file = "hiredis-2.2.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c3b8be557e08b234774925622e196f0ee36fe4eab66cd19df934d3efd8f3743"}, + {file = "hiredis-2.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3f5446068197b35a11ccc697720c41879c8657e2e761aaa8311783aac84cef20"}, + {file = "hiredis-2.2.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa17a3b22b3726d54d7af20394f65d4a1735a842a4e0f557dc67a90f6965c4bc"}, + {file = "hiredis-2.2.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7df645b6b7800e8b748c217fbd6a4ca8361bcb9a1ae6206cc02377833ec8a1aa"}, + {file = "hiredis-2.2.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2fb9300959a0048138791f3d68359d61a788574ec9556bddf1fec07f2dbc5320"}, + {file = "hiredis-2.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d7e459fe7313925f395148d36d9b7f4f8dac65be06e45d7af356b187cef65fc"}, + {file = "hiredis-2.2.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8eceffca3941775b646cd585cd19b275d382de43cc3327d22f7c75d7b003d481"}, + {file = "hiredis-2.2.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b17baf702c6e5b4bb66e1281a3efbb1d749c9d06cdb92b665ad81e03118f78fc"}, + {file = "hiredis-2.2.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e43e2b5acaad09cf48c032f7e4926392bb3a3f01854416cf6d82ebff94d5467"}, + {file = "hiredis-2.2.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:a7205497d7276a81fe92951a29616ef96562ed2f91a02066f72b6f93cb34b40e"}, + {file = "hiredis-2.2.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:126623b03c31cb6ac3e0d138feb6fcc36dd43dd34fc7da7b7a0c38b5d75bc896"}, + {file = "hiredis-2.2.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:071c5814b850574036506a8118034f97c3cbf2fe9947ff45a27b07a48da56240"}, + {file = "hiredis-2.2.3-cp311-cp311-win32.whl", hash = "sha256:d1be9e30e675f5bc1cb534633324578f6f0944a1bcffe53242cf632f554f83b6"}, + {file = "hiredis-2.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:b9a7c987e161e3c58f992c63b7e26fea7fe0777f3b975799d23d65bbb8cb5899"}, + {file = "hiredis-2.2.3-cp37-cp37m-macosx_10_12_x86_64.whl", hash = "sha256:f2dcb8389fa3d453927b1299f46bdb38473c293c8269d5c777d33ea0e526b610"}, + {file = "hiredis-2.2.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a2df98f5e071320c7d84e8bd07c0542acdd0a7519307fc31774d60e4b842ec4f"}, + {file = "hiredis-2.2.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:61a72e4a523cdfc521762137559c08dfa360a3caef63620be58c699d1717dac1"}, + {file = "hiredis-2.2.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c9b9e5bde7030cae83aa900b5bd660decc65afd2db8c400f3c568c815a47ca2a"}, + {file = "hiredis-2.2.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd2614f17e261f72efc2f19f5e5ff2ee19e2296570c0dcf33409e22be30710de"}, + {file = "hiredis-2.2.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:46525fbd84523cac75af5bf524bc74aaac848beaf31b142d2df8a787d9b4bbc4"}, + {file = "hiredis-2.2.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d1a4ce40ba11da9382c14da31f4f9e88c18f7d294f523decd0fadfb81f51ad18"}, + {file = "hiredis-2.2.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:5cda592405bbd29d53942e0389dc3fa77b49c362640210d7e94a10c14a677d4d"}, + {file = "hiredis-2.2.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:5e6674a017629284ef373b50496d9fb1a89b85a20a7fa100ecd109484ec748e5"}, + {file = "hiredis-2.2.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:e62ec131816c6120eff40dffe43424e140264a15fa4ab88c301bd6a595913af3"}, + {file = "hiredis-2.2.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:17e938d9d3ee92e1adbff361706f1c36cc60eeb3e3eeca7a3a353eae344f4c91"}, + {file = "hiredis-2.2.3-cp37-cp37m-win32.whl", hash = "sha256:95d2305fd2a7b179cacb48b10f618872fc565c175f9f62b854e8d1acac3e8a9e"}, + {file = "hiredis-2.2.3-cp37-cp37m-win_amd64.whl", hash = "sha256:8f9dbe12f011a9b784f58faecc171d22465bb532c310bd588d769ba79a59ef5a"}, + {file = "hiredis-2.2.3-cp38-cp38-macosx_10_12_universal2.whl", hash = "sha256:5a4bcef114fc071d5f52c386c47f35aae0a5b43673197b9288a15b584da8fa3a"}, + {file = "hiredis-2.2.3-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:232d0a70519865741ba56e1dfefd160a580ae78c30a1517bad47b3cf95a3bc7d"}, + {file = "hiredis-2.2.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9076ce8429785c85f824650735791738de7143f61f43ae9ed83e163c0ca0fa44"}, + {file = "hiredis-2.2.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec58fb7c2062f835595c12f0f02dcda76d0eb0831423cc191d1e18c9276648de"}, + {file = "hiredis-2.2.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7f2b34a6444b8f9c1e9f84bd2c639388e5d14f128afd14a869dfb3d9af893aa2"}, + {file = "hiredis-2.2.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:818dfd310aa1020a13cd08ee48e116dd8c3bb2e23b8161f8ac4df587dd5093d7"}, + {file = "hiredis-2.2.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96d9ea6c8d4cbdeee2e0d43379ce2881e4af0454b00570677c59f33f2531cd38"}, + {file = "hiredis-2.2.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1eadbcd3de55ac42310ff82550d3302cb4efcd4e17d76646a17b6e7004bb42b"}, + {file = "hiredis-2.2.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:477c34c4489666dc73cb5e89dafe2617c3e13da1298917f73d55aac4696bd793"}, + {file = "hiredis-2.2.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:14824e457e4f5cda685c3345d125da13949bcf3bb1c88eb5d248c8d2c3dee08f"}, + {file = "hiredis-2.2.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:9cd32326dfa6ce87edf754153b0105aca64486bebe93b9600ccff74fa0b224df"}, + {file = "hiredis-2.2.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:51341e70b467004dcbec3a6ce8c478d2d6241e0f6b01e4c56764afd5022e1e9d"}, + {file = "hiredis-2.2.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2443659c76b226267e2a04dbbb21bc2a3f91aa53bdc0c22964632753ae43a247"}, + {file = "hiredis-2.2.3-cp38-cp38-win32.whl", hash = "sha256:4e3e3e31423f888d396b1fc1f936936e52af868ac1ec17dd15e3eeba9dd4de24"}, + {file = "hiredis-2.2.3-cp38-cp38-win_amd64.whl", hash = "sha256:20f509e3a1a20d6e5f5794fc37ceb21f70f409101fcfe7a8bde783894d51b369"}, + {file = "hiredis-2.2.3-cp39-cp39-macosx_10_12_universal2.whl", hash = "sha256:d20891e3f33803b26d54c77fd5745878497091e33f4bbbdd454cf6e71aee8890"}, + {file = "hiredis-2.2.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:50171f985e17970f87d5a29e16603d1e5b03bdbf5c2691a37e6c912942a6b657"}, + {file = "hiredis-2.2.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9944a2cac25ffe049a7e89f306e11b900640837d1ef38d9be0eaa4a4e2b73a52"}, + {file = "hiredis-2.2.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a5c8019ff94988d56eb49b15de76fe83f6b42536d76edeb6565dbf7fe14b973"}, + {file = "hiredis-2.2.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a286ded34eb16501002e3713b3130c987366eee2ba0d58c33c72f27778e31676"}, + {file = "hiredis-2.2.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b3e974ad15eb32b1f537730dea70b93a4c3db7b026de3ad2b59da49c6f7454d"}, + {file = "hiredis-2.2.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08415ea74c1c29b9d6a4ca3dd0e810dc1af343c1d1d442e15ba133b11ab5be6a"}, + {file = "hiredis-2.2.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e17d04ea58ab8cf3f2dc52e875db16077c6357846006780086fff3189fb199d"}, + {file = "hiredis-2.2.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6ccdcb635dae85b006592f78e32d97f4bc7541cb27829d505f9c7fefcef48298"}, + {file = "hiredis-2.2.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:69536b821dd1bc78058a6e7541743f8d82bf2d981b91280b14c4daa6cdc7faba"}, + {file = "hiredis-2.2.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:3753df5f873d473f055e1f8837bfad0bd3b277c86f3c9bf058c58f14204cd901"}, + {file = "hiredis-2.2.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:6f88cafe46612b6fa68e6dea49e25bebf160598bba00101caa51cc8c1f18d597"}, + {file = "hiredis-2.2.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:33ee3ea5cad3a8cb339352cd230b411eb437a2e75d7736c4899acab32056ccdb"}, + {file = "hiredis-2.2.3-cp39-cp39-win32.whl", hash = "sha256:b4f3d06dc16671b88a13ae85d8ca92534c0b637d59e49f0558d040a691246422"}, + {file = "hiredis-2.2.3-cp39-cp39-win_amd64.whl", hash = "sha256:4f674e309cd055ee7a48304ceb8cf43265d859faf4d7d01d270ce45e976ae9d3"}, + {file = "hiredis-2.2.3-pp37-pypy37_pp73-macosx_10_12_x86_64.whl", hash = "sha256:8f280ab4e043b089777b43b4227bdc2035f88da5072ab36588e0ccf77d45d058"}, + {file = "hiredis-2.2.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15c2a551f3b8a26f7940d6ee10b837810201754b8d7e6f6b1391655370882c5a"}, + {file = "hiredis-2.2.3-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60c4e3c258eafaab21b174b17270a0cc093718d61cdbde8c03f85ec4bf835343"}, + {file = "hiredis-2.2.3-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cc36a9dded458d4e37492fe3e619c6c83caae794d26ad925adbce61d592f8428"}, + {file = "hiredis-2.2.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:4ed68a3b1ccb4313d2a42546fd7e7439ad4745918a48b6c9bcaa61e1e3e42634"}, + {file = "hiredis-2.2.3-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3bf4b5bae472630c229518e4a814b1b68f10a3d9b00aeaec45f1a330f03a0251"}, + {file = "hiredis-2.2.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33a94d264e6e12a79d9bb8af333b01dc286b9f39c99072ab5fef94ce1f018e17"}, + {file = "hiredis-2.2.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3fa6811a618653164f918b891a0fa07052bd71a799defa5c44d167cac5557b26"}, + {file = "hiredis-2.2.3-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:af33f370be90b48bbaf0dab32decbdcc522b1fa95d109020a963282086518a8e"}, + {file = "hiredis-2.2.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:b9953d87418ac228f508d93898ab572775e4d3b0eeb886a1a7734553bcdaf291"}, + {file = "hiredis-2.2.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5e7bb4dd524f50b71c20ef5a12bd61da9b463f8894b18a06130942fe31509881"}, + {file = "hiredis-2.2.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89a258424158eb8b3ed9f65548d68998da334ef155d09488c5637723eb1cd697"}, + {file = "hiredis-2.2.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f4a65276f6ecdebe75f2a53f578fbc40e8d2860658420d5e0611c56bbf5054c"}, + {file = "hiredis-2.2.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:334f2738700b20faa04a0d813366fb16ed17287430a6b50584161d5ad31ca6d7"}, + {file = "hiredis-2.2.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d194decd9608f11c777946f596f31d5aacad13972a0a87829ae1e6f2d26c1885"}, + {file = "hiredis-2.2.3.tar.gz", hash = "sha256:e75163773a309e56a9b58165cf5a50e0f84b755f6ff863b2c01a38918fe92daa"}, ] -[package.dependencies] -anyio = ">=3.0,<5.0" -certifi = "*" -h11 = ">=0.13,<0.15" -sniffio = ">=1.0.0,<2.0.0" - -[package.extras] -http2 = ["h2 (>=3,<5)"] -socks = ["socksio (>=1.0.0,<2.0.0)"] - -[[package]] -name = "httpx" -version = "0.23.3" -description = "The next generation HTTP client." -category = "main" -optional = false -python-versions = ">=3.7" -files = [ - {file = "httpx-0.23.3-py3-none-any.whl", hash = "sha256:a211fcce9b1254ea24f0cd6af9869b3d29aba40154e947d2a07bb499b3e310d6"}, - {file = "httpx-0.23.3.tar.gz", hash = "sha256:9818458eb565bb54898ccb9b8b251a28785dd4a55afbc23d0eb410754fe7d0f9"}, -] - -[package.dependencies] -certifi = "*" -httpcore = ">=0.15.0,<0.17.0" -rfc3986 = {version = ">=1.3,<2", extras = ["idna2008"]} -sniffio = "*" - -[package.extras] -brotli = ["brotli", "brotlicffi"] -cli = ["click (>=8.0.0,<9.0.0)", "pygments (>=2.0.0,<3.0.0)", "rich (>=10,<13)"] -http2 = ["h2 (>=3,<5)"] -socks = ["socksio (>=1.0.0,<2.0.0)"] - [[package]] name = "identify" -version = "2.5.20" +version = "2.5.24" description = "File identification library for Python" -category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "identify-2.5.20-py2.py3-none-any.whl", hash = "sha256:5dfef8a745ca4f2c95f27e9db74cb4c8b6d9916383988e8791f3595868f78a33"}, - {file = "identify-2.5.20.tar.gz", hash = "sha256:c8b288552bc5f05a08aff09af2f58e6976bf8ac87beb38498a0e3d98ba64eb18"}, + {file = "identify-2.5.24-py2.py3-none-any.whl", hash = "sha256:986dbfb38b1140e763e413e6feb44cd731faf72d1909543178aa79b0e258265d"}, + {file = "identify-2.5.24.tar.gz", hash = "sha256:0aac67d5b4812498056d28a9a512a483f5085cc28640b02b258a59dac34301d4"}, ] [package.extras] @@ -1260,7 +1509,6 @@ license = ["ukkonen"] name = "idna" version = "3.4" description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -1268,11 +1516,21 @@ files = [ {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, ] +[[package]] +name = "imagesize" +version = "1.4.1" +description = "Getting image size from png/jpeg/jpeg2000/gif file" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, + {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, +] + [[package]] name = "import-expression" version = "1.1.4" description = "Parses a superset of Python allowing for inline module import expressions" -category = "dev" optional = false python-versions = "*" files = [ @@ -1288,14 +1546,13 @@ test = ["pytest", "pytest-cov"] [[package]] name = "importlib-metadata" -version = "6.1.0" +version = "6.7.0" description = "Read metadata from Python packages" -category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "importlib_metadata-6.1.0-py3-none-any.whl", hash = "sha256:ff80f3b5394912eb1b108fcfd444dc78b7f1f3e16b16188054bd01cb9cb86f09"}, - {file = "importlib_metadata-6.1.0.tar.gz", hash = "sha256:43ce9281e097583d758c2c708c4376371261a02c34682491a8e98352365aad20"}, + {file = "importlib_metadata-6.7.0-py3-none-any.whl", hash = "sha256:cb52082e659e97afc5dac71e79de97d8681de3aa07ff18578330904a9d18e5b5"}, + {file = "importlib_metadata-6.7.0.tar.gz", hash = "sha256:1aaf550d4f73e5d6783e7acb77aec43d49da8017410afae93822cc9cca98c4d4"}, ] [package.dependencies] @@ -1304,13 +1561,30 @@ zipp = ">=0.5" [package.extras] docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] perf = ["ipython"] -testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] +testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] + +[[package]] +name = "importlib-resources" +version = "5.12.0" +description = "Read resources from Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "importlib_resources-5.12.0-py3-none-any.whl", hash = "sha256:7b1deeebbf351c7578e09bf2f63fa2ce8b5ffec296e0d349139d43cca061a81a"}, + {file = "importlib_resources-5.12.0.tar.gz", hash = "sha256:4be82589bf5c1d7999aedf2a45159d10cb3ca4f19b2271f8792bc8e6da7b22f6"}, +] + +[package.dependencies] +zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] [[package]] name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1322,7 +1596,6 @@ files = [ name = "jinja2" version = "3.1.2" description = "A very fast and expressive template engine." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1340,7 +1613,6 @@ i18n = ["Babel (>=2.7)"] name = "jishaku" version = "2.5.1" description = "A discord.py extension including useful tools for bot development and debugging." -category = "dev" optional = false python-versions = ">=3.8.0" files = [ @@ -1364,11 +1636,87 @@ 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 = "kiwisolver" +version = "1.4.4" +description = "A fast implementation of the Cassowary constraint solver" +optional = false +python-versions = ">=3.7" +files = [ + {file = "kiwisolver-1.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2f5e60fabb7343a836360c4f0919b8cd0d6dbf08ad2ca6b9cf90bf0c76a3c4f6"}, + {file = "kiwisolver-1.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:10ee06759482c78bdb864f4109886dff7b8a56529bc1609d4f1112b93fe6423c"}, + {file = "kiwisolver-1.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c79ebe8f3676a4c6630fd3f777f3cfecf9289666c84e775a67d1d358578dc2e3"}, + {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:abbe9fa13da955feb8202e215c4018f4bb57469b1b78c7a4c5c7b93001699938"}, + {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7577c1987baa3adc4b3c62c33bd1118c3ef5c8ddef36f0f2c950ae0b199e100d"}, + {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8ad8285b01b0d4695102546b342b493b3ccc6781fc28c8c6a1bb63e95d22f09"}, + {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8ed58b8acf29798b036d347791141767ccf65eee7f26bde03a71c944449e53de"}, + {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a68b62a02953b9841730db7797422f983935aeefceb1679f0fc85cbfbd311c32"}, + {file = "kiwisolver-1.4.4-cp310-cp310-win32.whl", hash = "sha256:e92a513161077b53447160b9bd8f522edfbed4bd9759e4c18ab05d7ef7e49408"}, + {file = "kiwisolver-1.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:3fe20f63c9ecee44560d0e7f116b3a747a5d7203376abeea292ab3152334d004"}, + {file = "kiwisolver-1.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e0ea21f66820452a3f5d1655f8704a60d66ba1191359b96541eaf457710a5fc6"}, + {file = "kiwisolver-1.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bc9db8a3efb3e403e4ecc6cd9489ea2bac94244f80c78e27c31dcc00d2790ac2"}, + {file = "kiwisolver-1.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d5b61785a9ce44e5a4b880272baa7cf6c8f48a5180c3e81c59553ba0cb0821ca"}, + {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c2dbb44c3f7e6c4d3487b31037b1bdbf424d97687c1747ce4ff2895795c9bf69"}, + {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6295ecd49304dcf3bfbfa45d9a081c96509e95f4b9d0eb7ee4ec0530c4a96514"}, + {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4bd472dbe5e136f96a4b18f295d159d7f26fd399136f5b17b08c4e5f498cd494"}, + {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bf7d9fce9bcc4752ca4a1b80aabd38f6d19009ea5cbda0e0856983cf6d0023f5"}, + {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78d6601aed50c74e0ef02f4204da1816147a6d3fbdc8b3872d263338a9052c51"}, + {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:877272cf6b4b7e94c9614f9b10140e198d2186363728ed0f701c6eee1baec1da"}, + {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:db608a6757adabb32f1cfe6066e39b3706d8c3aa69bbc353a5b61edad36a5cb4"}, + {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:5853eb494c71e267912275e5586fe281444eb5e722de4e131cddf9d442615626"}, + {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:f0a1dbdb5ecbef0d34eb77e56fcb3e95bbd7e50835d9782a45df81cc46949750"}, + {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:283dffbf061a4ec60391d51e6155e372a1f7a4f5b15d59c8505339454f8989e4"}, + {file = "kiwisolver-1.4.4-cp311-cp311-win32.whl", hash = "sha256:d06adcfa62a4431d404c31216f0f8ac97397d799cd53800e9d3efc2fbb3cf14e"}, + {file = "kiwisolver-1.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:e7da3fec7408813a7cebc9e4ec55afed2d0fd65c4754bc376bf03498d4e92686"}, + {file = "kiwisolver-1.4.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:62ac9cc684da4cf1778d07a89bf5f81b35834cb96ca523d3a7fb32509380cbf6"}, + {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41dae968a94b1ef1897cb322b39360a0812661dba7c682aa45098eb8e193dbdf"}, + {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:02f79693ec433cb4b5f51694e8477ae83b3205768a6fb48ffba60549080e295b"}, + {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d0611a0a2a518464c05ddd5a3a1a0e856ccc10e67079bb17f265ad19ab3c7597"}, + {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:db5283d90da4174865d520e7366801a93777201e91e79bacbac6e6927cbceede"}, + {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1041feb4cda8708ce73bb4dcb9ce1ccf49d553bf87c3954bdfa46f0c3f77252c"}, + {file = "kiwisolver-1.4.4-cp37-cp37m-win32.whl", hash = "sha256:a553dadda40fef6bfa1456dc4be49b113aa92c2a9a9e8711e955618cd69622e3"}, + {file = "kiwisolver-1.4.4-cp37-cp37m-win_amd64.whl", hash = "sha256:03baab2d6b4a54ddbb43bba1a3a2d1627e82d205c5cf8f4c924dc49284b87166"}, + {file = "kiwisolver-1.4.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:841293b17ad704d70c578f1f0013c890e219952169ce8a24ebc063eecf775454"}, + {file = "kiwisolver-1.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f4f270de01dd3e129a72efad823da90cc4d6aafb64c410c9033aba70db9f1ff0"}, + {file = "kiwisolver-1.4.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f9f39e2f049db33a908319cf46624a569b36983c7c78318e9726a4cb8923b26c"}, + {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c97528e64cb9ebeff9701e7938653a9951922f2a38bd847787d4a8e498cc83ae"}, + {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d1573129aa0fd901076e2bfb4275a35f5b7aa60fbfb984499d661ec950320b0"}, + {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ad881edc7ccb9d65b0224f4e4d05a1e85cf62d73aab798943df6d48ab0cd79a1"}, + {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b428ef021242344340460fa4c9185d0b1f66fbdbfecc6c63eff4b7c29fad429d"}, + {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:2e407cb4bd5a13984a6c2c0fe1845e4e41e96f183e5e5cd4d77a857d9693494c"}, + {file = "kiwisolver-1.4.4-cp38-cp38-win32.whl", hash = "sha256:75facbe9606748f43428fc91a43edb46c7ff68889b91fa31f53b58894503a191"}, + {file = "kiwisolver-1.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:5bce61af018b0cb2055e0e72e7d65290d822d3feee430b7b8203d8a855e78766"}, + {file = "kiwisolver-1.4.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8c808594c88a025d4e322d5bb549282c93c8e1ba71b790f539567932722d7bd8"}, + {file = "kiwisolver-1.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f0a71d85ecdd570ded8ac3d1c0f480842f49a40beb423bb8014539a9f32a5897"}, + {file = "kiwisolver-1.4.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b533558eae785e33e8c148a8d9921692a9fe5aa516efbdff8606e7d87b9d5824"}, + {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:efda5fc8cc1c61e4f639b8067d118e742b812c930f708e6667a5ce0d13499e29"}, + {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7c43e1e1206cd421cd92e6b3280d4385d41d7166b3ed577ac20444b6995a445f"}, + {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc8d3bd6c72b2dd9decf16ce70e20abcb3274ba01b4e1c96031e0c4067d1e7cd"}, + {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4ea39b0ccc4f5d803e3337dd46bcce60b702be4d86fd0b3d7531ef10fd99a1ac"}, + {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:968f44fdbf6dd757d12920d63b566eeb4d5b395fd2d00d29d7ef00a00582aac9"}, + {file = "kiwisolver-1.4.4-cp39-cp39-win32.whl", hash = "sha256:da7e547706e69e45d95e116e6939488d62174e033b763ab1496b4c29b76fabea"}, + {file = "kiwisolver-1.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:ba59c92039ec0a66103b1d5fe588fa546373587a7d68f5c96f743c3396afc04b"}, + {file = "kiwisolver-1.4.4-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:91672bacaa030f92fc2f43b620d7b337fd9a5af28b0d6ed3f77afc43c4a64b5a"}, + {file = "kiwisolver-1.4.4-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:787518a6789009c159453da4d6b683f468ef7a65bbde796bcea803ccf191058d"}, + {file = "kiwisolver-1.4.4-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da152d8cdcab0e56e4f45eb08b9aea6455845ec83172092f09b0e077ece2cf7a"}, + {file = "kiwisolver-1.4.4-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:ecb1fa0db7bf4cff9dac752abb19505a233c7f16684c5826d1f11ebd9472b871"}, + {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:28bc5b299f48150b5f822ce68624e445040595a4ac3d59251703779836eceff9"}, + {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:81e38381b782cc7e1e46c4e14cd997ee6040768101aefc8fa3c24a4cc58e98f8"}, + {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2a66fdfb34e05b705620dd567f5a03f239a088d5a3f321e7b6ac3239d22aa286"}, + {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:872b8ca05c40d309ed13eb2e582cab0c5a05e81e987ab9c521bf05ad1d5cf5cb"}, + {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:70e7c2e7b750585569564e2e5ca9845acfaa5da56ac46df68414f29fea97be9f"}, + {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9f85003f5dfa867e86d53fac6f7e6f30c045673fa27b603c397753bebadc3008"}, + {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e307eb9bd99801f82789b44bb45e9f541961831c7311521b13a6c85afc09767"}, + {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1792d939ec70abe76f5054d3f36ed5656021dcad1322d1cc996d4e54165cef9"}, + {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6cb459eea32a4e2cf18ba5fcece2dbdf496384413bc1bae15583f19e567f3b2"}, + {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:36dafec3d6d6088d34e2de6b85f9d8e2324eb734162fba59d2ba9ed7a2043d5b"}, + {file = "kiwisolver-1.4.4.tar.gz", hash = "sha256:d41997519fcba4a1e46eb4a2fe31bc12f0ff957b2b81bac28db24744f333e955"}, +] + [[package]] name = "line-profiler" version = "4.0.3" description = "Line-by-line profiler" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1427,71 +1775,294 @@ ipython-strict = ["IPython (==0.13)", "IPython (==0.13)"] tests = ["IPython", "IPython", "coverage[toml]", "pytest", "pytest-cov", "ubelt"] tests-strict = ["IPython (==0.13)", "IPython (==0.13)", "coverage[toml] (==5.3)", "pytest (==4.6.11)", "pytest-cov (==2.10.1)", "ubelt (==1.0.1)"] +[[package]] +name = "livereload" +version = "2.6.3" +description = "Python LiveReload is an awesome tool for web developers" +optional = false +python-versions = "*" +files = [ + {file = "livereload-2.6.3-py2.py3-none-any.whl", hash = "sha256:ad4ac6f53b2d62bb6ce1a5e6e96f1f00976a32348afedcb4b6d68df2a1d346e4"}, + {file = "livereload-2.6.3.tar.gz", hash = "sha256:776f2f865e59fde56490a56bcc6773b6917366bce0c267c60ee8aaf1a0959869"}, +] + +[package.dependencies] +six = "*" +tornado = {version = "*", markers = "python_version > \"2.7\""} + +[[package]] +name = "lru-dict" +version = "1.2.0" +description = "An Dict like LRU container." +optional = false +python-versions = "*" +files = [ + {file = "lru-dict-1.2.0.tar.gz", hash = "sha256:13c56782f19d68ddf4d8db0170041192859616514c706b126d0df2ec72a11bd7"}, + {file = "lru_dict-1.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:de906e5486b5c053d15b7731583c25e3c9147c288ac8152a6d1f9bccdec72641"}, + {file = "lru_dict-1.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:604d07c7604b20b3130405d137cae61579578b0e8377daae4125098feebcb970"}, + {file = "lru_dict-1.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:203b3e78d03d88f491fa134f85a42919020686b6e6f2d09759b2f5517260c651"}, + {file = "lru_dict-1.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:020b93870f8c7195774cbd94f033b96c14f51c57537969965c3af300331724fe"}, + {file = "lru_dict-1.2.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1184d91cfebd5d1e659d47f17a60185bbf621635ca56dcdc46c6a1745d25df5c"}, + {file = "lru_dict-1.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:fc42882b554a86e564e0b662da47b8a4b32fa966920bd165e27bb8079a323bc1"}, + {file = "lru_dict-1.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:18ee88ada65bd2ffd483023be0fa1c0a6a051ef666d1cd89e921dcce134149f2"}, + {file = "lru_dict-1.2.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:756230c22257597b7557eaef7f90484c489e9ba78e5bb6ab5a5bcfb6b03cb075"}, + {file = "lru_dict-1.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c4da599af36618881748b5db457d937955bb2b4800db891647d46767d636c408"}, + {file = "lru_dict-1.2.0-cp310-cp310-win32.whl", hash = "sha256:35a142a7d1a4fd5d5799cc4f8ab2fff50a598d8cee1d1c611f50722b3e27874f"}, + {file = "lru_dict-1.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:6da5b8099766c4da3bf1ed6e7d7f5eff1681aff6b5987d1258a13bd2ed54f0c9"}, + {file = "lru_dict-1.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b20b7c9beb481e92e07368ebfaa363ed7ef61e65ffe6e0edbdbaceb33e134124"}, + {file = "lru_dict-1.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22147367b296be31cc858bf167c448af02435cac44806b228c9be8117f1bfce4"}, + {file = "lru_dict-1.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:34a3091abeb95e707f381a8b5b7dc8e4ee016316c659c49b726857b0d6d1bd7a"}, + {file = "lru_dict-1.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:877801a20f05c467126b55338a4e9fa30e2a141eb7b0b740794571b7d619ee11"}, + {file = "lru_dict-1.2.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d3336e901acec897bcd318c42c2b93d5f1d038e67688f497045fc6bad2c0be7"}, + {file = "lru_dict-1.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8dafc481d2defb381f19b22cc51837e8a42631e98e34b9e0892245cc96593deb"}, + {file = "lru_dict-1.2.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:87bbad3f5c3de8897b8c1263a9af73bbb6469fb90e7b57225dad89b8ef62cd8d"}, + {file = "lru_dict-1.2.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:25f9e0bc2fe8f41c2711ccefd2871f8a5f50a39e6293b68c3dec576112937aad"}, + {file = "lru_dict-1.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ae301c282a499dc1968dd633cfef8771dd84228ae9d40002a3ea990e4ff0c469"}, + {file = "lru_dict-1.2.0-cp311-cp311-win32.whl", hash = "sha256:c9617583173a29048e11397f165501edc5ae223504a404b2532a212a71ecc9ed"}, + {file = "lru_dict-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6b7a031e47421d4b7aa626b8c91c180a9f037f89e5d0a71c4bb7afcf4036c774"}, + {file = "lru_dict-1.2.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:ea2ac3f7a7a2f32f194c84d82a034e66780057fd908b421becd2f173504d040e"}, + {file = "lru_dict-1.2.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd46c94966f631a81ffe33eee928db58e9fbee15baba5923d284aeadc0e0fa76"}, + {file = "lru_dict-1.2.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:086ce993414f0b28530ded7e004c77dc57c5748fa6da488602aa6e7f79e6210e"}, + {file = "lru_dict-1.2.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df25a426446197488a6702954dcc1de511deee20c9db730499a2aa83fddf0df1"}, + {file = "lru_dict-1.2.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c53b12b89bd7a6c79f0536ff0d0a84fdf4ab5f6252d94b24b9b753bd9ada2ddf"}, + {file = "lru_dict-1.2.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:f9484016e6765bd295708cccc9def49f708ce07ac003808f69efa386633affb9"}, + {file = "lru_dict-1.2.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:d0f7ec902a0097ac39f1922c89be9eaccf00eb87751e28915320b4f72912d057"}, + {file = "lru_dict-1.2.0-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:981ef3edc82da38d39eb60eae225b88a538d47b90cce2e5808846fd2cf64384b"}, + {file = "lru_dict-1.2.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e25b2e90a032dc248213af7f3f3e975e1934b204f3b16aeeaeaff27a3b65e128"}, + {file = "lru_dict-1.2.0-cp36-cp36m-win32.whl", hash = "sha256:59f3df78e94e07959f17764e7fa7ca6b54e9296953d2626a112eab08e1beb2db"}, + {file = "lru_dict-1.2.0-cp36-cp36m-win_amd64.whl", hash = "sha256:de24b47159e07833aeab517d9cb1c3c5c2d6445cc378b1c2f1d8d15fb4841d63"}, + {file = "lru_dict-1.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d0dd4cd58220351233002f910e35cc01d30337696b55c6578f71318b137770f9"}, + {file = "lru_dict-1.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a87bdc291718bbdf9ea4be12ae7af26cbf0706fa62c2ac332748e3116c5510a7"}, + {file = "lru_dict-1.2.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05fb8744f91f58479cbe07ed80ada6696ec7df21ea1740891d4107a8dd99a970"}, + {file = "lru_dict-1.2.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00f6e8a3fc91481b40395316a14c94daa0f0a5de62e7e01a7d589f8d29224052"}, + {file = "lru_dict-1.2.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5b172fce0a0ffc0fa6d282c14256d5a68b5db1e64719c2915e69084c4b6bf555"}, + {file = "lru_dict-1.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:e707d93bae8f0a14e6df1ae8b0f076532b35f00e691995f33132d806a88e5c18"}, + {file = "lru_dict-1.2.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b9ec7a4a0d6b8297102aa56758434fb1fca276a82ed7362e37817407185c3abb"}, + {file = "lru_dict-1.2.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:f404dcc8172da1f28da9b1f0087009578e608a4899b96d244925c4f463201f2a"}, + {file = "lru_dict-1.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:1171ad3bff32aa8086778be4a3bdff595cc2692e78685bcce9cb06b96b22dcc2"}, + {file = "lru_dict-1.2.0-cp37-cp37m-win32.whl", hash = "sha256:0c316dfa3897fabaa1fe08aae89352a3b109e5f88b25529bc01e98ac029bf878"}, + {file = "lru_dict-1.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:5919dd04446bc1ee8d6ecda2187deeebfff5903538ae71083e069bc678599446"}, + {file = "lru_dict-1.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fbf36c5a220a85187cacc1fcb7dd87070e04b5fc28df7a43f6842f7c8224a388"}, + {file = "lru_dict-1.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:712e71b64da181e1c0a2eaa76cd860265980cd15cb0e0498602b8aa35d5db9f8"}, + {file = "lru_dict-1.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f54908bf91280a9b8fa6a8c8f3c2f65850ce6acae2852bbe292391628ebca42f"}, + {file = "lru_dict-1.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3838e33710935da2ade1dd404a8b936d571e29268a70ff4ca5ba758abb3850df"}, + {file = "lru_dict-1.2.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5d5a5f976b39af73324f2b793862859902ccb9542621856d51a5993064f25e4"}, + {file = "lru_dict-1.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8bda3a9afd241ee0181661decaae25e5336ce513ac268ab57da737eacaa7871f"}, + {file = "lru_dict-1.2.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:bd2cd1b998ea4c8c1dad829fc4fa88aeed4dee555b5e03c132fc618e6123f168"}, + {file = "lru_dict-1.2.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:b55753ee23028ba8644fd22e50de7b8f85fa60b562a0fafaad788701d6131ff8"}, + {file = "lru_dict-1.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7e51fa6a203fa91d415f3b2900e5748ec8e06ad75777c98cc3aeb3983ca416d7"}, + {file = "lru_dict-1.2.0-cp38-cp38-win32.whl", hash = "sha256:cd6806313606559e6c7adfa0dbeb30fc5ab625f00958c3d93f84831e7a32b71e"}, + {file = "lru_dict-1.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:5d90a70c53b0566084447c3ef9374cc5a9be886e867b36f89495f211baabd322"}, + {file = "lru_dict-1.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a3ea7571b6bf2090a85ff037e6593bbafe1a8598d5c3b4560eb56187bcccb4dc"}, + {file = "lru_dict-1.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:287c2115a59c1c9ed0d5d8ae7671e594b1206c36ea9df2fca6b17b86c468ff99"}, + {file = "lru_dict-1.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b5ccfd2291c93746a286c87c3f895165b697399969d24c54804ec3ec559d4e43"}, + {file = "lru_dict-1.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b710f0f4d7ec4f9fa89dfde7002f80bcd77de8024017e70706b0911ea086e2ef"}, + {file = "lru_dict-1.2.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5345bf50e127bd2767e9fd42393635bbc0146eac01f6baf6ef12c332d1a6a329"}, + {file = "lru_dict-1.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:291d13f85224551913a78fe695cde04cbca9dcb1d84c540167c443eb913603c9"}, + {file = "lru_dict-1.2.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d5bb41bc74b321789803d45b124fc2145c1b3353b4ad43296d9d1d242574969b"}, + {file = "lru_dict-1.2.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0facf49b053bf4926d92d8d5a46fe07eecd2af0441add0182c7432d53d6da667"}, + {file = "lru_dict-1.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:987b73a06bcf5a95d7dc296241c6b1f9bc6cda42586948c9dabf386dc2bef1cd"}, + {file = "lru_dict-1.2.0-cp39-cp39-win32.whl", hash = "sha256:231d7608f029dda42f9610e5723614a35b1fff035a8060cf7d2be19f1711ace8"}, + {file = "lru_dict-1.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:71da89e134747e20ed5b8ad5b4ee93fc5b31022c2b71e8176e73c5a44699061b"}, + {file = "lru_dict-1.2.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:21b3090928c7b6cec509e755cc3ab742154b33660a9b433923bd12c37c448e3e"}, + {file = "lru_dict-1.2.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaecd7085212d0aa4cd855f38b9d61803d6509731138bf798a9594745953245b"}, + {file = "lru_dict-1.2.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ead83ac59a29d6439ddff46e205ce32f8b7f71a6bd8062347f77e232825e3d0a"}, + {file = "lru_dict-1.2.0-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:312b6b2a30188586fe71358f0f33e4bac882d33f5e5019b26f084363f42f986f"}, + {file = "lru_dict-1.2.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:b30122e098c80e36d0117810d46459a46313421ce3298709170b687dc1240b02"}, + {file = "lru_dict-1.2.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f010cfad3ab10676e44dc72a813c968cd586f37b466d27cde73d1f7f1ba158c2"}, + {file = "lru_dict-1.2.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20f5f411f7751ad9a2c02e80287cedf69ae032edd321fe696e310d32dd30a1f8"}, + {file = "lru_dict-1.2.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:afdadd73304c9befaed02eb42f5f09fdc16288de0a08b32b8080f0f0f6350aa6"}, + {file = "lru_dict-1.2.0-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7ab0c10c4fa99dc9e26b04e6b62ac32d2bcaea3aad9b81ec8ce9a7aa32b7b1b"}, + {file = "lru_dict-1.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:edad398d5d402c43d2adada390dd83c74e46e020945ff4df801166047013617e"}, + {file = "lru_dict-1.2.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:91d577a11b84387013815b1ad0bb6e604558d646003b44c92b3ddf886ad0f879"}, + {file = "lru_dict-1.2.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb12f19cdf9c4f2d9aa259562e19b188ff34afab28dd9509ff32a3f1c2c29326"}, + {file = "lru_dict-1.2.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e4c85aa8844bdca3c8abac3b7f78da1531c74e9f8b3e4890c6e6d86a5a3f6c0"}, + {file = "lru_dict-1.2.0-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c6acbd097b15bead4de8e83e8a1030bb4d8257723669097eac643a301a952f0"}, + {file = "lru_dict-1.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:b6613daa851745dd22b860651de930275be9d3e9373283a2164992abacb75b62"}, +] + +[package.extras] +test = ["pytest"] + +[[package]] +name = "markdown-it-py" +version = "3.0.0" +description = "Python port of markdown-it. Markdown parsing, done right!" +optional = false +python-versions = ">=3.8" +files = [ + {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, + {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, +] + +[package.dependencies] +mdurl = ">=0.1,<1.0" + +[package.extras] +benchmarking = ["psutil", "pytest", "pytest-benchmark"] +code-style = ["pre-commit (>=3.0,<4.0)"] +compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] +linkify = ["linkify-it-py (>=1,<3)"] +plugins = ["mdit-py-plugins"] +profiling = ["gprof2dot"] +rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] + [[package]] name = "markupsafe" -version = "2.1.2" +version = "2.1.3" description = "Safely add untrusted strings to HTML/XML markup." -category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:665a36ae6f8f20a4676b53224e33d456a6f5a72657d9c83c2aa00765072f31f7"}, - {file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:340bea174e9761308703ae988e982005aedf427de816d1afe98147668cc03036"}, - {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22152d00bf4a9c7c83960521fc558f55a1adbc0631fbb00a9471e097b19d72e1"}, - {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28057e985dace2f478e042eaa15606c7efccb700797660629da387eb289b9323"}, - {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca244fa73f50a800cf8c3ebf7fd93149ec37f5cb9596aa8873ae2c1d23498601"}, - {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d9d971ec1e79906046aa3ca266de79eac42f1dbf3612a05dc9368125952bd1a1"}, - {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7e007132af78ea9df29495dbf7b5824cb71648d7133cf7848a2a5dd00d36f9ff"}, - {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7313ce6a199651c4ed9d7e4cfb4aa56fe923b1adf9af3b420ee14e6d9a73df65"}, - {file = "MarkupSafe-2.1.2-cp310-cp310-win32.whl", hash = "sha256:c4a549890a45f57f1ebf99c067a4ad0cb423a05544accaf2b065246827ed9603"}, - {file = "MarkupSafe-2.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:835fb5e38fd89328e9c81067fd642b3593c33e1e17e2fdbf77f5676abb14a156"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2ec4f2d48ae59bbb9d1f9d7efb9236ab81429a764dedca114f5fdabbc3788013"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:608e7073dfa9e38a85d38474c082d4281f4ce276ac0010224eaba11e929dd53a"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65608c35bfb8a76763f37036547f7adfd09270fbdbf96608be2bead319728fcd"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2bfb563d0211ce16b63c7cb9395d2c682a23187f54c3d79bfec33e6705473c6"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da25303d91526aac3672ee6d49a2f3db2d9502a4a60b55519feb1a4c7714e07d"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9cad97ab29dfc3f0249b483412c85c8ef4766d96cdf9dcf5a1e3caa3f3661cf1"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:085fd3201e7b12809f9e6e9bc1e5c96a368c8523fad5afb02afe3c051ae4afcc"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1bea30e9bf331f3fef67e0a3877b2288593c98a21ccb2cf29b74c581a4eb3af0"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-win32.whl", hash = "sha256:7df70907e00c970c60b9ef2938d894a9381f38e6b9db73c5be35e59d92e06625"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:e55e40ff0cc8cc5c07996915ad367fa47da6b3fc091fdadca7f5403239c5fec3"}, - {file = "MarkupSafe-2.1.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a6e40afa7f45939ca356f348c8e23048e02cb109ced1eb8420961b2f40fb373a"}, - {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf877ab4ed6e302ec1d04952ca358b381a882fbd9d1b07cccbfd61783561f98a"}, - {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63ba06c9941e46fa389d389644e2d8225e0e3e5ebcc4ff1ea8506dce646f8c8a"}, - {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1cd098434e83e656abf198f103a8207a8187c0fc110306691a2e94a78d0abb2"}, - {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:55f44b440d491028addb3b88f72207d71eeebfb7b5dbf0643f7c023ae1fba619"}, - {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a6f2fcca746e8d5910e18782f976489939d54a91f9411c32051b4aab2bd7c513"}, - {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0b462104ba25f1ac006fdab8b6a01ebbfbce9ed37fd37fd4acd70c67c973e460"}, - {file = "MarkupSafe-2.1.2-cp37-cp37m-win32.whl", hash = "sha256:7668b52e102d0ed87cb082380a7e2e1e78737ddecdde129acadb0eccc5423859"}, - {file = "MarkupSafe-2.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6d6607f98fcf17e534162f0709aaad3ab7a96032723d8ac8750ffe17ae5a0666"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a806db027852538d2ad7555b203300173dd1b77ba116de92da9afbc3a3be3eed"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a4abaec6ca3ad8660690236d11bfe28dfd707778e2442b45addd2f086d6ef094"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f03a532d7dee1bed20bc4884194a16160a2de9ffc6354b3878ec9682bb623c54"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4cf06cdc1dda95223e9d2d3c58d3b178aa5dacb35ee7e3bbac10e4e1faacb419"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22731d79ed2eb25059ae3df1dfc9cb1546691cc41f4e3130fe6bfbc3ecbbecfa"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f8ffb705ffcf5ddd0e80b65ddf7bed7ee4f5a441ea7d3419e861a12eaf41af58"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8db032bf0ce9022a8e41a22598eefc802314e81b879ae093f36ce9ddf39ab1ba"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2298c859cfc5463f1b64bd55cb3e602528db6fa0f3cfd568d3605c50678f8f03"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-win32.whl", hash = "sha256:50c42830a633fa0cf9e7d27664637532791bfc31c731a87b202d2d8ac40c3ea2"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:bb06feb762bade6bf3c8b844462274db0c76acc95c52abe8dbed28ae3d44a147"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:99625a92da8229df6d44335e6fcc558a5037dd0a760e11d84be2260e6f37002f"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8bca7e26c1dd751236cfb0c6c72d4ad61d986e9a41bbf76cb445f69488b2a2bd"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40dfd3fefbef579ee058f139733ac336312663c6706d1163b82b3003fb1925c4"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:090376d812fb6ac5f171e5938e82e7f2d7adc2b629101cec0db8b267815c85e2"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2e7821bffe00aa6bd07a23913b7f4e01328c3d5cc0b40b36c0bd81d362faeb65"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c0a33bc9f02c2b17c3ea382f91b4db0e6cde90b63b296422a939886a7a80de1c"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b8526c6d437855442cdd3d87eede9c425c4445ea011ca38d937db299382e6fa3"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-win32.whl", hash = "sha256:137678c63c977754abe9086a3ec011e8fd985ab90631145dfb9294ad09c102a7"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:0576fe974b40a400449768941d5d0858cc624e3249dfd1e0c33674e5c7ca7aed"}, - {file = "MarkupSafe-2.1.2.tar.gz", hash = "sha256:abcabc8c2b26036d62d4c746381a6f7cf60aafcc653198ad678306986b09450d"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, + {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, +] + +[[package]] +name = "matplotlib" +version = "3.7.1" +description = "Python plotting package" +optional = false +python-versions = ">=3.8" +files = [ + {file = "matplotlib-3.7.1-cp310-cp310-macosx_10_12_universal2.whl", hash = "sha256:95cbc13c1fc6844ab8812a525bbc237fa1470863ff3dace7352e910519e194b1"}, + {file = "matplotlib-3.7.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:08308bae9e91aca1ec6fd6dda66237eef9f6294ddb17f0d0b3c863169bf82353"}, + {file = "matplotlib-3.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:544764ba51900da4639c0f983b323d288f94f65f4024dc40ecb1542d74dc0500"}, + {file = "matplotlib-3.7.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56d94989191de3fcc4e002f93f7f1be5da476385dde410ddafbb70686acf00ea"}, + {file = "matplotlib-3.7.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e99bc9e65901bb9a7ce5e7bb24af03675cbd7c70b30ac670aa263240635999a4"}, + {file = "matplotlib-3.7.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb7d248c34a341cd4c31a06fd34d64306624c8cd8d0def7abb08792a5abfd556"}, + {file = "matplotlib-3.7.1-cp310-cp310-win32.whl", hash = "sha256:ce463ce590f3825b52e9fe5c19a3c6a69fd7675a39d589e8b5fbe772272b3a24"}, + {file = "matplotlib-3.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:3d7bc90727351fb841e4d8ae620d2d86d8ed92b50473cd2b42ce9186104ecbba"}, + {file = "matplotlib-3.7.1-cp311-cp311-macosx_10_12_universal2.whl", hash = "sha256:770a205966d641627fd5cf9d3cb4b6280a716522cd36b8b284a8eb1581310f61"}, + {file = "matplotlib-3.7.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:f67bfdb83a8232cb7a92b869f9355d677bce24485c460b19d01970b64b2ed476"}, + {file = "matplotlib-3.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2bf092f9210e105f414a043b92af583c98f50050559616930d884387d0772aba"}, + {file = "matplotlib-3.7.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89768d84187f31717349c6bfadc0e0d8c321e8eb34522acec8a67b1236a66332"}, + {file = "matplotlib-3.7.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83111e6388dec67822e2534e13b243cc644c7494a4bb60584edbff91585a83c6"}, + {file = "matplotlib-3.7.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a867bf73a7eb808ef2afbca03bcdb785dae09595fbe550e1bab0cd023eba3de0"}, + {file = "matplotlib-3.7.1-cp311-cp311-win32.whl", hash = "sha256:fbdeeb58c0cf0595efe89c05c224e0a502d1aa6a8696e68a73c3efc6bc354304"}, + {file = "matplotlib-3.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:c0bd19c72ae53e6ab979f0ac6a3fafceb02d2ecafa023c5cca47acd934d10be7"}, + {file = "matplotlib-3.7.1-cp38-cp38-macosx_10_12_universal2.whl", hash = "sha256:6eb88d87cb2c49af00d3bbc33a003f89fd9f78d318848da029383bfc08ecfbfb"}, + {file = "matplotlib-3.7.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:cf0e4f727534b7b1457898c4f4ae838af1ef87c359b76dcd5330fa31893a3ac7"}, + {file = "matplotlib-3.7.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:46a561d23b91f30bccfd25429c3c706afe7d73a5cc64ef2dfaf2b2ac47c1a5dc"}, + {file = "matplotlib-3.7.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8704726d33e9aa8a6d5215044b8d00804561971163563e6e6591f9dcf64340cc"}, + {file = "matplotlib-3.7.1-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4cf327e98ecf08fcbb82685acaf1939d3338548620ab8dfa02828706402c34de"}, + {file = "matplotlib-3.7.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:617f14ae9d53292ece33f45cba8503494ee199a75b44de7717964f70637a36aa"}, + {file = "matplotlib-3.7.1-cp38-cp38-win32.whl", hash = "sha256:7c9a4b2da6fac77bcc41b1ea95fadb314e92508bf5493ceff058e727e7ecf5b0"}, + {file = "matplotlib-3.7.1-cp38-cp38-win_amd64.whl", hash = "sha256:14645aad967684e92fc349493fa10c08a6da514b3d03a5931a1bac26e6792bd1"}, + {file = "matplotlib-3.7.1-cp39-cp39-macosx_10_12_universal2.whl", hash = "sha256:81a6b377ea444336538638d31fdb39af6be1a043ca5e343fe18d0f17e098770b"}, + {file = "matplotlib-3.7.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:28506a03bd7f3fe59cd3cd4ceb2a8d8a2b1db41afede01f66c42561b9be7b4b7"}, + {file = "matplotlib-3.7.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8c587963b85ce41e0a8af53b9b2de8dddbf5ece4c34553f7bd9d066148dc719c"}, + {file = "matplotlib-3.7.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8bf26ade3ff0f27668989d98c8435ce9327d24cffb7f07d24ef609e33d582439"}, + {file = "matplotlib-3.7.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:def58098f96a05f90af7e92fd127d21a287068202aa43b2a93476170ebd99e87"}, + {file = "matplotlib-3.7.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f883a22a56a84dba3b588696a2b8a1ab0d2c3d41be53264115c71b0a942d8fdb"}, + {file = "matplotlib-3.7.1-cp39-cp39-win32.whl", hash = "sha256:4f99e1b234c30c1e9714610eb0c6d2f11809c9c78c984a613ae539ea2ad2eb4b"}, + {file = "matplotlib-3.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:3ba2af245e36990facf67fde840a760128ddd71210b2ab6406e640188d69d136"}, + {file = "matplotlib-3.7.1-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3032884084f541163f295db8a6536e0abb0db464008fadca6c98aaf84ccf4717"}, + {file = "matplotlib-3.7.1-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3a2cb34336110e0ed8bb4f650e817eed61fa064acbefeb3591f1b33e3a84fd96"}, + {file = "matplotlib-3.7.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b867e2f952ed592237a1828f027d332d8ee219ad722345b79a001f49df0936eb"}, + {file = "matplotlib-3.7.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:57bfb8c8ea253be947ccb2bc2d1bb3862c2bccc662ad1b4626e1f5e004557042"}, + {file = "matplotlib-3.7.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:438196cdf5dc8d39b50a45cb6e3f6274edbcf2254f85fa9b895bf85851c3a613"}, + {file = "matplotlib-3.7.1-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:21e9cff1a58d42e74d01153360de92b326708fb205250150018a52c70f43c290"}, + {file = "matplotlib-3.7.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75d4725d70b7c03e082bbb8a34639ede17f333d7247f56caceb3801cb6ff703d"}, + {file = "matplotlib-3.7.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:97cc368a7268141afb5690760921765ed34867ffb9655dd325ed207af85c7529"}, + {file = "matplotlib-3.7.1.tar.gz", hash = "sha256:7b73305f25eab4541bd7ee0b96d87e53ae9c9f1823be5659b806cd85786fe882"}, +] + +[package.dependencies] +contourpy = ">=1.0.1" +cycler = ">=0.10" +fonttools = ">=4.22.0" +importlib-resources = {version = ">=3.2.0", markers = "python_version < \"3.10\""} +kiwisolver = ">=1.0.1" +numpy = ">=1.20" +packaging = ">=20.0" +pillow = ">=6.2.0" +pyparsing = ">=2.3.1" +python-dateutil = ">=2.7" + +[[package]] +name = "mdit-py-plugins" +version = "0.4.0" +description = "Collection of plugins for markdown-it-py" +optional = false +python-versions = ">=3.8" +files = [ + {file = "mdit_py_plugins-0.4.0-py3-none-any.whl", hash = "sha256:b51b3bb70691f57f974e257e367107857a93b36f322a9e6d44ca5bf28ec2def9"}, + {file = "mdit_py_plugins-0.4.0.tar.gz", hash = "sha256:d8ab27e9aed6c38aa716819fedfde15ca275715955f8a185a8e1cf90fb1d2c1b"}, +] + +[package.dependencies] +markdown-it-py = ">=1.0.0,<4.0.0" + +[package.extras] +code-style = ["pre-commit"] +rtd = ["myst-parser", "sphinx-book-theme"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] + +[[package]] +name = "mdurl" +version = "0.1.2" +description = "Markdown URL utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, + {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, ] [[package]] name = "multidict" version = "6.0.4" description = "multidict implementation" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1571,16 +2142,41 @@ files = [ {file = "multidict-6.0.4.tar.gz", hash = "sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49"}, ] +[[package]] +name = "myst-parser" +version = "2.0.0" +description = "An extended [CommonMark](https://spec.commonmark.org/) compliant parser," +optional = false +python-versions = ">=3.8" +files = [ + {file = "myst_parser-2.0.0-py3-none-any.whl", hash = "sha256:7c36344ae39c8e740dad7fdabf5aa6fc4897a813083c6cc9990044eb93656b14"}, + {file = "myst_parser-2.0.0.tar.gz", hash = "sha256:ea929a67a6a0b1683cdbe19b8d2e724cd7643f8aa3e7bb18dd65beac3483bead"}, +] + +[package.dependencies] +docutils = ">=0.16,<0.21" +jinja2 = "*" +markdown-it-py = ">=3.0,<4.0" +mdit-py-plugins = ">=0.4,<1.0" +pyyaml = "*" +sphinx = ">=6,<8" + +[package.extras] +code-style = ["pre-commit (>=3.0,<4.0)"] +linkify = ["linkify-it-py (>=2.0,<3.0)"] +rtd = ["ipython", "pydata-sphinx-theme (==v0.13.0rc4)", "sphinx-autodoc2 (>=0.4.2,<0.5.0)", "sphinx-book-theme (==1.0.0rc2)", "sphinx-copybutton", "sphinx-design2", "sphinx-pyscript", "sphinx-tippy (>=0.3.1)", "sphinx-togglebutton", "sphinxext-opengraph (>=0.8.2,<0.9.0)", "sphinxext-rediraffe (>=0.2.7,<0.3.0)"] +testing = ["beautifulsoup4", "coverage[toml]", "pytest (>=7,<8)", "pytest-cov", "pytest-param-files (>=0.3.4,<0.4.0)", "pytest-regressions", "sphinx-pytest"] +testing-docutils = ["pygments", "pytest (>=7,<8)", "pytest-param-files (>=0.3.4,<0.4.0)"] + [[package]] name = "nodeenv" -version = "1.7.0" +version = "1.8.0" description = "Node.js virtual environment builder" -category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" files = [ - {file = "nodeenv-1.7.0-py2.py3-none-any.whl", hash = "sha256:27083a7b96a25f2f5e1d8cb4b6317ee8aeda3bdd121394e5ac54e498028a042e"}, - {file = "nodeenv-1.7.0.tar.gz", hash = "sha256:e0e7f7dfb85fc5394c6fe1e8fa98131a2473e04311a45afb6508f7cf1836fa2b"}, + {file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"}, + {file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"}, ] [package.dependencies] @@ -1588,35 +2184,33 @@ setuptools = "*" [[package]] name = "nox" -version = "2022.11.21" +version = "2023.4.22" description = "Flexible test automation." -category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "nox-2022.11.21-py3-none-any.whl", hash = "sha256:0e41a990e290e274cb205a976c4c97ee3c5234441a8132c8c3fd9ea3c22149eb"}, - {file = "nox-2022.11.21.tar.gz", hash = "sha256:e21c31de0711d1274ca585a2c5fde36b1aa962005ba8e9322bf5eeed16dcd684"}, + {file = "nox-2023.4.22-py3-none-any.whl", hash = "sha256:0b1adc619c58ab4fa57d6ab2e7823fe47a32e70202f287d78474adcc7bda1891"}, + {file = "nox-2023.4.22.tar.gz", hash = "sha256:46c0560b0dc609d7d967dc99e22cb463d3c4caf54a5fda735d6c11b5177e3a9f"}, ] [package.dependencies] -argcomplete = ">=1.9.4,<3.0" +argcomplete = ">=1.9.4,<4.0" colorlog = ">=2.6.1,<7.0.0" packaging = ">=20.9" virtualenv = ">=14" [package.extras] -tox-to-nox = ["jinja2", "tox"] +tox-to-nox = ["jinja2", "tox (<4)"] [[package]] name = "nox-poetry" -version = "1.0.2" +version = "1.0.3" description = "nox-poetry" -category = "dev" optional = false python-versions = ">=3.7,<4.0" files = [ - {file = "nox-poetry-1.0.2.tar.gz", hash = "sha256:22bc397979393a0283f5161af708a3a430e1c7e0cc2be274c7b27e9e46de0412"}, - {file = "nox_poetry-1.0.2-py3-none-any.whl", hash = "sha256:a53c36eccbd67f15b5b83dd6562d077dd326c71fd4a942528d8b2299c417dbbe"}, + {file = "nox_poetry-1.0.3-py3-none-any.whl", hash = "sha256:a2fffeb70ae81840479e68287afe1c772bf376f70f1e92f99832a20b3c64d064"}, + {file = "nox_poetry-1.0.3.tar.gz", hash = "sha256:dc7ecbbd812a333a0c0b558f57e5b37f7c12926cddbcecaf2264957fd373824e"}, ] [package.dependencies] @@ -1624,103 +2218,212 @@ nox = ">=2020.8.22" packaging = ">=20.9" tomlkit = ">=0.7" +[[package]] +name = "numpy" +version = "1.24.4" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "numpy-1.24.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c0bfb52d2169d58c1cdb8cc1f16989101639b34c7d3ce60ed70b19c63eba0b64"}, + {file = "numpy-1.24.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ed094d4f0c177b1b8e7aa9cba7d6ceed51c0e569a5318ac0ca9a090680a6a1b1"}, + {file = "numpy-1.24.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79fc682a374c4a8ed08b331bef9c5f582585d1048fa6d80bc6c35bc384eee9b4"}, + {file = "numpy-1.24.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ffe43c74893dbf38c2b0a1f5428760a1a9c98285553c89e12d70a96a7f3a4d6"}, + {file = "numpy-1.24.4-cp310-cp310-win32.whl", hash = "sha256:4c21decb6ea94057331e111a5bed9a79d335658c27ce2adb580fb4d54f2ad9bc"}, + {file = "numpy-1.24.4-cp310-cp310-win_amd64.whl", hash = "sha256:b4bea75e47d9586d31e892a7401f76e909712a0fd510f58f5337bea9572c571e"}, + {file = "numpy-1.24.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f136bab9c2cfd8da131132c2cf6cc27331dd6fae65f95f69dcd4ae3c3639c810"}, + {file = "numpy-1.24.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2926dac25b313635e4d6cf4dc4e51c8c0ebfed60b801c799ffc4c32bf3d1254"}, + {file = "numpy-1.24.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:222e40d0e2548690405b0b3c7b21d1169117391c2e82c378467ef9ab4c8f0da7"}, + {file = "numpy-1.24.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7215847ce88a85ce39baf9e89070cb860c98fdddacbaa6c0da3ffb31b3350bd5"}, + {file = "numpy-1.24.4-cp311-cp311-win32.whl", hash = "sha256:4979217d7de511a8d57f4b4b5b2b965f707768440c17cb70fbf254c4b225238d"}, + {file = "numpy-1.24.4-cp311-cp311-win_amd64.whl", hash = "sha256:b7b1fc9864d7d39e28f41d089bfd6353cb5f27ecd9905348c24187a768c79694"}, + {file = "numpy-1.24.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1452241c290f3e2a312c137a9999cdbf63f78864d63c79039bda65ee86943f61"}, + {file = "numpy-1.24.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:04640dab83f7c6c85abf9cd729c5b65f1ebd0ccf9de90b270cd61935eef0197f"}, + {file = "numpy-1.24.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5425b114831d1e77e4b5d812b69d11d962e104095a5b9c3b641a218abcc050e"}, + {file = "numpy-1.24.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd80e219fd4c71fc3699fc1dadac5dcf4fd882bfc6f7ec53d30fa197b8ee22dc"}, + {file = "numpy-1.24.4-cp38-cp38-win32.whl", hash = "sha256:4602244f345453db537be5314d3983dbf5834a9701b7723ec28923e2889e0bb2"}, + {file = "numpy-1.24.4-cp38-cp38-win_amd64.whl", hash = "sha256:692f2e0f55794943c5bfff12b3f56f99af76f902fc47487bdfe97856de51a706"}, + {file = "numpy-1.24.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2541312fbf09977f3b3ad449c4e5f4bb55d0dbf79226d7724211acc905049400"}, + {file = "numpy-1.24.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9667575fb6d13c95f1b36aca12c5ee3356bf001b714fc354eb5465ce1609e62f"}, + {file = "numpy-1.24.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3a86ed21e4f87050382c7bc96571755193c4c1392490744ac73d660e8f564a9"}, + {file = "numpy-1.24.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d11efb4dbecbdf22508d55e48d9c8384db795e1b7b51ea735289ff96613ff74d"}, + {file = "numpy-1.24.4-cp39-cp39-win32.whl", hash = "sha256:6620c0acd41dbcb368610bb2f4d83145674040025e5536954782467100aa8835"}, + {file = "numpy-1.24.4-cp39-cp39-win_amd64.whl", hash = "sha256:befe2bf740fd8373cf56149a5c23a0f601e82869598d41f8e188a0e9869926f8"}, + {file = "numpy-1.24.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:31f13e25b4e304632a4619d0e0777662c2ffea99fcae2029556b17d8ff958aef"}, + {file = "numpy-1.24.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95f7ac6540e95bc440ad77f56e520da5bf877f87dca58bd095288dce8940532a"}, + {file = "numpy-1.24.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e98f220aa76ca2a977fe435f5b04d7b3470c0a2e6312907b37ba6068f26787f2"}, + {file = "numpy-1.24.4.tar.gz", hash = "sha256:80f5e3a4e498641401868df4208b74581206afbee7cf7b8329daae82676d9463"}, +] + [[package]] name = "orjson" -version = "3.8.9" +version = "3.9.2" description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" -category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "orjson-3.8.9-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:5d029843eae7b6cbd6468b63517b8b61471afed6572162171d8b6471b6dbf41f"}, - {file = "orjson-3.8.9-cp310-cp310-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:405933c05490efb209d0f940d8ef1403d2932a97e47010a26d2694e9dd49f84d"}, - {file = "orjson-3.8.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:183de66eff4d41c330a3006f210ab0bce7affe398da6f6eda9579b67245a34ff"}, - {file = "orjson-3.8.9-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb4081fe340ed1df42dddfd055e1d50479cb0ccb976d13e6b5e8667a07fec6f4"}, - {file = "orjson-3.8.9-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d11593a2e736055dd7b9587dbf89cd1cbe4a42a70e70f186e51aee7e1b38902e"}, - {file = "orjson-3.8.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e20649359e28f34d01b2570e4650a076f439a959bae3a8bbe7f5923ad80f54e8"}, - {file = "orjson-3.8.9-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:c02ece4f36a160c83efe74adfba5f189c7c7702361f02b809ab73744923ee139"}, - {file = "orjson-3.8.9-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f0e19801836cf1b30f333d475b05d79051b8ae8639a8e2422fb5f64e82676ae7"}, - {file = "orjson-3.8.9-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d4850fe5650cead3c0f8822192e381cee9d4c3b8162eb082c86c927124572dc6"}, - {file = "orjson-3.8.9-cp310-none-win_amd64.whl", hash = "sha256:5fd4193f260d9d30112b5e379d0870b54dc88040807c93cbe8d67bfea148ba5a"}, - {file = "orjson-3.8.9-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:70eae063ad8d7405dc63873760567b600fc10728ba0da24a69d49c1a5d318d6d"}, - {file = "orjson-3.8.9-cp311-cp311-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:251653437632583d02203e6b118b72b99c04425175853f35340f4bac7034a36e"}, - {file = "orjson-3.8.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ea833751f017ba321c277e7425b51c0b1a18a2c60f8c9c0f4c6c4d7e16cbd6c"}, - {file = "orjson-3.8.9-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8563c2cdeb923b82a5cc5bfc76c28c786777428263ee39292d928e9687165fb4"}, - {file = "orjson-3.8.9-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6f33e9ea45b4c9457eedca0c40f38cf5732c91b0fb68f091ac59e6ea68e03eb2"}, - {file = "orjson-3.8.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:855dee152daecb7de7b4cd7069d7854e11aa291687bffe8433156af0a224417e"}, - {file = "orjson-3.8.9-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:74fa9e02589339defc9d3662de9e7eef51d8f9f3a7f6304b43b18b39d7bbf10f"}, - {file = "orjson-3.8.9-cp311-none-win_amd64.whl", hash = "sha256:6c5b10ba1e62df8f96cbc37f6d5ae9acb3f6475926dea8b1b6a1a60f201a64f7"}, - {file = "orjson-3.8.9-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:a651123d01bc399fcd866e56acc2d76512e62aae3673652b13b470ea69faf1f4"}, - {file = "orjson-3.8.9-cp37-cp37m-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:73019b6d2cc998c99556020c6bd8f8bc28420c69583186ca290c66a27916a3b7"}, - {file = "orjson-3.8.9-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f5c3daa8b02786ad5f0e14ae16a59bbb4e02cbae3a41989a25188e5a6c962ff"}, - {file = "orjson-3.8.9-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:598598b7f81f8fda7c3e09c88165f844152b7be223bc4ea929ec8ad59b00ea17"}, - {file = "orjson-3.8.9-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:090b10bdb06baae6d5cd3550d772ecbabd833bfceed7592ff167c0a82f5b4c20"}, - {file = "orjson-3.8.9-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd46f688ddf9c2ea10367446fe9bf3ceba0f7490c15b4f96420491c7f00bb283"}, - {file = "orjson-3.8.9-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:b8ed8d780e9fab01bc404a70d755a8b2b34ea6c0b6604b65de135daaaadaf9a9"}, - {file = "orjson-3.8.9-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:8a32c9fb742868a34346f3c52e12d893a9d27f8e0c0bf3c480db7e6903d8be28"}, - {file = "orjson-3.8.9-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2ba366009b98ac8899e935eff6fef7672d3ea43d3ce9deb3ee33452134b6cc3a"}, - {file = "orjson-3.8.9-cp37-none-win_amd64.whl", hash = "sha256:236b9313425cb2570626c64dd5cb6caff13882d1717d491da542cff228b96e97"}, - {file = "orjson-3.8.9-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:e8efc7e9ec35336f7cc98b6692536b1262046ff1d2a545295a4d89b8a2495903"}, - {file = "orjson-3.8.9-cp38-cp38-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:8c7eba3610ae69f4aba4032ecb61b0a6fbd1e4537283d1553eb8c1cb136e9118"}, - {file = "orjson-3.8.9-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7742649e4c357d4e7ad483a35ff5f55d519e895de56772cc486913614ee7d23b"}, - {file = "orjson-3.8.9-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b6566fb8daa538c7848fd6822e2409a7e1c41dae8e65e6536598d505f641a318"}, - {file = "orjson-3.8.9-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0ce8a2a667221e2e5160021e26b09e9c13eeedafb5cda1981340c8c0c0bc8f9d"}, - {file = "orjson-3.8.9-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c0399631b88fa4868956badef2561fba07dffcaf050bf53959ee50d26edf6f6"}, - {file = "orjson-3.8.9-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:189ccb16ed140a824d133fa1c55175cf0d2207edaade54f1db0456a526cb5fd8"}, - {file = "orjson-3.8.9-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b707fa4481e1af19b3052ec9352c688bad3f539d7bdd8aa4a451f6dd7e4bae73"}, - {file = "orjson-3.8.9-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c3d988eb562da1dda7d49e9abd8a64b3cabc632b4299d177fb9e0c0ca9f06b8c"}, - {file = "orjson-3.8.9-cp38-none-win_amd64.whl", hash = "sha256:b30240eb6b22daab604f1595f6aacf92bcdac0d29e2d7ad507dfac68d2b39182"}, - {file = "orjson-3.8.9-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:81869a6de00bc676d10056fa8bb28cbe805b1cf498a45c14cb7b1765eee33fcb"}, - {file = "orjson-3.8.9-cp39-cp39-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:a25a5a215b19d414de8d416a3c5414f29165843a06f704cc0345ded9eac34ac1"}, - {file = "orjson-3.8.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dec0f2bea52e30ea98ce095f1f42da04535791f9a31b2aab2499caa88307bc49"}, - {file = "orjson-3.8.9-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7b91d88fe96b698b28bb1b95b1fce226f72757ab3ab7d8d97551e23bc629c84f"}, - {file = "orjson-3.8.9-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7629841ccdcccd3c43ebc6a4165abe9844909fcedb2041994c0153470f610801"}, - {file = "orjson-3.8.9-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d875b304e19f4b2758d233bbf2b9d627c66fac50b3150b8d31a35ba6cda3db67"}, - {file = "orjson-3.8.9-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:723ec880c5290fe4de330febb8030e57c1978fbd624fc5b9399969e7d7d74984"}, - {file = "orjson-3.8.9-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b11f8a71c82d19fce11ce487efeec2ca0dc3bcf5b4564445fecfc68d9c268744"}, - {file = "orjson-3.8.9-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b2079bf86dec62731c1b90fdfea3211f993f0c894d9261e0ce9b68ed9c9dfbec"}, - {file = "orjson-3.8.9-cp39-none-win_amd64.whl", hash = "sha256:97d94322a2eaab767ba8d52f6bf9d0ec0f35313fe36287be6e6085dd65d55d37"}, - {file = "orjson-3.8.9.tar.gz", hash = "sha256:c40bece58c11cb09aff17424d21b41f6f767d2b1252b2f745ec3ff29cce6a240"}, + {file = "orjson-3.9.2-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:7323e4ca8322b1ecb87562f1ec2491831c086d9faa9a6c6503f489dadbed37d7"}, + {file = "orjson-3.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1272688ea1865f711b01ba479dea2d53e037ea00892fd04196b5875f7021d9d3"}, + {file = "orjson-3.9.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0b9a26f1d1427a9101a1e8910f2e2df1f44d3d18ad5480ba031b15d5c1cb282e"}, + {file = "orjson-3.9.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6a5ca55b0d8f25f18b471e34abaee4b175924b6cd62f59992945b25963443141"}, + {file = "orjson-3.9.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:877872db2c0f41fbe21f852ff642ca842a43bc34895b70f71c9d575df31fffb4"}, + {file = "orjson-3.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a39c2529d75373b7167bf84c814ef9b8f3737a339c225ed6c0df40736df8748"}, + {file = "orjson-3.9.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:84ebd6fdf138eb0eb4280045442331ee71c0aab5e16397ba6645f32f911bfb37"}, + {file = "orjson-3.9.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5a60a1cfcfe310547a1946506dd4f1ed0a7d5bd5b02c8697d9d5dcd8d2e9245e"}, + {file = "orjson-3.9.2-cp310-none-win_amd64.whl", hash = "sha256:c290c4f81e8fd0c1683638802c11610b2f722b540f8e5e858b6914b495cf90c8"}, + {file = "orjson-3.9.2-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:02ef014f9a605e84b675060785e37ec9c0d2347a04f1307a9d6840ab8ecd6f55"}, + {file = "orjson-3.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:992af54265ada1c1579500d6594ed73fe333e726de70d64919cf37f93defdd06"}, + {file = "orjson-3.9.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a40958f7af7c6d992ee67b2da4098dca8b770fc3b4b3834d540477788bfa76d3"}, + {file = "orjson-3.9.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:93864dec3e3dd058a2dbe488d11ac0345214a6a12697f53a63e34de7d28d4257"}, + {file = "orjson-3.9.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16fdf5a82df80c544c3c91516ab3882cd1ac4f1f84eefeafa642e05cef5f6699"}, + {file = "orjson-3.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:275b5a18fd9ed60b2720543d3ddac170051c43d680e47d04ff5203d2c6d8ebf1"}, + {file = "orjson-3.9.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b9aea6dcb99fcbc9f6d1dd84fca92322fda261da7fb014514bb4689c7c2097a8"}, + {file = "orjson-3.9.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7d74ae0e101d17c22ef67b741ba356ab896fc0fa64b301c2bf2bb0a4d874b190"}, + {file = "orjson-3.9.2-cp311-none-win_amd64.whl", hash = "sha256:6320b28e7bdb58c3a3a5efffe04b9edad3318d82409e84670a9b24e8035a249d"}, + {file = "orjson-3.9.2-cp37-cp37m-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:368e9cc91ecb7ac21f2aa475e1901204110cf3e714e98649c2502227d248f947"}, + {file = "orjson-3.9.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58e9e70f0dcd6a802c35887f306b555ff7a214840aad7de24901fc8bd9cf5dde"}, + {file = "orjson-3.9.2-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:00c983896c2e01c94c0ef72fd7373b2aa06d0c0eed0342c4884559f812a6835b"}, + {file = "orjson-3.9.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ee743e8890b16c87a2f89733f983370672272b61ee77429c0a5899b2c98c1a7"}, + {file = "orjson-3.9.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7b065942d362aad4818ff599d2f104c35a565c2cbcbab8c09ec49edba91da75"}, + {file = "orjson-3.9.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e46e9c5b404bb9e41d5555762fd410d5466b7eb1ec170ad1b1609cbebe71df21"}, + {file = "orjson-3.9.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:8170157288714678ffd64f5de33039e1164a73fd8b6be40a8a273f80093f5c4f"}, + {file = "orjson-3.9.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e3e2f087161947dafe8319ea2cfcb9cea4bb9d2172ecc60ac3c9738f72ef2909"}, + {file = "orjson-3.9.2-cp37-none-win_amd64.whl", hash = "sha256:d7de3dbbe74109ae598692113cec327fd30c5a30ebca819b21dfa4052f7b08ef"}, + {file = "orjson-3.9.2-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:8cd4385c59bbc1433cad4a80aca65d2d9039646a9c57f8084897549b55913b17"}, + {file = "orjson-3.9.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a74036aab1a80c361039290cdbc51aa7adc7ea13f56e5ef94e9be536abd227bd"}, + {file = "orjson-3.9.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1aaa46d7d4ae55335f635eadc9be0bd9bcf742e6757209fc6dc697e390010adc"}, + {file = "orjson-3.9.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e52c67ed6bb368083aa2078ea3ccbd9721920b93d4b06c43eb4e20c4c860046"}, + {file = "orjson-3.9.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a6cdfcf9c7dd4026b2b01fdff56986251dc0cc1e980c690c79eec3ae07b36e7"}, + {file = "orjson-3.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1882a70bb69595b9ec5aac0040a819e94d2833fe54901e2b32f5e734bc259a8b"}, + {file = "orjson-3.9.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:fc05e060d452145ab3c0b5420769e7356050ea311fc03cb9d79c481982917cca"}, + {file = "orjson-3.9.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f8bc2c40d9bb26efefb10949d261a47ca196772c308babc538dd9f4b73e8d386"}, + {file = "orjson-3.9.2-cp38-none-win_amd64.whl", hash = "sha256:3164fc20a585ec30a9aff33ad5de3b20ce85702b2b2a456852c413e3f0d7ab09"}, + {file = "orjson-3.9.2-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:7a6ccadf788531595ed4728aa746bc271955448d2460ff0ef8e21eb3f2a281ba"}, + {file = "orjson-3.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3245d230370f571c945f69aab823c279a868dc877352817e22e551de155cb06c"}, + {file = "orjson-3.9.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:205925b179550a4ee39b8418dd4c94ad6b777d165d7d22614771c771d44f57bd"}, + {file = "orjson-3.9.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0325fe2d69512187761f7368c8cda1959bcb75fc56b8e7a884e9569112320e57"}, + {file = "orjson-3.9.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:806704cd58708acc66a064a9a58e3be25cf1c3f9f159e8757bd3f515bfabdfa1"}, + {file = "orjson-3.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03fb36f187a0c19ff38f6289418863df8b9b7880cdbe279e920bef3a09d8dab1"}, + {file = "orjson-3.9.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:20925d07a97c49c6305bff1635318d9fc1804aa4ccacb5fb0deb8a910e57d97a"}, + {file = "orjson-3.9.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:eebfed53bec5674e981ebe8ed2cf00b3f7bcda62d634733ff779c264307ea505"}, + {file = "orjson-3.9.2-cp39-none-win_amd64.whl", hash = "sha256:869b961df5fcedf6c79f4096119b35679b63272362e9b745e668f0391a892d39"}, + {file = "orjson-3.9.2.tar.gz", hash = "sha256:24257c8f641979bf25ecd3e27251b5cc194cdd3a6e96004aac8446f5e63d9664"}, ] [[package]] name = "packaging" -version = "22.0" +version = "23.1" description = "Core utilities for Python packages" -category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "packaging-22.0-py3-none-any.whl", hash = "sha256:957e2148ba0e1a3b282772e791ef1d8083648bc131c8ab0c1feba110ce1146c3"}, - {file = "packaging-22.0.tar.gz", hash = "sha256:2198ec20bd4c017b8f9717e00f0c8714076fc2fd93816750ab48e2c41de2cfd3"}, + {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, + {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, ] +[[package]] +name = "pillow" +version = "9.5.0" +description = "Python Imaging Library (Fork)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "Pillow-9.5.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:ace6ca218308447b9077c14ea4ef381ba0b67ee78d64046b3f19cf4e1139ad16"}, + {file = "Pillow-9.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d3d403753c9d5adc04d4694d35cf0391f0f3d57c8e0030aac09d7678fa8030aa"}, + {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ba1b81ee69573fe7124881762bb4cd2e4b6ed9dd28c9c60a632902fe8db8b38"}, + {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe7e1c262d3392afcf5071df9afa574544f28eac825284596ac6db56e6d11062"}, + {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f36397bf3f7d7c6a3abdea815ecf6fd14e7fcd4418ab24bae01008d8d8ca15e"}, + {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:252a03f1bdddce077eff2354c3861bf437c892fb1832f75ce813ee94347aa9b5"}, + {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:85ec677246533e27770b0de5cf0f9d6e4ec0c212a1f89dfc941b64b21226009d"}, + {file = "Pillow-9.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b416f03d37d27290cb93597335a2f85ed446731200705b22bb927405320de903"}, + {file = "Pillow-9.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1781a624c229cb35a2ac31cc4a77e28cafc8900733a864870c49bfeedacd106a"}, + {file = "Pillow-9.5.0-cp310-cp310-win32.whl", hash = "sha256:8507eda3cd0608a1f94f58c64817e83ec12fa93a9436938b191b80d9e4c0fc44"}, + {file = "Pillow-9.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:d3c6b54e304c60c4181da1c9dadf83e4a54fd266a99c70ba646a9baa626819eb"}, + {file = "Pillow-9.5.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:7ec6f6ce99dab90b52da21cf0dc519e21095e332ff3b399a357c187b1a5eee32"}, + {file = "Pillow-9.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:560737e70cb9c6255d6dcba3de6578a9e2ec4b573659943a5e7e4af13f298f5c"}, + {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96e88745a55b88a7c64fa49bceff363a1a27d9a64e04019c2281049444a571e3"}, + {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d9c206c29b46cfd343ea7cdfe1232443072bbb270d6a46f59c259460db76779a"}, + {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cfcc2c53c06f2ccb8976fb5c71d448bdd0a07d26d8e07e321c103416444c7ad1"}, + {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:a0f9bb6c80e6efcde93ffc51256d5cfb2155ff8f78292f074f60f9e70b942d99"}, + {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:8d935f924bbab8f0a9a28404422da8af4904e36d5c33fc6f677e4c4485515625"}, + {file = "Pillow-9.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fed1e1cf6a42577953abbe8e6cf2fe2f566daebde7c34724ec8803c4c0cda579"}, + {file = "Pillow-9.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c1170d6b195555644f0616fd6ed929dfcf6333b8675fcca044ae5ab110ded296"}, + {file = "Pillow-9.5.0-cp311-cp311-win32.whl", hash = "sha256:54f7102ad31a3de5666827526e248c3530b3a33539dbda27c6843d19d72644ec"}, + {file = "Pillow-9.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:cfa4561277f677ecf651e2b22dc43e8f5368b74a25a8f7d1d4a3a243e573f2d4"}, + {file = "Pillow-9.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:965e4a05ef364e7b973dd17fc765f42233415974d773e82144c9bbaaaea5d089"}, + {file = "Pillow-9.5.0-cp312-cp312-win32.whl", hash = "sha256:22baf0c3cf0c7f26e82d6e1adf118027afb325e703922c8dfc1d5d0156bb2eeb"}, + {file = "Pillow-9.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:432b975c009cf649420615388561c0ce7cc31ce9b2e374db659ee4f7d57a1f8b"}, + {file = "Pillow-9.5.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:5d4ebf8e1db4441a55c509c4baa7a0587a0210f7cd25fcfe74dbbce7a4bd1906"}, + {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:375f6e5ee9620a271acb6820b3d1e94ffa8e741c0601db4c0c4d3cb0a9c224bf"}, + {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99eb6cafb6ba90e436684e08dad8be1637efb71c4f2180ee6b8f940739406e78"}, + {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dfaaf10b6172697b9bceb9a3bd7b951819d1ca339a5ef294d1f1ac6d7f63270"}, + {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:763782b2e03e45e2c77d7779875f4432e25121ef002a41829d8868700d119392"}, + {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:35f6e77122a0c0762268216315bf239cf52b88865bba522999dc38f1c52b9b47"}, + {file = "Pillow-9.5.0-cp37-cp37m-win32.whl", hash = "sha256:aca1c196f407ec7cf04dcbb15d19a43c507a81f7ffc45b690899d6a76ac9fda7"}, + {file = "Pillow-9.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:322724c0032af6692456cd6ed554bb85f8149214d97398bb80613b04e33769f6"}, + {file = "Pillow-9.5.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:a0aa9417994d91301056f3d0038af1199eb7adc86e646a36b9e050b06f526597"}, + {file = "Pillow-9.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f8286396b351785801a976b1e85ea88e937712ee2c3ac653710a4a57a8da5d9c"}, + {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c830a02caeb789633863b466b9de10c015bded434deb3ec87c768e53752ad22a"}, + {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fbd359831c1657d69bb81f0db962905ee05e5e9451913b18b831febfe0519082"}, + {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8fc330c3370a81bbf3f88557097d1ea26cd8b019d6433aa59f71195f5ddebbf"}, + {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:7002d0797a3e4193c7cdee3198d7c14f92c0836d6b4a3f3046a64bd1ce8df2bf"}, + {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:229e2c79c00e85989a34b5981a2b67aa079fd08c903f0aaead522a1d68d79e51"}, + {file = "Pillow-9.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9adf58f5d64e474bed00d69bcd86ec4bcaa4123bfa70a65ce72e424bfb88ed96"}, + {file = "Pillow-9.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:662da1f3f89a302cc22faa9f14a262c2e3951f9dbc9617609a47521c69dd9f8f"}, + {file = "Pillow-9.5.0-cp38-cp38-win32.whl", hash = "sha256:6608ff3bf781eee0cd14d0901a2b9cc3d3834516532e3bd673a0a204dc8615fc"}, + {file = "Pillow-9.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:e49eb4e95ff6fd7c0c402508894b1ef0e01b99a44320ba7d8ecbabefddcc5569"}, + {file = "Pillow-9.5.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:482877592e927fd263028c105b36272398e3e1be3269efda09f6ba21fd83ec66"}, + {file = "Pillow-9.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3ded42b9ad70e5f1754fb7c2e2d6465a9c842e41d178f262e08b8c85ed8a1d8e"}, + {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c446d2245ba29820d405315083d55299a796695d747efceb5717a8b450324115"}, + {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8aca1152d93dcc27dc55395604dcfc55bed5f25ef4c98716a928bacba90d33a3"}, + {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:608488bdcbdb4ba7837461442b90ea6f3079397ddc968c31265c1e056964f1ef"}, + {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:60037a8db8750e474af7ffc9faa9b5859e6c6d0a50e55c45576bf28be7419705"}, + {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:07999f5834bdc404c442146942a2ecadd1cb6292f5229f4ed3b31e0a108746b1"}, + {file = "Pillow-9.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a127ae76092974abfbfa38ca2d12cbeddcdeac0fb71f9627cc1135bedaf9d51a"}, + {file = "Pillow-9.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:489f8389261e5ed43ac8ff7b453162af39c3e8abd730af8363587ba64bb2e865"}, + {file = "Pillow-9.5.0-cp39-cp39-win32.whl", hash = "sha256:9b1af95c3a967bf1da94f253e56b6286b50af23392a886720f563c547e48e964"}, + {file = "Pillow-9.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:77165c4a5e7d5a284f10a6efaa39a0ae8ba839da344f20b111d62cc932fa4e5d"}, + {file = "Pillow-9.5.0-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:833b86a98e0ede388fa29363159c9b1a294b0905b5128baf01db683672f230f5"}, + {file = "Pillow-9.5.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aaf305d6d40bd9632198c766fb64f0c1a83ca5b667f16c1e79e1661ab5060140"}, + {file = "Pillow-9.5.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0852ddb76d85f127c135b6dd1f0bb88dbb9ee990d2cd9aa9e28526c93e794fba"}, + {file = "Pillow-9.5.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:91ec6fe47b5eb5a9968c79ad9ed78c342b1f97a091677ba0e012701add857829"}, + {file = "Pillow-9.5.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:cb841572862f629b99725ebaec3287fc6d275be9b14443ea746c1dd325053cbd"}, + {file = "Pillow-9.5.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:c380b27d041209b849ed246b111b7c166ba36d7933ec6e41175fd15ab9eb1572"}, + {file = "Pillow-9.5.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c9af5a3b406a50e313467e3565fc99929717f780164fe6fbb7704edba0cebbe"}, + {file = "Pillow-9.5.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5671583eab84af046a397d6d0ba25343c00cd50bce03787948e0fff01d4fd9b1"}, + {file = "Pillow-9.5.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:84a6f19ce086c1bf894644b43cd129702f781ba5751ca8572f08aa40ef0ab7b7"}, + {file = "Pillow-9.5.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:1e7723bd90ef94eda669a3c2c19d549874dd5badaeefabefd26053304abe5799"}, + {file = "Pillow-9.5.0.tar.gz", hash = "sha256:bf548479d336726d7a0eceb6e767e179fbde37833ae42794602631a070d630f1"}, +] + +[package.extras] +docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"] +tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] + [[package]] name = "platformdirs" -version = "2.6.2" +version = "3.8.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "platformdirs-2.6.2-py3-none-any.whl", hash = "sha256:83c8f6d04389165de7c9b6f0c682439697887bca0aa2f1c87ef1826be3584490"}, - {file = "platformdirs-2.6.2.tar.gz", hash = "sha256:e1fea1fe471b9ff8332e229df3cb7de4f53eeea4998d3b6bfff542115e998bd2"}, + {file = "platformdirs-3.8.0-py3-none-any.whl", hash = "sha256:ca9ed98ce73076ba72e092b23d3c93ea6c4e186b3f1c3dad6edd98ff6ffcca2e"}, + {file = "platformdirs-3.8.0.tar.gz", hash = "sha256:b0cabcb11063d21a0b261d557acb0a9d2126350e63b70cdf7db6347baea456dc"}, ] [package.extras] -docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.5)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.2.2)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] +docs = ["furo (>=2023.5.20)", "proselint (>=0.13)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)"] [[package]] name = "pluggy" -version = "1.0.0" +version = "1.2.0" description = "plugin and hook calling mechanisms for python" -category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, - {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, + {file = "pluggy-1.2.0-py3-none-any.whl", hash = "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849"}, + {file = "pluggy-1.2.0.tar.gz", hash = "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3"}, ] [package.extras] @@ -1729,14 +2432,13 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "pre-commit" -version = "3.2.1" +version = "3.3.3" description = "A framework for managing and maintaining multi-language pre-commit hooks." -category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "pre_commit-3.2.1-py2.py3-none-any.whl", hash = "sha256:a06a7fcce7f420047a71213c175714216498b49ebc81fe106f7716ca265f5bb6"}, - {file = "pre_commit-3.2.1.tar.gz", hash = "sha256:b5aee7d75dbba21ee161ba641b01e7ae10c5b91967ebf7b2ab0dfae12d07e1f1"}, + {file = "pre_commit-3.3.3-py2.py3-none-any.whl", hash = "sha256:10badb65d6a38caff29703362271d7dca483d01da88f9d7e05d0b97171c136cb"}, + {file = "pre_commit-3.3.3.tar.gz", hash = "sha256:a2256f489cd913d575c145132ae196fe335da32d91a8294b7afe6622335dd023"}, ] [package.dependencies] @@ -1747,36 +2449,35 @@ pyyaml = ">=5.1" virtualenv = ">=20.10.0" [[package]] -name = "prisma" -version = "0.8.2" -description = "Prisma Client Python is an auto-generated and fully type-safe database client" -category = "main" +name = "psutil" +version = "5.9.5" +description = "Cross-platform lib for process and system monitoring in Python." optional = false -python-versions = ">=3.7.0" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ - {file = "prisma-0.8.2-py3-none-any.whl", hash = "sha256:4d0ebf63ecd21bc4fda633867403429d24f89e6af5889944b2f9ca4203f50835"}, - {file = "prisma-0.8.2.tar.gz", hash = "sha256:b3a639941f4bf33dbd414fee3e02cf30218968d05b108f8a4d5b7c331dcbc19c"}, + {file = "psutil-5.9.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:be8929ce4313f9f8146caad4272f6abb8bf99fc6cf59344a3167ecd74f4f203f"}, + {file = "psutil-5.9.5-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ab8ed1a1d77c95453db1ae00a3f9c50227ebd955437bcf2a574ba8adbf6a74d5"}, + {file = "psutil-5.9.5-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:4aef137f3345082a3d3232187aeb4ac4ef959ba3d7c10c33dd73763fbc063da4"}, + {file = "psutil-5.9.5-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:ea8518d152174e1249c4f2a1c89e3e6065941df2fa13a1ab45327716a23c2b48"}, + {file = "psutil-5.9.5-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:acf2aef9391710afded549ff602b5887d7a2349831ae4c26be7c807c0a39fac4"}, + {file = "psutil-5.9.5-cp27-none-win32.whl", hash = "sha256:5b9b8cb93f507e8dbaf22af6a2fd0ccbe8244bf30b1baad6b3954e935157ae3f"}, + {file = "psutil-5.9.5-cp27-none-win_amd64.whl", hash = "sha256:8c5f7c5a052d1d567db4ddd231a9d27a74e8e4a9c3f44b1032762bd7b9fdcd42"}, + {file = "psutil-5.9.5-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:3c6f686f4225553615612f6d9bc21f1c0e305f75d7d8454f9b46e901778e7217"}, + {file = "psutil-5.9.5-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7a7dd9997128a0d928ed4fb2c2d57e5102bb6089027939f3b722f3a210f9a8da"}, + {file = "psutil-5.9.5-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89518112647f1276b03ca97b65cc7f64ca587b1eb0278383017c2a0dcc26cbe4"}, + {file = "psutil-5.9.5-cp36-abi3-win32.whl", hash = "sha256:104a5cc0e31baa2bcf67900be36acde157756b9c44017b86b2c049f11957887d"}, + {file = "psutil-5.9.5-cp36-abi3-win_amd64.whl", hash = "sha256:b258c0c1c9d145a1d5ceffab1134441c4c5113b2417fafff7315a917a026c3c9"}, + {file = "psutil-5.9.5-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:c607bb3b57dc779d55e1554846352b4e358c10fff3abf3514a7a6601beebdb30"}, + {file = "psutil-5.9.5.tar.gz", hash = "sha256:5410638e4df39c54d957fc51ce03048acd8e6d60abc0f5107af51e5fb566eb3c"}, ] -[package.dependencies] -click = ">=7.1.2" -httpx = ">=0.19.0" -jinja2 = ">=2.11.2" -nodeenv = "*" -pydantic = ">=1.8.0" -python-dotenv = ">=0.12.0" -tomlkit = "*" -typing-extensions = ">=3.7" - [package.extras] -all = ["nodejs-bin"] -node = ["nodejs-bin"] +test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] [[package]] name = "pycares" version = "4.3.0" description = "Python interface for c-ares" -category = "main" optional = false python-versions = "*" files = [ @@ -1844,7 +2545,6 @@ idna = ["idna (>=2.1)"] name = "pycparser" version = "2.21" description = "C parser in Python" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -1853,116 +2553,76 @@ files = [ ] [[package]] -name = "pydantic" -version = "1.10.4" -description = "Data validation and settings management using python type hints" -category = "main" +name = "pygments" +version = "2.15.1" +description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.7" files = [ - {file = "pydantic-1.10.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b5635de53e6686fe7a44b5cf25fcc419a0d5e5c1a1efe73d49d48fe7586db854"}, - {file = "pydantic-1.10.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6dc1cc241440ed7ca9ab59d9929075445da6b7c94ced281b3dd4cfe6c8cff817"}, - {file = "pydantic-1.10.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51bdeb10d2db0f288e71d49c9cefa609bca271720ecd0c58009bd7504a0c464c"}, - {file = "pydantic-1.10.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78cec42b95dbb500a1f7120bdf95c401f6abb616bbe8785ef09887306792e66e"}, - {file = "pydantic-1.10.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8775d4ef5e7299a2f4699501077a0defdaac5b6c4321173bcb0f3c496fbadf85"}, - {file = "pydantic-1.10.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:572066051eeac73d23f95ba9a71349c42a3e05999d0ee1572b7860235b850cc6"}, - {file = "pydantic-1.10.4-cp310-cp310-win_amd64.whl", hash = "sha256:7feb6a2d401f4d6863050f58325b8d99c1e56f4512d98b11ac64ad1751dc647d"}, - {file = "pydantic-1.10.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:39f4a73e5342b25c2959529f07f026ef58147249f9b7431e1ba8414a36761f53"}, - {file = "pydantic-1.10.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:983e720704431a6573d626b00662eb78a07148c9115129f9b4351091ec95ecc3"}, - {file = "pydantic-1.10.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75d52162fe6b2b55964fbb0af2ee58e99791a3138588c482572bb6087953113a"}, - {file = "pydantic-1.10.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fdf8d759ef326962b4678d89e275ffc55b7ce59d917d9f72233762061fd04a2d"}, - {file = "pydantic-1.10.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:05a81b006be15655b2a1bae5faa4280cf7c81d0e09fcb49b342ebf826abe5a72"}, - {file = "pydantic-1.10.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d88c4c0e5c5dfd05092a4b271282ef0588e5f4aaf345778056fc5259ba098857"}, - {file = "pydantic-1.10.4-cp311-cp311-win_amd64.whl", hash = "sha256:6a05a9db1ef5be0fe63e988f9617ca2551013f55000289c671f71ec16f4985e3"}, - {file = "pydantic-1.10.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:887ca463c3bc47103c123bc06919c86720e80e1214aab79e9b779cda0ff92a00"}, - {file = "pydantic-1.10.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdf88ab63c3ee282c76d652fc86518aacb737ff35796023fae56a65ced1a5978"}, - {file = "pydantic-1.10.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a48f1953c4a1d9bd0b5167ac50da9a79f6072c63c4cef4cf2a3736994903583e"}, - {file = "pydantic-1.10.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a9f2de23bec87ff306aef658384b02aa7c32389766af3c5dee9ce33e80222dfa"}, - {file = "pydantic-1.10.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:cd8702c5142afda03dc2b1ee6bc358b62b3735b2cce53fc77b31ca9f728e4bc8"}, - {file = "pydantic-1.10.4-cp37-cp37m-win_amd64.whl", hash = "sha256:6e7124d6855b2780611d9f5e1e145e86667eaa3bd9459192c8dc1a097f5e9903"}, - {file = "pydantic-1.10.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b53e1d41e97063d51a02821b80538053ee4608b9a181c1005441f1673c55423"}, - {file = "pydantic-1.10.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:55b1625899acd33229c4352ce0ae54038529b412bd51c4915349b49ca575258f"}, - {file = "pydantic-1.10.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:301d626a59edbe5dfb48fcae245896379a450d04baeed50ef40d8199f2733b06"}, - {file = "pydantic-1.10.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b6f9d649892a6f54a39ed56b8dfd5e08b5f3be5f893da430bed76975f3735d15"}, - {file = "pydantic-1.10.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d7b5a3821225f5c43496c324b0d6875fde910a1c2933d726a743ce328fbb2a8c"}, - {file = "pydantic-1.10.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f2f7eb6273dd12472d7f218e1fef6f7c7c2f00ac2e1ecde4db8824c457300416"}, - {file = "pydantic-1.10.4-cp38-cp38-win_amd64.whl", hash = "sha256:4b05697738e7d2040696b0a66d9f0a10bec0efa1883ca75ee9e55baf511909d6"}, - {file = "pydantic-1.10.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a9a6747cac06c2beb466064dda999a13176b23535e4c496c9d48e6406f92d42d"}, - {file = "pydantic-1.10.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:eb992a1ef739cc7b543576337bebfc62c0e6567434e522e97291b251a41dad7f"}, - {file = "pydantic-1.10.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:990406d226dea0e8f25f643b370224771878142155b879784ce89f633541a024"}, - {file = "pydantic-1.10.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e82a6d37a95e0b1b42b82ab340ada3963aea1317fd7f888bb6b9dfbf4fff57c"}, - {file = "pydantic-1.10.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9193d4f4ee8feca58bc56c8306bcb820f5c7905fd919e0750acdeeeef0615b28"}, - {file = "pydantic-1.10.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2b3ce5f16deb45c472dde1a0ee05619298c864a20cded09c4edd820e1454129f"}, - {file = "pydantic-1.10.4-cp39-cp39-win_amd64.whl", hash = "sha256:9cbdc268a62d9a98c56e2452d6c41c0263d64a2009aac69246486f01b4f594c4"}, - {file = "pydantic-1.10.4-py3-none-any.whl", hash = "sha256:4948f264678c703f3877d1c8877c4e3b2e12e549c57795107f08cf70c6ec7774"}, - {file = "pydantic-1.10.4.tar.gz", hash = "sha256:b9a3859f24eb4e097502a3be1fb4b2abb79b6103dd9e2e0edb70613a4459a648"}, + {file = "Pygments-2.15.1-py3-none-any.whl", hash = "sha256:db2db3deb4b4179f399a09054b023b6a586b76499d36965813c71aa8ed7b5fd1"}, + {file = "Pygments-2.15.1.tar.gz", hash = "sha256:8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c"}, ] -[package.dependencies] -typing-extensions = ">=4.2.0" - [package.extras] -dotenv = ["python-dotenv (>=0.10.4)"] -email = ["email-validator (>=1.0.3)"] +plugins = ["importlib-metadata"] [[package]] name = "pyinstrument" -version = "4.4.0" +version = "4.5.0" description = "Call stack profiler for Python. Shows you why your code is slow!" -category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "pyinstrument-4.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8874f8f58cfcb1ff134dc8e4a2b31ab9175adb271a4423596ed7ac8183592cf8"}, - {file = "pyinstrument-4.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e5f4d6e1c395f259f67a923a9c54dc3eaccd5f02540598da4f865c4bb3545762"}, - {file = "pyinstrument-4.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d70fed48ddd0078e287fb580daaeede4d8703a9edc8bf4f703308a77920bac37"}, - {file = "pyinstrument-4.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9fda1bd596e81ecd2b6a976eb9b930a757a5dd04071583d0141d059e34eed83f"}, - {file = "pyinstrument-4.4.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f57b61d39d3b1a4d773da16baa8456aa66102d6016ce1f39817051550cbe47e"}, - {file = "pyinstrument-4.4.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5a9aead0ca5579473f66fed4c449c693feee464802b5ba9b98772e64e02c575c"}, - {file = "pyinstrument-4.4.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:375a340c3fbebd922a35b0834de1c82d1b4fea681df49f99729439a6cb5e6ad4"}, - {file = "pyinstrument-4.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b9cbaf3bcda5ad9af4c9a7bf4f1b8f15bb32c4cadf554d0a2c723892c898021b"}, - {file = "pyinstrument-4.4.0-cp310-cp310-win32.whl", hash = "sha256:97cbeb5f5a048dc6eb047495f73db90c9e2ec97606e65298c7ea2c61fa52de38"}, - {file = "pyinstrument-4.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:73edbce7fda1b3d8cab0b6c39c43b012167d783c072f40928600c3357d1a5dc5"}, - {file = "pyinstrument-4.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7db8cb55182883be48520eb915bd1769f176a4813ce0cc38243aa2d1182e7ce7"}, - {file = "pyinstrument-4.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7c614e2c241fb558a25973019ff43ce027ba4958bcb87383f0b0789af9c4d03b"}, - {file = "pyinstrument-4.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c012422c851f0457c3cb82d8b1259d96fa0dcddc0f1e8bf4d97f0b2efe54485"}, - {file = "pyinstrument-4.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e4f5ad100710dda68f9f345961780bf4f0cbb9fd3e46295d099bb9ad65b179ea"}, - {file = "pyinstrument-4.4.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a4a053cd67102c6fcc313366ea6be97cfce7eae2b9e57e62c9be8adbbdebc17"}, - {file = "pyinstrument-4.4.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1d2a1e53615c8ef210286e4d2d93be0d3e8296995b090df29a0b7ddeae5d874b"}, - {file = "pyinstrument-4.4.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b2a6609ef74ad8ba292a11fbd975660bc86466c7eaab1ff11360d24e0300800b"}, - {file = "pyinstrument-4.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3643084ee8ad22d9ea2adb13d65d4b6e18810113e4176b19d026a011957f8c7c"}, - {file = "pyinstrument-4.4.0-cp311-cp311-win32.whl", hash = "sha256:fcd717910a8ab6deca353aded890403bbaea14a6dd99a87c3367f24721d2d6aa"}, - {file = "pyinstrument-4.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:be9ac54a4dd07d969d5941e4dcba67d5aef5f6826f43b9ddda65553816f6abca"}, - {file = "pyinstrument-4.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:39584c0fec147e3bbfa7b28454332f9801af5f93331f4143f24a4b0f9e3cb470"}, - {file = "pyinstrument-4.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5491a5cb3ae5e88436d48b4b3de8328286e843e7307116dc2cca397c9c2ffe21"}, - {file = "pyinstrument-4.4.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d66fcc94f0ebaab6bcbbdfa2482f833dd634352a20295616ea45286e990f7446"}, - {file = "pyinstrument-4.4.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b72bde0b1a03d1b2dc9b9d79546f551df6f67673cca816614e98ea0aebd3bc50"}, - {file = "pyinstrument-4.4.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0bbd169b92147ec5d67ed160c300dda504059cfd81e953ed5b059e8ef92bb482"}, - {file = "pyinstrument-4.4.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:e5233022ba511ef7ecfef2e07d162c0817048c995f0940f9aa2f6a1936afcb9c"}, - {file = "pyinstrument-4.4.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:5e750fc3afb9acc288ad84b183a5ccd863e9185c435b445fcc62e0c133af9b7f"}, - {file = "pyinstrument-4.4.0-cp37-cp37m-win32.whl", hash = "sha256:2d131b98f116fb895d759dfb8c1078e0e9fa8987a9f44f566d29221545f75bd4"}, - {file = "pyinstrument-4.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:de83152bafc9eed4e5469e340b6002be825151f0654c32bbb9a3a7e31708d227"}, - {file = "pyinstrument-4.4.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a7c774c4b8df21664b082d3e72fa8cbc0631fe9bb222bb9d285ccfe9cd9b4909"}, - {file = "pyinstrument-4.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7526f0b1dab721ddc19920fa1f4eeaa5bcb658a4d18ac9c50868e84f911f794b"}, - {file = "pyinstrument-4.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59f5f479db277b3dbeb0c6843a7e9a38ee8b7c23d75b9ef764d96cb522d96212"}, - {file = "pyinstrument-4.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffeeaa0d036a8bef31da6fc13c4ea097160f913d86319897314113bb9271af4c"}, - {file = "pyinstrument-4.4.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfc4e2fd670a570ea847f6897283d10d4b9606170e491f01b75488ed1aa37a81"}, - {file = "pyinstrument-4.4.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ffd9a9fa73fd83a40252430c6ebf8dfff7c668cc68eab4a92562b8b27c302598"}, - {file = "pyinstrument-4.4.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:09167ece8802bc03a63e97536dcefd9c1a340dae686f40914cf995099bc0d0af"}, - {file = "pyinstrument-4.4.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:9e7d1cc3affef4a7e4695bb87c6cfcd577e2dac508624a91481f24217ef78c57"}, - {file = "pyinstrument-4.4.0-cp38-cp38-win32.whl", hash = "sha256:b50cf50513a5318738c3c7147f02596cda4891089acf2f627bb65954fc5bcbfd"}, - {file = "pyinstrument-4.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:dd9625cf136eb6684d9ca555a5088f21a7ac6c6cb2ece3ae45d09772906ceba8"}, - {file = "pyinstrument-4.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a8afee175335005d2964848b77579bfc18f011ea74b59b79ab6d5b35433bf3e3"}, - {file = "pyinstrument-4.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ebc63b70845e3a44b673f7dcdc78ac2c475684db41b0402eea370f194da2a287"}, - {file = "pyinstrument-4.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb89033e41e74dc2ac4fd882269e91ddf677588efa665d2be8b718e96ea4cec6"}, - {file = "pyinstrument-4.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9b2bcd803d273c8addf01eaf75a42ae0a2a9196a58fb0ebb8d29be75abb88701"}, - {file = "pyinstrument-4.4.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50727b686a0961a11eba2fe6205d0899f3479c983bcf34abb114d6da70bc1b93"}, - {file = "pyinstrument-4.4.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4f07941bb5dd5cd730fc84eef6497ef9f0807c686e68d0c6b1f464589646a3b7"}, - {file = "pyinstrument-4.4.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:30f5ce299c3219559870117c5b0825f33243808be375be9c3525572ba050c2db"}, - {file = "pyinstrument-4.4.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9a13c75b24bf8eed5a4356ffa8a419cc534284a529f2b314f3e10275a820420f"}, - {file = "pyinstrument-4.4.0-cp39-cp39-win32.whl", hash = "sha256:e5583b0d23f87631af06bb9f3c184190c889c194b02553eed132de966324bdf9"}, - {file = "pyinstrument-4.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:a19784a898133b7e0ffe4489155bacd2d07ec48ea059f9bf50033dc2b814c273"}, - {file = "pyinstrument-4.4.0.tar.gz", hash = "sha256:be34a2e8118c14a616a64538e02430d9099d5d67d8a370f2888e4ac71e52bbb7"}, + {file = "pyinstrument-4.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b2f0a36c6edc1c1531f270aec9846b48304de19aea7fb819733d438a6fb4bf2e"}, + {file = "pyinstrument-4.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0d8122041f997274d64d0c78d183a4be2337a97bfb9fbec84c1ed8c9ba68265f"}, + {file = "pyinstrument-4.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94f061f3459cb9c509e57273ed6551e37d2d0c8cce0c327b1c8d5cbbc8fb9ddc"}, + {file = "pyinstrument-4.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f8ae4434c6b1552e36f32b19e7d3f9fc5c5dc246178adce049f027c4ebb28a98"}, + {file = "pyinstrument-4.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6b3071525ca0a7cea708d5ada359dad3561ed272dc58ec9027d46e1b3e11266"}, + {file = "pyinstrument-4.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:dcf4fd5c6e11d1ba42ec91ecaf97970c59c50bd07b4208f6cdbc1b16d635c130"}, + {file = "pyinstrument-4.5.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8543f2f1ed205e6578459b434ceeeba03ef841108d648b74b27e4e99c91fe439"}, + {file = "pyinstrument-4.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:53c9ab55fbf73f30a71dae43a2cc6a8733b22e29007091eae65a9b468648597b"}, + {file = "pyinstrument-4.5.0-cp310-cp310-win32.whl", hash = "sha256:ac631d289f271e284052dad3b008a0abcb2a2da72c5de72eb8d56841d6942f15"}, + {file = "pyinstrument-4.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:ec68f7d63c218e998aba55c40fc9cbd4132675ef1ae9b652dce58134e722c229"}, + {file = "pyinstrument-4.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e06159e0ecd013e352e4905b4342e71e71b630b77078503979679355a4a77c33"}, + {file = "pyinstrument-4.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0922ece1ef965215f912e7b4218cf24b385763ccbb2cbdbb41b2b35826410310"}, + {file = "pyinstrument-4.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83f9dd0d581e3b22ee47d1f076037b6ce0cd891fad1f09121ba8638436f1b760"}, + {file = "pyinstrument-4.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:af29ed2cd39d838473a81c9e40eccc7b8e1ab09acac743f54b80f62728fa4de3"}, + {file = "pyinstrument-4.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e15ede05892a2273ac062538f7d62136bcfedda0983d3554cf67c2d52a7c9dd9"}, + {file = "pyinstrument-4.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:385dfbfceee954e90febeed89f89570f27b0956f05cec5d206efa082efcf5acd"}, + {file = "pyinstrument-4.5.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eab6064f3abb9802449bf446a36502399c04714b6b81354fddc8bcae13f82376"}, + {file = "pyinstrument-4.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a53fb221e7a7f8658c2cc1afc242158cf721d4f68ba67189524ab0aae61cca12"}, + {file = "pyinstrument-4.5.0-cp311-cp311-win32.whl", hash = "sha256:f736cba0b250cc288571a4b1edf331194eefd36ca06c55ba987a2fcc30ead826"}, + {file = "pyinstrument-4.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:976bc13f2dee5a0a0b065b7695329634496b25574467d0d76285994360186371"}, + {file = "pyinstrument-4.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:58f538be679f55b9a2ce13e39d6c07c41683c23e37cac40d9a0789d6bc137d35"}, + {file = "pyinstrument-4.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bd24aeebcff18f5ef08961ece3c04bcffaba2ad55ffd243fd04c908b4a17674"}, + {file = "pyinstrument-4.5.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b33a63c5e291b2a2e073803ee6686f5be8536c84688969eeec67608ef76ebca1"}, + {file = "pyinstrument-4.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a9f4164c1534c04f07add30fd2e61f94ea0c68ae8bf082bd83655cb6c15a258"}, + {file = "pyinstrument-4.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ca1094ad94d155e709ab8622728ced9a7d19091d346dd18720d8e8ad83ccaf58"}, + {file = "pyinstrument-4.5.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:261356a7c47fa0d7da58349112e9e06761674a6dd99f08fc71ba672c01bf7f7c"}, + {file = "pyinstrument-4.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c948fd93c215042770c3d0d3d72746720dac26fed6e539020f5c7ad18be953ea"}, + {file = "pyinstrument-4.5.0-cp37-cp37m-win32.whl", hash = "sha256:e1ff10704ec28646ec37f972981763a45a718215b6bd9ff4bc30bafd0887acbb"}, + {file = "pyinstrument-4.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c880ea33fa5c2c5bbdfa6d896b7addf31bea8967e4d4c85ec921f7d052749e10"}, + {file = "pyinstrument-4.5.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ab0187c10fee5bf539bf9ba0ab4c081dac50a767a88c7f9e708c4fd0342e06aa"}, + {file = "pyinstrument-4.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2e98bf17f7d919dba2494fa6af3bc1704ba704a0adad37e80908a317371ee29d"}, + {file = "pyinstrument-4.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:876c915ac51ed4b69e8a6d6867f7bc8a02929b32dafb73e2c4ce4c8f06ac260c"}, + {file = "pyinstrument-4.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:05690e3dfff1ffca38da15f6fdcc8231e7931e92abeb32f36f64785cfc1dca83"}, + {file = "pyinstrument-4.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20589781fb8245fc9a1654ae46eca8c900d32a14923c947ba32398f8afd8fd1c"}, + {file = "pyinstrument-4.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e31b9adf6914ad1f2fb0debd04d2968cd12770dfa7903c0cc7bfc88a7faca0d6"}, + {file = "pyinstrument-4.5.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:33039688c0fadf132e70273657e9a4e30c22b648d2f9732c4711ced8a441c650"}, + {file = "pyinstrument-4.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fc9e6a617847b4e3d7b59a1e6f252d326703e8719891fc52efee83f56f065c80"}, + {file = "pyinstrument-4.5.0-cp38-cp38-win32.whl", hash = "sha256:57febfcfc61d54c117edb432a939fe974eecd49e4da9711efaa927e61a548fdd"}, + {file = "pyinstrument-4.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:488859b9ea05c36c99c48efcd02f7f781b1fa54b8d09a1f5eda96fdfb9e0356c"}, + {file = "pyinstrument-4.5.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:078bbbea6e626da73c38ec6692720a7f7b79a2e7fb6536861e26687f58f5f707"}, + {file = "pyinstrument-4.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:04e9b88131c1b4bd9917aab2dd4fd47468598cbf719448710f1a6d51bdc295d8"}, + {file = "pyinstrument-4.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63f1c1cf89536fc2084179c69fa65414fc018d1a3e48039f8cb6ff82b278ea28"}, + {file = "pyinstrument-4.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4ca55eee67a7a93bd2c24625da312dddf5c4ae6fa5ba59ba0f908e1bf24a84bd"}, + {file = "pyinstrument-4.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1309b6211ac1ddce56cda9e870c42d9d08ba51930c9d07505179435869c4db16"}, + {file = "pyinstrument-4.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bc78253cd2a203812d70a7637b045a6d00b68c4035ad73edf0e03f6828c465f5"}, + {file = "pyinstrument-4.5.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:0f122fd1f93a5db81a2acc05b56525b7c197a31e271fe2ae151d242ff599fcb8"}, + {file = "pyinstrument-4.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2a7d941df6d433662311cf6ad267b7782d30f78651ce34f3132539d5a7d65e0c"}, + {file = "pyinstrument-4.5.0-cp39-cp39-win32.whl", hash = "sha256:14e2c5f176babd747b4d2fb17c00de36fcbc91fa5e9a4152fb09cf9e19364f18"}, + {file = "pyinstrument-4.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:c28e2f82aedf35686cf95c7d5366a65cf54ef92490307de1ab38efe7b727e997"}, + {file = "pyinstrument-4.5.0.tar.gz", hash = "sha256:ce01c7304b76932761c2be3d998f08b1863fd409a3709851e48af6cc0dc10eb2"}, ] [package.extras] @@ -1972,7 +2632,6 @@ jupyter = ["ipython"] name = "pynacl" version = "1.5.0" description = "Python binding to the Networking and Cryptography (NaCl) library" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1995,16 +2654,29 @@ cffi = ">=1.4.1" 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.0" +description = "pyparsing module - Classes and methods to define and execute parsing grammars" +optional = false +python-versions = ">=3.6.8" +files = [ + {file = "pyparsing-3.1.0-py3-none-any.whl", hash = "sha256:d554a96d1a7d3ddaf7183104485bc19fd80543ad6ac5bdb6426719d766fb06c1"}, + {file = "pyparsing-3.1.0.tar.gz", hash = "sha256:edb662d6fe322d6e990b1594b5feaeadf806803359e3d4d42f11e295e588f0ea"}, +] + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] + [[package]] name = "pyright" -version = "1.1.301" +version = "1.1.316" description = "Command line wrapper for pyright" -category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "pyright-1.1.301-py3-none-any.whl", hash = "sha256:ecc3752ba8c866a8041c90becf6be79bd52f4c51f98472e4776cae6d55e12826"}, - {file = "pyright-1.1.301.tar.gz", hash = "sha256:6ac4afc0004dca3a977a4a04a8ba25b5b5aa55f8289550697bfc20e11be0d5f2"}, + {file = "pyright-1.1.316-py3-none-any.whl", hash = "sha256:7259d73287c882f933d8cd88c238ef02336e172171ae95117a963a962a1fed4a"}, + {file = "pyright-1.1.316.tar.gz", hash = "sha256:bac1baf8567b90f2082ec95b61fc1cb50a68917119212c5608a72210870c6a9a"}, ] [package.dependencies] @@ -2016,18 +2688,16 @@ dev = ["twine (>=3.4.1)"] [[package]] name = "pytest" -version = "7.2.2" +version = "7.4.0" description = "pytest: simple powerful testing with Python" -category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-7.2.2-py3-none-any.whl", hash = "sha256:130328f552dcfac0b1cec75c12e3f005619dc5f874f0a06e8ff7263f0ee6225e"}, - {file = "pytest-7.2.2.tar.gz", hash = "sha256:c99ab0c73aceb050f68929bc93af19ab6db0558791c6a0715723abe9d0ade9d4"}, + {file = "pytest-7.4.0-py3-none-any.whl", hash = "sha256:78bf16451a2eb8c7a2ea98e32dc119fd2aa758f1d5d66dbf0a59d69a3969df32"}, + {file = "pytest-7.4.0.tar.gz", hash = "sha256:b4bf8c45bd59934ed84001ad51e11b4ee40d40a1229d2c79f9c592b0a3f6bd8a"}, ] [package.dependencies] -attrs = ">=19.2.0" colorama = {version = "*", markers = "sys_platform == \"win32\""} exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} iniconfig = "*" @@ -2036,13 +2706,12 @@ pluggy = ">=0.12,<2.0" tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} [package.extras] -testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] +testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] [[package]] name = "pytest-asyncio" version = "0.21.0" description = "Pytest support for asyncio" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2059,14 +2728,13 @@ testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy [[package]] name = "pytest-cov" -version = "4.0.0" +version = "4.1.0" description = "Pytest plugin for measuring coverage." -category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "pytest-cov-4.0.0.tar.gz", hash = "sha256:996b79efde6433cdbd0088872dbc5fb3ed7fe1578b68cdbba634f14bb8dd0470"}, - {file = "pytest_cov-4.0.0-py3-none-any.whl", hash = "sha256:2feb1b751d66a8bd934e5edfa2e961d11309dc37b73b0eabe73b5945fee20f6b"}, + {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, + {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, ] [package.dependencies] @@ -2076,11 +2744,24 @@ pytest = ">=4.6" [package.extras] testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] +[[package]] +name = "python-dateutil" +version = "2.8.2" +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.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, +] + +[package.dependencies] +six = ">=1.5" + [[package]] name = "python-dotenv" version = "1.0.0" description = "Read key-value pairs from a .env file and set them as environment variables" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -2091,11 +2772,21 @@ files = [ [package.extras] cli = ["click (>=5.0)"] +[[package]] +name = "pytz" +version = "2023.3" +description = "World timezone definitions, modern and historical" +optional = false +python-versions = "*" +files = [ + {file = "pytz-2023.3-py2.py3-none-any.whl", hash = "sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb"}, + {file = "pytz-2023.3.tar.gz", hash = "sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588"}, +] + [[package]] name = "pyyaml" version = "6.0" description = "YAML parser and emitter for Python" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -2143,18 +2834,17 @@ files = [ [[package]] name = "redis" -version = "4.5.4" +version = "4.6.0" description = "Python client for Redis database and key-value store" -category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "redis-4.5.4-py3-none-any.whl", hash = "sha256:2c19e6767c474f2e85167909061d525ed65bea9301c0770bb151e041b7ac89a2"}, - {file = "redis-4.5.4.tar.gz", hash = "sha256:73ec35da4da267d6847e47f68730fdd5f62e2ca69e3ef5885c6a78a9374c3893"}, + {file = "redis-4.6.0-py3-none-any.whl", hash = "sha256:e2b03db868160ee4591de3cb90d40ebb50a90dd302138775937f6a42b7ed183c"}, + {file = "redis-4.6.0.tar.gz", hash = "sha256:585dc516b9eb042a619ef0a39c3d7d55fe81bdb4df09a52c9cdde0d07bf1aa7d"}, ] [package.dependencies] -async-timeout = {version = ">=4.0.2", markers = "python_version <= \"3.11.2\""} +async-timeout = {version = ">=4.0.2", markers = "python_full_version <= \"3.11.2\""} hiredis = {version = ">=1.0.0", optional = true, markers = "extra == \"hiredis\""} [package.extras] @@ -2163,78 +2853,71 @@ ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)" [[package]] name = "requests" -version = "2.28.2" +version = "2.31.0" description = "Python HTTP for Humans." -category = "main" optional = false -python-versions = ">=3.7, <4" +python-versions = ">=3.7" files = [ - {file = "requests-2.28.2-py3-none-any.whl", hash = "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa"}, - {file = "requests-2.28.2.tar.gz", hash = "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf"}, + {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, + {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, ] [package.dependencies] certifi = ">=2017.4.17" charset-normalizer = ">=2,<4" idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<1.27" +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 = "rfc3986" -version = "1.5.0" -description = "Validating URI References per RFC 3986" -category = "main" +name = "ruff" +version = "0.0.277" +description = "An extremely fast Python linter, written in Rust." optional = false -python-versions = "*" -files = [ - {file = "rfc3986-1.5.0-py2.py3-none-any.whl", hash = "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"}, - {file = "rfc3986-1.5.0.tar.gz", hash = "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835"}, -] - -[package.dependencies] -idna = {version = "*", optional = true, markers = "extra == \"idna2008\""} - -[package.extras] -idna2008 = ["idna"] - -[[package]] -name = "rin-exceptions" -version = "1.0.3" -description = "A set of base exceptions for Rin" -category = "main" -optional = false -python-versions = ">=3.7,<4.0" +python-versions = ">=3.7" files = [ - {file = "rin-exceptions-1.0.3.tar.gz", hash = "sha256:8044e89f50280b0d0e14639e0727405b49a0cf54a73b6993c2dd4cc56d0182c2"}, - {file = "rin_exceptions-1.0.3-py3-none-any.whl", hash = "sha256:7d0e467a313d208589f1340c6225636ae2325e5b0a7be39e0361f1ab0d986fee"}, + {file = "ruff-0.0.277-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:3250b24333ef419b7a232080d9724ccc4d2da1dbbe4ce85c4caa2290d83200f8"}, + {file = "ruff-0.0.277-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:3e60605e07482183ba1c1b7237eca827bd6cbd3535fe8a4ede28cbe2a323cb97"}, + {file = "ruff-0.0.277-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7baa97c3d7186e5ed4d5d4f6834d759a27e56cf7d5874b98c507335f0ad5aadb"}, + {file = "ruff-0.0.277-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:74e4b206cb24f2e98a615f87dbe0bde18105217cbcc8eb785bb05a644855ba50"}, + {file = "ruff-0.0.277-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:479864a3ccd8a6a20a37a6e7577bdc2406868ee80b1e65605478ad3b8eb2ba0b"}, + {file = "ruff-0.0.277-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:468bfb0a7567443cec3d03cf408d6f562b52f30c3c29df19927f1e0e13a40cd7"}, + {file = "ruff-0.0.277-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f32ec416c24542ca2f9cc8c8b65b84560530d338aaf247a4a78e74b99cd476b4"}, + {file = "ruff-0.0.277-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:14a7b2f00f149c5a295f188a643ac25226ff8a4d08f7a62b1d4b0a1dc9f9b85c"}, + {file = "ruff-0.0.277-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9879f59f763cc5628aa01c31ad256a0f4dc61a29355c7315b83c2a5aac932b5"}, + {file = "ruff-0.0.277-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f612e0a14b3d145d90eb6ead990064e22f6f27281d847237560b4e10bf2251f3"}, + {file = "ruff-0.0.277-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:323b674c98078be9aaded5b8b51c0d9c424486566fb6ec18439b496ce79e5998"}, + {file = "ruff-0.0.277-py3-none-musllinux_1_2_i686.whl", hash = "sha256:3a43fbe026ca1a2a8c45aa0d600a0116bec4dfa6f8bf0c3b871ecda51ef2b5dd"}, + {file = "ruff-0.0.277-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:734165ea8feb81b0d53e3bf523adc2413fdb76f1264cde99555161dd5a725522"}, + {file = "ruff-0.0.277-py3-none-win32.whl", hash = "sha256:88d0f2afb2e0c26ac1120e7061ddda2a566196ec4007bd66d558f13b374b9efc"}, + {file = "ruff-0.0.277-py3-none-win_amd64.whl", hash = "sha256:6fe81732f788894a00f6ade1fe69e996cc9e485b7c35b0f53fb00284397284b2"}, + {file = "ruff-0.0.277-py3-none-win_arm64.whl", hash = "sha256:2d4444c60f2e705c14cd802b55cd2b561d25bf4311702c463a002392d3116b22"}, + {file = "ruff-0.0.277.tar.gz", hash = "sha256:2dab13cdedbf3af6d4427c07f47143746b6b95d9e4a254ac369a0edb9280a0d2"}, ] [[package]] name = "setuptools" -version = "66.1.1" +version = "68.0.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" -category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "setuptools-66.1.1-py3-none-any.whl", hash = "sha256:6f590d76b713d5de4e49fe4fbca24474469f53c83632d5d0fd056f7ff7e8112b"}, - {file = "setuptools-66.1.1.tar.gz", hash = "sha256:ac4008d396bc9cd983ea483cb7139c0240a07bbc74ffb6232fceffedc6cf03a8"}, + {file = "setuptools-68.0.0-py3-none-any.whl", hash = "sha256:11e52c67415a381d10d6b462ced9cfb97066179f0e871399e006c4ab101fc85f"}, + {file = "setuptools-68.0.0.tar.gz", hash = "sha256:baf1fdb41c6da4cd2eae722e135500da913332ab3f2f5c7d33af9b492acb5235"}, ] [package.extras] docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[package]] name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -2246,7 +2929,6 @@ files = [ name = "sniffio" version = "1.3.0" description = "Sniff out which async library your code is running under" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2254,11 +2936,225 @@ files = [ {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, ] +[[package]] +name = "snowballstemmer" +version = "2.2.0" +description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." +optional = false +python-versions = "*" +files = [ + {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, + {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, +] + +[[package]] +name = "soupsieve" +version = "2.4.1" +description = "A modern CSS selector implementation for Beautiful Soup." +optional = false +python-versions = ">=3.7" +files = [ + {file = "soupsieve-2.4.1-py3-none-any.whl", hash = "sha256:1c1bfee6819544a3447586c889157365a27e10d88cde3ad3da0cf0ddf646feb8"}, + {file = "soupsieve-2.4.1.tar.gz", hash = "sha256:89d12b2d5dfcd2c9e8c22326da9d9aa9cb3dfab0a83a024f05704076ee8d35ea"}, +] + +[[package]] +name = "sphinx" +version = "7.0.1" +description = "Python documentation generator" +optional = false +python-versions = ">=3.8" +files = [ + {file = "Sphinx-7.0.1.tar.gz", hash = "sha256:61e025f788c5977d9412587e733733a289e2b9fdc2fef8868ddfbfc4ccfe881d"}, + {file = "sphinx-7.0.1-py3-none-any.whl", hash = "sha256:60c5e04756c1709a98845ed27a2eed7a556af3993afb66e77fec48189f742616"}, +] + +[package.dependencies] +alabaster = ">=0.7,<0.8" +babel = ">=2.9" +colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} +docutils = ">=0.18.1,<0.21" +imagesize = ">=1.3" +importlib-metadata = {version = ">=4.8", markers = "python_version < \"3.10\""} +Jinja2 = ">=3.0" +packaging = ">=21.0" +Pygments = ">=2.13" +requests = ">=2.25.0" +snowballstemmer = ">=2.0" +sphinxcontrib-applehelp = "*" +sphinxcontrib-devhelp = "*" +sphinxcontrib-htmlhelp = ">=2.0.0" +sphinxcontrib-jsmath = "*" +sphinxcontrib-qthelp = "*" +sphinxcontrib-serializinghtml = ">=1.1.5" + +[package.extras] +docs = ["sphinxcontrib-websupport"] +lint = ["docutils-stubs", "flake8 (>=3.5.0)", "flake8-simplify", "isort", "mypy (>=0.990)", "ruff", "sphinx-lint", "types-requests"] +test = ["cython", "filelock", "html5lib", "pytest (>=4.6)"] + +[[package]] +name = "sphinx-autobuild" +version = "2021.3.14" +description = "Rebuild Sphinx documentation on changes, with live-reload in the browser." +optional = false +python-versions = ">=3.6" +files = [ + {file = "sphinx-autobuild-2021.3.14.tar.gz", hash = "sha256:de1ca3b66e271d2b5b5140c35034c89e47f263f2cd5db302c9217065f7443f05"}, + {file = "sphinx_autobuild-2021.3.14-py3-none-any.whl", hash = "sha256:8fe8cbfdb75db04475232f05187c776f46f6e9e04cacf1e49ce81bdac649ccac"}, +] + +[package.dependencies] +colorama = "*" +livereload = "*" +sphinx = "*" + +[package.extras] +test = ["pytest", "pytest-cov"] + +[[package]] +name = "sphinx-basic-ng" +version = "1.0.0b1" +description = "A modern skeleton for Sphinx themes." +optional = false +python-versions = ">=3.7" +files = [ + {file = "sphinx_basic_ng-1.0.0b1-py3-none-any.whl", hash = "sha256:ade597a3029c7865b24ad0eda88318766bcc2f9f4cef60df7e28126fde94db2a"}, + {file = "sphinx_basic_ng-1.0.0b1.tar.gz", hash = "sha256:89374bd3ccd9452a301786781e28c8718e99960f2d4f411845ea75fc7bb5a9b0"}, +] + +[package.dependencies] +sphinx = ">=4.0" + +[package.extras] +docs = ["furo", "ipython", "myst-parser", "sphinx-copybutton", "sphinx-inline-tabs"] + +[[package]] +name = "sphinx-copybutton" +version = "0.5.2" +description = "Add a copy button to each of your code cells." +optional = false +python-versions = ">=3.7" +files = [ + {file = "sphinx-copybutton-0.5.2.tar.gz", hash = "sha256:4cf17c82fb9646d1bc9ca92ac280813a3b605d8c421225fd9913154103ee1fbd"}, + {file = "sphinx_copybutton-0.5.2-py3-none-any.whl", hash = "sha256:fb543fd386d917746c9a2c50360c7905b605726b9355cd26e9974857afeae06e"}, +] + +[package.dependencies] +sphinx = ">=1.8" + +[package.extras] +code-style = ["pre-commit (==2.12.1)"] +rtd = ["ipython", "myst-nb", "sphinx", "sphinx-book-theme", "sphinx-examples"] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "1.0.4" +description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" +optional = false +python-versions = ">=3.8" +files = [ + {file = "sphinxcontrib-applehelp-1.0.4.tar.gz", hash = "sha256:828f867945bbe39817c210a1abfd1bc4895c8b73fcaade56d45357a348a07d7e"}, + {file = "sphinxcontrib_applehelp-1.0.4-py3-none-any.whl", hash = "sha256:29d341f67fb0f6f586b23ad80e072c8e6ad0b48417db2bde114a4c9746feb228"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "1.0.2" +description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." +optional = false +python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"}, + {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.0.1" +description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" +optional = false +python-versions = ">=3.8" +files = [ + {file = "sphinxcontrib-htmlhelp-2.0.1.tar.gz", hash = "sha256:0cbdd302815330058422b98a113195c9249825d681e18f11e8b1f78a2f11efff"}, + {file = "sphinxcontrib_htmlhelp-2.0.1-py3-none-any.whl", hash = "sha256:c38cb46dccf316c79de6e5515e1770414b797162b23cd3d06e67020e1d2a6903"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["html5lib", "pytest"] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +description = "A sphinx extension which renders display math in HTML via JavaScript" +optional = false +python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, + {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, +] + +[package.extras] +test = ["flake8", "mypy", "pytest"] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "1.0.3" +description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." +optional = false +python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"}, + {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "1.1.5" +description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." +optional = false +python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"}, + {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "sphinxext-opengraph" +version = "0.8.2" +description = "Sphinx Extension to enable OGP support" +optional = false +python-versions = ">=3.7" +files = [ + {file = "sphinxext-opengraph-0.8.2.tar.gz", hash = "sha256:45a693b6704052c426576f0a1f630649c55b4188bc49eb63e9587e24a923db39"}, + {file = "sphinxext_opengraph-0.8.2-py3-none-any.whl", hash = "sha256:6a05bdfe5176d9dd0a1d58a504f17118362ab976631213cd36fb44c4c40544c9"}, +] + +[package.dependencies] +matplotlib = "*" +sphinx = ">=4.0" + [[package]] name = "tomli" version = "2.0.1" description = "A lil' TOML parser" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2268,33 +3164,50 @@ files = [ [[package]] name = "tomlkit" -version = "0.11.6" +version = "0.11.8" description = "Style preserving TOML library" -category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" +files = [ + {file = "tomlkit-0.11.8-py3-none-any.whl", hash = "sha256:8c726c4c202bdb148667835f68d68780b9a003a9ec34167b6c673b38eff2a171"}, + {file = "tomlkit-0.11.8.tar.gz", hash = "sha256:9330fc7faa1db67b541b28e62018c17d20be733177d290a13b24c62d1614e0c3"}, +] + +[[package]] +name = "tornado" +version = "6.3.2" +description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." +optional = false +python-versions = ">= 3.8" files = [ - {file = "tomlkit-0.11.6-py3-none-any.whl", hash = "sha256:07de26b0d8cfc18f871aec595fda24d95b08fef89d147caa861939f37230bf4b"}, - {file = "tomlkit-0.11.6.tar.gz", hash = "sha256:71b952e5721688937fb02cf9d354dbcf0785066149d2855e44531ebdd2b65d73"}, + {file = "tornado-6.3.2-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:c367ab6c0393d71171123ca5515c61ff62fe09024fa6bf299cd1339dc9456829"}, + {file = "tornado-6.3.2-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b46a6ab20f5c7c1cb949c72c1994a4585d2eaa0be4853f50a03b5031e964fc7c"}, + {file = "tornado-6.3.2-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c2de14066c4a38b4ecbbcd55c5cc4b5340eb04f1c5e81da7451ef555859c833f"}, + {file = "tornado-6.3.2-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:05615096845cf50a895026f749195bf0b10b8909f9be672f50b0fe69cba368e4"}, + {file = "tornado-6.3.2-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5b17b1cf5f8354efa3d37c6e28fdfd9c1c1e5122f2cb56dac121ac61baa47cbe"}, + {file = "tornado-6.3.2-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:29e71c847a35f6e10ca3b5c2990a52ce38b233019d8e858b755ea6ce4dcdd19d"}, + {file = "tornado-6.3.2-cp38-abi3-musllinux_1_1_i686.whl", hash = "sha256:834ae7540ad3a83199a8da8f9f2d383e3c3d5130a328889e4cc991acc81e87a0"}, + {file = "tornado-6.3.2-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:6a0848f1aea0d196a7c4f6772197cbe2abc4266f836b0aac76947872cd29b411"}, + {file = "tornado-6.3.2-cp38-abi3-win32.whl", hash = "sha256:7efcbcc30b7c654eb6a8c9c9da787a851c18f8ccd4a5a3a95b05c7accfa068d2"}, + {file = "tornado-6.3.2-cp38-abi3-win_amd64.whl", hash = "sha256:0c325e66c8123c606eea33084976c832aa4e766b7dff8aedd7587ea44a604cdf"}, + {file = "tornado-6.3.2.tar.gz", hash = "sha256:4b927c4f19b71e627b13f3db2324e4ae660527143f9e1f2e2fb404f3a187e2ba"}, ] [[package]] name = "typing-extensions" -version = "4.4.0" +version = "4.7.0" description = "Backported and Experimental Type Hints for Python 3.7+" -category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, - {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, + {file = "typing_extensions-4.7.0-py3-none-any.whl", hash = "sha256:5d8c9dac95c27d20df12fb1d97b9793ab8b2af8a3a525e68c80e21060c161771"}, + {file = "typing_extensions-4.7.0.tar.gz", hash = "sha256:935ccf31549830cda708b42289d44b6f74084d616a00be651601a4f968e77c82"}, ] [[package]] name = "update-checker" version = "0.18.0" description = "A python module that will check for package updates." -category = "main" optional = false python-versions = "*" files = [ @@ -2312,26 +3225,25 @@ test = ["pytest (>=2.7.3)"] [[package]] name = "urllib3" -version = "1.26.14" +version = "2.0.3" description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +python-versions = ">=3.7" files = [ - {file = "urllib3-1.26.14-py2.py3-none-any.whl", hash = "sha256:75edcdc2f7d85b137124a6c3c9fc3933cdeaa12ecb9a6a959f22797a0feca7e1"}, - {file = "urllib3-1.26.14.tar.gz", hash = "sha256:076907bf8fd355cde77728471316625a4d2f7e713c125f51953bb5b3eecf4f72"}, + {file = "urllib3-2.0.3-py3-none-any.whl", hash = "sha256:48e7fafa40319d358848e1bc6809b208340fafe2096f1725d05d67443d0483d1"}, + {file = "urllib3-2.0.3.tar.gz", hash = "sha256:bee28b5e56addb8226c96f7f13ac28cb4c301dd5ea8a6ca179c0b9835e032825"}, ] [package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] -secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] -socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] [[package]] name = "uvloop" version = "0.17.0" description = "Fast implementation of asyncio event loop on top of libuv" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2374,30 +3286,28 @@ test = ["Cython (>=0.29.32,<0.30.0)", "aiohttp", "flake8 (>=3.9.2,<3.10.0)", "my [[package]] name = "virtualenv" -version = "20.17.1" +version = "20.23.1" description = "Virtual Python Environment builder" -category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "virtualenv-20.17.1-py3-none-any.whl", hash = "sha256:ce3b1684d6e1a20a3e5ed36795a97dfc6af29bc3970ca8dab93e11ac6094b3c4"}, - {file = "virtualenv-20.17.1.tar.gz", hash = "sha256:f8b927684efc6f1cc206c9db297a570ab9ad0e51c16fa9e45487d36d1905c058"}, + {file = "virtualenv-20.23.1-py3-none-any.whl", hash = "sha256:34da10f14fea9be20e0fd7f04aba9732f84e593dac291b757ce42e3368a39419"}, + {file = "virtualenv-20.23.1.tar.gz", hash = "sha256:8ff19a38c1021c742148edc4f81cb43d7f8c6816d2ede2ab72af5b84c749ade1"}, ] [package.dependencies] distlib = ">=0.3.6,<1" -filelock = ">=3.4.1,<4" -platformdirs = ">=2.4,<3" +filelock = ">=3.12,<4" +platformdirs = ">=3.5.1,<4" [package.extras] -docs = ["proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-argparse (>=0.3.2)", "sphinx-rtd-theme (>=1)", "towncrier (>=22.8)"] -testing = ["coverage (>=6.2)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=21.3)", "pytest (>=7.0.1)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.6.1)", "pytest-randomly (>=3.10.3)", "pytest-timeout (>=2.1)"] +docs = ["furo (>=2023.5.20)", "proselint (>=0.13)", "sphinx (>=7.0.1)", "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.3.1)", "pytest-env (>=0.8.1)", "pytest-freezer (>=0.4.6)", "pytest-mock (>=3.10)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=67.8)", "time-machine (>=2.9)"] [[package]] name = "watchfiles" version = "0.19.0" description = "Simple, modern and high performance file watching and code reload in python." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2430,88 +3340,87 @@ anyio = ">=3.0.0" [[package]] name = "websockets" -version = "10.4" +version = "11.0.3" description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" -category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "websockets-10.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d58804e996d7d2307173d56c297cf7bc132c52df27a3efaac5e8d43e36c21c48"}, - {file = "websockets-10.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc0b82d728fe21a0d03e65f81980abbbcb13b5387f733a1a870672c5be26edab"}, - {file = "websockets-10.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ba089c499e1f4155d2a3c2a05d2878a3428cf321c848f2b5a45ce55f0d7d310c"}, - {file = "websockets-10.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33d69ca7612f0ddff3316b0c7b33ca180d464ecac2d115805c044bf0a3b0d032"}, - {file = "websockets-10.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62e627f6b6d4aed919a2052efc408da7a545c606268d5ab5bfab4432734b82b4"}, - {file = "websockets-10.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38ea7b82bfcae927eeffc55d2ffa31665dc7fec7b8dc654506b8e5a518eb4d50"}, - {file = "websockets-10.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e0cb5cc6ece6ffa75baccfd5c02cffe776f3f5c8bf486811f9d3ea3453676ce8"}, - {file = "websockets-10.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ae5e95cfb53ab1da62185e23b3130e11d64431179debac6dc3c6acf08760e9b1"}, - {file = "websockets-10.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7c584f366f46ba667cfa66020344886cf47088e79c9b9d39c84ce9ea98aaa331"}, - {file = "websockets-10.4-cp310-cp310-win32.whl", hash = "sha256:b029fb2032ae4724d8ae8d4f6b363f2cc39e4c7b12454df8df7f0f563ed3e61a"}, - {file = "websockets-10.4-cp310-cp310-win_amd64.whl", hash = "sha256:8dc96f64ae43dde92530775e9cb169979f414dcf5cff670455d81a6823b42089"}, - {file = "websockets-10.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:47a2964021f2110116cc1125b3e6d87ab5ad16dea161949e7244ec583b905bb4"}, - {file = "websockets-10.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e789376b52c295c4946403bd0efecf27ab98f05319df4583d3c48e43c7342c2f"}, - {file = "websockets-10.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7d3f0b61c45c3fa9a349cf484962c559a8a1d80dae6977276df8fd1fa5e3cb8c"}, - {file = "websockets-10.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f55b5905705725af31ccef50e55391621532cd64fbf0bc6f4bac935f0fccec46"}, - {file = "websockets-10.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00c870522cdb69cd625b93f002961ffb0c095394f06ba8c48f17eef7c1541f96"}, - {file = "websockets-10.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f38706e0b15d3c20ef6259fd4bc1700cd133b06c3c1bb108ffe3f8947be15fa"}, - {file = "websockets-10.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f2c38d588887a609191d30e902df2a32711f708abfd85d318ca9b367258cfd0c"}, - {file = "websockets-10.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:fe10ddc59b304cb19a1bdf5bd0a7719cbbc9fbdd57ac80ed436b709fcf889106"}, - {file = "websockets-10.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:90fcf8929836d4a0e964d799a58823547df5a5e9afa83081761630553be731f9"}, - {file = "websockets-10.4-cp311-cp311-win32.whl", hash = "sha256:b9968694c5f467bf67ef97ae7ad4d56d14be2751000c1207d31bf3bb8860bae8"}, - {file = "websockets-10.4-cp311-cp311-win_amd64.whl", hash = "sha256:a7a240d7a74bf8d5cb3bfe6be7f21697a28ec4b1a437607bae08ac7acf5b4882"}, - {file = "websockets-10.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:74de2b894b47f1d21cbd0b37a5e2b2392ad95d17ae983e64727e18eb281fe7cb"}, - {file = "websockets-10.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3a686ecb4aa0d64ae60c9c9f1a7d5d46cab9bfb5d91a2d303d00e2cd4c4c5cc"}, - {file = "websockets-10.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0d15c968ea7a65211e084f523151dbf8ae44634de03c801b8bd070b74e85033"}, - {file = "websockets-10.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00213676a2e46b6ebf6045bc11d0f529d9120baa6f58d122b4021ad92adabd41"}, - {file = "websockets-10.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:e23173580d740bf8822fd0379e4bf30aa1d5a92a4f252d34e893070c081050df"}, - {file = "websockets-10.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:dd500e0a5e11969cdd3320935ca2ff1e936f2358f9c2e61f100a1660933320ea"}, - {file = "websockets-10.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:4239b6027e3d66a89446908ff3027d2737afc1a375f8fd3eea630a4842ec9a0c"}, - {file = "websockets-10.4-cp37-cp37m-win32.whl", hash = "sha256:8a5cc00546e0a701da4639aa0bbcb0ae2bb678c87f46da01ac2d789e1f2d2038"}, - {file = "websockets-10.4-cp37-cp37m-win_amd64.whl", hash = "sha256:a9f9a735deaf9a0cadc2d8c50d1a5bcdbae8b6e539c6e08237bc4082d7c13f28"}, - {file = "websockets-10.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5c1289596042fad2cdceb05e1ebf7aadf9995c928e0da2b7a4e99494953b1b94"}, - {file = "websockets-10.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0cff816f51fb33c26d6e2b16b5c7d48eaa31dae5488ace6aae468b361f422b63"}, - {file = "websockets-10.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:dd9becd5fe29773d140d68d607d66a38f60e31b86df75332703757ee645b6faf"}, - {file = "websockets-10.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45ec8e75b7dbc9539cbfafa570742fe4f676eb8b0d3694b67dabe2f2ceed8aa6"}, - {file = "websockets-10.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f72e5cd0f18f262f5da20efa9e241699e0cf3a766317a17392550c9ad7b37d8"}, - {file = "websockets-10.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:185929b4808b36a79c65b7865783b87b6841e852ef5407a2fb0c03381092fa3b"}, - {file = "websockets-10.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7d27a7e34c313b3a7f91adcd05134315002aaf8540d7b4f90336beafaea6217c"}, - {file = "websockets-10.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:884be66c76a444c59f801ac13f40c76f176f1bfa815ef5b8ed44321e74f1600b"}, - {file = "websockets-10.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:931c039af54fc195fe6ad536fde4b0de04da9d5916e78e55405436348cfb0e56"}, - {file = "websockets-10.4-cp38-cp38-win32.whl", hash = "sha256:db3c336f9eda2532ec0fd8ea49fef7a8df8f6c804cdf4f39e5c5c0d4a4ad9a7a"}, - {file = "websockets-10.4-cp38-cp38-win_amd64.whl", hash = "sha256:48c08473563323f9c9debac781ecf66f94ad5a3680a38fe84dee5388cf5acaf6"}, - {file = "websockets-10.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:40e826de3085721dabc7cf9bfd41682dadc02286d8cf149b3ad05bff89311e4f"}, - {file = "websockets-10.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:56029457f219ade1f2fc12a6504ea61e14ee227a815531f9738e41203a429112"}, - {file = "websockets-10.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f5fc088b7a32f244c519a048c170f14cf2251b849ef0e20cbbb0fdf0fdaf556f"}, - {file = "websockets-10.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fc8709c00704194213d45e455adc106ff9e87658297f72d544220e32029cd3d"}, - {file = "websockets-10.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0154f7691e4fe6c2b2bc275b5701e8b158dae92a1ab229e2b940efe11905dff4"}, - {file = "websockets-10.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c6d2264f485f0b53adf22697ac11e261ce84805c232ed5dbe6b1bcb84b00ff0"}, - {file = "websockets-10.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9bc42e8402dc5e9905fb8b9649f57efcb2056693b7e88faa8fb029256ba9c68c"}, - {file = "websockets-10.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:edc344de4dac1d89300a053ac973299e82d3db56330f3494905643bb68801269"}, - {file = "websockets-10.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:84bc2a7d075f32f6ed98652db3a680a17a4edb21ca7f80fe42e38753a58ee02b"}, - {file = "websockets-10.4-cp39-cp39-win32.whl", hash = "sha256:c94ae4faf2d09f7c81847c63843f84fe47bf6253c9d60b20f25edfd30fb12588"}, - {file = "websockets-10.4-cp39-cp39-win_amd64.whl", hash = "sha256:bbccd847aa0c3a69b5f691a84d2341a4f8a629c6922558f2a70611305f902d74"}, - {file = "websockets-10.4-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:82ff5e1cae4e855147fd57a2863376ed7454134c2bf49ec604dfe71e446e2193"}, - {file = "websockets-10.4-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d210abe51b5da0ffdbf7b43eed0cfdff8a55a1ab17abbec4301c9ff077dd0342"}, - {file = "websockets-10.4-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:942de28af58f352a6f588bc72490ae0f4ccd6dfc2bd3de5945b882a078e4e179"}, - {file = "websockets-10.4-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9b27d6c1c6cd53dc93614967e9ce00ae7f864a2d9f99fe5ed86706e1ecbf485"}, - {file = "websockets-10.4-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:3d3cac3e32b2c8414f4f87c1b2ab686fa6284a980ba283617404377cd448f631"}, - {file = "websockets-10.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:da39dd03d130162deb63da51f6e66ed73032ae62e74aaccc4236e30edccddbb0"}, - {file = "websockets-10.4-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:389f8dbb5c489e305fb113ca1b6bdcdaa130923f77485db5b189de343a179393"}, - {file = "websockets-10.4-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09a1814bb15eff7069e51fed0826df0bc0702652b5cb8f87697d469d79c23576"}, - {file = "websockets-10.4-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff64a1d38d156d429404aaa84b27305e957fd10c30e5880d1765c9480bea490f"}, - {file = "websockets-10.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:b343f521b047493dc4022dd338fc6db9d9282658862756b4f6fd0e996c1380e1"}, - {file = "websockets-10.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:932af322458da7e4e35df32f050389e13d3d96b09d274b22a7aa1808f292fee4"}, - {file = "websockets-10.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6a4162139374a49eb18ef5b2f4da1dd95c994588f5033d64e0bbfda4b6b6fcf"}, - {file = "websockets-10.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c57e4c1349fbe0e446c9fa7b19ed2f8a4417233b6984277cce392819123142d3"}, - {file = "websockets-10.4-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b627c266f295de9dea86bd1112ed3d5fafb69a348af30a2422e16590a8ecba13"}, - {file = "websockets-10.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:05a7233089f8bd355e8cbe127c2e8ca0b4ea55467861906b80d2ebc7db4d6b72"}, - {file = "websockets-10.4.tar.gz", hash = "sha256:eef610b23933c54d5d921c92578ae5f89813438fded840c2e9809d378dc765d3"}, + {file = "websockets-11.0.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3ccc8a0c387629aec40f2fc9fdcb4b9d5431954f934da3eaf16cdc94f67dbfac"}, + {file = "websockets-11.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d67ac60a307f760c6e65dad586f556dde58e683fab03323221a4e530ead6f74d"}, + {file = "websockets-11.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:84d27a4832cc1a0ee07cdcf2b0629a8a72db73f4cf6de6f0904f6661227f256f"}, + {file = "websockets-11.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffd7dcaf744f25f82190856bc26ed81721508fc5cbf2a330751e135ff1283564"}, + {file = "websockets-11.0.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7622a89d696fc87af8e8d280d9b421db5133ef5b29d3f7a1ce9f1a7bf7fcfa11"}, + {file = "websockets-11.0.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bceab846bac555aff6427d060f2fcfff71042dba6f5fca7dc4f75cac815e57ca"}, + {file = "websockets-11.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:54c6e5b3d3a8936a4ab6870d46bdd6ec500ad62bde9e44462c32d18f1e9a8e54"}, + {file = "websockets-11.0.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:41f696ba95cd92dc047e46b41b26dd24518384749ed0d99bea0a941ca87404c4"}, + {file = "websockets-11.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:86d2a77fd490ae3ff6fae1c6ceaecad063d3cc2320b44377efdde79880e11526"}, + {file = "websockets-11.0.3-cp310-cp310-win32.whl", hash = "sha256:2d903ad4419f5b472de90cd2d40384573b25da71e33519a67797de17ef849b69"}, + {file = "websockets-11.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:1d2256283fa4b7f4c7d7d3e84dc2ece74d341bce57d5b9bf385df109c2a1a82f"}, + {file = "websockets-11.0.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e848f46a58b9fcf3d06061d17be388caf70ea5b8cc3466251963c8345e13f7eb"}, + {file = "websockets-11.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aa5003845cdd21ac0dc6c9bf661c5beddd01116f6eb9eb3c8e272353d45b3288"}, + {file = "websockets-11.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b58cbf0697721120866820b89f93659abc31c1e876bf20d0b3d03cef14faf84d"}, + {file = "websockets-11.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:660e2d9068d2bedc0912af508f30bbeb505bbbf9774d98def45f68278cea20d3"}, + {file = "websockets-11.0.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c1f0524f203e3bd35149f12157438f406eff2e4fb30f71221c8a5eceb3617b6b"}, + {file = "websockets-11.0.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:def07915168ac8f7853812cc593c71185a16216e9e4fa886358a17ed0fd9fcf6"}, + {file = "websockets-11.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b30c6590146e53149f04e85a6e4fcae068df4289e31e4aee1fdf56a0dead8f97"}, + {file = "websockets-11.0.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:619d9f06372b3a42bc29d0cd0354c9bb9fb39c2cbc1a9c5025b4538738dbffaf"}, + {file = "websockets-11.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:01f5567d9cf6f502d655151645d4e8b72b453413d3819d2b6f1185abc23e82dd"}, + {file = "websockets-11.0.3-cp311-cp311-win32.whl", hash = "sha256:e1459677e5d12be8bbc7584c35b992eea142911a6236a3278b9b5ce3326f282c"}, + {file = "websockets-11.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:e7837cb169eca3b3ae94cc5787c4fed99eef74c0ab9506756eea335e0d6f3ed8"}, + {file = "websockets-11.0.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9f59a3c656fef341a99e3d63189852be7084c0e54b75734cde571182c087b152"}, + {file = "websockets-11.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2529338a6ff0eb0b50c7be33dc3d0e456381157a31eefc561771ee431134a97f"}, + {file = "websockets-11.0.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34fd59a4ac42dff6d4681d8843217137f6bc85ed29722f2f7222bd619d15e95b"}, + {file = "websockets-11.0.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:332d126167ddddec94597c2365537baf9ff62dfcc9db4266f263d455f2f031cb"}, + {file = "websockets-11.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6505c1b31274723ccaf5f515c1824a4ad2f0d191cec942666b3d0f3aa4cb4007"}, + {file = "websockets-11.0.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f467ba0050b7de85016b43f5a22b46383ef004c4f672148a8abf32bc999a87f0"}, + {file = "websockets-11.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:9d9acd80072abcc98bd2c86c3c9cd4ac2347b5a5a0cae7ed5c0ee5675f86d9af"}, + {file = "websockets-11.0.3-cp37-cp37m-win32.whl", hash = "sha256:e590228200fcfc7e9109509e4d9125eace2042fd52b595dd22bbc34bb282307f"}, + {file = "websockets-11.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:b16fff62b45eccb9c7abb18e60e7e446998093cdcb50fed33134b9b6878836de"}, + {file = "websockets-11.0.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fb06eea71a00a7af0ae6aefbb932fb8a7df3cb390cc217d51a9ad7343de1b8d0"}, + {file = "websockets-11.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8a34e13a62a59c871064dfd8ffb150867e54291e46d4a7cf11d02c94a5275bae"}, + {file = "websockets-11.0.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4841ed00f1026dfbced6fca7d963c4e7043aa832648671b5138008dc5a8f6d99"}, + {file = "websockets-11.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a073fc9ab1c8aff37c99f11f1641e16da517770e31a37265d2755282a5d28aa"}, + {file = "websockets-11.0.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:68b977f21ce443d6d378dbd5ca38621755f2063d6fdb3335bda981d552cfff86"}, + {file = "websockets-11.0.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1a99a7a71631f0efe727c10edfba09ea6bee4166a6f9c19aafb6c0b5917d09c"}, + {file = "websockets-11.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bee9fcb41db2a23bed96c6b6ead6489702c12334ea20a297aa095ce6d31370d0"}, + {file = "websockets-11.0.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4b253869ea05a5a073ebfdcb5cb3b0266a57c3764cf6fe114e4cd90f4bfa5f5e"}, + {file = "websockets-11.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1553cb82942b2a74dd9b15a018dce645d4e68674de2ca31ff13ebc2d9f283788"}, + {file = "websockets-11.0.3-cp38-cp38-win32.whl", hash = "sha256:f61bdb1df43dc9c131791fbc2355535f9024b9a04398d3bd0684fc16ab07df74"}, + {file = "websockets-11.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:03aae4edc0b1c68498f41a6772d80ac7c1e33c06c6ffa2ac1c27a07653e79d6f"}, + {file = "websockets-11.0.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:777354ee16f02f643a4c7f2b3eff8027a33c9861edc691a2003531f5da4f6bc8"}, + {file = "websockets-11.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8c82f11964f010053e13daafdc7154ce7385ecc538989a354ccc7067fd7028fd"}, + {file = "websockets-11.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3580dd9c1ad0701169e4d6fc41e878ffe05e6bdcaf3c412f9d559389d0c9e016"}, + {file = "websockets-11.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f1a3f10f836fab6ca6efa97bb952300b20ae56b409414ca85bff2ad241d2a61"}, + {file = "websockets-11.0.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df41b9bc27c2c25b486bae7cf42fccdc52ff181c8c387bfd026624a491c2671b"}, + {file = "websockets-11.0.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:279e5de4671e79a9ac877427f4ac4ce93751b8823f276b681d04b2156713b9dd"}, + {file = "websockets-11.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1fdf26fa8a6a592f8f9235285b8affa72748dc12e964a5518c6c5e8f916716f7"}, + {file = "websockets-11.0.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:69269f3a0b472e91125b503d3c0b3566bda26da0a3261c49f0027eb6075086d1"}, + {file = "websockets-11.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:97b52894d948d2f6ea480171a27122d77af14ced35f62e5c892ca2fae9344311"}, + {file = "websockets-11.0.3-cp39-cp39-win32.whl", hash = "sha256:c7f3cb904cce8e1be667c7e6fef4516b98d1a6a0635a58a57528d577ac18a128"}, + {file = "websockets-11.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c792ea4eabc0159535608fc5658a74d1a81020eb35195dd63214dcf07556f67e"}, + {file = "websockets-11.0.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f2e58f2c36cc52d41f2659e4c0cbf7353e28c8c9e63e30d8c6d3494dc9fdedcf"}, + {file = "websockets-11.0.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de36fe9c02995c7e6ae6efe2e205816f5f00c22fd1fbf343d4d18c3d5ceac2f5"}, + {file = "websockets-11.0.3-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0ac56b661e60edd453585f4bd68eb6a29ae25b5184fd5ba51e97652580458998"}, + {file = "websockets-11.0.3-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e052b8467dd07d4943936009f46ae5ce7b908ddcac3fda581656b1b19c083d9b"}, + {file = "websockets-11.0.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:42cc5452a54a8e46a032521d7365da775823e21bfba2895fb7b77633cce031bb"}, + {file = "websockets-11.0.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e6316827e3e79b7b8e7d8e3b08f4e331af91a48e794d5d8b099928b6f0b85f20"}, + {file = "websockets-11.0.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8531fdcad636d82c517b26a448dcfe62f720e1922b33c81ce695d0edb91eb931"}, + {file = "websockets-11.0.3-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c114e8da9b475739dde229fd3bc6b05a6537a88a578358bc8eb29b4030fac9c9"}, + {file = "websockets-11.0.3-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e063b1865974611313a3849d43f2c3f5368093691349cf3c7c8f8f75ad7cb280"}, + {file = "websockets-11.0.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:92b2065d642bf8c0a82d59e59053dd2fdde64d4ed44efe4870fa816c1232647b"}, + {file = "websockets-11.0.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0ee68fe502f9031f19d495dae2c268830df2760c0524cbac5d759921ba8c8e82"}, + {file = "websockets-11.0.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcacf2c7a6c3a84e720d1bb2b543c675bf6c40e460300b628bab1b1efc7c034c"}, + {file = "websockets-11.0.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b67c6f5e5a401fc56394f191f00f9b3811fe843ee93f4a70df3c389d1adf857d"}, + {file = "websockets-11.0.3-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d5023a4b6a5b183dc838808087033ec5df77580485fc533e7dab2567851b0a4"}, + {file = "websockets-11.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ed058398f55163a79bb9f06a90ef9ccc063b204bb346c4de78efc5d15abfe602"}, + {file = "websockets-11.0.3-py3-none-any.whl", hash = "sha256:6681ba9e7f8f3b19440921e99efbb40fc89f26cd71bf539e45d8c8a25c976dc6"}, + {file = "websockets-11.0.3.tar.gz", hash = "sha256:88fc51d9a26b10fc331be344f1781224a375b78488fc343620184e95a4b27016"}, ] [[package]] name = "wheel" version = "0.40.0" description = "A built-package format for Python" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2522,88 +3431,101 @@ files = [ [package.extras] test = ["pytest (>=6.0.0)"] +[[package]] +name = "winloop" +version = "0.0.6" +description = "An Alternative library for uvloop compatability with windows" +optional = false +python-versions = "*" +files = [ + {file = "winloop-0.0.6-cp39-cp39-win_amd64.whl", hash = "sha256:d5fa5d9ac5e56d428f556691e902bb65e14bacf79433ca18a09e6e0721f1941a"}, + {file = "winloop-0.0.6.tar.gz", hash = "sha256:5708ac397ed5a14db574f96115f693077e049968075a1e7d8ee4db52b468d8c3"}, +] + +[package.dependencies] +cython = "*" + [[package]] name = "yarl" -version = "1.8.2" +version = "1.9.2" description = "Yet another URL library" -category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "yarl-1.8.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:bb81f753c815f6b8e2ddd2eef3c855cf7da193b82396ac013c661aaa6cc6b0a5"}, - {file = "yarl-1.8.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:47d49ac96156f0928f002e2424299b2c91d9db73e08c4cd6742923a086f1c863"}, - {file = "yarl-1.8.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3fc056e35fa6fba63248d93ff6e672c096f95f7836938241ebc8260e062832fe"}, - {file = "yarl-1.8.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58a3c13d1c3005dbbac5c9f0d3210b60220a65a999b1833aa46bd6677c69b08e"}, - {file = "yarl-1.8.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:10b08293cda921157f1e7c2790999d903b3fd28cd5c208cf8826b3b508026996"}, - {file = "yarl-1.8.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:de986979bbd87272fe557e0a8fcb66fd40ae2ddfe28a8b1ce4eae22681728fef"}, - {file = "yarl-1.8.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c4fcfa71e2c6a3cb568cf81aadc12768b9995323186a10827beccf5fa23d4f8"}, - {file = "yarl-1.8.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae4d7ff1049f36accde9e1ef7301912a751e5bae0a9d142459646114c70ecba6"}, - {file = "yarl-1.8.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:bf071f797aec5b96abfc735ab97da9fd8f8768b43ce2abd85356a3127909d146"}, - {file = "yarl-1.8.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:74dece2bfc60f0f70907c34b857ee98f2c6dd0f75185db133770cd67300d505f"}, - {file = "yarl-1.8.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:df60a94d332158b444301c7f569659c926168e4d4aad2cfbf4bce0e8fb8be826"}, - {file = "yarl-1.8.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:63243b21c6e28ec2375f932a10ce7eda65139b5b854c0f6b82ed945ba526bff3"}, - {file = "yarl-1.8.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cfa2bbca929aa742b5084fd4663dd4b87c191c844326fcb21c3afd2d11497f80"}, - {file = "yarl-1.8.2-cp310-cp310-win32.whl", hash = "sha256:b05df9ea7496df11b710081bd90ecc3a3db6adb4fee36f6a411e7bc91a18aa42"}, - {file = "yarl-1.8.2-cp310-cp310-win_amd64.whl", hash = "sha256:24ad1d10c9db1953291f56b5fe76203977f1ed05f82d09ec97acb623a7976574"}, - {file = "yarl-1.8.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2a1fca9588f360036242f379bfea2b8b44cae2721859b1c56d033adfd5893634"}, - {file = "yarl-1.8.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f37db05c6051eff17bc832914fe46869f8849de5b92dc4a3466cd63095d23dfd"}, - {file = "yarl-1.8.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:77e913b846a6b9c5f767b14dc1e759e5aff05502fe73079f6f4176359d832581"}, - {file = "yarl-1.8.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0978f29222e649c351b173da2b9b4665ad1feb8d1daa9d971eb90df08702668a"}, - {file = "yarl-1.8.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:388a45dc77198b2460eac0aca1efd6a7c09e976ee768b0d5109173e521a19daf"}, - {file = "yarl-1.8.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2305517e332a862ef75be8fad3606ea10108662bc6fe08509d5ca99503ac2aee"}, - {file = "yarl-1.8.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42430ff511571940d51e75cf42f1e4dbdded477e71c1b7a17f4da76c1da8ea76"}, - {file = "yarl-1.8.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3150078118f62371375e1e69b13b48288e44f6691c1069340081c3fd12c94d5b"}, - {file = "yarl-1.8.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c15163b6125db87c8f53c98baa5e785782078fbd2dbeaa04c6141935eb6dab7a"}, - {file = "yarl-1.8.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4d04acba75c72e6eb90745447d69f84e6c9056390f7a9724605ca9c56b4afcc6"}, - {file = "yarl-1.8.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e7fd20d6576c10306dea2d6a5765f46f0ac5d6f53436217913e952d19237efc4"}, - {file = "yarl-1.8.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:75c16b2a900b3536dfc7014905a128a2bea8fb01f9ee26d2d7d8db0a08e7cb2c"}, - {file = "yarl-1.8.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6d88056a04860a98341a0cf53e950e3ac9f4e51d1b6f61a53b0609df342cc8b2"}, - {file = "yarl-1.8.2-cp311-cp311-win32.whl", hash = "sha256:fb742dcdd5eec9f26b61224c23baea46c9055cf16f62475e11b9b15dfd5c117b"}, - {file = "yarl-1.8.2-cp311-cp311-win_amd64.whl", hash = "sha256:8c46d3d89902c393a1d1e243ac847e0442d0196bbd81aecc94fcebbc2fd5857c"}, - {file = "yarl-1.8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:ceff9722e0df2e0a9e8a79c610842004fa54e5b309fe6d218e47cd52f791d7ef"}, - {file = "yarl-1.8.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f6b4aca43b602ba0f1459de647af954769919c4714706be36af670a5f44c9c1"}, - {file = "yarl-1.8.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1684a9bd9077e922300ecd48003ddae7a7474e0412bea38d4631443a91d61077"}, - {file = "yarl-1.8.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ebb78745273e51b9832ef90c0898501006670d6e059f2cdb0e999494eb1450c2"}, - {file = "yarl-1.8.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3adeef150d528ded2a8e734ebf9ae2e658f4c49bf413f5f157a470e17a4a2e89"}, - {file = "yarl-1.8.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57a7c87927a468e5a1dc60c17caf9597161d66457a34273ab1760219953f7f4c"}, - {file = "yarl-1.8.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:efff27bd8cbe1f9bd127e7894942ccc20c857aa8b5a0327874f30201e5ce83d0"}, - {file = "yarl-1.8.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a783cd344113cb88c5ff7ca32f1f16532a6f2142185147822187913eb989f739"}, - {file = "yarl-1.8.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:705227dccbe96ab02c7cb2c43e1228e2826e7ead880bb19ec94ef279e9555b5b"}, - {file = "yarl-1.8.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:34c09b43bd538bf6c4b891ecce94b6fa4f1f10663a8d4ca589a079a5018f6ed7"}, - {file = "yarl-1.8.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a48f4f7fea9a51098b02209d90297ac324241bf37ff6be6d2b0149ab2bd51b37"}, - {file = "yarl-1.8.2-cp37-cp37m-win32.whl", hash = "sha256:0414fd91ce0b763d4eadb4456795b307a71524dbacd015c657bb2a39db2eab89"}, - {file = "yarl-1.8.2-cp37-cp37m-win_amd64.whl", hash = "sha256:d881d152ae0007809c2c02e22aa534e702f12071e6b285e90945aa3c376463c5"}, - {file = "yarl-1.8.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5df5e3d04101c1e5c3b1d69710b0574171cc02fddc4b23d1b2813e75f35a30b1"}, - {file = "yarl-1.8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7a66c506ec67eb3159eea5096acd05f5e788ceec7b96087d30c7d2865a243918"}, - {file = "yarl-1.8.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2b4fa2606adf392051d990c3b3877d768771adc3faf2e117b9de7eb977741229"}, - {file = "yarl-1.8.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e21fb44e1eff06dd6ef971d4bdc611807d6bd3691223d9c01a18cec3677939e"}, - {file = "yarl-1.8.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:93202666046d9edadfe9f2e7bf5e0782ea0d497b6d63da322e541665d65a044e"}, - {file = "yarl-1.8.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fc77086ce244453e074e445104f0ecb27530d6fd3a46698e33f6c38951d5a0f1"}, - {file = "yarl-1.8.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dd68a92cab699a233641f5929a40f02a4ede8c009068ca8aa1fe87b8c20ae3"}, - {file = "yarl-1.8.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1b372aad2b5f81db66ee7ec085cbad72c4da660d994e8e590c997e9b01e44901"}, - {file = "yarl-1.8.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e6f3515aafe0209dd17fb9bdd3b4e892963370b3de781f53e1746a521fb39fc0"}, - {file = "yarl-1.8.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:dfef7350ee369197106805e193d420b75467b6cceac646ea5ed3049fcc950a05"}, - {file = "yarl-1.8.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:728be34f70a190566d20aa13dc1f01dc44b6aa74580e10a3fb159691bc76909d"}, - {file = "yarl-1.8.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:ff205b58dc2929191f68162633d5e10e8044398d7a45265f90a0f1d51f85f72c"}, - {file = "yarl-1.8.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:baf211dcad448a87a0d9047dc8282d7de59473ade7d7fdf22150b1d23859f946"}, - {file = "yarl-1.8.2-cp38-cp38-win32.whl", hash = "sha256:272b4f1599f1b621bf2aabe4e5b54f39a933971f4e7c9aa311d6d7dc06965165"}, - {file = "yarl-1.8.2-cp38-cp38-win_amd64.whl", hash = "sha256:326dd1d3caf910cd26a26ccbfb84c03b608ba32499b5d6eeb09252c920bcbe4f"}, - {file = "yarl-1.8.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f8ca8ad414c85bbc50f49c0a106f951613dfa5f948ab69c10ce9b128d368baf8"}, - {file = "yarl-1.8.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:418857f837347e8aaef682679f41e36c24250097f9e2f315d39bae3a99a34cbf"}, - {file = "yarl-1.8.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ae0eec05ab49e91a78700761777f284c2df119376e391db42c38ab46fd662b77"}, - {file = "yarl-1.8.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:009a028127e0a1755c38b03244c0bea9d5565630db9c4cf9572496e947137a87"}, - {file = "yarl-1.8.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3edac5d74bb3209c418805bda77f973117836e1de7c000e9755e572c1f7850d0"}, - {file = "yarl-1.8.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da65c3f263729e47351261351b8679c6429151ef9649bba08ef2528ff2c423b2"}, - {file = "yarl-1.8.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ef8fb25e52663a1c85d608f6dd72e19bd390e2ecaf29c17fb08f730226e3a08"}, - {file = "yarl-1.8.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bcd7bb1e5c45274af9a1dd7494d3c52b2be5e6bd8d7e49c612705fd45420b12d"}, - {file = "yarl-1.8.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:44ceac0450e648de86da8e42674f9b7077d763ea80c8ceb9d1c3e41f0f0a9951"}, - {file = "yarl-1.8.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:97209cc91189b48e7cfe777237c04af8e7cc51eb369004e061809bcdf4e55220"}, - {file = "yarl-1.8.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:48dd18adcf98ea9cd721a25313aef49d70d413a999d7d89df44f469edfb38a06"}, - {file = "yarl-1.8.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:e59399dda559688461762800d7fb34d9e8a6a7444fd76ec33220a926c8be1516"}, - {file = "yarl-1.8.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d617c241c8c3ad5c4e78a08429fa49e4b04bedfc507b34b4d8dceb83b4af3588"}, - {file = "yarl-1.8.2-cp39-cp39-win32.whl", hash = "sha256:cb6d48d80a41f68de41212f3dfd1a9d9898d7841c8f7ce6696cf2fd9cb57ef83"}, - {file = "yarl-1.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:6604711362f2dbf7160df21c416f81fac0de6dbcf0b5445a2ef25478ecc4c778"}, - {file = "yarl-1.8.2.tar.gz", hash = "sha256:49d43402c6e3013ad0978602bf6bf5328535c48d192304b91b97a3c6790b1562"}, + {file = "yarl-1.9.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8c2ad583743d16ddbdf6bb14b5cd76bf43b0d0006e918809d5d4ddf7bde8dd82"}, + {file = "yarl-1.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:82aa6264b36c50acfb2424ad5ca537a2060ab6de158a5bd2a72a032cc75b9eb8"}, + {file = "yarl-1.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c0c77533b5ed4bcc38e943178ccae29b9bcf48ffd1063f5821192f23a1bd27b9"}, + {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee4afac41415d52d53a9833ebae7e32b344be72835bbb589018c9e938045a560"}, + {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9bf345c3a4f5ba7f766430f97f9cc1320786f19584acc7086491f45524a551ac"}, + {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a96c19c52ff442a808c105901d0bdfd2e28575b3d5f82e2f5fd67e20dc5f4ea"}, + {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:891c0e3ec5ec881541f6c5113d8df0315ce5440e244a716b95f2525b7b9f3608"}, + {file = "yarl-1.9.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c3a53ba34a636a256d767c086ceb111358876e1fb6b50dfc4d3f4951d40133d5"}, + {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:566185e8ebc0898b11f8026447eacd02e46226716229cea8db37496c8cdd26e0"}, + {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:2b0738fb871812722a0ac2154be1f049c6223b9f6f22eec352996b69775b36d4"}, + {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:32f1d071b3f362c80f1a7d322bfd7b2d11e33d2adf395cc1dd4df36c9c243095"}, + {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:e9fdc7ac0d42bc3ea78818557fab03af6181e076a2944f43c38684b4b6bed8e3"}, + {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:56ff08ab5df8429901ebdc5d15941b59f6253393cb5da07b4170beefcf1b2528"}, + {file = "yarl-1.9.2-cp310-cp310-win32.whl", hash = "sha256:8ea48e0a2f931064469bdabca50c2f578b565fc446f302a79ba6cc0ee7f384d3"}, + {file = "yarl-1.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:50f33040f3836e912ed16d212f6cc1efb3231a8a60526a407aeb66c1c1956dde"}, + {file = "yarl-1.9.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:646d663eb2232d7909e6601f1a9107e66f9791f290a1b3dc7057818fe44fc2b6"}, + {file = "yarl-1.9.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aff634b15beff8902d1f918012fc2a42e0dbae6f469fce134c8a0dc51ca423bb"}, + {file = "yarl-1.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a83503934c6273806aed765035716216cc9ab4e0364f7f066227e1aaea90b8d0"}, + {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b25322201585c69abc7b0e89e72790469f7dad90d26754717f3310bfe30331c2"}, + {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:22a94666751778629f1ec4280b08eb11815783c63f52092a5953faf73be24191"}, + {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ec53a0ea2a80c5cd1ab397925f94bff59222aa3cf9c6da938ce05c9ec20428d"}, + {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:159d81f22d7a43e6eabc36d7194cb53f2f15f498dbbfa8edc8a3239350f59fe7"}, + {file = "yarl-1.9.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:832b7e711027c114d79dffb92576acd1bd2decc467dec60e1cac96912602d0e6"}, + {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:95d2ecefbcf4e744ea952d073c6922e72ee650ffc79028eb1e320e732898d7e8"}, + {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d4e2c6d555e77b37288eaf45b8f60f0737c9efa3452c6c44626a5455aeb250b9"}, + {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:783185c75c12a017cc345015ea359cc801c3b29a2966c2655cd12b233bf5a2be"}, + {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:b8cc1863402472f16c600e3e93d542b7e7542a540f95c30afd472e8e549fc3f7"}, + {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:822b30a0f22e588b32d3120f6d41e4ed021806418b4c9f0bc3048b8c8cb3f92a"}, + {file = "yarl-1.9.2-cp311-cp311-win32.whl", hash = "sha256:a60347f234c2212a9f0361955007fcf4033a75bf600a33c88a0a8e91af77c0e8"}, + {file = "yarl-1.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:be6b3fdec5c62f2a67cb3f8c6dbf56bbf3f61c0f046f84645cd1ca73532ea051"}, + {file = "yarl-1.9.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:38a3928ae37558bc1b559f67410df446d1fbfa87318b124bf5032c31e3447b74"}, + {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac9bb4c5ce3975aeac288cfcb5061ce60e0d14d92209e780c93954076c7c4367"}, + {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3da8a678ca8b96c8606bbb8bfacd99a12ad5dd288bc6f7979baddd62f71c63ef"}, + {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:13414591ff516e04fcdee8dc051c13fd3db13b673c7a4cb1350e6b2ad9639ad3"}, + {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf74d08542c3a9ea97bb8f343d4fcbd4d8f91bba5ec9d5d7f792dbe727f88938"}, + {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e7221580dc1db478464cfeef9b03b95c5852cc22894e418562997df0d074ccc"}, + {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:494053246b119b041960ddcd20fd76224149cfea8ed8777b687358727911dd33"}, + {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:52a25809fcbecfc63ac9ba0c0fb586f90837f5425edfd1ec9f3372b119585e45"}, + {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:e65610c5792870d45d7b68c677681376fcf9cc1c289f23e8e8b39c1485384185"}, + {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:1b1bba902cba32cdec51fca038fd53f8beee88b77efc373968d1ed021024cc04"}, + {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:662e6016409828ee910f5d9602a2729a8a57d74b163c89a837de3fea050c7582"}, + {file = "yarl-1.9.2-cp37-cp37m-win32.whl", hash = "sha256:f364d3480bffd3aa566e886587eaca7c8c04d74f6e8933f3f2c996b7f09bee1b"}, + {file = "yarl-1.9.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6a5883464143ab3ae9ba68daae8e7c5c95b969462bbe42e2464d60e7e2698368"}, + {file = "yarl-1.9.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5610f80cf43b6202e2c33ba3ec2ee0a2884f8f423c8f4f62906731d876ef4fac"}, + {file = "yarl-1.9.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b9a4e67ad7b646cd6f0938c7ebfd60e481b7410f574c560e455e938d2da8e0f4"}, + {file = "yarl-1.9.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:83fcc480d7549ccebe9415d96d9263e2d4226798c37ebd18c930fce43dfb9574"}, + {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fcd436ea16fee7d4207c045b1e340020e58a2597301cfbcfdbe5abd2356c2fb"}, + {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84e0b1599334b1e1478db01b756e55937d4614f8654311eb26012091be109d59"}, + {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3458a24e4ea3fd8930e934c129b676c27452e4ebda80fbe47b56d8c6c7a63a9e"}, + {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:838162460b3a08987546e881a2bfa573960bb559dfa739e7800ceeec92e64417"}, + {file = "yarl-1.9.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f4e2d08f07a3d7d3e12549052eb5ad3eab1c349c53ac51c209a0e5991bbada78"}, + {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:de119f56f3c5f0e2fb4dee508531a32b069a5f2c6e827b272d1e0ff5ac040333"}, + {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:149ddea5abf329752ea5051b61bd6c1d979e13fbf122d3a1f9f0c8be6cb6f63c"}, + {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:674ca19cbee4a82c9f54e0d1eee28116e63bc6fd1e96c43031d11cbab8b2afd5"}, + {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:9b3152f2f5677b997ae6c804b73da05a39daa6a9e85a512e0e6823d81cdad7cc"}, + {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5415d5a4b080dc9612b1b63cba008db84e908b95848369aa1da3686ae27b6d2b"}, + {file = "yarl-1.9.2-cp38-cp38-win32.whl", hash = "sha256:f7a3d8146575e08c29ed1cd287068e6d02f1c7bdff8970db96683b9591b86ee7"}, + {file = "yarl-1.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:63c48f6cef34e6319a74c727376e95626f84ea091f92c0250a98e53e62c77c72"}, + {file = "yarl-1.9.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:75df5ef94c3fdc393c6b19d80e6ef1ecc9ae2f4263c09cacb178d871c02a5ba9"}, + {file = "yarl-1.9.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c027a6e96ef77d401d8d5a5c8d6bc478e8042f1e448272e8d9752cb0aff8b5c8"}, + {file = "yarl-1.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3b078dbe227f79be488ffcfc7a9edb3409d018e0952cf13f15fd6512847f3f7"}, + {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59723a029760079b7d991a401386390c4be5bfec1e7dd83e25a6a0881859e716"}, + {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b03917871bf859a81ccb180c9a2e6c1e04d2f6a51d953e6a5cdd70c93d4e5a2a"}, + {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c1012fa63eb6c032f3ce5d2171c267992ae0c00b9e164efe4d73db818465fac3"}, + {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a74dcbfe780e62f4b5a062714576f16c2f3493a0394e555ab141bf0d746bb955"}, + {file = "yarl-1.9.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c56986609b057b4839968ba901944af91b8e92f1725d1a2d77cbac6972b9ed1"}, + {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2c315df3293cd521033533d242d15eab26583360b58f7ee5d9565f15fee1bef4"}, + {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:b7232f8dfbd225d57340e441d8caf8652a6acd06b389ea2d3222b8bc89cbfca6"}, + {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:53338749febd28935d55b41bf0bcc79d634881195a39f6b2f767870b72514caf"}, + {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:066c163aec9d3d073dc9ffe5dd3ad05069bcb03fcaab8d221290ba99f9f69ee3"}, + {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8288d7cd28f8119b07dd49b7230d6b4562f9b61ee9a4ab02221060d21136be80"}, + {file = "yarl-1.9.2-cp39-cp39-win32.whl", hash = "sha256:b124e2a6d223b65ba8768d5706d103280914d61f5cae3afbc50fc3dfcc016623"}, + {file = "yarl-1.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:61016e7d582bc46a5378ffdd02cd0314fb8ba52f40f9cf4d9a5e7dbef88dee18"}, + {file = "yarl-1.9.2.tar.gz", hash = "sha256:04ab9d4b9f587c06d801c2abfe9317b77cdf996c65a90d5e84ecc45010823571"}, ] [package.dependencies] @@ -2614,7 +3536,6 @@ multidict = ">=4.0" name = "zipp" version = "3.15.0" description = "Backport of pathlib-compatible object wrapper for zip files" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2629,4 +3550,4 @@ testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more [metadata] lock-version = "2.0" python-versions = ">=3.8,<4.0" -content-hash = "268867ed882e0ca50ec340b1920179b5a8ae7650ec1a46bed8af8f73556d4f4f" +content-hash = "0bbb12b1f58711c36ba44ebfbacbd8935a11dec3d373303eb9f9b9f49e8f60a8" diff --git a/pyproject.toml b/pyproject.toml index a7fd937c..d6403305 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "Kumiko" -version = "0.7.0" +version = "0.9.0" description = "A multipurpose Discord bot built with freedom and choice in mind" authors = ["No767 <73260931+No767@users.noreply.github.com>"] license = "Apache-2.0" @@ -10,32 +10,48 @@ python = ">=3.8,<4.0" python-dotenv = "^1.0.0" aiodns = "^3.0.0" Brotli = "^1.0.9" -orjson = "^3.8.9" +orjson = "^3.9.2" asyncpraw = "^7.7.0" -uvloop = "^0.17.0" -gql = { extras = ["aiohttp"], version = "^3.4.0" } -rin-exceptions = "^1.0.3" -better-ipc = "^2.0.1" -prisma = "^0.8.2" -redis = {extras = ["hiredis"], version = "^4.5.4"} +uvloop = {markers = "sys_platform != \"win32\"", version = "^0.17.0"} +gql = { extras = ["aiohttp"], version = "^3.4.1" } +better-ipc = "^2.0.3" +redis = {extras = ["hiredis"], version = "^4.6.0"} ciso8601 = "^2.3.0" faust-cchardet = "^2.1.18" -discord-py = {extras = ["voice"], version = "^2.2.2"} +discord-py = {extras = ["voice"], version = "^2.3.1"} discord-ext-menus = {git = "https://github.com/Rapptz/discord-ext-menus", rev = "8686b5d1bbc1d3c862292eb436ab630d6e9c9b53"} +asyncpg = "^0.28.0" +asyncpg-trek = "^0.3.1" +lru-dict = "^1.2.0" +psutil = "^5.9.5" +winloop = {markers = "sys_platform == \"win32\"", version = "^0.0.6"} +anyio = "^3.7.1" [tool.poetry.group.test.dependencies] -pytest = "^7.2.2" +pytest = "^7.4.0" pytest-asyncio = "^0.21.0" -nox = "^2022.11.21" -nox-poetry = "^1.0.2" -pytest-cov = "^4.0.0" +nox = "^2023.4.22" +nox-poetry = "^1.0.3" +pytest-cov = "^4.1.0" +dpytest = "^0.7.0" [tool.poetry.group.dev.dependencies] -pre-commit = "^3.2.1" -pyinstrument = "^4.4.0" -pyright = "^1.1.301" +pre-commit = "^3.3.3" +pyinstrument = "^4.5.0" +pyright = "^1.1.316" watchfiles = "^0.19.0" jishaku = "^2.5.1" +ruff = "^0.0.277" + + +[tool.poetry.group.docs.dependencies] +sphinx = "^7.0.1" +myst-parser = "^2.0.0" +sphinx-autobuild = "^2021.3.14" +furo = "^2023.5.20" +sphinxext-opengraph = "^0.8.2" +sphinx-copybutton = "^0.5.2" + [tool.isort] profile = 'black' @@ -50,6 +66,9 @@ reportMissingTypeStubs = false [tool.bandit] skips = ["B311", "B101"] +[tool.ruff] +ignore = ["E501"] + [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" diff --git a/schema.prisma b/schema.prisma deleted file mode 100644 index efcd2b36..00000000 --- a/schema.prisma +++ /dev/null @@ -1,71 +0,0 @@ -datasource db { - provider = "postgresql" - url = env("DATABASE_URL") -} - -generator client { - provider = "prisma-client-py" - recursive_type_depth = 5 -} - -model User { - id BigInt @id @unique - name String - rank Int @default(0) - petals Int @default(0) - date_joined DateTime @default(now()) - marketplace Marketplace? // A user can also optionally own a marketplace - inv Inv[] // Give the user a default inventory that has one item that is worth 0 petals - // quests Quest[] // This default quest may be used to get the user to explore the marketplace - // QuestClaimer QuestClaimer? // A user can only claim one quest -} - -// model QuestClaimer { -// id BigInt @id @unique -// user User @relation(fields: [user_id], references: [id]) -// user_id BigInt @unique -// claimed_at DateTime -// quest Quest[] -// } - -model Inv { - id BigInt @id @unique - user User @relation(fields: [user_id], references: [id]) - user_id BigInt -} - -// model Quest { -// id String @id @default(uuid()) -// author User @relation(fields: [author_id], references: [id]) -// author_id BigInt -// claimer QuestClaimer @relation(fields: [claimer_id], references: [id]) -// claimer_id BigInt -// name String -// description String @db.Text -// reward Int @default(0) -// active Boolean @default(true) -// claimed Boolean @default(false) -// date_created DateTime @default(now()) -// date_completed DateTime? -// end_datetime DateTime? -// } - -model Item { - id String @id @default(uuid()) - marketplace Marketplace @relation(fields: [marketplace_id], references: [id]) - marketplace_id String - name String - description String @db.Text - price Float - amount Int -} - -model Marketplace { - id String @id @default(uuid()) - name String - description String @db.Text - date_created DateTime @default(now()) - owner User @relation(fields: [owner_id], references: [id]) - owner_id BigInt @unique - items Item[] -} diff --git a/tests/cache_utils/test_event_logs_utils.py b/tests/cache_utils/test_event_logs_utils.py new file mode 100644 index 00000000..1a79390d --- /dev/null +++ b/tests/cache_utils/test_event_logs_utils.py @@ -0,0 +1,37 @@ +import sys +from pathlib import Path + +import pytest +from redis.asyncio.connection import ConnectionPool + +path = Path(__file__).parents[2].joinpath("Bot") +sys.path.append(str(path)) + +from Libs.cache import KumikoCache +from Libs.cog_utils.events_log import set_or_update_cache + + +@pytest.fixture(scope="session") +def get_data(): + return {"id": 123, "channel_id": 2342634575000} + + +@pytest.mark.asyncio +async def test_set_or_update_cache(get_data): + connPool = ConnectionPool() + key = "cache:kumiko:123:config" + cache = KumikoCache(connPool) + await set_or_update_cache(key=key, redis_pool=connPool, data=get_data) + res = await cache.getJSONCache(key=key) + assert res == get_data + + +@pytest.mark.asyncio +async def test_cached_set_or_update(get_data): + connPool = ConnectionPool() + key = "cache:kumiko:1234:config" + cache = KumikoCache(connPool) + res = await cache.setJSONCache(key=key, value=get_data) + await set_or_update_cache(key=key, redis_pool=connPool, data=get_data) + res = await cache.getJSONCache(key=key) + assert res == get_data and res["channel_id"] == get_data["channel_id"] # type: ignore diff --git a/tests/db/test_db_conn.py b/tests/db/test_db_conn.py index 4b123ffd..eae96412 100644 --- a/tests/db/test_db_conn.py +++ b/tests/db/test_db_conn.py @@ -1,18 +1,29 @@ +import os import sys from pathlib import Path import pytest +from dotenv import load_dotenv path = Path(__file__).parents[2].joinpath("Bot") sys.path.append(str(path)) +load_dotenv() -from Libs.utils.postgresql import PrismaClientSession -from prisma.models import User +import asyncpg +from Libs.utils.postgresql import ensureOpenPostgresConn + + +@pytest.fixture(scope="session") +def get_uri(): + pg_uri = os.getenv("POSTGRES_URI") + if pg_uri is None: + return "postgresql://postgres:postgres@localhost:5432/test" + return pg_uri @pytest.mark.asyncio -async def test_prisma_client_session(): - async with PrismaClientSession(): - res = await User.prisma().find_first(where={"id": 454357482102587393}) - assert (res is None) or (isinstance(res, User)) # nosec +async def test_open_postgres_conn(get_uri): + async with asyncpg.create_pool(dsn=get_uri) as pool: + res = await ensureOpenPostgresConn(conn_pool=pool) + assert res is True diff --git a/tests/exceptions/test_exceptions.py b/tests/exceptions/test_exceptions.py index 28f0e8e2..71feb513 100644 --- a/tests/exceptions/test_exceptions.py +++ b/tests/exceptions/test_exceptions.py @@ -12,6 +12,7 @@ KumikoException, NoItemsError, NotFoundError, + ValidationError, ) @@ -64,3 +65,12 @@ def test_not_found_error(): and (e.value.status == 404) and ("Resource or endpoint not found" in str(e.value)) ) + + +def test_validation_error(): + with pytest.raises(ValidationError) as e: + raise ValidationError("There is an validation error") + + assert (e.type == ValidationError) and ( + "There is an validation error" in str(e.value) + ) diff --git a/tests/redis/test_cache_deco.py b/tests/redis/test_cache_deco.py index 4293935e..cca18f5b 100644 --- a/tests/redis/test_cache_deco.py +++ b/tests/redis/test_cache_deco.py @@ -1,36 +1,74 @@ -import sys -from pathlib import Path - -import pytest - -path = Path(__file__).parents[2].joinpath("Bot") -sys.path.append(str(path)) - -from Libs.cache import cached, cachedJson -from redis.asyncio.connection import ConnectionPool - - -@pytest.mark.asyncio -async def test_cache_deco(): - connPool = ConnectionPool(max_connections=25) - - @cached(connection_pool=connPool, command_key=None) - async def testFunc(): - return "Hello World" - - res = await testFunc() - assert (await testFunc() == "Hello World") and isinstance(res, str) # nosec - - -@pytest.mark.asyncio -async def test_cache_deco_json(): - connPool = ConnectionPool(max_connections=25) - - @cachedJson(connection_pool=connPool, command_key=None) - async def testFuncJSON(): - return {"message": "Hello World"} - - res = await testFuncJSON() - assert (await testFuncJSON() == {"message": "Hello World"}) and isinstance( # nosec - res, dict - ) +import sys +from pathlib import Path + +import pytest + +path = Path(__file__).parents[2].joinpath("Bot") +sys.path.append(str(path)) + +from Libs.cache import cache, cacheJson +from redis.asyncio.connection import ConnectionPool + + +@pytest.mark.asyncio +async def test_cache_deco(): + connPool = ConnectionPool(max_connections=25) + + @cache() + async def testFunc( + id=1235, redis_pool=ConnectionPool.from_url("redis://localhost:6379/0") + ): + return "Hello World" + + res = await testFunc(1235, connPool) + assert ( + await testFunc(1235, connPool) == "Hello World".encode("utf-8") + ) and isinstance( + res, str + ) # nosec + + +@pytest.mark.asyncio +async def test_cache_deco_json(): + connPool = ConnectionPool(max_connections=25) + + @cacheJson() + async def testFuncJSON( + id=182348478, redis_pool=ConnectionPool.from_url("redis://localhost:6379/0") + ): + return {"message": "Hello World"} + + res = await testFuncJSON(182348478, connPool) + assert ( + await testFuncJSON(182348478, connPool) == {"message": "Hello World"} + ) and isinstance( # nosec + res, dict + ) + + +# the results of these should be the types returned +# within the decos, there is code that refuses to cache if the return type is not what is needed +@pytest.mark.asyncio +async def test_cache_deco_invalid(): + connPool = ConnectionPool() + + @cache() + async def testFuncInvalid(id=2345973453, redis_pool=ConnectionPool()): + return 23464354 + + res = await testFuncInvalid(2345973453, connPool) + assert await testFuncInvalid(2345973453, connPool) == 23464354 and isinstance( + res, int + ) + + +@pytest.mark.asyncio +async def test_cache_deco_json_invalid(): + connPool = ConnectionPool() + + @cacheJson() + async def testFuncJSONInvalid(id=2345973453, redis_pool=ConnectionPool()): + return [1, 2, 3, 4, 5] + + res = await testFuncJSONInvalid(2345973453, connPool) + assert 1 in res and isinstance(res, list) # type: ignore diff --git a/tests/redis/test_global_cache.py b/tests/redis/test_global_cache.py index 22a84f7d..cb03acc6 100644 --- a/tests/redis/test_global_cache.py +++ b/tests/redis/test_global_cache.py @@ -4,15 +4,33 @@ path = Path(__file__).parents[2].joinpath("Bot") sys.path.append(str(path)) -from Libs.cache import kumikoCP +import pytest +from Libs.cache import KumikoCPManager from redis.asyncio.connection import ConnectionPool +REDIS_URI = "redis://localhost:6379/0" + + +@pytest.mark.asyncio +async def test_cpm(): + async with KumikoCPManager(uri=REDIS_URI) as cpm: + assert isinstance(cpm, ConnectionPool) + + +def test_creation_cp(): + kumikoCP = KumikoCPManager(uri=REDIS_URI) + connPool = kumikoCP.createPool() + assert isinstance(connPool, ConnectionPool) + def test_get_cp(): + kumikoCP = KumikoCPManager(uri=REDIS_URI) connPool = kumikoCP.getConnPool() assert isinstance(connPool, ConnectionPool) -def test_creation_cp(): - connPool = kumikoCP.createConnPool() - assert isinstance(connPool, ConnectionPool) +def test_created_cp(): + kumikoCP = KumikoCPManager(uri=REDIS_URI) + kumikoCP.createPool() + newConnPool = kumikoCP.getConnPool() + assert isinstance(newConnPool, ConnectionPool) diff --git a/tests/redis/test_mem_cache.py b/tests/redis/test_mem_cache.py deleted file mode 100644 index d1f4014b..00000000 --- a/tests/redis/test_mem_cache.py +++ /dev/null @@ -1,55 +0,0 @@ -import sys -from pathlib import Path - -import pytest - -path = Path(__file__).parents[2].joinpath("Bot") -sys.path.append(str(path)) - -from Libs.cache import MemoryCache -from redis.connection import ConnectionPool - - -@pytest.fixture(autouse=True, scope="session") -def load_conn_pool() -> ConnectionPool: - return ConnectionPool().from_url("redis://localhost:6379/0") - - -def test_mem_cache_set(load_conn_pool): - memCache = MemoryCache() - memCache.set(key="main", value=load_conn_pool) - res = memCache.get(key="main") - assert isinstance(res, ConnectionPool) # nosec - - -def test_mem_cache_add(load_conn_pool): - memCache = MemoryCache() - memCache.add(key="main4", value=load_conn_pool) - res = memCache.get(key="main4") - assert isinstance(res, ConnectionPool) # nosec - - -def test_mem_cache_add_error(load_conn_pool): - with pytest.raises(ValueError) as execinfo: - memCache = MemoryCache() - memCache.add(key="main", value=load_conn_pool) - memCache.add(key="main", value=load_conn_pool) - assert ( # nosec - str(execinfo.value) - == "Key main already exists. Please use .set to update it" # nosec - ) # nosec - - -def test_mem_cache_delete_return(load_conn_pool): - memCache = MemoryCache() - memCache.set(key="main3", value=load_conn_pool) - res = memCache.delete(key="main3") - assert isinstance(res, ConnectionPool) # nosec - - -def test_mem_cache_delete(load_conn_pool): - memCache = MemoryCache() - memCache.set(key="main2", value=load_conn_pool) - memCache.delete(key="main2") - currCache = memCache.getAll() - assert len(currCache) == 0 # nosec diff --git a/tests/redis/test_redis_cache.py b/tests/redis/test_redis_cache.py index b965063b..0b0bbb0a 100644 --- a/tests/redis/test_redis_cache.py +++ b/tests/redis/test_redis_cache.py @@ -42,3 +42,23 @@ async def test_key_exists(): await cache.setBasicCache(key=key, value=DATA) res = await cache.cacheExists(key=key) assert res is True # nosec + + +@pytest.mark.asyncio +async def test_get_json_cache_if_none(): + key = CommandKeyBuilder(id=123564343, command="ayo_what_mate") + connPool = ConnectionPool().from_url("redis://localhost:6379/0") + cache = KumikoCache(connection_pool=connPool) + res = await cache.getJSONCache(key=key) + assert res is None + + +@pytest.mark.asyncio +async def test_delete_json_cache(): + key = CommandKeyBuilder(id=123564343453453, command="nicer") + connPool = ConnectionPool().from_url("redis://localhost:6379/0") + cache = KumikoCache(connection_pool=connPool) + await cache.setJSONCache(key=key, value=DATA) + await cache.deleteJSONCache(key=key) + res = await cache.cacheExists(key=key) + assert res is False diff --git a/tests/redis/test_redis_conn.py b/tests/redis/test_redis_conn.py index e91384b2..994e44f8 100644 --- a/tests/redis/test_redis_conn.py +++ b/tests/redis/test_redis_conn.py @@ -7,22 +7,11 @@ path = Path(__file__).parents[2].joinpath("Bot") sys.path.append(str(path)) -from Libs.cache import MemoryCache -from Libs.utils.redis import pingRedis, redisCheck +from Libs.utils.redis import ensureOpenRedisConn @pytest.mark.asyncio -async def test_redis_ping(): - memCache = MemoryCache() - memCache.add( - key="main", value=ConnectionPool().from_url("redis://localhost:6379/0") - ) - connPool = memCache.get(key="main") - res = await pingRedis(connection_pool=connPool) - assert res is True # nosec - - -@pytest.mark.asyncio -async def test_redis_check(): - res = await redisCheck() +async def test_open_conn(): + connPool = ConnectionPool().from_url("redis://localhost:6379/0") + res = await ensureOpenRedisConn(redis_pool=connPool) assert res is True # nosec diff --git a/tests/redis/test_redis_conn_pool_cache.py b/tests/redis/test_redis_conn_pool_cache.py deleted file mode 100644 index 26a5889f..00000000 --- a/tests/redis/test_redis_conn_pool_cache.py +++ /dev/null @@ -1,47 +0,0 @@ -import sys -from pathlib import Path - -import redis - -path = Path(__file__).parents[2].joinpath("Bot") -sys.path.append(str(path)) - -from Libs.cache import MemoryCache -from redis.connection import ConnectionPool - - -def test_pool_mem_cache_add(): - connPool = ConnectionPool(max_connections=25) - memCache = MemoryCache() - memCache.add(key="conn1", value=connPool) - assert isinstance(memCache.get(key="conn1"), ConnectionPool) # nosec - - -def test_pool_mem_cache_delete(): - connPool = ConnectionPool(max_connections=25) - memCache = MemoryCache() - memCache.add(key="conn1", value=connPool) - memCache.delete(key="conn1") - assert memCache.get(key="conn1") is None # nosec - - -def test_pool_mem_cache_set(): - connPool = ConnectionPool(max_connections=25) - memCache = MemoryCache() - memCache.set(key="conn2", value=connPool) - memCache.set(key="conn1", value=connPool) - assert ( # nosec - memCache.get(key="conn1") is not None and memCache.get(key="conn2") is not None - ) - - -def test_pool_mem_integration(): - connPool = ConnectionPool(max_connections=25) - memCache = MemoryCache() - memCache.add(key="conn1", value=connPool) - currPool = memCache.get(key="conn1") - r = redis.Redis(connection_pool=currPool) - r.set("foo", "bar") - res = r.get("foo") - r.close() - assert res == b"bar" # nosec diff --git a/tests/utils/test_backoff.py b/tests/utils/test_backoff.py deleted file mode 100644 index c164c576..00000000 --- a/tests/utils/test_backoff.py +++ /dev/null @@ -1,30 +0,0 @@ -import sys -from pathlib import Path - -import pytest - -path = Path(__file__).parents[2].joinpath("Bot") -sys.path.append(str(path)) - -from Libs.utils import backoff - - -@pytest.fixture(scope="session") -def backoff_values(): - return (5, 0) - - -def test_backoff(backoff_values): - backoff_sec, backoff_sec_index = backoff_values - sleepAmt = backoff(backoff_sec, backoff_sec_index) - assert sleepAmt <= 6.0 and sleepAmt >= 5.0 # nosec - - -def test_backoff_loop(backoff_values): - backoff_sec, backoff_sec_index = backoff_values - backoffTime = 0 - for _ in range(5): - backoffTime = backoff(backoff_sec, backoff_sec_index) - backoff_sec_index += 1 - - assert backoffTime == 60.0 # nosec diff --git a/tests/utils/test_converters.py b/tests/utils/test_converters.py new file mode 100644 index 00000000..b6df7a2c --- /dev/null +++ b/tests/utils/test_converters.py @@ -0,0 +1,83 @@ +import sys +from pathlib import Path + +path = Path(__file__).parents[2].joinpath("Bot") +sys.path.append(str(path)) +import discord +import discord.ext.test as dpytest +import pytest +import pytest_asyncio +from discord.ext import commands +from Libs.utils import PrefixConverter + + +class PrefixCog(commands.Cog): + def __init__(self, bot): + self.bot = bot + + @commands.command(name="prefix") + async def prefix(self, ctx, prefix: PrefixConverter): + await ctx.send(f"{prefix}") + + +@pytest_asyncio.fixture +async def bot(): + # Setup + intents = discord.Intents.default() + intents.members = True + intents.message_content = True + b = commands.Bot(command_prefix=">", intents=intents) + await b._async_setup_hook() # setup the loop + await b.add_cog(PrefixCog(b)) + + dpytest.configure(b) + + yield b + + # Teardown + await dpytest.empty_queue() + + +@pytest.mark.asyncio +async def test_valid_prefix(bot): + await dpytest.message(">prefix !") + assert dpytest.verify().message().content("!") + + +@pytest.mark.asyncio +async def test_invalid_prefix(bot): + finalStr = "" + for _ in range(103): + finalStr += "a" + with pytest.raises(commands.BadArgument) as e: + await dpytest.message(f">prefix {finalStr}") + # assert dpytest.verify().message().content("!") + assert e.type == commands.BadArgument and "That prefix is too long." in str( + e.value + ) + + +@pytest.mark.asyncio +async def test_invalid_ping_prefix(bot): + user_id = bot.user.id + finalStr = f"<@{user_id}>" + + with pytest.raises(commands.BadArgument) as e: + await dpytest.message(f">prefix {finalStr}") + assert ( + e.type == commands.BadArgument + and "That is a reserved prefix already in use." in str(e.value) + ) + + +@pytest.mark.asyncio +async def test_invalid_mention_prefix(bot): + user_id = bot.user.id + finalStr = f"<@!{user_id}>" + + with pytest.raises(commands.BadArgument) as e: + await dpytest.message(f">prefix {finalStr}") + assert ( + e.type == commands.BadArgument + and "That is a reserved prefix already in use." in str(e.value) + ) diff --git a/tests/utils/test_greedy_formatter.py b/tests/utils/test_greedy_formatter.py new file mode 100644 index 00000000..7241a280 --- /dev/null +++ b/tests/utils/test_greedy_formatter.py @@ -0,0 +1,25 @@ +import sys +from pathlib import Path + +path = Path(__file__).parents[2].joinpath("Bot") +sys.path.append(str(path)) + +from Libs.utils import formatGreedy + + +def test_format_greedy_3plus(): + assert (formatGreedy(["a", "b", "c"]) == "a, b, and c") and ( + formatGreedy(["a", "b", "c", "d"]) == "a, b, c, and d" + ) + + +def test_format_greedy_2(): + assert formatGreedy(["a", "b"]) == "a and b" + + +def test_format_greedy_1(): + assert formatGreedy(["a"]) == "a" + + +def test_format_greedy_empty(): + assert formatGreedy([]) == "" diff --git a/tests/utils/test_parse_subreddits.py b/tests/utils/test_parse_subreddits.py index 39f8a9ef..e8f3ef5f 100644 --- a/tests/utils/test_parse_subreddits.py +++ b/tests/utils/test_parse_subreddits.py @@ -13,3 +13,7 @@ def test_rslash_egg_irl(): def test_egg_irl(): assert parseSubreddit("egg_irl") == "egg_irl" + + +def test_none_subreddit(): + assert parseSubreddit(subreddit=None) == "all"