aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Numerlor <[email protected]>2022-09-18 19:10:24 +0200
committerGravatar Numerlor <[email protected]>2022-09-18 19:14:08 +0200
commitb6f033e7f5fcdb827e7fed29a4ed21108e54a414 (patch)
tree99be74f8d90217e8d2dbeba442afce7ea04d5de6
parentensure tuples from pos arg and kwarg tuples are differentiated (diff)
parentMerge pull request #138 from python-discord/bump-d.py (diff)
Merge remote-tracking branch 'upstream/main' into no-duplicate-deco
-rw-r--r--.dockerignore9
-rw-r--r--.github/workflows/docs.yaml17
-rw-r--r--.github/workflows/lint-test.yaml10
-rw-r--r--.gitignore3
-rw-r--r--botcore/_bot.py21
-rw-r--r--botcore/site_api.py5
-rw-r--r--botcore/utils/__init__.py16
-rw-r--r--botcore/utils/_monkey_patches.py5
-rw-r--r--botcore/utils/commands.py38
-rw-r--r--botcore/utils/cooldown.py3
-rw-r--r--botcore/utils/function.py3
-rw-r--r--botcore/utils/interactions.py98
-rw-r--r--botcore/utils/members.py9
-rw-r--r--botcore/utils/regex.py7
-rw-r--r--botcore/utils/scheduling.py32
-rw-r--r--dev/Dockerfile23
-rw-r--r--dev/README.rst53
-rw-r--r--dev/bot/__init__.py24
-rw-r--r--dev/bot/__main__.py34
-rw-r--r--dev/bot/cog.py33
-rw-r--r--dev/docker-compose.yaml27
-rw-r--r--docker-compose.yaml80
-rw-r--r--docs/changelog.rst54
-rw-r--r--docs/development.rst2
-rw-r--r--docs/index.rst2
-rw-r--r--docs/utils.py1
-rw-r--r--poetry.lock380
-rw-r--r--pyproject.toml33
-rw-r--r--tests/botcore/utils/test_regex.py69
-rw-r--r--tox.ini2
30 files changed, 702 insertions, 391 deletions
diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 00000000..9fb3df72
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,9 @@
+*
+
+!botcore/
+!docs/
+!tests/
+
+!pyproject.toml
+!poetry.lock
+!tox.ini
diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml
index 5254d524..42c9e742 100644
--- a/.github/workflows/docs.yaml
+++ b/.github/workflows/docs.yaml
@@ -11,7 +11,6 @@ concurrency:
group: docs-deployment-${{ github.ref }}
cancel-in-progress: true
-
jobs:
latest-build:
# We only need to verify that the docs build with no warnings here
@@ -22,16 +21,12 @@ jobs:
- uses: actions/checkout@v2
- name: Install Python Dependencies
- uses: HassanAbouelela/actions/setup-python@setup-python_v1.1.0
+ uses: HassanAbouelela/actions/setup-python@setup-python_v1.3.1
with:
dev: true
- python_version: 3.9
+ python_version: "3.10"
install_args: "--extras async-rediscache"
- # Undeclared dependency for `releases`... whoops
- # https://github.com/bitprophet/releases/pull/82
- - run: pip install six
-
- name: Generate HTML Site
run: sphinx-build -nW -j auto -b html docs docs/build
@@ -51,16 +46,12 @@ jobs:
fetch-depth: 0 # We need to check out the entire repository to find all tags
- name: Install Python Dependencies
- uses: HassanAbouelela/actions/setup-python@setup-python_v1.1.0
+ uses: HassanAbouelela/actions/setup-python@setup-python_v1.3.1
with:
dev: true
- python_version: 3.9
+ python_version: "3.10"
install_args: "--extras async-rediscache"
- # Undeclared dependency for `releases`... whoops
- # https://github.com/bitprophet/releases/pull/82
- - run: pip install six
-
- name: Build All Doc Versions
run: sphinx-multiversion docs docs/build -n -j auto
env:
diff --git a/.github/workflows/lint-test.yaml b/.github/workflows/lint-test.yaml
index a51623cb..e9821677 100644
--- a/.github/workflows/lint-test.yaml
+++ b/.github/workflows/lint-test.yaml
@@ -14,21 +14,19 @@ jobs:
lint:
name: Run Linting & Test Suites
runs-on: ubuntu-latest
-
steps:
- name: Install Python Dependencies
- uses: HassanAbouelela/actions/setup-python@setup-python_v1.1.0
+ uses: HassanAbouelela/actions/setup-python@setup-python_v1.3.1
with:
# Set dev=true to run pre-commit which is a dev dependency
dev: true
- python_version: 3.9
+ python_version: "3.10"
install_args: "--extras async-rediscache"
# We will not run `flake8` here, as we will use a separate flake8
- # action. As pre-commit does not support user installs, we set
- # PIP_USER=0 to not do a user install.
+ # action.
- name: Run pre-commit hooks
- run: export PIP_USER=0; SKIP=flake8 pre-commit run --all-files
+ run: SKIP=flake8 pre-commit run --all-files
# Run flake8 and have it format the linting errors in the format of
# the GitHub Workflow command to register error annotations. This
diff --git a/.gitignore b/.gitignore
index 46a561fd..4e410cd3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -134,3 +134,6 @@ dmypy.json
# Vscode
.vscode
+
+# Development & prototyping environment
+/bot/
diff --git a/botcore/_bot.py b/botcore/_bot.py
index e9eba5c5..bb25c0b5 100644
--- a/botcore/_bot.py
+++ b/botcore/_bot.py
@@ -51,7 +51,7 @@ class BotBase(commands.Bot):
Initialise the base bot instance.
Args:
- guild_id: The ID of the guild use for :func:`wait_until_guild_available`.
+ guild_id: The ID of the guild used for :func:`wait_until_guild_available`.
allowed_roles: A list of role IDs that the bot is allowed to mention.
http_session (aiohttp.ClientSession): The session to use for the bot.
redis_session: The `async_rediscache.RedisSession`_ to use for the bot.
@@ -197,7 +197,7 @@ class BotBase(commands.Bot):
if not guild.roles or not guild.members or not guild.channels:
msg = "Guild available event was dispatched but the cache appears to still be empty!"
- self.log_to_dev_log(msg)
+ await self.log_to_dev_log(msg)
return
self._guild_available.set()
@@ -234,14 +234,16 @@ class BotBase(commands.Bot):
)
self.http.connector = self._connector
- if getattr(self, "redis_session", False) and self.redis_session.closed:
+ if getattr(self, "redis_session", False) and not self.redis_session.valid:
# If the RedisSession was somehow closed, we try to reconnect it
# here. Normally, this shouldn't happen.
- await self.redis_session.connect()
+ await self.redis_session.connect(ping=True)
- # Create dummy stats client first, in case `statsd_url` is unreachable within `_connect_statsd()`
+ # Create dummy stats client first, in case `statsd_url` is unreachable or None
self.stats = AsyncStatsClient(loop, "127.0.0.1")
- self._connect_statsd(self.statsd_url, loop)
+ if self.statsd_url:
+ self._connect_statsd(self.statsd_url, loop)
+
await self.stats.create_socket()
try:
@@ -249,7 +251,7 @@ class BotBase(commands.Bot):
except Exception as e:
raise StartupError(e)
- async def ping_services() -> None:
+ async def ping_services(self) -> None:
"""Ping all required services on setup to ensure they are up before starting."""
...
@@ -279,11 +281,8 @@ class BotBase(commands.Bot):
if self._resolver:
await self._resolver.close()
- if self.stats._transport:
+ if getattr(self.stats, "_transport", False):
self.stats._transport.close()
- if getattr(self, "redis_session", False):
- await self.redis_session.close()
-
if self._statsd_timerhandle:
self._statsd_timerhandle.cancel()
diff --git a/botcore/site_api.py b/botcore/site_api.py
index dbdf4f3b..44309f9d 100644
--- a/botcore/site_api.py
+++ b/botcore/site_api.py
@@ -26,7 +26,7 @@ class ResponseCodeError(ValueError):
Args:
response (:obj:`aiohttp.ClientResponse`): The response object from the request.
response_json: The JSON response returned from the request, if any.
- request_text: The text of the request, if any.
+ response_text: The text of the request, if any.
"""
self.status = response.status
self.response_json = response_json or {}
@@ -76,7 +76,8 @@ class APIClient:
"""Close the aiohttp session."""
await self.session.close()
- async def maybe_raise_for_status(self, response: aiohttp.ClientResponse, should_raise: bool) -> None:
+ @staticmethod
+ async def maybe_raise_for_status(response: aiohttp.ClientResponse, should_raise: bool) -> None:
"""
Raise :exc:`ResponseCodeError` for non-OK response if an exception should be raised.
diff --git a/botcore/utils/__init__.py b/botcore/utils/__init__.py
index cfc5e99d..09aaa45f 100644
--- a/botcore/utils/__init__.py
+++ b/botcore/utils/__init__.py
@@ -1,6 +1,18 @@
"""Useful utilities and tools for Discord bot development."""
-from botcore.utils import _monkey_patches, caching, channel, cooldown, function, logging, members, regex, scheduling
+from botcore.utils import (
+ _monkey_patches,
+ caching,
+ channel,
+ commands,
+ cooldown,
+ function,
+ interactions,
+ logging,
+ members,
+ regex,
+ scheduling,
+)
from botcore.utils._extensions import unqualify
@@ -24,8 +36,10 @@ __all__ = [
apply_monkey_patches,
caching,
channel,
+ commands,
cooldown,
function,
+ interactions,
logging,
members,
regex,
diff --git a/botcore/utils/_monkey_patches.py b/botcore/utils/_monkey_patches.py
index f2c6c100..c2f8aa10 100644
--- a/botcore/utils/_monkey_patches.py
+++ b/botcore/utils/_monkey_patches.py
@@ -1,6 +1,7 @@
"""Contains all common monkey patches, used to alter discord to fit our needs."""
import logging
+import typing
from datetime import datetime, timedelta
from functools import partial, partialmethod
@@ -46,9 +47,9 @@ def _patch_typing() -> None:
log.debug("Patching send_typing, which should fix things breaking when Discord disables typing events. Stay safe!")
original = http.HTTPClient.send_typing
- last_403 = None
+ last_403: typing.Optional[datetime] = None
- async def honeybadger_type(self, channel_id: int) -> None: # noqa: ANN001
+ async def honeybadger_type(self: http.HTTPClient, channel_id: int) -> None:
nonlocal last_403
if last_403 and (datetime.utcnow() - last_403) < timedelta(minutes=5):
log.warning("Not sending typing event, we got a 403 less than 5 minutes ago.")
diff --git a/botcore/utils/commands.py b/botcore/utils/commands.py
new file mode 100644
index 00000000..7afd8137
--- /dev/null
+++ b/botcore/utils/commands.py
@@ -0,0 +1,38 @@
+from typing import Optional
+
+from discord import Message
+from discord.ext.commands import BadArgument, Context, clean_content
+
+
+async def clean_text_or_reply(ctx: Context, text: Optional[str] = None) -> str:
+ """
+ Cleans a text argument or replied message's content.
+
+ Args:
+ ctx: The command's context
+ text: The provided text argument of the command (if given)
+
+ Raises:
+ :exc:`discord.ext.commands.BadArgument`
+ `text` wasn't provided and there's no reply message / reply message content.
+
+ Returns:
+ The cleaned version of `text`, if given, else replied message.
+ """
+ clean_content_converter = clean_content(fix_channel_mentions=True)
+
+ if text:
+ return await clean_content_converter.convert(ctx, text)
+
+ if (
+ (replied_message := getattr(ctx.message.reference, "resolved", None)) # message has a cached reference
+ and isinstance(replied_message, Message) # referenced message hasn't been deleted
+ ):
+ if not (content := ctx.message.reference.resolved.content):
+ # The referenced message doesn't have a content (e.g. embed/image), so raise error
+ raise BadArgument("The referenced message doesn't have a text content.")
+
+ return await clean_content_converter.convert(ctx, content)
+
+ # No text provided, and either no message was referenced or we can't access the content
+ raise BadArgument("Couldn't find text to clean. Provide a string or reply to a message to use its content.")
diff --git a/botcore/utils/cooldown.py b/botcore/utils/cooldown.py
index b9149b48..ee65033d 100644
--- a/botcore/utils/cooldown.py
+++ b/botcore/utils/cooldown.py
@@ -7,10 +7,9 @@ import random
import time
import typing
import weakref
-from collections.abc import Awaitable, Hashable, Iterable
+from collections.abc import Awaitable, Callable, Hashable, Iterable
from contextlib import suppress
from dataclasses import dataclass
-from typing import Callable # sphinx-autodoc-typehints breaks with collections.abc.Callable
import discord
from discord.ext.commands import CommandError, Context
diff --git a/botcore/utils/function.py b/botcore/utils/function.py
index e8d24e90..0e90d4c5 100644
--- a/botcore/utils/function.py
+++ b/botcore/utils/function.py
@@ -5,8 +5,7 @@ from __future__ import annotations
import functools
import types
import typing
-from collections.abc import Sequence, Set
-from typing import Callable # sphinx-autodoc-typehints breaks with collections.abc.Callable
+from collections.abc import Callable, Sequence, Set
__all__ = ["command_wraps", "GlobalNameConflictError", "update_wrapper_globals"]
diff --git a/botcore/utils/interactions.py b/botcore/utils/interactions.py
new file mode 100644
index 00000000..26bd92f2
--- /dev/null
+++ b/botcore/utils/interactions.py
@@ -0,0 +1,98 @@
+import contextlib
+from typing import Optional, Sequence
+
+from discord import ButtonStyle, Interaction, Message, NotFound, ui
+
+from botcore.utils.logging import get_logger
+
+log = get_logger(__name__)
+
+
+class ViewWithUserAndRoleCheck(ui.View):
+ """
+ A view that allows the original invoker and moderators to interact with it.
+
+ Args:
+ allowed_users: A sequence of user's ids who are allowed to interact with the view.
+ allowed_roles: A sequence of role ids that are allowed to interact with the view.
+ timeout: Timeout in seconds from last interaction with the UI before no longer accepting input.
+ If ``None`` then there is no timeout.
+ message: The message to remove the view from on timeout. This can also be set with
+ ``view.message = await ctx.send( ... )``` , or similar, after the view is instantiated.
+ """
+
+ def __init__(
+ self,
+ *,
+ allowed_users: Sequence[int],
+ allowed_roles: Sequence[int],
+ timeout: Optional[float] = 180.0,
+ message: Optional[Message] = None
+ ) -> None:
+ super().__init__(timeout=timeout)
+ self.allowed_users = allowed_users
+ self.allowed_roles = allowed_roles
+ self.message = message
+
+ async def interaction_check(self, interaction: Interaction) -> bool:
+ """
+ Ensure the user clicking the button is the view invoker, or a moderator.
+
+ Args:
+ interaction: The interaction that occurred.
+ """
+ if interaction.user.id in self.allowed_users:
+ log.trace(
+ "Allowed interaction by %s (%d) on %d as they are an allowed user.",
+ interaction.user,
+ interaction.user.id,
+ interaction.message.id,
+ )
+ return True
+
+ if any(role.id in self.allowed_roles for role in getattr(interaction.user, "roles", [])):
+ log.trace(
+ "Allowed interaction by %s (%d)on %d as they have an allowed role.",
+ interaction.user,
+ interaction.user.id,
+ interaction.message.id,
+ )
+ return True
+
+ await interaction.response.send_message("This is not your button to click!", ephemeral=True)
+ return False
+
+ async def on_timeout(self) -> None:
+ """Remove the view from ``self.message`` if set."""
+ if self.message:
+ with contextlib.suppress(NotFound):
+ # Cover the case where this message has already been deleted by external means
+ await self.message.edit(view=None)
+
+
+class DeleteMessageButton(ui.Button):
+ """
+ A button that can be added to a view to delete the message containing the view on click.
+
+ This button itself carries out no interaction checks, these should be done by the parent view.
+
+ See :obj:`botcore.utils.interactions.ViewWithUserAndRoleCheck` for a view that implements basic checks.
+
+ Args:
+ style (:literal-url:`ButtonStyle <https://discordpy.readthedocs.io/en/latest/interactions/api.html#discord.ButtonStyle>`):
+ The style of the button, set to ``ButtonStyle.secondary`` if not specified.
+ label: The label of the button, set to "Delete" if not specified.
+ """ # noqa: E501
+
+ def __init__(
+ self,
+ *,
+ style: ButtonStyle = ButtonStyle.secondary,
+ label: str = "Delete",
+ **kwargs
+ ):
+ super().__init__(style=style, label=label, **kwargs)
+
+ async def callback(self, interaction: Interaction) -> None:
+ """Delete the original message on button click."""
+ await interaction.message.delete()
diff --git a/botcore/utils/members.py b/botcore/utils/members.py
index e89b4618..1536a8d1 100644
--- a/botcore/utils/members.py
+++ b/botcore/utils/members.py
@@ -1,6 +1,6 @@
"""Useful helper functions for interactin with :obj:`discord.Member` objects."""
-
import typing
+from collections import abc
import discord
@@ -30,18 +30,19 @@ async def get_or_fetch_member(guild: discord.Guild, member_id: int) -> typing.Op
async def handle_role_change(
member: discord.Member,
- coro: typing.Callable[..., typing.Coroutine],
+ coro: typing.Callable[[discord.Role], abc.Coroutine],
role: discord.Role
) -> None:
"""
- Await the given ``coro`` with ``member`` as the sole argument.
+ Await the given ``coro`` with ``role`` as the sole argument.
Handle errors that we expect to be raised from
:obj:`discord.Member.add_roles` and :obj:`discord.Member.remove_roles`.
Args:
- member: The member to pass to ``coro``.
+ member: The member that is being modified for logging purposes.
coro: This is intended to be :obj:`discord.Member.add_roles` or :obj:`discord.Member.remove_roles`.
+ role: The role to be passed to ``coro``.
"""
try:
await coro(role)
diff --git a/botcore/utils/regex.py b/botcore/utils/regex.py
index 56c50dad..de82a1ed 100644
--- a/botcore/utils/regex.py
+++ b/botcore/utils/regex.py
@@ -3,6 +3,7 @@
import re
DISCORD_INVITE = re.compile(
+ r"(https?://)?(www\.)?" # Optional http(s) and www.
r"(discord([.,]|dot)gg|" # Could be discord.gg/
r"discord([.,]|dot)com(/|slash)invite|" # or discord.com/invite/
r"discordapp([.,]|dot)com(/|slash)invite|" # or discordapp.com/invite/
@@ -10,7 +11,7 @@ DISCORD_INVITE = re.compile(
r"discord([.,]|dot)li|" # or discord.li
r"discord([.,]|dot)io|" # or discord.io.
r"((?<!\w)([.,]|dot))gg" # or .gg/
- r")([/]|slash)" # / or 'slash'
+ r")(/|slash)" # / or 'slash'
r"(?P<invite>\S+)", # the invite code itself
flags=re.IGNORECASE
)
@@ -32,7 +33,7 @@ FORMATTED_CODE_REGEX = re.compile(
r"(?P<code>.*?)" # extract all code inside the markup
r"\s*" # any more whitespace before the end of the code markup
r"(?P=delim)", # match the exact same delimiter from the start again
- re.DOTALL | re.IGNORECASE # "." also matches newlines, case insensitive
+ flags=re.DOTALL | re.IGNORECASE # "." also matches newlines, case insensitive
)
"""
Regex for formatted code, using Discord's code blocks.
@@ -44,7 +45,7 @@ RAW_CODE_REGEX = re.compile(
r"^(?:[ \t]*\n)*" # any blank (empty or tabs/spaces only) lines before the code
r"(?P<code>.*?)" # extract all the rest as code
r"\s*$", # any trailing whitespace until the end of the string
- re.DOTALL # "." also matches newlines
+ flags=re.DOTALL # "." also matches newlines
)
"""
Regex for raw code, *not* using Discord's code blocks.
diff --git a/botcore/utils/scheduling.py b/botcore/utils/scheduling.py
index 164f6b10..9517df6d 100644
--- a/botcore/utils/scheduling.py
+++ b/botcore/utils/scheduling.py
@@ -4,6 +4,7 @@ import asyncio
import contextlib
import inspect
import typing
+from collections import abc
from datetime import datetime
from functools import partial
@@ -38,9 +39,9 @@ class Scheduler:
self.name = name
self._log = logging.get_logger(f"{__name__}.{name}")
- self._scheduled_tasks: typing.Dict[typing.Hashable, asyncio.Task] = {}
+ self._scheduled_tasks: dict[abc.Hashable, asyncio.Task] = {}
- def __contains__(self, task_id: typing.Hashable) -> bool:
+ def __contains__(self, task_id: abc.Hashable) -> bool:
"""
Return :obj:`True` if a task with the given ``task_id`` is currently scheduled.
@@ -52,7 +53,7 @@ class Scheduler:
"""
return task_id in self._scheduled_tasks
- def schedule(self, task_id: typing.Hashable, coroutine: typing.Coroutine) -> None:
+ def schedule(self, task_id: abc.Hashable, coroutine: abc.Coroutine) -> None:
"""
Schedule the execution of a ``coroutine``.
@@ -79,7 +80,7 @@ class Scheduler:
self._scheduled_tasks[task_id] = task
self._log.debug(f"Scheduled task #{task_id} {id(task)}.")
- def schedule_at(self, time: datetime, task_id: typing.Hashable, coroutine: typing.Coroutine) -> None:
+ def schedule_at(self, time: datetime, task_id: abc.Hashable, coroutine: abc.Coroutine) -> None:
"""
Schedule ``coroutine`` to be executed at the given ``time``.
@@ -106,8 +107,8 @@ class Scheduler:
def schedule_later(
self,
delay: typing.Union[int, float],
- task_id: typing.Hashable,
- coroutine: typing.Coroutine
+ task_id: abc.Hashable,
+ coroutine: abc.Coroutine
) -> None:
"""
Schedule ``coroutine`` to be executed after ``delay`` seconds.
@@ -122,7 +123,7 @@ class Scheduler:
"""
self.schedule(task_id, self._await_later(delay, task_id, coroutine))
- def cancel(self, task_id: typing.Hashable) -> None:
+ def cancel(self, task_id: abc.Hashable) -> None:
"""
Unschedule the task identified by ``task_id``. Log a warning if the task doesn't exist.
@@ -150,8 +151,8 @@ class Scheduler:
async def _await_later(
self,
delay: typing.Union[int, float],
- task_id: typing.Hashable,
- coroutine: typing.Coroutine
+ task_id: abc.Hashable,
+ coroutine: abc.Coroutine
) -> None:
"""Await ``coroutine`` after ``delay`` seconds."""
try:
@@ -173,7 +174,7 @@ class Scheduler:
else:
self._log.debug(f"Finally block reached for #{task_id}; {state=}")
- def _task_done_callback(self, task_id: typing.Hashable, done_task: asyncio.Task) -> None:
+ def _task_done_callback(self, task_id: abc.Hashable, done_task: asyncio.Task) -> None:
"""
Delete the task and raise its exception if one exists.
@@ -208,13 +209,16 @@ class Scheduler:
self._log.error(f"Error in task #{task_id} {id(done_task)}!", exc_info=exception)
+TASK_RETURN = typing.TypeVar("TASK_RETURN")
+
+
def create_task(
- coro: typing.Awaitable,
+ coro: abc.Coroutine[typing.Any, typing.Any, TASK_RETURN],
*,
- suppressed_exceptions: tuple[typing.Type[Exception]] = (),
+ suppressed_exceptions: tuple[type[Exception], ...] = (),
event_loop: typing.Optional[asyncio.AbstractEventLoop] = None,
**kwargs,
-) -> asyncio.Task:
+) -> asyncio.Task[TASK_RETURN]:
"""
Wrapper for creating an :obj:`asyncio.Task` which logs exceptions raised in the task.
@@ -238,7 +242,7 @@ def create_task(
return task
-def _log_task_exception(task: asyncio.Task, *, suppressed_exceptions: typing.Tuple[typing.Type[Exception]]) -> None:
+def _log_task_exception(task: asyncio.Task, *, suppressed_exceptions: tuple[type[Exception], ...]) -> None:
"""Retrieve and log the exception raised in ``task`` if one exists."""
with contextlib.suppress(asyncio.CancelledError):
exception = task.exception()
diff --git a/dev/Dockerfile b/dev/Dockerfile
index 738fc51a..0b35724a 100644
--- a/dev/Dockerfile
+++ b/dev/Dockerfile
@@ -1,21 +1,16 @@
-FROM python:3.9-slim
+FROM --platform=linux/amd64 ghcr.io/chrislovering/python-poetry-base:3.10-slim
-# Set pip to have no saved cache
-ENV PIP_NO_CACHE_DIR=false \
- POETRY_VIRTUALENVS_CREATE=false
-
-ENTRYPOINT ["/bin/bash"]
-CMD ["./docker-entrypoint.sh"]
-
-# Install poetry
-RUN pip install -U poetry
-
-RUN mkdir bot
-WORKDIR /bot
# Install project dependencies
+WORKDIR /app
COPY pyproject.toml poetry.lock ./
-RUN poetry install --no-dev
+RUN poetry install --no-root
# Copy the source code in last to optimize rebuilding the image
COPY . .
+
+# Install again, this time with the root project
+RUN poetry install
+
+ENTRYPOINT ["python"]
+CMD ["-m", "bot"]
diff --git a/dev/README.rst b/dev/README.rst
new file mode 100644
index 00000000..ae4f3adc
--- /dev/null
+++ b/dev/README.rst
@@ -0,0 +1,53 @@
+Local Development & Testing
+===========================
+
+To test your features locally, there are a few possible approaches:
+
+1. Install your local copy of botcore into a pre-existing project such as bot
+2. Use the provided template from the :repo-file:`dev/bot <dev/bot>` folder
+
+See below for more info on both approaches.
+
+What's going to be common between them is you'll need to write code to test your feature.
+This might mean adding new commands, modifying existing ones, changing utilities, etc.
+The steps below should provide most of the groundwork you need, but the exact requirements will
+vary by the feature you're working on.
+
+
+Option 1
+--------
+1. Navigate to the project you want to install bot-core in, such as bot or sir-lancebot
+2. Run ``pip install /path/to/botcore`` in the project's environment
+
+ - The path provided to install should be the root directory of this project on your machine.
+ That is, the folder which contains the ``pyproject.toml`` file.
+ - Make sure to install in the correct environment. Most Python Discord projects use
+ poetry, so you can run ``poetry run pip install /path/to/botcore``.
+
+3. You can now use features from your local bot-core changes.
+ To load new changes, run the install command again.
+
+
+Option 2
+--------
+1. Copy the :repo-file:`bot template folder <dev/bot>` to the root of the bot-core project.
+ This copy is going to be git-ignored, so you're free to modify it however you like.
+2. Run the project
+
+ - Locally: You can run it on your system using ``python -m bot``
+ - Docker: You can run on docker using ``docker compose up -d bot``.
+
+3. Configure the environment variables used by the program.
+ You can set them in an ``.env`` file in the project root directory. The variables are:
+
+ - ``BOT_TOKEN`` (required): Discord bot token, with all intents enabled
+ - ``GUILD_ID`` (required): The guild the bot should monitor
+ - ``PREFIX``: The prefix to use for invoking bot commands. Defaults to mentions and ``!``
+ - ``ALLOWED_ROLES``: A comma seperated list of role IDs which the bot is allowed to mention
+
+4. You can now test your changes. You do not need to do anything to reinstall the
+ library if you modify your code.
+
+.. tip::
+ The docker-compose included contains services from our other applications
+ to help you test out certain features. Use them as needed.
diff --git a/dev/bot/__init__.py b/dev/bot/__init__.py
new file mode 100644
index 00000000..71871209
--- /dev/null
+++ b/dev/bot/__init__.py
@@ -0,0 +1,24 @@
+import asyncio
+import logging
+import os
+import sys
+
+import botcore
+
+if os.name == "nt":
+ # Change the event loop policy on Windows to avoid exceptions on exit
+ asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
+
+# Some basic logging to get existing loggers to show
+logging.getLogger().addHandler(logging.StreamHandler())
+logging.getLogger().setLevel(logging.DEBUG)
+logging.getLogger("discord").setLevel(logging.ERROR)
+
+
+class Bot(botcore.BotBase):
+ """Sample Bot implementation."""
+
+ async def setup_hook(self) -> None:
+ """Load extensions on startup."""
+ await super().setup_hook()
+ asyncio.create_task(self.load_extensions(sys.modules[__name__]))
diff --git a/dev/bot/__main__.py b/dev/bot/__main__.py
new file mode 100644
index 00000000..42d212c2
--- /dev/null
+++ b/dev/bot/__main__.py
@@ -0,0 +1,34 @@
+import asyncio
+import os
+
+import aiohttp
+import discord
+import dotenv
+from discord.ext import commands
+
+import botcore
+from . import Bot
+
+dotenv.load_dotenv()
+botcore.utils.apply_monkey_patches()
+
+roles = os.getenv("ALLOWED_ROLES")
+roles = [int(role) for role in roles.split(",")] if roles else []
+
+bot = Bot(
+ guild_id=int(os.getenv("GUILD_ID")),
+ http_session=None, # type: ignore # We need to instantiate the session in an async context
+ allowed_roles=roles,
+ command_prefix=commands.when_mentioned_or(os.getenv("PREFIX", "!")),
+ intents=discord.Intents.all(),
+ description="Bot-core test bot.",
+)
+
+
+async def main() -> None:
+ """Run the bot."""
+ bot.http_session = aiohttp.ClientSession()
+ async with bot:
+ await bot.start(os.getenv("BOT_TOKEN"))
+
+asyncio.run(main())
diff --git a/dev/bot/cog.py b/dev/bot/cog.py
new file mode 100644
index 00000000..7746c54e
--- /dev/null
+++ b/dev/bot/cog.py
@@ -0,0 +1,33 @@
+from discord.ext import commands
+
+from . import Bot
+
+
+class Cog(commands.Cog):
+ """A simple discord.py cog."""
+
+ def __init__(self, _bot: Bot):
+ self.bot = _bot
+
+ @commands.Cog.listener()
+ async def on_ready(self) -> None:
+ """Print a message when the client (re)connects."""
+ print("Client is ready.")
+
+ @commands.command()
+ async def reload(self, ctx: commands.Context) -> None:
+ """Reload all available cogs."""
+ message = await ctx.send(":hourglass_flowing_sand: Reloading")
+ for ext in list(self.bot.extensions):
+ await self.bot.reload_extension(ext)
+ await message.edit(content=":white_check_mark: Done")
+
+ @commands.command()
+ async def ping(self, ctx: commands.Context) -> None:
+ """Test if the bot is online."""
+ await ctx.send("We are live!")
+
+
+async def setup(_bot: Bot) -> None:
+ """Install the cog."""
+ await _bot.add_cog(Cog(_bot))
diff --git a/dev/docker-compose.yaml b/dev/docker-compose.yaml
deleted file mode 100644
index e1dca5bb..00000000
--- a/dev/docker-compose.yaml
+++ /dev/null
@@ -1,27 +0,0 @@
-version: "3.9"
-
-x-logging: &logging
- logging:
- driver: "json-file"
- options:
- max-file: "5"
- max-size: "10m"
-
-x-restart-policy: &restart_policy
- restart: unless-stopped
-
-services:
- botcore:
- <<: *logging
- <<: *restart_policy
- build:
- context: .
- dockerfile: Dockerfile
- container_name: botcore
-
- volumes:
- - ./logs:/bot/logs
- - .:/bot:ro
-
- env_file:
- - .env
diff --git a/docker-compose.yaml b/docker-compose.yaml
new file mode 100644
index 00000000..af882428
--- /dev/null
+++ b/docker-compose.yaml
@@ -0,0 +1,80 @@
+# Modified version of python-discord/bot
+
+version: "3.8"
+
+x-restart-policy: &restart_policy
+ restart: unless-stopped
+
+services:
+ postgres:
+ << : *restart_policy
+ image: postgres:13-alpine
+ environment:
+ POSTGRES_DB: pysite
+ POSTGRES_PASSWORD: pysite
+ POSTGRES_USER: pysite
+ healthcheck:
+ test: ["CMD-SHELL", "pg_isready -U pysite"]
+ interval: 2s
+ timeout: 1s
+ retries: 5
+
+ metricity:
+ restart: on-failure
+ depends_on:
+ postgres:
+ condition: service_healthy
+ image: ghcr.io/python-discord/metricity:latest
+ env_file:
+ - .env
+ environment:
+ DATABASE_URI: postgres://pysite:pysite@postgres/metricity
+ USE_METRICITY: ${USE_METRICITY-false}
+ volumes:
+ - .:/tmp/bot:ro
+
+ redis:
+ << : *restart_policy
+ image: redis:5.0.9
+ ports:
+ - "6379:6379"
+
+ snekbox:
+ << : *restart_policy
+ image: ghcr.io/python-discord/snekbox:latest
+ init: true
+ ipc: none
+ ports:
+ - "8060:8060"
+ privileged: true
+
+ web:
+ << : *restart_policy
+ image: ghcr.io/python-discord/site:latest
+ command: ["run", "--debug"]
+ ports:
+ - "8000:8000"
+ tty: true
+ environment:
+ DATABASE_URL: postgres://pysite:pysite@postgres:5432/pysite
+ METRICITY_DB_URL: postgres://pysite:pysite@postgres:5432/metricity
+ SECRET_KEY: suitable-for-development-only
+ STATIC_ROOT: /var/www/static
+ depends_on:
+ - metricity
+
+ bot:
+ << : *restart_policy
+ build:
+ context: .
+ dockerfile: dev/Dockerfile
+ volumes: # Don't do .:/app here to ensure project venv from host doens't overwrite venv in image
+ - ./botcore:/app/botcore:ro
+ - ./bot:/app/bot:ro
+ tty: true
+ depends_on:
+ - web
+ env_file:
+ - .env
+ environment:
+ BOT_API_KEY: badbot13m0n8f570f942013fc818f234916ca531
diff --git a/docs/changelog.rst b/docs/changelog.rst
index e666a266..3e3c7149 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -4,6 +4,60 @@
Changelog
=========
+- :release:`8.2.1 <18th September 2022>`
+- :bug:`138` Bump Discord.py to :literal-url:`2.0.1 <https://discordpy.readthedocs.io/en/latest/whats_new.html#v2-0-1>`.
+
+
+- :release:`8.2.0 <18th August 2022>`
+- :support:`125` Bump Discord.py to the stable :literal-url:`2.0 release <https://discordpy.readthedocs.io/en/latest/migrating.html>`.
+
+
+- :release:`8.1.0 <16th August 2022>`
+- :support:`124` Updated :obj:`botcore.utils.regex.DISCORD_INVITE` regex to optionally match leading "http[s]" and "www".
+
+
+- :release:`8.0.0 <27th July 2022>`
+- :breaking:`110` Bump async-rediscache to v1.0.0-rc2
+- :support:`108` Bump Python version to 3.10.*
+- :bug:`107 major` Declare aiodns as a project dependency.
+- :support:`107` Add a sample project with boilerplate and documentation explaining how to develop for bot-core.
+
+
+- :release:`7.5.0 <23rd July 2022>`
+- :feature:`101` Add a utility to clean a string or referenced message's content
+
+
+- :release:`7.4.0 <17th July 2022>`
+- :feature:`106` Add an optional ``message`` attr to :obj:`botcore.utils.interactions.ViewWithUserAndRoleCheck`. On view timeout, this message has its view removed if set.
+
+
+- :release:`7.3.1 <16th July 2022>`
+- :bug:`104` Fix :obj:`botcore.utils.interactions.DeleteMessageButton` not working due to using wrong delete method.
+
+
+- :release:`7.3.0 <16th July 2022>`
+- :feature:`103` Add a generic view :obj:`botcore.utils.interactions.ViewWithUserAndRoleCheck` that only allows specified users and roles to interaction with it
+- :feature:`103` Add a button :obj:`botcore.utils.interactions.DeleteMessageButton` that deletes the message attached to its parent view.
+
+
+- :release:`7.2.2 <9th July 2022>`
+- :bug:`98` Only close ``BotBase.stats._transport`` if ``BotBase.stats`` was created
+
+
+- :release:`7.2.1 <30th June 2022>`
+- :bug:`96` Fix attempts to connect to ``BotBase.statsd_url`` when it is None.
+- :bug:`91` Fix incorrect docstring for ``botcore.utils.member.handle_role_change``.
+- :bug:`91` Pass missing self parameter to ``BotBase.ping_services``.
+- :bug:`91` Add missing await to ``BotBase.ping_services`` in some cases.
+
+
+- :release:`7.2.0 <28th June 2022>`
+- :support:`93` Bump Discord.py to :literal-url:`0eb3d26 <https://github.com/Rapptz/discord.py/commit/0eb3d26343969a25ffc43ba72eca42538d2e7e7a>`:
+
+ - Adds support for auto mod, of which the new auto_mod MESSAGE_TYPE is needed for our filter system.
+
+
+- :release:`7.1.3 <30th May 2022>` 79
- :support:`79` Add `sphinx-multiversion <https://pypi.org/project/sphinx-multiversion/>`_ to make available older doc versions.
- :support:`79` Restore on-site changelog.
diff --git a/docs/development.rst b/docs/development.rst
new file mode 100644
index 00000000..25b8e0a7
--- /dev/null
+++ b/docs/development.rst
@@ -0,0 +1,2 @@
+.. Stub file to expose the README to sphinx
+.. include:: ../dev/README.rst
diff --git a/docs/index.rst b/docs/index.rst
index 0a375b90..aee7b269 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -17,6 +17,7 @@ Reference
:caption: Other:
:hidden:
+ development
changelog
@@ -26,4 +27,5 @@ Extras
* :ref:`genindex`
* :ref:`search`
* :repo-file:`Information <docs/README.md>`
+* :doc:`development`
* :doc:`changelog`
diff --git a/docs/utils.py b/docs/utils.py
index 9d299ebf..c8bbc895 100644
--- a/docs/utils.py
+++ b/docs/utils.py
@@ -64,6 +64,7 @@ def linkcode_resolve(repo_link: str, domain: str, info: dict[str, str]) -> typin
try:
lines, start = inspect.getsourcelines(symbol[-1])
+ module = inspect.getmodule(symbol[-1])
end = start + len(lines)
except TypeError:
# Find variables by parsing the ast
diff --git a/poetry.lock b/poetry.lock
index 236f3e0f..a3e1b8b6 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1,4 +1,15 @@
[[package]]
+name = "aiodns"
+version = "3.0.0"
+description = "Simple DNS resolver for asyncio"
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+pycares = ">=4.0.0"
+
+[[package]]
name = "aiohttp"
version = "3.8.1"
description = "Async http client/server framework (asyncio)"
@@ -19,21 +30,6 @@ yarl = ">=1.0,<2.0"
speedups = ["aiodns", "brotli", "cchardet"]
[[package]]
-name = "aioredis"
-version = "2.0.1"
-description = "asyncio (PEP 3156) Redis support"
-category = "main"
-optional = true
-python-versions = ">=3.6"
-
-[package.dependencies]
-async-timeout = "*"
-typing-extensions = "*"
-
-[package.extras]
-hiredis = ["hiredis (>=1.0)"]
-
-[[package]]
name = "aiosignal"
version = "1.2.0"
description = "aiosignal: a list of registered asynchronous callbacks"
@@ -54,18 +50,18 @@ python-versions = "*"
[[package]]
name = "async-rediscache"
-version = "0.2.0"
+version = "1.0.0rc2"
description = "An easy to use asynchronous Redis cache"
category = "main"
optional = true
python-versions = "~=3.7"
[package.dependencies]
-aioredis = ">=1"
-fakeredis = {version = ">=1.4.4", extras = ["lua"], optional = true, markers = "extra == \"fakeredis\""}
+fakeredis = {version = ">=1.7.1", extras = ["lua"], optional = true, markers = "extra == \"fakeredis\""}
+redis = ">=4.2,<5.0"
[package.extras]
-fakeredis = ["fakeredis[lua] (>=1.4.4)"]
+fakeredis = ["fakeredis[lua] (>=1.7.1)"]
[[package]]
name = "async-timeout"
@@ -76,14 +72,6 @@ optional = false
python-versions = ">=3.6"
[[package]]
-name = "atomicwrites"
-version = "1.4.0"
-description = "Atomic file writes."
-category = "dev"
-optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-
-[[package]]
name = "attrs"
version = "21.4.0"
description = "Classes Without Boilerplate"
@@ -132,6 +120,17 @@ optional = false
python-versions = ">=3.6"
[[package]]
+name = "cffi"
+version = "1.15.1"
+description = "Foreign Function Interface for Python calling C code."
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+pycparser = "*"
+
+[[package]]
name = "cfgv"
version = "3.3.1"
description = "Validate configuration and produce human readable error messages."
@@ -160,7 +159,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]]
name = "coverage"
-version = "6.4.1"
+version = "6.4.4"
description = "Code coverage measurement for Python"
category = "dev"
optional = false
@@ -188,7 +187,7 @@ dev = ["tox", "bump2version (<1)", "sphinx (<2)", "importlib-metadata (<3)", "im
[[package]]
name = "discord.py"
-version = "2.0.0a0"
+version = "2.0.1"
description = "A Python wrapper for the Discord API"
category = "main"
optional = false
@@ -198,14 +197,10 @@ python-versions = ">=3.8.0"
aiohttp = ">=3.7.4,<4"
[package.extras]
-docs = ["sphinx (==4.4.0)", "sphinxcontrib-trio (==1.1.2)", "sphinxcontrib-websupport", "typing-extensions"]
-speed = ["orjson (>=3.5.4)", "aiodns (>=1.1)", "brotli", "cchardet"]
-test = ["coverage", "pytest", "pytest-asyncio", "pytest-cov", "pytest-mock"]
voice = ["PyNaCl (>=1.3.0,<1.6)"]
-
-[package.source]
-type = "url"
-url = "https://github.com/Rapptz/discord.py/archive/4cbe8f58e16f6a76371ce45a69e0832130d6d24f.zip"
+test = ["typing-extensions (>=4.3,<5)", "pytest-mock", "pytest-cov", "pytest-asyncio", "pytest", "coverage"]
+speed = ["cchardet (==2.1.7)", "brotli", "aiodns (>=1.1)", "orjson (>=3.5.4)"]
+docs = ["typing-extensions (>=4.3,<5)", "sphinxcontrib-websupport", "sphinxcontrib-trio (==1.1.2)", "sphinx (==4.4.0)"]
[[package]]
name = "distlib"
@@ -236,7 +231,7 @@ testing = ["pre-commit"]
[[package]]
name = "fakeredis"
-version = "1.8.1"
+version = "1.9.1"
description = "Fake implementation of redis API for testing purposes."
category = "main"
optional = true
@@ -266,32 +261,32 @@ testing = ["covdefaults (>=1.2.0)", "coverage (>=4)", "pytest (>=4)", "pytest-co
[[package]]
name = "flake8"
-version = "4.0.1"
+version = "5.0.4"
description = "the modular source code checker: pep8 pyflakes and co"
category = "dev"
optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.6.1"
[package.dependencies]
-mccabe = ">=0.6.0,<0.7.0"
-pycodestyle = ">=2.8.0,<2.9.0"
-pyflakes = ">=2.4.0,<2.5.0"
+mccabe = ">=0.7.0,<0.8.0"
+pycodestyle = ">=2.9.0,<2.10.0"
+pyflakes = ">=2.5.0,<2.6.0"
[[package]]
name = "flake8-annotations"
-version = "2.9.0"
+version = "2.9.1"
description = "Flake8 Type Annotation Checks"
category = "dev"
optional = false
python-versions = ">=3.7,<4.0"
[package.dependencies]
-attrs = ">=21.4,<22.0"
+attrs = ">=21.4"
flake8 = ">=3.7"
[[package]]
name = "flake8-bugbear"
-version = "22.4.25"
+version = "22.9.11"
description = "A plugin for flake8 finding likely bugs and design problems in your program. Contains warnings that don't belong in pyflakes and pycodestyle."
category = "dev"
optional = false
@@ -328,17 +323,6 @@ python-versions = "*"
pycodestyle = "*"
[[package]]
-name = "flake8-polyfill"
-version = "1.0.2"
-description = "Polyfill package for Flake8 plugins"
-category = "dev"
-optional = false
-python-versions = "*"
-
-[package.dependencies]
-flake8 = "*"
-
-[[package]]
name = "flake8-string-format"
version = "0.3.0"
description = "string format checker, plugin for flake8"
@@ -381,16 +365,17 @@ python-versions = ">=3.7"
[[package]]
name = "furo"
-version = "2022.4.7"
+version = "2022.9.15"
description = "A clean customisable Sphinx documentation theme."
category = "dev"
optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
[package.dependencies]
beautifulsoup4 = "*"
-pygments = ">=2.7,<3.0"
-sphinx = ">=4.0,<5.0"
+pygments = ">=2.7"
+sphinx = ">=4.0,<6.0"
+sphinx-basic-ng = "*"
[[package]]
name = "gitdb"
@@ -442,22 +427,6 @@ optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[[package]]
-name = "importlib-metadata"
-version = "4.11.4"
-description = "Read metadata from Python packages"
-category = "dev"
-optional = false
-python-versions = ">=3.7"
-
-[package.dependencies]
-zipp = ">=0.5"
-
-[package.extras]
-docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"]
-perf = ["ipython"]
-testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "importlib-resources (>=1.3)"]
-
-[[package]]
name = "iniconfig"
version = "1.1.1"
description = "iniconfig: brain-dead simple config-ini parsing"
@@ -497,11 +466,11 @@ python-versions = ">=3.7"
[[package]]
name = "mccabe"
-version = "0.6.1"
+version = "0.7.0"
description = "McCabe checker, plugin for flake8"
category = "dev"
optional = false
-python-versions = "*"
+python-versions = ">=3.6"
[[package]]
name = "mslex"
@@ -540,15 +509,14 @@ pyparsing = ">=2.0.2,<3.0.5 || >3.0.5"
[[package]]
name = "pep8-naming"
-version = "0.12.1"
+version = "0.13.2"
description = "Check PEP-8 naming conventions, plugin for flake8"
category = "dev"
optional = false
-python-versions = "*"
+python-versions = ">=3.7"
[package.dependencies]
flake8 = ">=3.9.1"
-flake8-polyfill = ">=1.0.2,<2"
[[package]]
name = "platformdirs"
@@ -576,7 +544,7 @@ testing = ["pytest", "pytest-benchmark"]
[[package]]
name = "pre-commit"
-version = "2.19.0"
+version = "2.20.0"
description = "A framework for managing and maintaining multi-language pre-commit hooks."
category = "dev"
optional = false
@@ -610,12 +578,34 @@ optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]]
+name = "pycares"
+version = "4.2.2"
+description = "Python interface for c-ares"
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+cffi = ">=1.5.0"
+
+[package.extras]
+idna = ["idna (>=2.1)"]
+
+[[package]]
name = "pycodestyle"
-version = "2.8.0"
+version = "2.9.1"
description = "Python style guide checker"
category = "dev"
optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+python-versions = ">=3.6"
+
+[[package]]
+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.*"
[[package]]
name = "pydocstyle"
@@ -633,11 +623,11 @@ toml = ["toml"]
[[package]]
name = "pyflakes"
-version = "2.4.0"
+version = "2.5.0"
description = "passive checker of Python programs"
category = "dev"
optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+python-versions = ">=3.6"
[[package]]
name = "pygments"
@@ -660,14 +650,13 @@ diagrams = ["railroad-diagrams", "jinja2"]
[[package]]
name = "pytest"
-version = "7.1.2"
+version = "7.1.3"
description = "pytest: simple powerful testing with Python"
category = "dev"
optional = false
python-versions = ">=3.7"
[package.dependencies]
-atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""}
attrs = ">=19.2.0"
colorama = {version = "*", markers = "sys_platform == \"win32\""}
iniconfig = "*"
@@ -726,11 +715,11 @@ testing = ["filelock"]
[[package]]
name = "python-dotenv"
-version = "0.20.0"
+version = "0.21.0"
description = "Read key-value pairs from a .env file and set them as environment variables"
category = "dev"
optional = false
-python-versions = ">=3.5"
+python-versions = ">=3.7"
[package.extras]
cli = ["click (>=5.0)"]
@@ -848,7 +837,7 @@ python-versions = ">=3.6"
[[package]]
name = "sphinx"
-version = "4.5.0"
+version = "5.1.1"
description = "Python documentation generator"
category = "dev"
optional = false
@@ -858,9 +847,8 @@ python-versions = ">=3.6"
alabaster = ">=0.7,<0.8"
babel = ">=1.3"
colorama = {version = ">=0.3.5", markers = "sys_platform == \"win32\""}
-docutils = ">=0.14,<0.18"
+docutils = ">=0.14,<0.20"
imagesize = "*"
-importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""}
Jinja2 = ">=2.3"
packaging = "*"
Pygments = ">=2.0"
@@ -875,23 +863,37 @@ sphinxcontrib-serializinghtml = ">=1.1.5"
[package.extras]
docs = ["sphinxcontrib-websupport"]
-lint = ["flake8 (>=3.5.0)", "isort", "mypy (>=0.931)", "docutils-stubs", "types-typed-ast", "types-requests"]
-test = ["pytest", "pytest-cov", "html5lib", "cython", "typed-ast"]
+lint = ["flake8 (>=3.5.0)", "flake8-comprehensions", "flake8-bugbear", "isort", "mypy (>=0.971)", "sphinx-lint", "docutils-stubs", "types-typed-ast", "types-requests"]
+test = ["pytest (>=4.6)", "html5lib", "cython", "typed-ast"]
[[package]]
name = "sphinx-autodoc-typehints"
-version = "1.18.1"
+version = "1.19.2"
description = "Type hints (PEP 484) support for the Sphinx autodoc extension"
category = "dev"
optional = false
python-versions = ">=3.7"
[package.dependencies]
-Sphinx = ">=4.5"
+Sphinx = ">=5.1.1"
[package.extras]
-testing = ["covdefaults (>=2.2)", "coverage (>=6.3)", "diff-cover (>=6.4)", "nptyping (>=2)", "pytest (>=7.1)", "pytest-cov (>=3)", "sphobjinv (>=2)", "typing-extensions (>=4.1)"]
-type_comments = ["typed-ast (>=1.5.2)"]
+testing = ["covdefaults (>=2.2)", "coverage (>=6.4.2)", "diff-cover (>=6.5.1)", "nptyping (>=2.2)", "pytest (>=7.1.2)", "pytest-cov (>=3)", "sphobjinv (>=2.2.2)", "typing-extensions (>=4.3)"]
+type_comments = ["typed-ast (>=1.5.4)"]
+
+[[package]]
+name = "sphinx-basic-ng"
+version = "0.0.1a12"
+description = "A modern skeleton for Sphinx themes."
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+sphinx = ">=4.0,<6.0"
+
+[package.extras]
+docs = ["furo", "myst-parser", "sphinx-copybutton", "sphinx-inline-tabs", "ipython"]
[[package]]
name = "sphinx-multiversion"
@@ -985,7 +987,7 @@ python-versions = "*"
[[package]]
name = "taskipy"
-version = "1.10.2"
+version = "1.10.3"
description = "tasks runner for python projects"
category = "dev"
optional = false
@@ -1015,9 +1017,9 @@ python-versions = ">=3.7"
[[package]]
name = "typing-extensions"
-version = "4.2.0"
+version = "4.3.0"
description = "Backported and Experimental Type Hints for Python 3.7+"
-category = "main"
+category = "dev"
optional = false
python-versions = ">=3.7"
@@ -1072,27 +1074,19 @@ python-versions = ">=3.6"
idna = ">=2.0"
multidict = ">=4.0"
-[[package]]
-name = "zipp"
-version = "3.8.0"
-description = "Backport of pathlib-compatible object wrapper for zip files"
-category = "dev"
-optional = false
-python-versions = ">=3.7"
-
-[package.extras]
-docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"]
-testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"]
-
[extras]
async-rediscache = ["async-rediscache"]
[metadata]
lock-version = "1.1"
-python-versions = "3.9.*"
-content-hash = "fa1b3253f14aee762b0ca0e5e719c4b1a61d35fae23c51f74a56c728741d8f3c"
+python-versions = "3.10.*"
+content-hash = "02800700a2a02f4a842b4172f6d715da119bf7b3b4144bdb23a5cb50bc6d2364"
[metadata.files]
+aiodns = [
+ {file = "aiodns-3.0.0-py3-none-any.whl", hash = "sha256:2b19bc5f97e5c936638d28e665923c093d8af2bf3aa88d35c43417fa25d136a2"},
+ {file = "aiodns-3.0.0.tar.gz", hash = "sha256:946bdfabe743fceeeb093c8a010f5d1645f708a241be849e17edfb0e49e08cd6"},
+]
aiohttp = [
{file = "aiohttp-3.8.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1ed0b6477896559f17b9eaeb6d38e07f7f9ffe40b9f0f9627ae8b9926ae260a8"},
{file = "aiohttp-3.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7dadf3c307b31e0e61689cbf9e06be7a867c563d5a63ce9dca578f956609abf8"},
@@ -1167,10 +1161,6 @@ aiohttp = [
{file = "aiohttp-3.8.1-cp39-cp39-win_amd64.whl", hash = "sha256:1c182cb873bc91b411e184dab7a2b664d4fea2743df0e4d57402f7f3fa644bac"},
{file = "aiohttp-3.8.1.tar.gz", hash = "sha256:fc5471e1a54de15ef71c1bc6ebe80d4dc681ea600e68bfd1cbce40427f0b7578"},
]
-aioredis = [
- {file = "aioredis-2.0.1-py3-none-any.whl", hash = "sha256:9ac0d0b3b485d293b8ca1987e6de8658d7dafcca1cddfcd1d506cae8cdebfdd6"},
- {file = "aioredis-2.0.1.tar.gz", hash = "sha256:eaa51aaf993f2d71f54b70527c440437ba65340588afeb786cd87c55c89cd98e"},
-]
aiosignal = [
{file = "aiosignal-1.2.0-py3-none-any.whl", hash = "sha256:26e62109036cd181df6e6ad646f91f0dcfd05fe16d0cb924138ff2ab75d64e3a"},
{file = "aiosignal-1.2.0.tar.gz", hash = "sha256:78ed67db6c7b7ced4f98e495e572106d5c432a93e1ddd1bf475e1dc05f5b7df2"},
@@ -1179,18 +1169,11 @@ alabaster = [
{file = "alabaster-0.7.12-py2.py3-none-any.whl", hash = "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359"},
{file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"},
]
-async-rediscache = [
- {file = "async-rediscache-0.2.0.tar.gz", hash = "sha256:c1fd95fe530211b999748ebff96e2e9b629f2664957f9b36916b898e42fc57c4"},
- {file = "async_rediscache-0.2.0-py3-none-any.whl", hash = "sha256:710676211b407399c9ad94afa66fa04c22a936be11ba6f227e6c74cfa140ce78"},
-]
+async-rediscache = []
async-timeout = [
{file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"},
{file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"},
]
-atomicwrites = [
- {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"},
- {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"},
-]
attrs = [
{file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"},
{file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"},
@@ -1207,6 +1190,7 @@ certifi = [
{file = "certifi-2022.5.18.1-py3-none-any.whl", hash = "sha256:f1d53542ee8cbedbe2118b5686372fb33c297fcd6379b050cca0ef13a597382a"},
{file = "certifi-2022.5.18.1.tar.gz", hash = "sha256:9c5705e395cd70084351dd8ad5c41e65655e08ce46f2ec9cf6c2c08390f71eb7"},
]
+cffi = []
cfgv = [
{file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"},
{file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"},
@@ -1219,49 +1203,7 @@ colorama = [
{file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
{file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"},
]
-coverage = [
- {file = "coverage-6.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f1d5aa2703e1dab4ae6cf416eb0095304f49d004c39e9db1d86f57924f43006b"},
- {file = "coverage-6.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4ce1b258493cbf8aec43e9b50d89982346b98e9ffdfaae8ae5793bc112fb0068"},
- {file = "coverage-6.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83c4e737f60c6936460c5be330d296dd5b48b3963f48634c53b3f7deb0f34ec4"},
- {file = "coverage-6.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84e65ef149028516c6d64461b95a8dbcfce95cfd5b9eb634320596173332ea84"},
- {file = "coverage-6.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f69718750eaae75efe506406c490d6fc5a6161d047206cc63ce25527e8a3adad"},
- {file = "coverage-6.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e57816f8ffe46b1df8f12e1b348f06d164fd5219beba7d9433ba79608ef011cc"},
- {file = "coverage-6.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:01c5615d13f3dd3aa8543afc069e5319cfa0c7d712f6e04b920431e5c564a749"},
- {file = "coverage-6.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:75ab269400706fab15981fd4bd5080c56bd5cc07c3bccb86aab5e1d5a88dc8f4"},
- {file = "coverage-6.4.1-cp310-cp310-win32.whl", hash = "sha256:a7f3049243783df2e6cc6deafc49ea123522b59f464831476d3d1448e30d72df"},
- {file = "coverage-6.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:ee2ddcac99b2d2aec413e36d7a429ae9ebcadf912946b13ffa88e7d4c9b712d6"},
- {file = "coverage-6.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fb73e0011b8793c053bfa85e53129ba5f0250fdc0392c1591fd35d915ec75c46"},
- {file = "coverage-6.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:106c16dfe494de3193ec55cac9640dd039b66e196e4641fa8ac396181578b982"},
- {file = "coverage-6.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:87f4f3df85aa39da00fd3ec4b5abeb7407e82b68c7c5ad181308b0e2526da5d4"},
- {file = "coverage-6.4.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:961e2fb0680b4f5ad63234e0bf55dfb90d302740ae9c7ed0120677a94a1590cb"},
- {file = "coverage-6.4.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:cec3a0f75c8f1031825e19cd86ee787e87cf03e4fd2865c79c057092e69e3a3b"},
- {file = "coverage-6.4.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:129cd05ba6f0d08a766d942a9ed4b29283aff7b2cccf5b7ce279d50796860bb3"},
- {file = "coverage-6.4.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:bf5601c33213d3cb19d17a796f8a14a9eaa5e87629a53979a5981e3e3ae166f6"},
- {file = "coverage-6.4.1-cp37-cp37m-win32.whl", hash = "sha256:269eaa2c20a13a5bf17558d4dc91a8d078c4fa1872f25303dddcbba3a813085e"},
- {file = "coverage-6.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f02cbbf8119db68455b9d763f2f8737bb7db7e43720afa07d8eb1604e5c5ae28"},
- {file = "coverage-6.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ffa9297c3a453fba4717d06df579af42ab9a28022444cae7fa605af4df612d54"},
- {file = "coverage-6.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:145f296d00441ca703a659e8f3eb48ae39fb083baba2d7ce4482fb2723e050d9"},
- {file = "coverage-6.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d44996140af8b84284e5e7d398e589574b376fb4de8ccd28d82ad8e3bea13"},
- {file = "coverage-6.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2bd9a6fc18aab8d2e18f89b7ff91c0f34ff4d5e0ba0b33e989b3cd4194c81fd9"},
- {file = "coverage-6.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3384f2a3652cef289e38100f2d037956194a837221edd520a7ee5b42d00cc605"},
- {file = "coverage-6.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9b3e07152b4563722be523e8cd0b209e0d1a373022cfbde395ebb6575bf6790d"},
- {file = "coverage-6.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1480ff858b4113db2718848d7b2d1b75bc79895a9c22e76a221b9d8d62496428"},
- {file = "coverage-6.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:865d69ae811a392f4d06bde506d531f6a28a00af36f5c8649684a9e5e4a85c83"},
- {file = "coverage-6.4.1-cp38-cp38-win32.whl", hash = "sha256:664a47ce62fe4bef9e2d2c430306e1428ecea207ffd68649e3b942fa8ea83b0b"},
- {file = "coverage-6.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:26dff09fb0d82693ba9e6231248641d60ba606150d02ed45110f9ec26404ed1c"},
- {file = "coverage-6.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d9c80df769f5ec05ad21ea34be7458d1dc51ff1fb4b2219e77fe24edf462d6df"},
- {file = "coverage-6.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:39ee53946bf009788108b4dd2894bf1349b4e0ca18c2016ffa7d26ce46b8f10d"},
- {file = "coverage-6.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5b66caa62922531059bc5ac04f836860412f7f88d38a476eda0a6f11d4724f4"},
- {file = "coverage-6.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd180ed867e289964404051a958f7cccabdeed423f91a899829264bb7974d3d3"},
- {file = "coverage-6.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84631e81dd053e8a0d4967cedab6db94345f1c36107c71698f746cb2636c63e3"},
- {file = "coverage-6.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8c08da0bd238f2970230c2a0d28ff0e99961598cb2e810245d7fc5afcf1254e8"},
- {file = "coverage-6.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d42c549a8f41dc103a8004b9f0c433e2086add8a719da00e246e17cbe4056f72"},
- {file = "coverage-6.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:309ce4a522ed5fca432af4ebe0f32b21d6d7ccbb0f5fcc99290e71feba67c264"},
- {file = "coverage-6.4.1-cp39-cp39-win32.whl", hash = "sha256:fdb6f7bd51c2d1714cea40718f6149ad9be6a2ee7d93b19e9f00934c0f2a74d9"},
- {file = "coverage-6.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:342d4aefd1c3e7f620a13f4fe563154d808b69cccef415415aece4c786665397"},
- {file = "coverage-6.4.1-pp36.pp37.pp38-none-any.whl", hash = "sha256:4803e7ccf93230accb928f3a68f00ffa80a88213af98ed338a57ad021ef06815"},
- {file = "coverage-6.4.1.tar.gz", hash = "sha256:4321f075095a096e70aff1d002030ee612b65a205a0a0f5b815280d5dc58100c"},
-]
+coverage = []
deprecated = [
{file = "Deprecated-1.2.13-py2.py3-none-any.whl", hash = "sha256:64756e3e14c8c5eea9795d93c524551432a0be75629f8f29e67ab8caf076c76d"},
{file = "Deprecated-1.2.13.tar.gz", hash = "sha256:43ac5335da90c31c24ba028af536a91d41d53f9e6901ddb021bcc572ce44e38d"},
@@ -1279,26 +1221,14 @@ execnet = [
{file = "execnet-1.9.0-py2.py3-none-any.whl", hash = "sha256:a295f7cc774947aac58dde7fdc85f4aa00c42adf5d8f5468fc630c1acf30a142"},
{file = "execnet-1.9.0.tar.gz", hash = "sha256:8f694f3ba9cc92cab508b152dcfe322153975c29bda272e2fd7f3f00f36e47c5"},
]
-fakeredis = [
- {file = "fakeredis-1.8.1-py3-none-any.whl", hash = "sha256:4a0f8fe0d5c18147864db50ae2e86f667420ea06653bec08b3a5fccfd3fbde6f"},
- {file = "fakeredis-1.8.1.tar.gz", hash = "sha256:ca516f86181f85615cd8210854b43acbe7b1f37ed8a082c5557749c73f2f0dd3"},
-]
+fakeredis = []
filelock = [
{file = "filelock-3.7.0-py3-none-any.whl", hash = "sha256:c7b5fdb219b398a5b28c8e4c1893ef5f98ece6a38c6ab2c22e26ec161556fed6"},
{file = "filelock-3.7.0.tar.gz", hash = "sha256:b795f1b42a61bbf8ec7113c341dad679d772567b936fbd1bf43c9a238e673e20"},
]
-flake8 = [
- {file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"},
- {file = "flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"},
-]
-flake8-annotations = [
- {file = "flake8-annotations-2.9.0.tar.gz", hash = "sha256:63fb3f538970b6a8dfd84125cf5af16f7b22e52d5032acb3b7eb23645ecbda9b"},
- {file = "flake8_annotations-2.9.0-py3-none-any.whl", hash = "sha256:84f46de2964cb18fccea968d9eafce7cf857e34d913d515120795b9af6498d56"},
-]
-flake8-bugbear = [
- {file = "flake8-bugbear-22.4.25.tar.gz", hash = "sha256:f7c080563fca75ee6b205d06b181ecba22b802babb96b0b084cc7743d6908a55"},
- {file = "flake8_bugbear-22.4.25-py3-none-any.whl", hash = "sha256:ec374101cddf65bd7a96d393847d74e58d3b98669dbf9768344c39b6290e8bd6"},
-]
+flake8 = []
+flake8-annotations = []
+flake8-bugbear = []
flake8-docstrings = [
{file = "flake8-docstrings-1.6.0.tar.gz", hash = "sha256:9fe7c6a306064af8e62a055c2f61e9eb1da55f84bb39caef2b84ce53708ac34b"},
{file = "flake8_docstrings-1.6.0-py2.py3-none-any.whl", hash = "sha256:99cac583d6c7e32dd28bbfbef120a7c0d1b6dde4adb5a9fd441c4227a6534bde"},
@@ -1307,10 +1237,6 @@ flake8-import-order = [
{file = "flake8-import-order-0.18.1.tar.gz", hash = "sha256:a28dc39545ea4606c1ac3c24e9d05c849c6e5444a50fb7e9cdd430fc94de6e92"},
{file = "flake8_import_order-0.18.1-py2.py3-none-any.whl", hash = "sha256:90a80e46886259b9c396b578d75c749801a41ee969a235e163cfe1be7afd2543"},
]
-flake8-polyfill = [
- {file = "flake8-polyfill-1.0.2.tar.gz", hash = "sha256:e44b087597f6da52ec6393a709e7108b2905317d0c0b744cdca6208e670d8eda"},
- {file = "flake8_polyfill-1.0.2-py2.py3-none-any.whl", hash = "sha256:12be6a34ee3ab795b19ca73505e7b55826d5f6ad7230d31b18e106400169b9e9"},
-]
flake8-string-format = [
{file = "flake8-string-format-0.3.0.tar.gz", hash = "sha256:65f3da786a1461ef77fca3780b314edb2853c377f2e35069723348c8917deaa2"},
{file = "flake8_string_format-0.3.0-py2.py3-none-any.whl", hash = "sha256:812ff431f10576a74c89be4e85b8e075a705be39bc40c4b4278b5b13e2afa9af"},
@@ -1383,10 +1309,7 @@ frozenlist = [
{file = "frozenlist-1.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:772965f773757a6026dea111a15e6e2678fbd6216180f82a48a40b27de1ee2ab"},
{file = "frozenlist-1.3.0.tar.gz", hash = "sha256:ce6f2ba0edb7b0c1d8976565298ad2deba6f8064d2bebb6ffce2ca896eb35b0b"},
]
-furo = [
- {file = "furo-2022.4.7-py3-none-any.whl", hash = "sha256:7f3e3d2fb977483590f8ecb2c2cd511bd82661b79c18efb24de9558bc9cdf2d7"},
- {file = "furo-2022.4.7.tar.gz", hash = "sha256:96204ab7cd047e4b6c523996e0279c4c629a8fc31f4f109b2efd470c17f49c80"},
-]
+furo = []
gitdb = [
{file = "gitdb-4.0.9-py3-none-any.whl", hash = "sha256:8033ad4e853066ba6ca92050b9df2f89301b8fc8bf7e9324d412a63f8bf1a8fd"},
{file = "gitdb-4.0.9.tar.gz", hash = "sha256:bac2fd45c0a1c9cf619e63a90d62bdc63892ef92387424b855792a6cabe789aa"},
@@ -1407,10 +1330,6 @@ imagesize = [
{file = "imagesize-1.3.0-py2.py3-none-any.whl", hash = "sha256:1db2f82529e53c3e929e8926a1fa9235aa82d0bd0c580359c67ec31b2fddaa8c"},
{file = "imagesize-1.3.0.tar.gz", hash = "sha256:cd1750d452385ca327479d45b64d9c7729ecf0b3969a58148298c77092261f9d"},
]
-importlib-metadata = [
- {file = "importlib_metadata-4.11.4-py3-none-any.whl", hash = "sha256:c58c8eb8a762858f49e18436ff552e83914778e50e9d2f1660535ffb364552ec"},
- {file = "importlib_metadata-4.11.4.tar.gz", hash = "sha256:5d26852efe48c0a32b0509ffbc583fda1a2266545a78d104a6f4aff3db17d700"},
-]
iniconfig = [
{file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
{file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"},
@@ -1529,10 +1448,7 @@ markupsafe = [
{file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"},
{file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"},
]
-mccabe = [
- {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"},
- {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"},
-]
+mccabe = []
mslex = [
{file = "mslex-0.3.0-py2.py3-none-any.whl", hash = "sha256:380cb14abf8fabf40e56df5c8b21a6d533dc5cbdcfe42406bbf08dda8f42e42a"},
{file = "mslex-0.3.0.tar.gz", hash = "sha256:4a1ac3f25025cad78ad2fe499dd16d42759f7a3801645399cce5c404415daa97"},
@@ -1606,10 +1522,7 @@ packaging = [
{file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
{file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
]
-pep8-naming = [
- {file = "pep8-naming-0.12.1.tar.gz", hash = "sha256:bb2455947757d162aa4cad55dba4ce029005cd1692f2899a21d51d8630ca7841"},
- {file = "pep8_naming-0.12.1-py2.py3-none-any.whl", hash = "sha256:4a8daeaeb33cfcde779309fc0c9c0a68a3bbe2ad8a8308b763c5068f86eb9f37"},
-]
+pep8-naming = []
platformdirs = [
{file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"},
{file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"},
@@ -1618,10 +1531,7 @@ pluggy = [
{file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
{file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"},
]
-pre-commit = [
- {file = "pre_commit-2.19.0-py2.py3-none-any.whl", hash = "sha256:10c62741aa5704faea2ad69cb550ca78082efe5697d6f04e5710c3c229afdd10"},
- {file = "pre_commit-2.19.0.tar.gz", hash = "sha256:4233a1e38621c87d9dda9808c6606d7e7ba0e087cd56d3fe03202a01d2919615"},
-]
+pre-commit = []
psutil = [
{file = "psutil-5.9.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:799759d809c31aab5fe4579e50addf84565e71c1dc9f1c31258f159ff70d3f87"},
{file = "psutil-5.9.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:9272167b5f5fbfe16945be3db475b3ce8d792386907e673a209da686176552af"},
@@ -1660,18 +1570,17 @@ py = [
{file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"},
{file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"},
]
-pycodestyle = [
- {file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"},
- {file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"},
+pycares = []
+pycodestyle = []
+pycparser = [
+ {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"},
+ {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"},
]
pydocstyle = [
{file = "pydocstyle-6.1.1-py3-none-any.whl", hash = "sha256:6987826d6775056839940041beef5c08cc7e3d71d63149b48e36727f70144dc4"},
{file = "pydocstyle-6.1.1.tar.gz", hash = "sha256:1d41b7c459ba0ee6c345f2eb9ae827cab14a7533a88c5c6f7e94923f72df92dc"},
]
-pyflakes = [
- {file = "pyflakes-2.4.0-py2.py3-none-any.whl", hash = "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e"},
- {file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"},
-]
+pyflakes = []
pygments = [
{file = "Pygments-2.12.0-py3-none-any.whl", hash = "sha256:dc9c10fb40944260f6ed4c688ece0cd2048414940f1cea51b8b226318411c519"},
{file = "Pygments-2.12.0.tar.gz", hash = "sha256:5eb116118f9612ff1ee89ac96437bb6b49e8f04d8a13b514ba26f620208e26eb"},
@@ -1680,10 +1589,7 @@ pyparsing = [
{file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"},
{file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"},
]
-pytest = [
- {file = "pytest-7.1.2-py3-none-any.whl", hash = "sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c"},
- {file = "pytest-7.1.2.tar.gz", hash = "sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45"},
-]
+pytest = []
pytest-cov = [
{file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"},
{file = "pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"},
@@ -1696,10 +1602,7 @@ pytest-xdist = [
{file = "pytest-xdist-2.5.0.tar.gz", hash = "sha256:4580deca3ff04ddb2ac53eba39d76cb5dd5edeac050cb6fbc768b0dd712b4edf"},
{file = "pytest_xdist-2.5.0-py3-none-any.whl", hash = "sha256:6fe5c74fec98906deb8f2d2b616b5c782022744978e7bd4695d39c8f42d0ce65"},
]
-python-dotenv = [
- {file = "python-dotenv-0.20.0.tar.gz", hash = "sha256:b7e3b04a59693c42c36f9ab1cc2acc46fa5df8c78e178fc33a8d4cd05c8d498f"},
- {file = "python_dotenv-0.20.0-py3-none-any.whl", hash = "sha256:d92a187be61fe482e4fd675b6d52200e7be63a12b724abbf931a40ce4fa92938"},
-]
+python-dotenv = []
pytz = [
{file = "pytz-2022.1-py2.py3-none-any.whl", hash = "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c"},
{file = "pytz-2022.1.tar.gz", hash = "sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7"},
@@ -1775,14 +1678,9 @@ soupsieve = [
{file = "soupsieve-2.3.2.post1-py3-none-any.whl", hash = "sha256:3b2503d3c7084a42b1ebd08116e5f81aadfaea95863628c80a3b774a11b7c759"},
{file = "soupsieve-2.3.2.post1.tar.gz", hash = "sha256:fc53893b3da2c33de295667a0e19f078c14bf86544af307354de5fcf12a3f30d"},
]
-sphinx = [
- {file = "Sphinx-4.5.0-py3-none-any.whl", hash = "sha256:ebf612653238bcc8f4359627a9b7ce44ede6fdd75d9d30f68255c7383d3a6226"},
- {file = "Sphinx-4.5.0.tar.gz", hash = "sha256:7bf8ca9637a4ee15af412d1a1d9689fec70523a68ca9bb9127c2f3eeb344e2e6"},
-]
-sphinx-autodoc-typehints = [
- {file = "sphinx_autodoc_typehints-1.18.1-py3-none-any.whl", hash = "sha256:f8f5bb7c13a9a71537dc2be2eb3b9e28a9711e2454df63587005eacf6fbac453"},
- {file = "sphinx_autodoc_typehints-1.18.1.tar.gz", hash = "sha256:07631c5f0c6641e5ba27143494aefc657e029bed3982138d659250e617f6f929"},
-]
+sphinx = []
+sphinx-autodoc-typehints = []
+sphinx-basic-ng = []
sphinx-multiversion = [
{file = "sphinx-multiversion-0.2.4.tar.gz", hash = "sha256:5cd1ca9ecb5eed63cb8d6ce5e9c438ca13af4fa98e7eb6f376be541dd4990bcb"},
{file = "sphinx_multiversion-0.2.4-py3-none-any.whl", hash = "sha256:dec29f2a5890ad68157a790112edc0eb63140e70f9df0a363743c6258fbeb478"},
@@ -1815,10 +1713,7 @@ statsd = [
{file = "statsd-3.3.0-py2.py3-none-any.whl", hash = "sha256:c610fb80347fca0ef62666d241bce64184bd7cc1efe582f9690e045c25535eaa"},
{file = "statsd-3.3.0.tar.gz", hash = "sha256:e3e6db4c246f7c59003e51c9720a51a7f39a396541cb9b147ff4b14d15b5dd1f"},
]
-taskipy = [
- {file = "taskipy-1.10.2-py3-none-any.whl", hash = "sha256:58d5382d90d5dd94ca8c612855377e5a98b9cb669c208ebb55d6a45946de3f9b"},
- {file = "taskipy-1.10.2.tar.gz", hash = "sha256:eae4feb74909da3ad0ca0275802e1c2f56048612529bd763feb922d284d8a253"},
-]
+taskipy = []
toml = [
{file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
{file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
@@ -1827,10 +1722,7 @@ tomli = [
{file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
]
-typing-extensions = [
- {file = "typing_extensions-4.2.0-py3-none-any.whl", hash = "sha256:6657594ee297170d19f67d55c05852a874e7eb634f4f753dbd667855e07c1708"},
- {file = "typing_extensions-4.2.0.tar.gz", hash = "sha256:f1c24655a0da0d1b67f07e17a5e6b2a105894e6824b92096378bb3668ef02376"},
-]
+typing-extensions = []
urllib3 = [
{file = "urllib3-1.26.9-py2.py3-none-any.whl", hash = "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14"},
{file = "urllib3-1.26.9.tar.gz", hash = "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e"},
@@ -1979,7 +1871,3 @@ yarl = [
{file = "yarl-1.7.2-cp39-cp39-win_amd64.whl", hash = "sha256:797c2c412b04403d2da075fb93c123df35239cd7b4cc4e0cd9e5839b73f52c58"},
{file = "yarl-1.7.2.tar.gz", hash = "sha256:45399b46d60c253327a460e99856752009fcee5f5d3c80b2f7c0cae1c38d56dd"},
]
-zipp = [
- {file = "zipp-3.8.0-py3-none-any.whl", hash = "sha256:c4f6e5bbf48e74f7a38e7cc5b0480ff42b0ae5178957d564d18932525d5cf099"},
- {file = "zipp-3.8.0.tar.gz", hash = "sha256:56bf8aadb83c24db6c4b577e13de374ccfb67da2078beba1d037c17980bf43ad"},
-]
diff --git a/pyproject.toml b/pyproject.toml
index b6ab4a23..6386c6ad 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[tool.poetry]
name = "bot-core"
-version = "7.1.1"
+version = "8.2.1"
description = "Bot-Core provides the core functionality and utilities for the bots of the Python Discord community."
authors = ["Python Discord <[email protected]>"]
license = "MIT"
@@ -15,35 +15,36 @@ packages = [
exclude = ["tests", "tests.*"]
[tool.poetry.dependencies]
-python = "3.9.*"
-"discord.py" = {url = "https://github.com/Rapptz/discord.py/archive/4cbe8f58e16f6a76371ce45a69e0832130d6d24f.zip"}
-async-rediscache = { version = "0.2.0", extras = ["fakeredis"], optional = true }
+python = "3.10.*"
+"discord.py" = "2.0.1"
+async-rediscache = { version = "1.0.0rc2", extras = ["fakeredis"], optional = true }
statsd = "3.3.0"
+aiodns = "3.0.0"
[tool.poetry.extras]
async-rediscache = ["async-rediscache"]
[tool.poetry.dev-dependencies]
-typing-extensions = "4.2.0"
-flake8 = "4.0.1"
-flake8-annotations = "2.9.0"
-flake8-bugbear = "22.4.25"
+typing-extensions = "4.3.0"
+flake8 = "5.0.4"
+flake8-annotations = "2.9.1"
+flake8-bugbear = "22.9.11"
flake8-docstrings = "1.6.0"
flake8-import-order = "0.18.1"
flake8-string-format = "0.3.0"
flake8-tidy-imports = "4.8.0"
flake8-todo = "0.7"
-pep8-naming = "0.12.1"
-pre-commit = "2.19.0"
-taskipy = "1.10.2"
-python-dotenv = "0.20.0"
-pytest = "7.1.2"
+pep8-naming = "0.13.2"
+pre-commit = "2.20.0"
+taskipy = "1.10.3"
+python-dotenv = "0.21.0"
+pytest = "7.1.3"
pytest-cov = "3.0.0"
pytest-xdist = "2.5.0"
-Sphinx = "4.5.0"
+Sphinx = "5.1.1"
GitPython = "3.1.27"
-sphinx-autodoc-typehints = "1.18.1"
-furo = "2022.4.7"
+sphinx-autodoc-typehints = "1.19.2"
+furo = "2022.9.15"
releases = "1.6.3"
sphinx-multiversion = "0.2.4"
diff --git a/tests/botcore/utils/test_regex.py b/tests/botcore/utils/test_regex.py
index 2ffd0e46..491e22bd 100644
--- a/tests/botcore/utils/test_regex.py
+++ b/tests/botcore/utils/test_regex.py
@@ -4,8 +4,18 @@ from typing import Optional
from botcore.utils.regex import DISCORD_INVITE
-def use_regex(s: str) -> Optional[str]:
- """Helper function to run the Regex on a string.
+def match_regex(s: str) -> Optional[str]:
+ """Helper function to run re.match on a string.
+
+ Return the invite capture group, if the string matches the pattern
+ else return None
+ """
+ result = DISCORD_INVITE.match(s)
+ return result if result is None else result.group("invite")
+
+
+def search_regex(s: str) -> Optional[str]:
+ """Helper function to run re.search on a string.
Return the invite capture group, if the string matches the pattern
else return None
@@ -19,32 +29,37 @@ class UtilsRegexTests(unittest.TestCase):
def test_discord_invite_positives(self):
"""Test the DISCORD_INVITE regex on a set of strings we would expect to capture."""
- self.assertEqual(use_regex("discord.gg/python"), "python")
- self.assertEqual(use_regex("https://discord.gg/python"), "python")
- self.assertEqual(use_regex("discord.com/invite/python"), "python")
- self.assertEqual(use_regex("discordapp.com/invite/python"), "python")
- self.assertEqual(use_regex("discord.me/python"), "python")
- self.assertEqual(use_regex("discord.li/python"), "python")
- self.assertEqual(use_regex("discord.io/python"), "python")
- self.assertEqual(use_regex(".gg/python"), "python")
-
- self.assertEqual(use_regex("discord.gg/python/but/extra"), "python/but/extra")
- self.assertEqual(use_regex("discord.me/this/isnt/python"), "this/isnt/python")
- self.assertEqual(use_regex(".gg/a/a/a/a/a/a/a/a/a/a/a"), "a/a/a/a/a/a/a/a/a/a/a")
- self.assertEqual(use_regex("discordapp.com/invite/python/snakescord"), "python/snakescord")
- self.assertEqual(use_regex("http://discord.gg/python/%20/notpython"), "python/%20/notpython")
- self.assertEqual(use_regex("discord.gg/python?=ts/notpython"), "python?=ts/notpython")
- self.assertEqual(use_regex("https://discord.gg/python#fragment/notpython"), "python#fragment/notpython")
- self.assertEqual(use_regex("https://discord.gg/python/~/notpython"), "python/~/notpython")
-
- self.assertEqual(use_regex("https://discord.gg/python with whitespace"), "python")
- self.assertEqual(use_regex(" https://discord.gg/python "), "python")
+ self.assertEqual(match_regex("discord.gg/python"), "python")
+ self.assertEqual(match_regex("https://discord.gg/python"), "python")
+ self.assertEqual(match_regex("https://www.discord.gg/python"), "python")
+ self.assertEqual(match_regex("discord.com/invite/python"), "python")
+ self.assertEqual(match_regex("www.discord.com/invite/python"), "python")
+ self.assertEqual(match_regex("discordapp.com/invite/python"), "python")
+ self.assertEqual(match_regex("discord.me/python"), "python")
+ self.assertEqual(match_regex("discord.li/python"), "python")
+ self.assertEqual(match_regex("discord.io/python"), "python")
+ self.assertEqual(match_regex(".gg/python"), "python")
+
+ self.assertEqual(match_regex("discord.gg/python/but/extra"), "python/but/extra")
+ self.assertEqual(match_regex("discord.me/this/isnt/python"), "this/isnt/python")
+ self.assertEqual(match_regex(".gg/a/a/a/a/a/a/a/a/a/a/a"), "a/a/a/a/a/a/a/a/a/a/a")
+ self.assertEqual(match_regex("discordapp.com/invite/python/snakescord"), "python/snakescord")
+ self.assertEqual(match_regex("http://discord.gg/python/%20/notpython"), "python/%20/notpython")
+ self.assertEqual(match_regex("discord.gg/python?=ts/notpython"), "python?=ts/notpython")
+ self.assertEqual(match_regex("https://discord.gg/python#fragment/notpython"), "python#fragment/notpython")
+ self.assertEqual(match_regex("https://discord.gg/python/~/notpython"), "python/~/notpython")
+
+ self.assertEqual(search_regex("https://discord.gg/python with whitespace"), "python")
+ self.assertEqual(search_regex(" https://discord.gg/python "), "python")
def test_discord_invite_negatives(self):
"""Test the DISCORD_INVITE regex on a set of strings we would expect to not capture."""
- self.assertEqual(use_regex("another string"), None)
- self.assertEqual(use_regex("https://pythondiscord.com"), None)
- self.assertEqual(use_regex("https://discord.com"), None)
- self.assertEqual(use_regex("https://discord.gg"), None)
- self.assertEqual(use_regex("https://discord.gg/ python"), None)
+ self.assertEqual(match_regex("another string"), None)
+ self.assertEqual(match_regex("https://pythondiscord.com"), None)
+ self.assertEqual(match_regex("https://discord.com"), None)
+ self.assertEqual(match_regex("https://discord.gg"), None)
+ self.assertEqual(match_regex("https://discord.gg/ python"), None)
+
+ self.assertEqual(search_regex("https://discord.com with whitespace"), None)
+ self.assertEqual(search_regex(" https://discord.com "), None)
diff --git a/tox.ini b/tox.ini
index 390bffb9..717e412d 100644
--- a/tox.ini
+++ b/tox.ini
@@ -3,7 +3,7 @@ max-line-length=120
docstring-convention=all
import-order-style=pycharm
application_import_names=botcore,docs,tests
-exclude=.cache,.venv,.git,constants.py
+exclude=.cache,.venv,.git,constants.py,bot/
ignore=
B311,W503,E226,S311,T000,E731
# Missing Docstrings