aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/lint-test.yml6
-rw-r--r--.pre-commit-config.yaml5
-rw-r--r--bot/__main__.py6
-rw-r--r--bot/api.py5
-rw-r--r--bot/bot.py4
-rw-r--r--bot/constants.py12
-rw-r--r--bot/converters.py7
-rw-r--r--bot/decorators.py4
-rw-r--r--bot/exts/backend/branding/_cog.py4
-rw-r--r--bot/exts/backend/branding/_repository.py4
-rw-r--r--bot/exts/backend/config_verifier.py5
-rw-r--r--bot/exts/backend/error_handler.py4
-rw-r--r--bot/exts/backend/logging.py5
-rw-r--r--bot/exts/backend/sync/_cog.py4
-rw-r--r--bot/exts/backend/sync/_syncers.py4
-rw-r--r--bot/exts/events/code_jams/_channels.py4
-rw-r--r--bot/exts/events/code_jams/_cog.py4
-rw-r--r--bot/exts/filters/antimalware.py4
-rw-r--r--bot/exts/filters/antispam.py19
-rw-r--r--bot/exts/filters/filter_lists.py4
-rw-r--r--bot/exts/filters/filtering.py13
-rw-r--r--bot/exts/filters/security.py5
-rw-r--r--bot/exts/filters/token_remover.py6
-rw-r--r--bot/exts/filters/webhook_remover.py6
-rw-r--r--bot/exts/fun/duck_pond.py8
-rw-r--r--bot/exts/fun/off_topic_names.py4
-rw-r--r--bot/exts/help_channels/__init__.py5
-rw-r--r--bot/exts/help_channels/_channel.py4
-rw-r--r--bot/exts/help_channels/_cog.py4
-rw-r--r--bot/exts/help_channels/_message.py4
-rw-r--r--bot/exts/help_channels/_name.py4
-rw-r--r--bot/exts/help_channels/_stats.py5
-rw-r--r--bot/exts/info/code_snippets.py3
-rw-r--r--bot/exts/info/codeblock/_cog.py4
-rw-r--r--bot/exts/info/codeblock/_instructions.py4
-rw-r--r--bot/exts/info/codeblock/_parsing.py4
-rw-r--r--bot/exts/info/doc/__init__.py1
-rw-r--r--bot/exts/info/doc/_batch_parser.py5
-rw-r--r--bot/exts/info/doc/_cog.py5
-rw-r--r--bot/exts/info/doc/_html.py5
-rw-r--r--bot/exts/info/doc/_inventory_parser.py4
-rw-r--r--bot/exts/info/doc/_parsing.py6
-rw-r--r--bot/exts/info/doc/_redis_cache.py1
-rw-r--r--bot/exts/info/help.py4
-rw-r--r--bot/exts/info/information.py6
-rw-r--r--bot/exts/info/pep.py4
-rw-r--r--bot/exts/info/pypi.py4
-rw-r--r--bot/exts/info/python_news.py4
-rw-r--r--bot/exts/info/site.py5
-rw-r--r--bot/exts/info/tags.py4
-rw-r--r--bot/exts/moderation/defcon.py46
-rw-r--r--bot/exts/moderation/dm_relay.py5
-rw-r--r--bot/exts/moderation/incidents.py9
-rw-r--r--bot/exts/moderation/infraction/_scheduler.py38
-rw-r--r--bot/exts/moderation/infraction/_utils.py4
-rw-r--r--bot/exts/moderation/infraction/infractions.py4
-rw-r--r--bot/exts/moderation/infraction/management.py13
-rw-r--r--bot/exts/moderation/infraction/superstarify.py4
-rw-r--r--bot/exts/moderation/metabase.py4
-rw-r--r--bot/exts/moderation/modlog.py136
-rw-r--r--bot/exts/moderation/modpings.py4
-rw-r--r--bot/exts/moderation/silence.py42
-rw-r--r--bot/exts/moderation/slowmode.py4
-rw-r--r--bot/exts/moderation/stream.py7
-rw-r--r--bot/exts/moderation/verification.py4
-rw-r--r--bot/exts/moderation/voice_gate.py10
-rw-r--r--bot/exts/moderation/watchchannels/_watchchannel.py12
-rw-r--r--bot/exts/moderation/watchchannels/bigbrother.py12
-rw-r--r--bot/exts/recruitment/talentpool/_cog.py4
-rw-r--r--bot/exts/recruitment/talentpool/_review.py4
-rw-r--r--bot/exts/utils/bot.py21
-rw-r--r--bot/exts/utils/clean.py10
-rw-r--r--bot/exts/utils/extensions.py4
-rw-r--r--bot/exts/utils/internal.py4
-rw-r--r--bot/exts/utils/ping.py2
-rw-r--r--bot/exts/utils/reminders.py11
-rw-r--r--bot/exts/utils/snekbox.py4
-rw-r--r--bot/exts/utils/utils.py4
-rw-r--r--bot/log.py65
-rw-r--r--bot/monkey_patches.py5
-rw-r--r--bot/pagination.py4
-rw-r--r--bot/resources/tags/async-await.md15
-rw-r--r--bot/resources/tags/contribute.md12
-rw-r--r--bot/resources/tags/traceback.md14
-rw-r--r--bot/resources/tags/windows-path.md23
-rw-r--r--bot/resources/tags/xy-problem.md4
-rw-r--r--bot/resources/tags/ytdl.md2
-rw-r--r--bot/resources/tags/zip.md2
-rw-r--r--bot/rules/discord_emojis.py1
-rw-r--r--bot/rules/links.py1
-rw-r--r--bot/utils/channel.py5
-rw-r--r--bot/utils/checks.py20
-rw-r--r--bot/utils/function.py5
-rw-r--r--bot/utils/lock.py4
-rw-r--r--bot/utils/members.py5
-rw-r--r--bot/utils/messages.py6
-rw-r--r--bot/utils/scheduling.py7
-rw-r--r--bot/utils/services.py4
-rw-r--r--bot/utils/webhooks.py4
-rw-r--r--config-default.yml6
-rw-r--r--poetry.lock679
-rw-r--r--pyproject.toml13
-rw-r--r--tests/__init__.py3
-rw-r--r--tests/base.py5
-rw-r--r--tests/bot/exts/backend/sync/test_base.py1
-rw-r--r--tests/bot/exts/backend/test_error_handler.py2
-rw-r--r--tests/bot/exts/events/test_code_jams.py4
-rw-r--r--tests/bot/exts/filters/test_token_remover.py4
-rw-r--r--tests/bot/exts/info/test_information.py5
-rw-r--r--tests/bot/exts/moderation/test_incidents.py15
-rw-r--r--tests/bot/exts/moderation/test_silence.py73
-rw-r--r--tests/bot/test_converters.py8
-rw-r--r--tests/bot/utils/test_checks.py1
-rw-r--r--tests/helpers.py27
-rw-r--r--tests/test_base.py11
-rw-r--r--tox.ini2
116 files changed, 1036 insertions, 712 deletions
diff --git a/.github/workflows/lint-test.yml b/.github/workflows/lint-test.yml
index 2f42f1895..f2c9dfb6c 100644
--- a/.github/workflows/lint-test.yml
+++ b/.github/workflows/lint-test.yml
@@ -81,12 +81,14 @@ jobs:
pip install poetry
poetry install
- # Check all the dependencies are compatible with the MIT license.
+ # Check all of our non-dev dependencies are compatible with the MIT license.
# If you added a new dependencies that is being rejected,
# please make sure it is compatible with the license for this project,
# and add it to the ALLOWED_LICENSE variable
- name: Check Dependencies License
- run: pip-licenses --allow-only="$ALLOWED_LICENSE"
+ run: |
+ pip-licenses --allow-only="$ALLOWED_LICENSE" \
+ --package $(poetry export -f requirements.txt --without-hashes | sed "s/==.*//g" | tr "\n" " ")
# This step caches our pre-commit environment. To make sure we
# do create a new environment when our pre-commit setup changes,
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index a9412f07d..d8a90ac00 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -13,6 +13,11 @@ repos:
rev: v1.5.1
hooks:
- id: python-check-blanket-noqa
+ - repo: https://github.com/pycqa/isort
+ rev: 5.8.0
+ hooks:
+ - id: isort
+ name: isort (python)
- repo: local
hooks:
- id: flake8
diff --git a/bot/__main__.py b/bot/__main__.py
index 9317563c8..0d3fce180 100644
--- a/bot/__main__.py
+++ b/bot/__main__.py
@@ -1,11 +1,9 @@
-import logging
-
import aiohttp
import bot
from bot import constants
from bot.bot import Bot, StartupError
-from bot.log import setup_sentry
+from bot.log import get_logger, setup_sentry
setup_sentry()
@@ -21,7 +19,7 @@ except StartupError as e:
message = "Could not connect to Redis. Is it running?"
# The exception is logged with an empty message so the actual message is visible at the bottom
- log = logging.getLogger("bot")
+ log = get_logger("bot")
log.fatal("", exc_info=e.exception)
log.fatal(message)
diff --git a/bot/api.py b/bot/api.py
index 6ce9481f4..856f7c865 100644
--- a/bot/api.py
+++ b/bot/api.py
@@ -1,13 +1,14 @@
import asyncio
-import logging
from typing import Optional
from urllib.parse import quote as quote_url
import aiohttp
+from bot.log import get_logger
+
from .constants import Keys, URLs
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
class ResponseCodeError(ValueError):
diff --git a/bot/bot.py b/bot/bot.py
index db3d651a3..94783a466 100644
--- a/bot/bot.py
+++ b/bot/bot.py
@@ -1,5 +1,4 @@
import asyncio
-import logging
import socket
import warnings
from collections import defaultdict
@@ -14,8 +13,9 @@ from sentry_sdk import push_scope
from bot import api, constants
from bot.async_stats import AsyncStatsClient
+from bot.log import get_logger
-log = logging.getLogger('bot')
+log = get_logger('bot')
LOCALHOST = "127.0.0.1"
diff --git a/bot/constants.py b/bot/constants.py
index 33c911874..7a7ed8f42 100644
--- a/bot/constants.py
+++ b/bot/constants.py
@@ -9,8 +9,6 @@ the custom configuration. Any settings left
out in the custom user configuration will stay
their default values from `config-default.yml`.
"""
-
-import logging
import os
from collections.abc import Mapping
from enum import Enum
@@ -25,8 +23,6 @@ try:
except ModuleNotFoundError:
pass
-log = logging.getLogger(__name__)
-
def _env_var_constructor(loader, node):
"""
@@ -104,7 +100,7 @@ def _recursive_update(original, new):
if Path("config.yml").exists():
- log.info("Found `config.yml` file, loading constants from it.")
+ print("Found `config.yml` file, loading constants from it.")
with open("config.yml", encoding="UTF-8") as f:
user_config = yaml.safe_load(f)
_recursive_update(_CONFIG_YAML, user_config)
@@ -123,11 +119,10 @@ def check_required_keys(keys):
if lookup is None:
raise KeyError(key)
except KeyError:
- log.critical(
+ raise KeyError(
f"A configuration for `{key_path}` is required, but was not found. "
"Please set it in `config.yml` or setup an environment variable and try again."
)
- raise
try:
@@ -186,8 +181,7 @@ class YAMLGetter(type):
(cls.section, cls.subsection, name)
if cls.subsection is not None else (cls.section, name)
)
- # Only an INFO log since this can be caught through `hasattr` or `getattr`.
- log.info(f"Tried accessing configuration variable at `{dotted_path}`, but it could not be found.")
+ print(f"Tried accessing configuration variable at `{dotted_path}`, but it could not be found.")
raise AttributeError(repr(name)) from e
def __getitem__(cls, name):
diff --git a/bot/converters.py b/bot/converters.py
index c96e2c984..4a4d3b544 100644
--- a/bot/converters.py
+++ b/bot/converters.py
@@ -1,6 +1,5 @@
from __future__ import annotations
-import logging
import re
import typing as t
from datetime import datetime
@@ -19,13 +18,15 @@ from bot.api import ResponseCodeError
from bot.constants import URLs
from bot.errors import InvalidInfraction
from bot.exts.info.doc import _inventory_parser
+from bot.log import get_logger
from bot.utils.extensions import EXTENSIONS, unqualify
from bot.utils.regex import INVITE_RE
from bot.utils.time import parse_duration_string
+
if t.TYPE_CHECKING:
from bot.exts.info.source import SourceType
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
DISCORD_EPOCH_DT = datetime.utcfromtimestamp(DISCORD_EPOCH / 1000)
RE_USER_MENTION = re.compile(r"<@!?([0-9]+)>$")
@@ -272,7 +273,7 @@ class Snowflake(IDConverter):
snowflake = int(arg)
try:
- time = snowflake_time(snowflake)
+ time = snowflake_time(snowflake).replace(tzinfo=None)
except (OverflowError, OSError) as e:
# Not sure if this can ever even happen, but let's be safe.
raise BadArgument(f"{error}: {e}")
diff --git a/bot/decorators.py b/bot/decorators.py
index ee210be26..048a2a09a 100644
--- a/bot/decorators.py
+++ b/bot/decorators.py
@@ -1,6 +1,5 @@
import asyncio
import functools
-import logging
import types
import typing as t
from contextlib import suppress
@@ -10,11 +9,12 @@ from discord.ext import commands
from discord.ext.commands import Cog, Context
from bot.constants import Channels, DEBUG_MODE, RedirectOutput
+from bot.log import get_logger
from bot.utils import function, scheduling
from bot.utils.checks import ContextCheckFailure, in_whitelist_check
from bot.utils.function import command_wraps
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
def in_whitelist(
diff --git a/bot/exts/backend/branding/_cog.py b/bot/exts/backend/branding/_cog.py
index ab0a761ff..9c5bdbb4e 100644
--- a/bot/exts/backend/branding/_cog.py
+++ b/bot/exts/backend/branding/_cog.py
@@ -1,6 +1,5 @@
import asyncio
import contextlib
-import logging
import random
import typing as t
from datetime import timedelta
@@ -17,9 +16,10 @@ from bot.bot import Bot
from bot.constants import Branding as BrandingConfig, Channels, Colours, Guild, MODERATION_ROLES
from bot.decorators import mock_in_debug
from bot.exts.backend.branding._repository import BrandingRepository, Event, RemoteObject
+from bot.log import get_logger
from bot.utils import scheduling
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
class AssetType(Enum):
diff --git a/bot/exts/backend/branding/_repository.py b/bot/exts/backend/branding/_repository.py
index 7b09d4641..d88ea67f3 100644
--- a/bot/exts/backend/branding/_repository.py
+++ b/bot/exts/backend/branding/_repository.py
@@ -1,4 +1,3 @@
-import logging
import typing as t
from datetime import date, datetime
@@ -7,6 +6,7 @@ import frontmatter
from bot.bot import Bot
from bot.constants import Keys
from bot.errors import BrandingMisconfiguration
+from bot.log import get_logger
# Base URL for requests into the branding repository.
BRANDING_URL = "https://api.github.com/repos/python-discord/branding/contents"
@@ -25,7 +25,7 @@ ARBITRARY_YEAR = 2020
# Format used to parse date strings after we inject `ARBITRARY_YEAR` at the end.
DATE_FMT = "%B %d %Y" # Ex: July 10 2020
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
class RemoteObject:
diff --git a/bot/exts/backend/config_verifier.py b/bot/exts/backend/config_verifier.py
index c24cb324f..dc85a65a2 100644
--- a/bot/exts/backend/config_verifier.py
+++ b/bot/exts/backend/config_verifier.py
@@ -1,12 +1,11 @@
-import logging
-
from discord.ext.commands import Cog
from bot import constants
from bot.bot import Bot
+from bot.log import get_logger
from bot.utils import scheduling
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
class ConfigVerifier(Cog):
diff --git a/bot/exts/backend/error_handler.py b/bot/exts/backend/error_handler.py
index 578c372c3..7644b93ae 100644
--- a/bot/exts/backend/error_handler.py
+++ b/bot/exts/backend/error_handler.py
@@ -1,5 +1,4 @@
import difflib
-import logging
import typing as t
from discord import Embed
@@ -11,9 +10,10 @@ from bot.bot import Bot
from bot.constants import Colours, Icons, MODERATION_ROLES
from bot.converters import TagNameConverter
from bot.errors import InvalidInfractedUserError, LockedResourceError
+from bot.log import get_logger
from bot.utils.checks import ContextCheckFailure
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
class ErrorHandler(Cog):
diff --git a/bot/exts/backend/logging.py b/bot/exts/backend/logging.py
index 8f1b8026f..2d03cd580 100644
--- a/bot/exts/backend/logging.py
+++ b/bot/exts/backend/logging.py
@@ -1,13 +1,12 @@
-import logging
-
from discord import Embed
from discord.ext.commands import Cog
from bot.bot import Bot
from bot.constants import Channels, DEBUG_MODE
+from bot.log import get_logger
from bot.utils import scheduling
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
class Logging(Cog):
diff --git a/bot/exts/backend/sync/_cog.py b/bot/exts/backend/sync/_cog.py
index f88dcf538..80f5750bc 100644
--- a/bot/exts/backend/sync/_cog.py
+++ b/bot/exts/backend/sync/_cog.py
@@ -1,4 +1,3 @@
-import logging
from typing import Any, Dict
from discord import Member, Role, User
@@ -9,9 +8,10 @@ from bot import constants
from bot.api import ResponseCodeError
from bot.bot import Bot
from bot.exts.backend.sync import _syncers
+from bot.log import get_logger
from bot.utils import scheduling
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
class Sync(Cog):
diff --git a/bot/exts/backend/sync/_syncers.py b/bot/exts/backend/sync/_syncers.py
index 50016df0c..45301b098 100644
--- a/bot/exts/backend/sync/_syncers.py
+++ b/bot/exts/backend/sync/_syncers.py
@@ -1,5 +1,4 @@
import abc
-import logging
import typing as t
from collections import namedtuple
@@ -9,9 +8,10 @@ from more_itertools import chunked
import bot
from bot.api import ResponseCodeError
+from bot.log import get_logger
from bot.utils.members import get_or_fetch_member
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
CHUNK_SIZE = 1000
diff --git a/bot/exts/events/code_jams/_channels.py b/bot/exts/events/code_jams/_channels.py
index 34ff0ad41..e8cf5f7bf 100644
--- a/bot/exts/events/code_jams/_channels.py
+++ b/bot/exts/events/code_jams/_channels.py
@@ -1,11 +1,11 @@
-import logging
import typing as t
import discord
from bot.constants import Categories, Channels, Roles
+from bot.log import get_logger
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
MAX_CHANNELS = 50
CATEGORY_NAME = "Code Jam"
diff --git a/bot/exts/events/code_jams/_cog.py b/bot/exts/events/code_jams/_cog.py
index 7b0831ab4..b31d628d5 100644
--- a/bot/exts/events/code_jams/_cog.py
+++ b/bot/exts/events/code_jams/_cog.py
@@ -1,6 +1,5 @@
import asyncio
import csv
-import logging
import typing as t
from collections import defaultdict
@@ -11,10 +10,11 @@ from discord.ext import commands
from bot.bot import Bot
from bot.constants import Emojis, Roles
from bot.exts.events.code_jams import _channels
+from bot.log import get_logger
from bot.utils.members import get_or_fetch_member
from bot.utils.services import send_to_paste_service
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
TEAM_LEADERS_COLOUR = 0x11806a
DELETION_REACTION = "\U0001f4a5"
diff --git a/bot/exts/filters/antimalware.py b/bot/exts/filters/antimalware.py
index e708e5149..d727f7940 100644
--- a/bot/exts/filters/antimalware.py
+++ b/bot/exts/filters/antimalware.py
@@ -1,4 +1,3 @@
-import logging
import typing as t
from os.path import splitext
@@ -8,8 +7,9 @@ from discord.ext.commands import Cog
from bot.bot import Bot
from bot.constants import Channels, Filter, URLs
from bot.exts.events.code_jams._channels import CATEGORY_NAME as JAM_CATEGORY_NAME
+from bot.log import get_logger
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
PY_EMBED_DESCRIPTION = (
"It looks like you tried to attach a Python file - "
diff --git a/bot/exts/filters/antispam.py b/bot/exts/filters/antispam.py
index 8bae159d2..37ac70508 100644
--- a/bot/exts/filters/antispam.py
+++ b/bot/exts/filters/antispam.py
@@ -1,5 +1,4 @@
import asyncio
-import logging
from collections import defaultdict
from collections.abc import Mapping
from dataclasses import dataclass, field
@@ -14,19 +13,17 @@ from discord.ext.commands import Cog
from bot import rules
from bot.bot import Bot
from bot.constants import (
- AntiSpam as AntiSpamConfig, Channels,
- Colours, DEBUG_MODE, Event, Filter,
- Guild as GuildConfig, Icons,
+ AntiSpam as AntiSpamConfig, Channels, Colours, DEBUG_MODE, Event, Filter, Guild as GuildConfig, Icons
)
from bot.converters import Duration
from bot.exts.events.code_jams._channels import CATEGORY_NAME as JAM_CATEGORY_NAME
from bot.exts.moderation.modlog import ModLog
+from bot.log import get_logger
from bot.utils import lock, scheduling
from bot.utils.message_cache import MessageCache
from bot.utils.messages import format_user, send_attachments
-
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
RULE_FUNCTION_MAPPING = {
'attachments': rules.apply_attachments,
@@ -109,7 +106,7 @@ class DeletionContext:
colour=Colour(Colours.soft_red),
title="Spam detected!",
text=mod_alert_message,
- thumbnail=first_message.author.avatar_url_as(static_format="png"),
+ thumbnail=first_message.author.display_avatar.url,
channel_id=Channels.mod_alerts,
ping_everyone=AntiSpamConfig.ping_everyone
)
@@ -181,7 +178,9 @@ class AntiSpam(Cog):
self.cache.append(message)
earliest_relevant_at = datetime.utcnow() - timedelta(seconds=self.max_interval)
- relevant_messages = list(takewhile(lambda msg: msg.created_at > earliest_relevant_at, self.cache))
+ relevant_messages = list(
+ takewhile(lambda msg: msg.created_at.replace(tzinfo=None) > earliest_relevant_at, self.cache)
+ )
for rule_name in AntiSpamConfig.rules:
rule_config = AntiSpamConfig.rules[rule_name]
@@ -190,7 +189,9 @@ class AntiSpam(Cog):
# Create a list of messages that were sent in the interval that the rule cares about.
latest_interesting_stamp = datetime.utcnow() - timedelta(seconds=rule_config['interval'])
messages_for_rule = list(
- takewhile(lambda msg: msg.created_at > latest_interesting_stamp, relevant_messages)
+ takewhile(
+ lambda msg: msg.created_at.replace(tzinfo=None) > latest_interesting_stamp, relevant_messages
+ )
)
result = await rule_function(message, messages_for_rule, rule_config)
diff --git a/bot/exts/filters/filter_lists.py b/bot/exts/filters/filter_lists.py
index a06437f3d..4b5200684 100644
--- a/bot/exts/filters/filter_lists.py
+++ b/bot/exts/filters/filter_lists.py
@@ -1,4 +1,3 @@
-import logging
from typing import Optional
from discord import Colour, Embed
@@ -8,10 +7,11 @@ from bot import constants
from bot.api import ResponseCodeError
from bot.bot import Bot
from bot.converters import ValidDiscordServerInvite, ValidFilterListType
+from bot.log import get_logger
from bot.pagination import LinePaginator
from bot.utils import scheduling
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
class FilterLists(Cog):
diff --git a/bot/exts/filters/filtering.py b/bot/exts/filters/filtering.py
index 64f3b82af..7faf063b9 100644
--- a/bot/exts/filters/filtering.py
+++ b/bot/exts/filters/filtering.py
@@ -1,5 +1,4 @@
import asyncio
-import logging
import re
from datetime import datetime, timedelta
from typing import Any, Dict, List, Mapping, NamedTuple, Optional, Tuple, Union
@@ -15,17 +14,15 @@ from discord.utils import escape_markdown
from bot.api import ResponseCodeError
from bot.bot import Bot
-from bot.constants import (
- Channels, Colours, Filter,
- Guild, Icons, URLs
-)
+from bot.constants import Channels, Colours, Filter, Guild, Icons, URLs
from bot.exts.events.code_jams._channels import CATEGORY_NAME as JAM_CATEGORY_NAME
from bot.exts.moderation.modlog import ModLog
+from bot.log import get_logger
from bot.utils import scheduling
from bot.utils.messages import format_user
from bot.utils.regex import INVITE_RE
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
# Regular expressions
CODE_BLOCK_RE = re.compile(
@@ -226,7 +223,7 @@ class Filtering(Cog):
title="Username filtering alert",
text=log_string,
channel_id=Channels.mod_alerts,
- thumbnail=member.avatar_url
+ thumbnail=member.display_avatar.url
)
# Update time when alert sent
@@ -386,7 +383,7 @@ class Filtering(Cog):
colour=Colour(Colours.soft_red),
title=f"{_filter['type'].title()} triggered!",
text=message,
- thumbnail=msg.author.avatar_url_as(static_format="png"),
+ thumbnail=msg.author.display_avatar.url,
channel_id=Channels.mod_alerts,
ping_everyone=ping_everyone,
additional_embeds=stats.additional_embeds,
diff --git a/bot/exts/filters/security.py b/bot/exts/filters/security.py
index c680c5e27..fe3918423 100644
--- a/bot/exts/filters/security.py
+++ b/bot/exts/filters/security.py
@@ -1,10 +1,9 @@
-import logging
-
from discord.ext.commands import Cog, Context, NoPrivateMessage
from bot.bot import Bot
+from bot.log import get_logger
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
class Security(Cog):
diff --git a/bot/exts/filters/token_remover.py b/bot/exts/filters/token_remover.py
index 6c86ff849..520283ba3 100644
--- a/bot/exts/filters/token_remover.py
+++ b/bot/exts/filters/token_remover.py
@@ -1,6 +1,5 @@
import base64
import binascii
-import logging
import re
import typing as t
@@ -11,10 +10,11 @@ from bot import utils
from bot.bot import Bot
from bot.constants import Channels, Colours, Event, Icons
from bot.exts.moderation.modlog import ModLog
+from bot.log import get_logger
from bot.utils.members import get_or_fetch_member
from bot.utils.messages import format_user
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
LOG_MESSAGE = (
"Censored a seemingly valid token sent by {author} in {channel}, "
@@ -109,7 +109,7 @@ class TokenRemover(Cog):
colour=Colour(Colours.soft_red),
title="Token removed!",
text=log_message + "\n" + userid_message,
- thumbnail=msg.author.avatar_url_as(static_format="png"),
+ thumbnail=msg.author.display_avatar.url,
channel_id=Channels.mod_alerts,
ping_everyone=mention_everyone,
)
diff --git a/bot/exts/filters/webhook_remover.py b/bot/exts/filters/webhook_remover.py
index 25e267426..96334317c 100644
--- a/bot/exts/filters/webhook_remover.py
+++ b/bot/exts/filters/webhook_remover.py
@@ -1,4 +1,3 @@
-import logging
import re
from discord import Colour, Message, NotFound
@@ -7,6 +6,7 @@ from discord.ext.commands import Cog
from bot.bot import Bot
from bot.constants import Channels, Colours, Event, Icons
from bot.exts.moderation.modlog import ModLog
+from bot.log import get_logger
from bot.utils.messages import format_user
WEBHOOK_URL_RE = re.compile(
@@ -21,7 +21,7 @@ ALERT_MESSAGE_TEMPLATE = (
"mistake, please let us know."
)
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
class WebhookRemover(Cog):
@@ -63,7 +63,7 @@ class WebhookRemover(Cog):
colour=Colour(Colours.soft_red),
title="Discord webhook URL removed!",
text=message,
- thumbnail=msg.author.avatar_url_as(static_format="png"),
+ thumbnail=msg.author.display_avatar.url,
channel_id=Channels.mod_alerts
)
diff --git a/bot/exts/fun/duck_pond.py b/bot/exts/fun/duck_pond.py
index 8ced6922c..c51656343 100644
--- a/bot/exts/fun/duck_pond.py
+++ b/bot/exts/fun/duck_pond.py
@@ -1,5 +1,4 @@
import asyncio
-import logging
from typing import Union
import discord
@@ -9,12 +8,13 @@ from discord.ext.commands import Cog, Context, command
from bot import constants
from bot.bot import Bot
from bot.converters import MemberOrUser
+from bot.log import get_logger
from bot.utils import scheduling
from bot.utils.checks import has_any_role
from bot.utils.messages import count_unique_users_reaction, send_attachments
from bot.utils.webhooks import send_webhook
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
class DuckPond(Cog):
@@ -94,7 +94,7 @@ class DuckPond(Cog):
webhook=self.webhook,
content=message.clean_content,
username=message.author.display_name,
- avatar_url=message.author.avatar_url
+ avatar_url=message.author.display_avatar.url
)
if message.attachments:
@@ -109,7 +109,7 @@ class DuckPond(Cog):
webhook=self.webhook,
embed=e,
username=message.author.display_name,
- avatar_url=message.author.avatar_url
+ avatar_url=message.author.display_avatar.url
)
except discord.HTTPException:
log.exception("Failed to send an attachment to the webhook")
diff --git a/bot/exts/fun/off_topic_names.py b/bot/exts/fun/off_topic_names.py
index 2f56aa5ba..427667c66 100644
--- a/bot/exts/fun/off_topic_names.py
+++ b/bot/exts/fun/off_topic_names.py
@@ -1,5 +1,4 @@
import difflib
-import logging
from datetime import datetime, timedelta
from discord import Colour, Embed
@@ -10,11 +9,12 @@ from bot.api import ResponseCodeError
from bot.bot import Bot
from bot.constants import Channels, MODERATION_ROLES
from bot.converters import OffTopicName
+from bot.log import get_logger
from bot.pagination import LinePaginator
from bot.utils import scheduling
CHANNELS = (Channels.off_topic_0, Channels.off_topic_1, Channels.off_topic_2)
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
async def update_names(bot: Bot) -> None:
diff --git a/bot/exts/help_channels/__init__.py b/bot/exts/help_channels/__init__.py
index 781f40449..beba18aa6 100644
--- a/bot/exts/help_channels/__init__.py
+++ b/bot/exts/help_channels/__init__.py
@@ -1,10 +1,9 @@
-import logging
-
from bot import constants
from bot.bot import Bot
from bot.exts.help_channels._channel import MAX_CHANNELS_PER_CATEGORY
+from bot.log import get_logger
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
def validate_config() -> None:
diff --git a/bot/exts/help_channels/_channel.py b/bot/exts/help_channels/_channel.py
index f1bcea171..e43c1e789 100644
--- a/bot/exts/help_channels/_channel.py
+++ b/bot/exts/help_channels/_channel.py
@@ -1,4 +1,3 @@
-import logging
import typing as t
from datetime import timedelta
from enum import Enum
@@ -10,9 +9,10 @@ from arrow import Arrow
import bot
from bot import constants
from bot.exts.help_channels import _caches, _message
+from bot.log import get_logger
from bot.utils.channel import get_or_fetch_channel
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
MAX_CHANNELS_PER_CATEGORY = 50
EXCLUDED_CHANNELS = (constants.Channels.cooldown,)
diff --git a/bot/exts/help_channels/_cog.py b/bot/exts/help_channels/_cog.py
index 7c39bc132..498305b47 100644
--- a/bot/exts/help_channels/_cog.py
+++ b/bot/exts/help_channels/_cog.py
@@ -1,5 +1,4 @@
import asyncio
-import logging
import random
import typing as t
from datetime import timedelta
@@ -14,9 +13,10 @@ from bot import constants
from bot.bot import Bot
from bot.constants import Channels, RedirectOutput
from bot.exts.help_channels import _caches, _channel, _message, _name, _stats
+from bot.log import get_logger
from bot.utils import channel as channel_utils, lock, members, scheduling
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
NAMESPACE = "help"
HELP_CHANNEL_TOPIC = """
diff --git a/bot/exts/help_channels/_message.py b/bot/exts/help_channels/_message.py
index 077b20b47..a52c67570 100644
--- a/bot/exts/help_channels/_message.py
+++ b/bot/exts/help_channels/_message.py
@@ -1,4 +1,3 @@
-import logging
import textwrap
import typing as t
@@ -9,8 +8,9 @@ from arrow import Arrow
import bot
from bot import constants
from bot.exts.help_channels import _caches
+from bot.log import get_logger
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
ASKING_GUIDE_URL = "https://pythondiscord.com/pages/asking-good-questions/"
diff --git a/bot/exts/help_channels/_name.py b/bot/exts/help_channels/_name.py
index 061f855ae..a9d9b2df1 100644
--- a/bot/exts/help_channels/_name.py
+++ b/bot/exts/help_channels/_name.py
@@ -1,5 +1,4 @@
import json
-import logging
import typing as t
from collections import deque
from pathlib import Path
@@ -8,8 +7,9 @@ import discord
from bot import constants
from bot.exts.help_channels._channel import MAX_CHANNELS_PER_CATEGORY, get_category_channels
+from bot.log import get_logger
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
def create_name_queue(*categories: discord.CategoryChannel) -> deque:
diff --git a/bot/exts/help_channels/_stats.py b/bot/exts/help_channels/_stats.py
index eb34e75e1..4698c26de 100644
--- a/bot/exts/help_channels/_stats.py
+++ b/bot/exts/help_channels/_stats.py
@@ -1,12 +1,11 @@
-import logging
-
from more_itertools import ilen
import bot
from bot import constants
from bot.exts.help_channels import _caches, _channel
+from bot.log import get_logger
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
def report_counts() -> None:
diff --git a/bot/exts/info/code_snippets.py b/bot/exts/info/code_snippets.py
index 4a90a0668..07b1b8a2d 100644
--- a/bot/exts/info/code_snippets.py
+++ b/bot/exts/info/code_snippets.py
@@ -10,9 +10,10 @@ from discord.ext.commands import Cog
from bot.bot import Bot
from bot.constants import Channels
+from bot.log import get_logger
from bot.utils.messages import wait_for_deletion
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
GITHUB_RE = re.compile(
r'https://github\.com/(?P<repo>[a-zA-Z0-9-]+/[\w.-]+)/blob/'
diff --git a/bot/exts/info/codeblock/_cog.py b/bot/exts/info/codeblock/_cog.py
index f63a459ff..a859d8cef 100644
--- a/bot/exts/info/codeblock/_cog.py
+++ b/bot/exts/info/codeblock/_cog.py
@@ -1,4 +1,3 @@
-import logging
import time
from typing import Optional
@@ -11,11 +10,12 @@ from bot.bot import Bot
from bot.exts.filters.token_remover import TokenRemover
from bot.exts.filters.webhook_remover import WEBHOOK_URL_RE
from bot.exts.info.codeblock._instructions import get_instructions
+from bot.log import get_logger
from bot.utils import has_lines, scheduling
from bot.utils.channel import is_help_channel
from bot.utils.messages import wait_for_deletion
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
class CodeBlockCog(Cog, name="Code Block"):
diff --git a/bot/exts/info/codeblock/_instructions.py b/bot/exts/info/codeblock/_instructions.py
index dadb5e1ef..8fcadeec2 100644
--- a/bot/exts/info/codeblock/_instructions.py
+++ b/bot/exts/info/codeblock/_instructions.py
@@ -1,11 +1,11 @@
"""This module generates and formats instructional messages about fixing Markdown code blocks."""
-import logging
from typing import Optional
from bot.exts.info.codeblock import _parsing
+from bot.log import get_logger
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
_EXAMPLE_PY = "{lang}\nprint('Hello, world!')" # Make sure to escape any Markdown symbols here.
_EXAMPLE_CODE_BLOCKS = (
diff --git a/bot/exts/info/codeblock/_parsing.py b/bot/exts/info/codeblock/_parsing.py
index 73fd11b94..3c193d6c5 100644
--- a/bot/exts/info/codeblock/_parsing.py
+++ b/bot/exts/info/codeblock/_parsing.py
@@ -1,15 +1,15 @@
"""This module provides functions for parsing Markdown code blocks."""
import ast
-import logging
import re
import textwrap
from typing import NamedTuple, Optional, Sequence
from bot import constants
+from bot.log import get_logger
from bot.utils import has_lines
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
BACKTICK = "`"
PY_LANG_CODES = ("python-repl", "python", "pycon", "py") # Order is important; "py" is last cause it's a subset.
diff --git a/bot/exts/info/doc/__init__.py b/bot/exts/info/doc/__init__.py
index 38a8975c0..facdf4d0b 100644
--- a/bot/exts/info/doc/__init__.py
+++ b/bot/exts/info/doc/__init__.py
@@ -1,4 +1,5 @@
from bot.bot import Bot
+
from ._redis_cache import DocRedisCache
MAX_SIGNATURE_AMOUNT = 3
diff --git a/bot/exts/info/doc/_batch_parser.py b/bot/exts/info/doc/_batch_parser.py
index 51ee29b68..92f814c9d 100644
--- a/bot/exts/info/doc/_batch_parser.py
+++ b/bot/exts/info/doc/_batch_parser.py
@@ -2,7 +2,6 @@ from __future__ import annotations
import asyncio
import collections
-import logging
from collections import defaultdict
from contextlib import suppress
from operator import attrgetter
@@ -13,11 +12,13 @@ from bs4 import BeautifulSoup
import bot
from bot.constants import Channels
+from bot.log import get_logger
from bot.utils import scheduling
+
from . import _cog, doc_cache
from ._parsing import get_symbol_markdown
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
class StaleInventoryNotifier:
diff --git a/bot/exts/info/doc/_cog.py b/bot/exts/info/doc/_cog.py
index ca6af946b..fbbcd4a10 100644
--- a/bot/exts/info/doc/_cog.py
+++ b/bot/exts/info/doc/_cog.py
@@ -1,7 +1,6 @@
from __future__ import annotations
import asyncio
-import logging
import sys
import textwrap
from collections import defaultdict
@@ -17,15 +16,17 @@ from bot.api import ResponseCodeError
from bot.bot import Bot
from bot.constants import MODERATION_ROLES, RedirectOutput
from bot.converters import Inventory, PackageName, ValidURL, allowed_strings
+from bot.log import get_logger
from bot.pagination import LinePaginator
from bot.utils import scheduling
from bot.utils.lock import SharedEvent, lock
from bot.utils.messages import send_denial, wait_for_deletion
from bot.utils.scheduling import Scheduler
+
from . import NAMESPACE, PRIORITY_PACKAGES, _batch_parser, doc_cache
from ._inventory_parser import InvalidHeaderError, InventoryDict, fetch_inventory
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
# symbols with a group contained here will get the group prefixed on duplicates
FORCE_PREFIX_GROUPS = (
diff --git a/bot/exts/info/doc/_html.py b/bot/exts/info/doc/_html.py
index 94efd81b7..ca0a0ac4a 100644
--- a/bot/exts/info/doc/_html.py
+++ b/bot/exts/info/doc/_html.py
@@ -1,4 +1,3 @@
-import logging
import re
from functools import partial
from typing import Callable, Container, Iterable, List, Union
@@ -6,9 +5,11 @@ from typing import Callable, Container, Iterable, List, Union
from bs4 import BeautifulSoup
from bs4.element import NavigableString, PageElement, SoupStrainer, Tag
+from bot.log import get_logger
+
from . import MAX_SIGNATURE_AMOUNT
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
_UNWANTED_SIGNATURE_SYMBOLS_RE = re.compile(r"\[source]|\\\\|¶")
_SEARCH_END_TAG_ATTRS = (
diff --git a/bot/exts/info/doc/_inventory_parser.py b/bot/exts/info/doc/_inventory_parser.py
index 61924d070..e69246d47 100644
--- a/bot/exts/info/doc/_inventory_parser.py
+++ b/bot/exts/info/doc/_inventory_parser.py
@@ -1,4 +1,3 @@
-import logging
import re
import zlib
from collections import defaultdict
@@ -7,8 +6,9 @@ from typing import AsyncIterator, DefaultDict, List, Optional, Tuple
import aiohttp
import bot
+from bot.log import get_logger
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
FAILED_REQUEST_ATTEMPTS = 3
_V2_LINE_RE = re.compile(r'(?x)(.+?)\s+(\S*:\S*)\s+(-?\d+)\s+?(\S*)\s+(.*)')
diff --git a/bot/exts/info/doc/_parsing.py b/bot/exts/info/doc/_parsing.py
index 1a0d42c47..6ab38eb3d 100644
--- a/bot/exts/info/doc/_parsing.py
+++ b/bot/exts/info/doc/_parsing.py
@@ -1,6 +1,5 @@
from __future__ import annotations
-import logging
import re
import string
import textwrap
@@ -10,14 +9,17 @@ from typing import Collection, Iterable, Iterator, List, Optional, TYPE_CHECKING
from bs4 import BeautifulSoup
from bs4.element import NavigableString, Tag
+from bot.log import get_logger
from bot.utils.helpers import find_nth_occurrence
+
from . import MAX_SIGNATURE_AMOUNT
from ._html import get_dd_description, get_general_description, get_signatures
from ._markdown import DocMarkdownConverter
+
if TYPE_CHECKING:
from ._cog import DocItem
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
_WHITESPACE_AFTER_NEWLINES_RE = re.compile(r"(?<=\n\n)(\s+)")
_PARAMETERS_RE = re.compile(r"\((.+)\)")
diff --git a/bot/exts/info/doc/_redis_cache.py b/bot/exts/info/doc/_redis_cache.py
index ad764816f..79648893a 100644
--- a/bot/exts/info/doc/_redis_cache.py
+++ b/bot/exts/info/doc/_redis_cache.py
@@ -4,6 +4,7 @@ import datetime
from typing import Optional, TYPE_CHECKING
from async_rediscache.types.base import RedisObject, namespace_lock
+
if TYPE_CHECKING:
from ._cog import DocItem
diff --git a/bot/exts/info/help.py b/bot/exts/info/help.py
index 21a6cf752..f413caded 100644
--- a/bot/exts/info/help.py
+++ b/bot/exts/info/help.py
@@ -1,5 +1,4 @@
import itertools
-import logging
from collections import namedtuple
from contextlib import suppress
from typing import List, Union
@@ -12,10 +11,11 @@ from rapidfuzz.utils import default_process
from bot import constants
from bot.constants import Channels, STAFF_PARTNERS_COMMUNITY_ROLES
from bot.decorators import redirect_output
+from bot.log import get_logger
from bot.pagination import LinePaginator
from bot.utils.messages import wait_for_deletion
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
COMMANDS_PER_PAGE = 8
PREFIX = constants.Bot.prefix
diff --git a/bot/exts/info/information.py b/bot/exts/info/information.py
index c60fd2127..1b3e28e79 100644
--- a/bot/exts/info/information.py
+++ b/bot/exts/info/information.py
@@ -1,5 +1,4 @@
import colorsys
-import logging
import pprint
import textwrap
from collections import defaultdict
@@ -16,13 +15,14 @@ from bot.bot import Bot
from bot.converters import MemberOrUser
from bot.decorators import in_whitelist
from bot.errors import NonExistentRoleError
+from bot.log import get_logger
from bot.pagination import LinePaginator
from bot.utils.channel import is_mod_channel, is_staff_channel
from bot.utils.checks import cooldown_with_role_bypass, has_no_roles_check, in_whitelist_check
from bot.utils.members import get_or_fetch_member
from bot.utils.time import TimestampFormats, discord_timestamp, humanize_delta
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
class Information(Cog):
@@ -315,7 +315,7 @@ class Information(Cog):
for field_name, field_content in fields:
embed.add_field(name=field_name, value=field_content, inline=False)
- embed.set_thumbnail(url=user.avatar_url_as(static_format="png"))
+ embed.set_thumbnail(url=user.display_avatar.url)
embed.colour = user.colour if user.colour != Colour.default() else Colour.blurple()
return embed
diff --git a/bot/exts/info/pep.py b/bot/exts/info/pep.py
index bbd112911..259095b50 100644
--- a/bot/exts/info/pep.py
+++ b/bot/exts/info/pep.py
@@ -1,4 +1,3 @@
-import logging
from datetime import datetime, timedelta
from email.parser import HeaderParser
from io import StringIO
@@ -9,10 +8,11 @@ from discord.ext.commands import Cog, Context, command
from bot.bot import Bot
from bot.constants import Keys
+from bot.log import get_logger
from bot.utils import scheduling
from bot.utils.caching import AsyncCache
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
ICON_URL = "https://www.python.org/static/opengraph-icon-200x200.png"
BASE_PEP_URL = "http://www.python.org/dev/peps/pep-"
diff --git a/bot/exts/info/pypi.py b/bot/exts/info/pypi.py
index 62498ce0b..c3d2e2a3c 100644
--- a/bot/exts/info/pypi.py
+++ b/bot/exts/info/pypi.py
@@ -1,5 +1,4 @@
import itertools
-import logging
import random
import re
from contextlib import suppress
@@ -10,6 +9,7 @@ from discord.utils import escape_markdown
from bot.bot import Bot
from bot.constants import Colours, NEGATIVE_REPLIES, RedirectOutput
+from bot.log import get_logger
from bot.utils.messages import wait_for_deletion
URL = "https://pypi.org/pypi/{package}/json"
@@ -20,7 +20,7 @@ PYPI_COLOURS = itertools.cycle((Colours.yellow, Colours.blue, Colours.white))
ILLEGAL_CHARACTERS = re.compile(r"[^-_.a-zA-Z0-9]+")
INVALID_INPUT_DELETE_DELAY = RedirectOutput.delete_delay
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
class PyPi(Cog):
diff --git a/bot/exts/info/python_news.py b/bot/exts/info/python_news.py
index 2a8b64f32..2fad9d2ab 100644
--- a/bot/exts/info/python_news.py
+++ b/bot/exts/info/python_news.py
@@ -1,4 +1,3 @@
-import logging
import re
import typing as t
from datetime import date, datetime
@@ -11,6 +10,7 @@ from discord.ext.tasks import loop
from bot import constants
from bot.bot import Bot
+from bot.log import get_logger
from bot.utils import scheduling
from bot.utils.webhooks import send_webhook
@@ -31,7 +31,7 @@ MARKDOWN_REGEX = re.compile(
re.DOTALL # required to support multi-line codeblocks
)
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
class PythonNews(Cog):
diff --git a/bot/exts/info/site.py b/bot/exts/info/site.py
index 28eb558a6..e1f2f5153 100644
--- a/bot/exts/info/site.py
+++ b/bot/exts/info/site.py
@@ -1,13 +1,12 @@
-import logging
-
from discord import Colour, Embed
from discord.ext.commands import Cog, Context, Greedy, group
from bot.bot import Bot
from bot.constants import URLs
+from bot.log import get_logger
from bot.pagination import LinePaginator
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
BASE_URL = f"{URLs.site_schema}{URLs.site}"
diff --git a/bot/exts/info/tags.py b/bot/exts/info/tags.py
index bb91a8563..842647555 100644
--- a/bot/exts/info/tags.py
+++ b/bot/exts/info/tags.py
@@ -1,4 +1,3 @@
-import logging
import re
import time
from pathlib import Path
@@ -10,10 +9,11 @@ from discord.ext.commands import Cog, Context, group
from bot import constants
from bot.bot import Bot
from bot.converters import TagNameConverter
+from bot.log import get_logger
from bot.pagination import LinePaginator
from bot.utils.messages import wait_for_deletion
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
TEST_CHANNELS = (
constants.Channels.bot_commands,
diff --git a/bot/exts/moderation/defcon.py b/bot/exts/moderation/defcon.py
index ac813d6ba..80ba10112 100644
--- a/bot/exts/moderation/defcon.py
+++ b/bot/exts/moderation/defcon.py
@@ -1,4 +1,3 @@
-import logging
import traceback
from collections import namedtuple
from datetime import datetime
@@ -8,7 +7,7 @@ from typing import Optional, Union
from aioredis import RedisError
from async_rediscache import RedisCache
from dateutil.relativedelta import relativedelta
-from discord import Colour, Embed, Forbidden, Member, User
+from discord import Colour, Embed, Forbidden, Member, TextChannel, User
from discord.ext import tasks
from discord.ext.commands import Cog, Context, group, has_any_role
@@ -16,6 +15,7 @@ from bot.bot import Bot
from bot.constants import Channels, Colours, Emojis, Event, Icons, MODERATION_ROLES, Roles
from bot.converters import DurationDelta, Expiry
from bot.exts.moderation.modlog import ModLog
+from bot.log import get_logger
from bot.utils import scheduling
from bot.utils.messages import format_user
from bot.utils.scheduling import Scheduler
@@ -23,7 +23,7 @@ from bot.utils.time import (
TimestampFormats, discord_timestamp, humanize_delta, parse_duration_string, relativedelta_to_timedelta
)
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
REJECTION_MESSAGE = """
Hi, {user} - Thanks for your interest in our server!
@@ -111,7 +111,7 @@ class Defcon(Cog):
if self.threshold:
now = datetime.utcnow()
- if now - member.created_at < relativedelta_to_timedelta(self.threshold):
+ if now - member.created_at.replace(tzinfo=None) < relativedelta_to_timedelta(self.threshold):
log.info(f"Rejecting user {member}: Account is too new")
message_sent = False
@@ -137,7 +137,7 @@ class Defcon(Cog):
await self.mod_log.send_log_message(
Icons.defcon_denied, Colours.soft_red, "Entry denied",
- message, member.avatar_url_as(static_format="png")
+ message, member.display_avatar.url
)
@group(name='defcon', aliases=('dc',), invoke_without_command=True)
@@ -176,7 +176,7 @@ class Defcon(Cog):
"""
if isinstance(threshold, int):
threshold = relativedelta(days=threshold)
- await self._update_threshold(ctx.author, threshold=threshold, expiry=expiry)
+ await self._update_threshold(ctx.author, ctx.channel, threshold, expiry)
@defcon_group.command()
@has_any_role(Roles.admins)
@@ -185,7 +185,12 @@ class Defcon(Cog):
role = ctx.guild.default_role
permissions = role.permissions
- permissions.update(send_messages=False, add_reactions=False, connect=False)
+ permissions.update(
+ send_messages=False,
+ add_reactions=False,
+ send_messages_in_threads=False,
+ connect=False
+ )
await role.edit(reason="DEFCON shutdown", permissions=permissions)
await ctx.send(f"{Action.SERVER_SHUTDOWN.value.emoji} Server shut down.")
@@ -196,7 +201,12 @@ class Defcon(Cog):
role = ctx.guild.default_role
permissions = role.permissions
- permissions.update(send_messages=True, add_reactions=True, connect=True)
+ permissions.update(
+ send_messages=True,
+ add_reactions=True,
+ send_messages_in_threads=True,
+ connect=True
+ )
await role.edit(reason="DEFCON unshutdown", permissions=permissions)
await ctx.send(f"{Action.SERVER_OPEN.value.emoji} Server reopened.")
@@ -208,7 +218,13 @@ class Defcon(Cog):
scheduling.create_task(self.channel.edit(topic=new_topic))
@defcon_settings.atomic_transaction
- async def _update_threshold(self, author: User, threshold: relativedelta, expiry: Optional[Expiry] = None) -> None:
+ async def _update_threshold(
+ self,
+ author: User,
+ channel: TextChannel,
+ threshold: relativedelta,
+ expiry: Optional[Expiry] = None
+ ) -> None:
"""Update the new threshold in the cog, cache, defcon channel, and logs, and additionally schedule expiry."""
self.threshold = threshold
if threshold == relativedelta(days=0): # If the threshold is 0, we don't need to schedule anything
@@ -248,9 +264,13 @@ class Defcon(Cog):
else:
channel_message = "removed"
- await self.channel.send(
- f"{action.value.emoji} DEFCON threshold {channel_message}{error}."
- )
+ message = f"{action.value.emoji} DEFCON threshold {channel_message}{error}."
+ await self.channel.send(message)
+
+ # If invoked outside of #defcon send to `ctx.channel` too
+ if channel != self.channel:
+ await channel.send(message)
+
await self._send_defcon_log(action, author)
self._update_channel_topic()
@@ -258,7 +278,7 @@ class Defcon(Cog):
async def _remove_threshold(self) -> None:
"""Resets the threshold back to 0."""
- await self._update_threshold(self.bot.user, relativedelta(days=0))
+ await self._update_threshold(self.bot.user, self.channel, relativedelta(days=0))
@staticmethod
def _stringify_relativedelta(delta: relativedelta) -> str:
diff --git a/bot/exts/moderation/dm_relay.py b/bot/exts/moderation/dm_relay.py
index 0051db82f..566422e29 100644
--- a/bot/exts/moderation/dm_relay.py
+++ b/bot/exts/moderation/dm_relay.py
@@ -1,14 +1,13 @@
-import logging
-
import discord
from discord.ext.commands import Cog, Context, command, has_any_role
from bot.bot import Bot
from bot.constants import Emojis, MODERATION_ROLES
+from bot.log import get_logger
from bot.utils.channel import is_mod_channel
from bot.utils.services import send_to_paste_service
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
class DMRelay(Cog):
diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py
index 92b4fd5cf..5c5efdb15 100644
--- a/bot/exts/moderation/incidents.py
+++ b/bot/exts/moderation/incidents.py
@@ -1,5 +1,4 @@
import asyncio
-import logging
import re
from datetime import datetime
from enum import Enum
@@ -11,10 +10,11 @@ from discord.ext.commands import Cog, Context, MessageConverter, MessageNotFound
from bot.bot import Bot
from bot.constants import Channels, Colours, Emojis, Guild, Roles, Webhooks
+from bot.log import get_logger
from bot.utils import scheduling
from bot.utils.messages import format_user, sub_clyde
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
# Amount of messages for `crawl_task` to process at most on start-up - limited to 50
# as in practice, there should never be this many messages, and if there are,
@@ -102,7 +102,7 @@ async def make_embed(incident: discord.Message, outcome: Signal, actioned_by: di
timestamp=datetime.utcnow(),
colour=colour,
)
- embed.set_footer(text=footer, icon_url=actioned_by.avatar_url)
+ embed.set_footer(text=footer, icon_url=actioned_by.display_avatar.url)
if incident.attachments:
attachment = incident.attachments[0] # User-sent messages can only contain one attachment
@@ -295,6 +295,7 @@ class Incidents(Cog):
def __init__(self, bot: Bot) -> None:
"""Prepare `event_lock` and schedule `crawl_task` on start-up."""
self.bot = bot
+ self.incidents_webhook = None
scheduling.create_task(self.fetch_webhook(), event_loop=self.bot.loop)
@@ -369,7 +370,7 @@ class Incidents(Cog):
await webhook.send(
embed=embed,
username=sub_clyde(incident.author.name),
- avatar_url=incident.author.avatar_url,
+ avatar_url=incident.author.display_avatar.url,
file=attachment_file,
)
except Exception:
diff --git a/bot/exts/moderation/infraction/_scheduler.py b/bot/exts/moderation/infraction/_scheduler.py
index 8e844822d..d4e96b10b 100644
--- a/bot/exts/moderation/infraction/_scheduler.py
+++ b/bot/exts/moderation/infraction/_scheduler.py
@@ -1,4 +1,3 @@
-import logging
import textwrap
import typing as t
from abc import abstractmethod
@@ -16,10 +15,11 @@ from bot.constants import Colours
from bot.converters import MemberOrUser
from bot.exts.moderation.infraction import _utils
from bot.exts.moderation.modlog import ModLog
+from bot.log import get_logger
from bot.utils import messages, scheduling, time
from bot.utils.channel import is_mod_channel
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
class InfractionScheduler:
@@ -81,12 +81,16 @@ class InfractionScheduler:
apply_coro: t.Optional[t.Awaitable]
) -> None:
"""Reapply an infraction if it's still active or deactivate it if less than 60 sec left."""
- # Calculate the time remaining, in seconds, for the mute.
- expiry = dateutil.parser.isoparse(infraction["expires_at"]).replace(tzinfo=None)
- delta = (expiry - datetime.utcnow()).total_seconds()
+ if infraction["expires_at"] is not None:
+ # Calculate the time remaining, in seconds, for the mute.
+ expiry = dateutil.parser.isoparse(infraction["expires_at"]).replace(tzinfo=None)
+ delta = (expiry - datetime.utcnow()).total_seconds()
+ else:
+ # If the infraction is permanent, it is not possible to get the time remaining.
+ delta = None
- # Mark as inactive if less than a minute remains.
- if delta < 60:
+ # Mark as inactive if the infraction is not permanent and less than a minute remains.
+ if delta is not None and delta < 60:
log.info(
"Infraction will be deactivated instead of re-applied "
"because less than 1 minute remains."
@@ -161,11 +165,11 @@ class InfractionScheduler:
# send DMs to user that it doesn't share a guild with. If we were to
# apply kick/ban infractions first, this would mean that we'd make it
# impossible for us to deliver a DM. See python-discord/bot#982.
- if not infraction["hidden"]:
+ if not infraction["hidden"] and infr_type in {"ban", "kick"}:
dm_result = f"{constants.Emojis.failmail} "
dm_log_text = "\nDM: **Failed**"
- # Accordingly display whether the user was successfully notified via DM.
+ # Accordingly update whether the user was successfully notified via DM.
if await _utils.notify_infraction(user, infr_type.replace("_", " ").title(), expiry, user_reason, icon):
dm_result = ":incoming_envelope: "
dm_log_text = "\nDM: Sent"
@@ -228,6 +232,16 @@ class InfractionScheduler:
else:
infr_message = f" **{purge}{' '.join(infr_type.split('_'))}** to {user.mention}{expiry_msg}{end_msg}"
+ # If we need to DM and haven't already tried to
+ if not infraction["hidden"] and infr_type not in {"ban", "kick"}:
+ dm_result = f"{constants.Emojis.failmail} "
+ dm_log_text = "\nDM: **Failed**"
+
+ # Accordingly update whether the user was successfully notified via DM.
+ if await _utils.notify_infraction(user, infr_type.replace("_", " ").title(), expiry, user_reason, icon):
+ dm_result = ":incoming_envelope: "
+ dm_log_text = "\nDM: Sent"
+
# Send a confirmation message to the invoking context.
log.trace(f"Sending infraction #{id_} confirmation message.")
await ctx.send(f"{dm_result}{confirm_msg}{infr_message}.")
@@ -239,7 +253,7 @@ class InfractionScheduler:
icon_url=icon,
colour=Colours.soft_red,
title=f"Infraction {log_title}: {' '.join(infr_type.split('_'))}",
- thumbnail=user.avatar_url_as(static_format="png"),
+ thumbnail=user.display_avatar.url,
text=textwrap.dedent(f"""
Member: {messages.format_user(user)}
Actor: {ctx.author.mention}{dm_log_text}{expiry_log_text}
@@ -333,7 +347,7 @@ class InfractionScheduler:
icon_url=_utils.INFRACTION_ICONS[infr_type][1],
colour=Colours.soft_green,
title=f"Infraction {log_title}: {' '.join(infr_type.split('_'))}",
- thumbnail=user.avatar_url_as(static_format="png"),
+ thumbnail=user.display_avatar.url,
text="\n".join(f"{k}: {v}" for k, v in log_text.items()),
footer=footer,
content=log_content,
@@ -450,7 +464,7 @@ class InfractionScheduler:
log_title = "expiration failed" if "Failure" in log_text else "expired"
user = self.bot.get_user(user_id)
- avatar = user.avatar_url_as(static_format="png") if user else None
+ avatar = user.display_avatar.url if user else None
# Move reason to end so when reason is too long, this is not gonna cut out required items.
log_text["Reason"] = log_text.pop("Reason")
diff --git a/bot/exts/moderation/infraction/_utils.py b/bot/exts/moderation/infraction/_utils.py
index b20ef1d06..89718c857 100644
--- a/bot/exts/moderation/infraction/_utils.py
+++ b/bot/exts/moderation/infraction/_utils.py
@@ -1,4 +1,3 @@
-import logging
import typing as t
from datetime import datetime
@@ -9,8 +8,9 @@ from bot.api import ResponseCodeError
from bot.constants import Colours, Icons
from bot.converters import MemberOrUser
from bot.errors import InvalidInfractedUserError
+from bot.log import get_logger
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
# apply icon, pardon icon
INFRACTION_ICONS = {
diff --git a/bot/exts/moderation/infraction/infractions.py b/bot/exts/moderation/infraction/infractions.py
index a7f7dcb7f..e495a94b3 100644
--- a/bot/exts/moderation/infraction/infractions.py
+++ b/bot/exts/moderation/infraction/infractions.py
@@ -1,4 +1,3 @@
-import logging
import textwrap
import typing as t
@@ -14,10 +13,11 @@ from bot.converters import Duration, Expiry, MemberOrUser, UnambiguousMemberOrUs
from bot.decorators import respect_role_hierarchy
from bot.exts.moderation.infraction import _utils
from bot.exts.moderation.infraction._scheduler import InfractionScheduler
+from bot.log import get_logger
from bot.utils.members import get_or_fetch_member
from bot.utils.messages import format_user
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
class Infractions(InfractionScheduler, commands.Cog):
diff --git a/bot/exts/moderation/infraction/management.py b/bot/exts/moderation/infraction/management.py
index 0cb2a8b60..b1c8b64dc 100644
--- a/bot/exts/moderation/infraction/management.py
+++ b/bot/exts/moderation/infraction/management.py
@@ -1,4 +1,3 @@
-import logging
import textwrap
import typing as t
from datetime import datetime
@@ -16,13 +15,14 @@ from bot.converters import Expiry, Infraction, MemberOrUser, Snowflake, Unambigu
from bot.errors import InvalidInfraction
from bot.exts.moderation.infraction.infractions import Infractions
from bot.exts.moderation.modlog import ModLog
+from bot.log import get_logger
from bot.pagination import LinePaginator
from bot.utils import messages, time
from bot.utils.channel import is_mod_channel
from bot.utils.members import get_or_fetch_member
from bot.utils.time import humanize_delta, until_expiration
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
class ModManagement(commands.Cog):
@@ -141,10 +141,11 @@ class ModManagement(commands.Cog):
log_text = ""
if duration is not None and not infraction['active']:
- if reason is None:
+ if (infr_type := infraction['type']) in ('note', 'warning'):
+ await ctx.send(f":x: Cannot edit the expiration of a {infr_type}.")
+ else:
await ctx.send(":x: Cannot edit the expiration of an expired infraction.")
- return
- confirm_messages.append("expiry unchanged (infraction already expired)")
+ return
elif isinstance(duration, str):
request_data['expires_at'] = None
confirm_messages.append("marked as permanent")
@@ -195,7 +196,7 @@ class ModManagement(commands.Cog):
if user:
user_text = messages.format_user(user)
- thumbnail = user.avatar_url_as(static_format="png")
+ thumbnail = user.display_avatar.url
else:
user_text = f"<@{user_id}>"
thumbnail = None
diff --git a/bot/exts/moderation/infraction/superstarify.py b/bot/exts/moderation/infraction/superstarify.py
index 17cde68f6..08c92b8f3 100644
--- a/bot/exts/moderation/infraction/superstarify.py
+++ b/bot/exts/moderation/infraction/superstarify.py
@@ -1,5 +1,4 @@
import json
-import logging
import random
import textwrap
import typing as t
@@ -14,11 +13,12 @@ from bot.bot import Bot
from bot.converters import Duration, Expiry
from bot.exts.moderation.infraction import _utils
from bot.exts.moderation.infraction._scheduler import InfractionScheduler
+from bot.log import get_logger
from bot.utils.members import get_or_fetch_member
from bot.utils.messages import format_user
from bot.utils.time import format_infraction
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
NICKNAME_POLICY_URL = "https://pythondiscord.com/pages/rules/#nickname-policy"
SUPERSTARIFY_DEFAULT_DURATION = "1h"
diff --git a/bot/exts/moderation/metabase.py b/bot/exts/moderation/metabase.py
index 6eadd4bad..ce9c220b3 100644
--- a/bot/exts/moderation/metabase.py
+++ b/bot/exts/moderation/metabase.py
@@ -1,6 +1,5 @@
import csv
import json
-import logging
from datetime import timedelta
from io import StringIO
from typing import Dict, List, Optional
@@ -14,11 +13,12 @@ from discord.ext.commands import Cog, Context, group, has_any_role
from bot.bot import Bot
from bot.constants import Metabase as MetabaseConfig, Roles
from bot.converters import allowed_strings
+from bot.log import get_logger
from bot.utils import scheduling, send_to_paste_service
from bot.utils.channel import is_mod_channel
from bot.utils.scheduling import Scheduler
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
BASE_HEADERS = {
"Content-Type": "application/json"
diff --git a/bot/exts/moderation/modlog.py b/bot/exts/moderation/modlog.py
index be2245650..b09eb2d14 100644
--- a/bot/exts/moderation/modlog.py
+++ b/bot/exts/moderation/modlog.py
@@ -1,7 +1,6 @@
import asyncio
import difflib
import itertools
-import logging
import typing as t
from datetime import datetime
from itertools import zip_longest
@@ -9,17 +8,18 @@ from itertools import zip_longest
import discord
from dateutil.relativedelta import relativedelta
from deepdiff import DeepDiff
-from discord import Colour
+from discord import Colour, Message, Thread
from discord.abc import GuildChannel
from discord.ext.commands import Cog, Context
from discord.utils import escape_markdown
from bot.bot import Bot
from bot.constants import Categories, Channels, Colours, Emojis, Event, Guild as GuildConstant, Icons, Roles, URLs
+from bot.log import get_logger
from bot.utils.messages import format_user
from bot.utils.time import humanize_delta
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
GUILD_CHANNEL = t.Union[discord.CategoryChannel, discord.TextChannel, discord.VoiceChannel]
@@ -394,7 +394,7 @@ class ModLog(Cog, name="ModLog"):
await self.send_log_message(
Icons.user_ban, Colours.soft_red,
"User banned", format_user(member),
- thumbnail=member.avatar_url_as(static_format="png"),
+ thumbnail=member.display_avatar.url,
channel_id=Channels.user_log
)
@@ -405,7 +405,7 @@ class ModLog(Cog, name="ModLog"):
return
now = datetime.utcnow()
- difference = abs(relativedelta(now, member.created_at))
+ difference = abs(relativedelta(now, member.created_at.replace(tzinfo=None)))
message = format_user(member) + "\n\n**Account age:** " + humanize_delta(difference)
@@ -415,7 +415,7 @@ class ModLog(Cog, name="ModLog"):
await self.send_log_message(
Icons.sign_in, Colours.soft_green,
"User joined", message,
- thumbnail=member.avatar_url_as(static_format="png"),
+ thumbnail=member.display_avatar.url,
channel_id=Channels.user_log
)
@@ -432,7 +432,7 @@ class ModLog(Cog, name="ModLog"):
await self.send_log_message(
Icons.sign_out, Colours.soft_red,
"User left", format_user(member),
- thumbnail=member.avatar_url_as(static_format="png"),
+ thumbnail=member.display_avatar.url,
channel_id=Channels.user_log
)
@@ -449,7 +449,7 @@ class ModLog(Cog, name="ModLog"):
await self.send_log_message(
Icons.user_unban, Colour.blurple(),
"User unbanned", format_user(member),
- thumbnail=member.avatar_url_as(static_format="png"),
+ thumbnail=member.display_avatar.url,
channel_id=Channels.mod_log
)
@@ -515,21 +515,50 @@ class ModLog(Cog, name="ModLog"):
colour=Colour.blurple(),
title="Member updated",
text=message,
- thumbnail=after.avatar_url_as(static_format="png"),
+ thumbnail=after.display_avatar.url,
channel_id=Channels.user_log
)
+ def is_message_blacklisted(self, message: Message) -> bool:
+ """Return true if the message is in a blacklisted thread or channel."""
+ # Ignore bots or DMs
+ if message.author.bot or not message.guild:
+ return True
+
+ return self.is_channel_ignored(message.channel.id)
+
+ def is_channel_ignored(self, channel_id: int) -> bool:
+ """
+ Return true if the channel, or parent channel in the case of threads, passed should be ignored by modlog.
+
+ Currently ignored channels are:
+ 1. Channels not in the guild we care about (constants.Guild.id).
+ 2. Channels that mods do not have view permissions to
+ 3. Channels in constants.Guild.modlog_blacklist
+ """
+ channel = self.bot.get_channel(channel_id)
+
+ # Ignore not found channels, DMs, and messages outside of the main guild.
+ if not channel or channel.guild and channel.guild.id != GuildConstant.id:
+ return True
+
+ # Look at the parent channel of a thread.
+ if isinstance(channel, Thread):
+ channel = channel.parent
+
+ # Mod team doesn't have view permission to the channel.
+ if not channel.permissions_for(channel.guild.get_role(Roles.mod_team)).view_channel:
+ return True
+
+ return channel.id in GuildConstant.modlog_blacklist
+
@Cog.listener()
async def on_message_delete(self, message: discord.Message) -> None:
"""Log message delete event to message change log."""
channel = message.channel
author = message.author
- # Ignore DMs.
- if not message.guild:
- return
-
- if message.guild.id != GuildConstant.id or channel.id in GuildConstant.modlog_blacklist:
+ if self.is_message_blacklisted(message):
return
self._cached_deletes.append(message.id)
@@ -584,7 +613,7 @@ class ModLog(Cog, name="ModLog"):
@Cog.listener()
async def on_raw_message_delete(self, event: discord.RawMessageDeleteEvent) -> None:
"""Log raw message delete event to message change log."""
- if event.guild_id != GuildConstant.id or event.channel_id in GuildConstant.modlog_blacklist:
+ if self.is_channel_ignored(event.channel_id):
return
await asyncio.sleep(1) # Wait here in case the normal event was fired
@@ -625,12 +654,7 @@ class ModLog(Cog, name="ModLog"):
@Cog.listener()
async def on_message_edit(self, msg_before: discord.Message, msg_after: discord.Message) -> None:
"""Log message edit event to message change log."""
- if (
- not msg_before.guild
- or msg_before.guild.id != GuildConstant.id
- or msg_before.channel.id in GuildConstant.modlog_blacklist
- or msg_before.author.bot
- ):
+ if self.is_message_blacklisted(msg_before):
return
self._cached_edits.append(msg_before.id)
@@ -707,12 +731,7 @@ class ModLog(Cog, name="ModLog"):
except discord.NotFound: # Was deleted before we got the event
return
- if (
- not message.guild
- or message.guild.id != GuildConstant.id
- or message.channel.id in GuildConstant.modlog_blacklist
- or message.author.bot
- ):
+ if self.is_message_blacklisted(message):
return
await asyncio.sleep(1) # Wait here in case the normal event was fired
@@ -752,6 +771,64 @@ class ModLog(Cog, name="ModLog"):
)
@Cog.listener()
+ async def on_thread_update(self, before: Thread, after: Thread) -> None:
+ """Log thread archiving, un-archiving and name edits."""
+ if before.name != after.name:
+ await self.send_log_message(
+ Icons.hash_blurple,
+ Colour.blurple(),
+ "Thread name edited",
+ (
+ f"Thread {after.mention} (`{after.id}`) from {after.parent.mention} (`{after.parent.id}`): "
+ f"`{before.name}` -> `{after.name}`"
+ )
+ )
+ return
+
+ if not before.archived and after.archived:
+ colour = Colour.red()
+ action = "archived"
+ icon = Icons.hash_red
+ elif before.archived and not after.archived:
+ colour = Colour.green()
+ action = "un-archived"
+ icon = Icons.hash_green
+ else:
+ return
+
+ await self.send_log_message(
+ icon,
+ colour,
+ f"Thread {action}",
+ f"Thread {after.mention} (`{after.id}`) from {after.parent.mention} (`{after.parent.id}`) was {action}"
+ )
+
+ @Cog.listener()
+ async def on_thread_delete(self, thread: Thread) -> None:
+ """Log thread deletion."""
+ await self.send_log_message(
+ Icons.hash_red,
+ Colour.red(),
+ "Thread deleted",
+ f"Thread {thread.mention} (`{thread.id}`) from {thread.parent.mention} (`{thread.parent.id}`) deleted"
+ )
+
+ @Cog.listener()
+ async def on_thread_join(self, thread: Thread) -> None:
+ """Log thread creation."""
+ # If we are in the thread already we can most probably assume we already logged it?
+ # We don't really have a better way of doing this since the API doesn't make any difference between the two
+ if thread.me:
+ return
+
+ await self.send_log_message(
+ Icons.hash_green,
+ Colour.green(),
+ "Thread created",
+ f"Thread {thread.mention} (`{thread.id}`) from {thread.parent.mention} (`{thread.parent.id}`) created"
+ )
+
+ @Cog.listener()
async def on_voice_state_update(
self,
member: discord.Member,
@@ -761,7 +838,8 @@ class ModLog(Cog, name="ModLog"):
"""Log member voice state changes to the voice log channel."""
if (
member.guild.id != GuildConstant.id
- or (before.channel and before.channel.id in GuildConstant.modlog_blacklist)
+ or (before.channel and self.is_channel_ignored(before.channel.id))
+ or (after.channel and self.is_channel_ignored(after.channel.id))
):
return
@@ -820,7 +898,7 @@ class ModLog(Cog, name="ModLog"):
colour=colour,
title="Voice state updated",
text=message,
- thumbnail=member.avatar_url_as(static_format="png"),
+ thumbnail=member.display_avatar.url,
channel_id=Channels.voice_log
)
diff --git a/bot/exts/moderation/modpings.py b/bot/exts/moderation/modpings.py
index d775cdedf..a7ccb8162 100644
--- a/bot/exts/moderation/modpings.py
+++ b/bot/exts/moderation/modpings.py
@@ -1,5 +1,4 @@
import datetime
-import logging
from async_rediscache import RedisCache
from dateutil.parser import isoparse
@@ -9,10 +8,11 @@ from discord.ext.commands import Cog, Context, group, has_any_role
from bot.bot import Bot
from bot.constants import Colours, Emojis, Guild, Icons, MODERATION_ROLES, Roles
from bot.converters import Expiry
+from bot.log import get_logger
from bot.utils import scheduling
from bot.utils.scheduling import Scheduler
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
class ModPings(Cog):
diff --git a/bot/exts/moderation/silence.py b/bot/exts/moderation/silence.py
index 2ee6496df..511520252 100644
--- a/bot/exts/moderation/silence.py
+++ b/bot/exts/moderation/silence.py
@@ -1,23 +1,24 @@
import json
-import logging
import typing
from contextlib import suppress
from datetime import datetime, timedelta, timezone
from typing import Optional, OrderedDict, Union
from async_rediscache import RedisCache
-from discord import Guild, PermissionOverwrite, TextChannel, VoiceChannel
+from discord import Guild, PermissionOverwrite, TextChannel, Thread, VoiceChannel
from discord.ext import commands, tasks
from discord.ext.commands import Context
+from discord.utils import MISSING
from bot import constants
from bot.bot import Bot
from bot.converters import HushDurationConverter
+from bot.log import get_logger
from bot.utils import scheduling
from bot.utils.lock import LockedResourceError, lock, lock_arg
from bot.utils.scheduling import Scheduler
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
LOCK_NAMESPACE = "silence"
@@ -48,7 +49,16 @@ class SilenceNotifier(tasks.Loop):
"""Loop notifier for posting notices to `alert_channel` containing added channels."""
def __init__(self, alert_channel: TextChannel):
- super().__init__(self._notifier, seconds=1, minutes=0, hours=0, count=None, reconnect=True, loop=None)
+ super().__init__(
+ self._notifier,
+ seconds=1,
+ minutes=0,
+ hours=0,
+ count=None,
+ reconnect=True,
+ loop=None,
+ time=MISSING
+ )
self._silenced_channels = {}
self._alert_channel = alert_channel
@@ -173,6 +183,12 @@ class Silence(commands.Cog):
channel_info = f"#{channel} ({channel.id})"
log.debug(f"{ctx.author} is silencing channel {channel_info}.")
+ # Since threads don't have specific overrides, we cannot silence them individually.
+ # The parent channel has to be muted or the thread should be archived.
+ if isinstance(channel, Thread):
+ await ctx.send(":x: Threads cannot be silenced.")
+ return
+
if not await self._set_silence_overwrites(channel, kick=kick):
log.info(f"Tried to silence channel {channel_info} but the channel was already silenced.")
await self.send_message(MSG_SILENCE_FAIL, ctx.channel, channel, alert_target=False)
@@ -223,7 +239,13 @@ class Silence(commands.Cog):
if isinstance(channel, TextChannel):
role = self._everyone_role
overwrite = channel.overwrites_for(role)
- prev_overwrites = dict(send_messages=overwrite.send_messages, add_reactions=overwrite.add_reactions)
+ prev_overwrites = dict(
+ send_messages=overwrite.send_messages,
+ add_reactions=overwrite.add_reactions,
+ create_private_threads=overwrite.create_private_threads,
+ create_public_threads=overwrite.create_public_threads,
+ send_messages_in_threads=overwrite.send_messages_in_threads
+ )
else:
role = self._verified_voice_role
@@ -323,7 +345,15 @@ class Silence(commands.Cog):
# Check if old overwrites were not stored
if prev_overwrites is None:
log.info(f"Missing previous overwrites for #{channel} ({channel.id}); defaulting to None.")
- overwrite.update(send_messages=None, add_reactions=None, speak=None, connect=None)
+ overwrite.update(
+ send_messages=None,
+ add_reactions=None,
+ create_private_threads=None,
+ create_public_threads=None,
+ send_messages_in_threads=None,
+ speak=None,
+ connect=None
+ )
else:
overwrite.update(**json.loads(prev_overwrites))
diff --git a/bot/exts/moderation/slowmode.py b/bot/exts/moderation/slowmode.py
index d8baff76a..9583597e0 100644
--- a/bot/exts/moderation/slowmode.py
+++ b/bot/exts/moderation/slowmode.py
@@ -1,4 +1,3 @@
-import logging
from typing import Optional
from dateutil.relativedelta import relativedelta
@@ -8,9 +7,10 @@ from discord.ext.commands import Cog, Context, group, has_any_role
from bot.bot import Bot
from bot.constants import Channels, Emojis, MODERATION_ROLES
from bot.converters import DurationDelta
+from bot.log import get_logger
from bot.utils import time
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
SLOWMODE_MAX_DELAY = 21600 # seconds
diff --git a/bot/exts/moderation/stream.py b/bot/exts/moderation/stream.py
index a179a9acc..99bbd8721 100644
--- a/bot/exts/moderation/stream.py
+++ b/bot/exts/moderation/stream.py
@@ -1,4 +1,3 @@
-import logging
from datetime import timedelta, timezone
from operator import itemgetter
@@ -10,16 +9,16 @@ from discord.ext import commands
from bot.bot import Bot
from bot.constants import (
- Colours, Emojis, Guild, MODERATION_ROLES, Roles,
- STAFF_PARTNERS_COMMUNITY_ROLES, VideoPermission
+ Colours, Emojis, Guild, MODERATION_ROLES, Roles, STAFF_PARTNERS_COMMUNITY_ROLES, VideoPermission
)
from bot.converters import Expiry
+from bot.log import get_logger
from bot.pagination import LinePaginator
from bot.utils import scheduling
from bot.utils.members import get_or_fetch_member
from bot.utils.time import discord_timestamp, format_infraction_with_duration
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
class Stream(commands.Cog):
diff --git a/bot/exts/moderation/verification.py b/bot/exts/moderation/verification.py
index bfe9b74b4..ed5571d2a 100644
--- a/bot/exts/moderation/verification.py
+++ b/bot/exts/moderation/verification.py
@@ -1,4 +1,3 @@
-import logging
import typing as t
import discord
@@ -7,9 +6,10 @@ from discord.ext.commands import Cog, Context, command, has_any_role
from bot import constants
from bot.bot import Bot
from bot.decorators import in_whitelist
+from bot.log import get_logger
from bot.utils.checks import InWhitelistCheckFailure
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
# Sent via DMs once user joins the guild
ON_JOIN_MESSAGE = """
diff --git a/bot/exts/moderation/voice_gate.py b/bot/exts/moderation/voice_gate.py
index 8494a1e2e..8fdc7c76b 100644
--- a/bot/exts/moderation/voice_gate.py
+++ b/bot/exts/moderation/voice_gate.py
@@ -1,5 +1,4 @@
import asyncio
-import logging
from contextlib import suppress
from datetime import datetime, timedelta
@@ -8,15 +7,15 @@ from async_rediscache import RedisCache
from discord import Colour, Member, VoiceState
from discord.ext.commands import Cog, Context, command
-
from bot.api import ResponseCodeError
from bot.bot import Bot
from bot.constants import Channels, Event, MODERATION_ROLES, Roles, VoiceGate as GateConf
from bot.decorators import has_no_roles, in_whitelist
from bot.exts.moderation.modlog import ModLog
+from bot.log import get_logger
from bot.utils.checks import InWhitelistCheckFailure
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
# Flag written to the cog's RedisCache as a value when the Member's (key) notification
# was already removed ~ this signals both that no further notifications should be sent,
@@ -166,7 +165,10 @@ class VoiceGate(Cog):
return
checks = {
- "joined_at": ctx.author.joined_at > datetime.utcnow() - timedelta(days=GateConf.minimum_days_member),
+ "joined_at": (
+ ctx.author.joined_at.replace(tzinfo=None) > datetime.utcnow()
+ - timedelta(days=GateConf.minimum_days_member)
+ ),
"total_messages": data["total_messages"] < GateConf.minimum_messages,
"voice_banned": data["voice_banned"],
"activity_blocks": data["activity_blocks"] < GateConf.minimum_activity_blocks
diff --git a/bot/exts/moderation/watchchannels/_watchchannel.py b/bot/exts/moderation/watchchannels/_watchchannel.py
index 3fafd097b..8f97130ca 100644
--- a/bot/exts/moderation/watchchannels/_watchchannel.py
+++ b/bot/exts/moderation/watchchannels/_watchchannel.py
@@ -1,5 +1,4 @@
import asyncio
-import logging
import re
import textwrap
from abc import abstractmethod
@@ -17,12 +16,13 @@ from bot.constants import BigBrother as BigBrotherConfig, Guild as GuildConfig,
from bot.exts.filters.token_remover import TokenRemover
from bot.exts.filters.webhook_remover import WEBHOOK_URL_RE
from bot.exts.moderation.modlog import ModLog
+from bot.log import CustomLogger, get_logger
from bot.pagination import LinePaginator
from bot.utils import CogABCMeta, messages, scheduling
from bot.utils.members import get_or_fetch_member
from bot.utils.time import get_time_delta
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
URL_RE = re.compile(r"(https?://[^\s]+)")
@@ -47,7 +47,7 @@ class WatchChannel(metaclass=CogABCMeta):
webhook_id: int,
api_endpoint: str,
api_default_params: dict,
- logger: logging.Logger,
+ logger: CustomLogger,
*,
disable_header: bool = False
) -> None:
@@ -250,7 +250,7 @@ class WatchChannel(metaclass=CogABCMeta):
await self.webhook_send(
cleaned_content,
username=msg.author.display_name,
- avatar_url=msg.author.avatar_url
+ avatar_url=msg.author.display_avatar.url
)
if msg.attachments:
@@ -264,7 +264,7 @@ class WatchChannel(metaclass=CogABCMeta):
await self.webhook_send(
embed=e,
username=msg.author.display_name,
- avatar_url=msg.author.avatar_url
+ avatar_url=msg.author.display_avatar.url
)
except discord.HTTPException as exc:
self.log.exception(
@@ -301,7 +301,7 @@ class WatchChannel(metaclass=CogABCMeta):
embed = Embed(description=f"{msg.author.mention} {message_jump}")
embed.set_footer(text=textwrap.shorten(footer, width=256, placeholder="..."))
- await self.webhook_send(embed=embed, username=msg.author.display_name, avatar_url=msg.author.avatar_url)
+ await self.webhook_send(embed=embed, username=msg.author.display_name, avatar_url=msg.author.display_avatar.url)
async def list_watched_users(
self, ctx: Context, oldest_first: bool = False, update_cache: bool = True
diff --git a/bot/exts/moderation/watchchannels/bigbrother.py b/bot/exts/moderation/watchchannels/bigbrother.py
index 3aa253fea..ab37b1b80 100644
--- a/bot/exts/moderation/watchchannels/bigbrother.py
+++ b/bot/exts/moderation/watchchannels/bigbrother.py
@@ -1,4 +1,3 @@
-import logging
import textwrap
from collections import ChainMap
@@ -9,8 +8,9 @@ from bot.constants import Channels, MODERATION_ROLES, Webhooks
from bot.converters import MemberOrUser
from bot.exts.moderation.infraction._utils import post_infraction
from bot.exts.moderation.watchchannels._watchchannel import WatchChannel
+from bot.log import get_logger
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
class BigBrother(WatchChannel, Cog, name="Big Brother"):
@@ -87,11 +87,11 @@ class BigBrother(WatchChannel, Cog, name="Big Brother"):
return
if not await self.fetch_user_cache():
- await ctx.send(f":x: Updating the user cache failed, can't watch user {user}")
+ await ctx.send(f":x: Updating the user cache failed, can't watch user {user.mention}")
return
if user.id in self.watched_users:
- await ctx.send(f":x: {user} is already being watched.")
+ await ctx.send(f":x: {user.mention} is already being watched.")
return
# discord.User instances don't have a roles attribute
@@ -103,7 +103,7 @@ class BigBrother(WatchChannel, Cog, name="Big Brother"):
if response is not None:
self.watched_users[user.id] = response
- msg = f":white_check_mark: Messages sent by {user} will now be relayed to Big Brother."
+ msg = f":white_check_mark: Messages sent by {user.mention} will now be relayed to Big Brother."
history = await self.bot.api_client.get(
self.api_endpoint,
@@ -156,7 +156,7 @@ class BigBrother(WatchChannel, Cog, name="Big Brother"):
log.debug(f"Perma-banned user {user} was unwatched.")
return
log.trace("User is not banned. Sending message to channel")
- message = f":white_check_mark: Messages sent by {user} will no longer be relayed."
+ message = f":white_check_mark: Messages sent by {user.mention} will no longer be relayed."
else:
log.trace("No active watches found for user.")
diff --git a/bot/exts/recruitment/talentpool/_cog.py b/bot/exts/recruitment/talentpool/_cog.py
index f9c836bbd..2fafaec97 100644
--- a/bot/exts/recruitment/talentpool/_cog.py
+++ b/bot/exts/recruitment/talentpool/_cog.py
@@ -1,4 +1,3 @@
-import logging
import textwrap
from collections import ChainMap, defaultdict
from io import StringIO
@@ -14,6 +13,7 @@ from bot.bot import Bot
from bot.constants import Channels, Emojis, Guild, MODERATION_ROLES, Roles, STAFF_ROLES
from bot.converters import MemberOrUser, UnambiguousMemberOrUser
from bot.exts.recruitment.talentpool._review import Reviewer
+from bot.log import get_logger
from bot.pagination import LinePaginator
from bot.utils import scheduling, time
from bot.utils.members import get_or_fetch_member
@@ -22,7 +22,7 @@ from bot.utils.time import get_time_delta
AUTOREVIEW_ENABLED_KEY = "autoreview_enabled"
REASON_MAX_CHARS = 1000
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
class TalentPool(Cog, name="Talentpool"):
diff --git a/bot/exts/recruitment/talentpool/_review.py b/bot/exts/recruitment/talentpool/_review.py
index 14a8dd4c0..dcf73c2cb 100644
--- a/bot/exts/recruitment/talentpool/_review.py
+++ b/bot/exts/recruitment/talentpool/_review.py
@@ -1,6 +1,5 @@
import asyncio
import contextlib
-import logging
import random
import re
import textwrap
@@ -16,6 +15,7 @@ from discord.ext.commands import Context
from bot.api import ResponseCodeError
from bot.bot import Bot
from bot.constants import Channels, Colours, Emojis, Guild
+from bot.log import get_logger
from bot.utils.members import get_or_fetch_member
from bot.utils.messages import count_unique_users_reaction, pin_no_system_message
from bot.utils.scheduling import Scheduler
@@ -24,7 +24,7 @@ from bot.utils.time import get_time_delta, time_since
if typing.TYPE_CHECKING:
from bot.exts.recruitment.talentpool._cog import TalentPool
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
# Maximum amount of days before an automatic review is posted.
MAX_DAYS_IN_POOL = 30
diff --git a/bot/exts/utils/bot.py b/bot/exts/utils/bot.py
index d84709616..788692777 100644
--- a/bot/exts/utils/bot.py
+++ b/bot/exts/utils/bot.py
@@ -1,13 +1,14 @@
-import logging
+from contextlib import suppress
from typing import Optional
-from discord import Embed, TextChannel
+from discord import Embed, Forbidden, TextChannel, Thread
from discord.ext.commands import Cog, Context, command, group, has_any_role
from bot.bot import Bot
from bot.constants import Guild, MODERATION_ROLES, URLs
+from bot.log import get_logger
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
class BotCog(Cog, name="Bot"):
@@ -16,6 +17,20 @@ class BotCog(Cog, name="Bot"):
def __init__(self, bot: Bot):
self.bot = bot
+ @Cog.listener()
+ async def on_thread_join(self, thread: Thread) -> None:
+ """
+ Try to join newly created threads.
+
+ Despite the event name being misleading, this is dispatched when new threads are created.
+ """
+ if thread.me:
+ # We have already joined this thread
+ return
+
+ with suppress(Forbidden):
+ await thread.join()
+
@group(invoke_without_command=True, name="bot", hidden=True)
async def botinfo_group(self, ctx: Context) -> None:
"""Bot informational commands."""
diff --git a/bot/exts/utils/clean.py b/bot/exts/utils/clean.py
index cb662e852..a2e2d3eed 100644
--- a/bot/exts/utils/clean.py
+++ b/bot/exts/utils/clean.py
@@ -1,4 +1,3 @@
-import logging
import random
import re
from typing import Iterable, Optional
@@ -8,12 +7,11 @@ from discord.ext import commands
from discord.ext.commands import Cog, Context, group, has_any_role
from bot.bot import Bot
-from bot.constants import (
- Channels, CleanMessages, Colours, Event, Icons, MODERATION_ROLES, NEGATIVE_REPLIES
-)
+from bot.constants import Channels, CleanMessages, Colours, Event, Icons, MODERATION_ROLES, NEGATIVE_REPLIES
from bot.exts.moderation.modlog import ModLog
+from bot.log import get_logger
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
class Clean(Cog):
@@ -107,7 +105,7 @@ class Clean(Cog):
elif regex:
predicate = predicate_regex # Delete messages that match regex
else:
- predicate = None # Delete all messages
+ predicate = lambda *_: True # Delete all messages
# Default to using the invoking context's channel
if not channels:
diff --git a/bot/exts/utils/extensions.py b/bot/exts/utils/extensions.py
index 309126d0e..fa5d38917 100644
--- a/bot/exts/utils/extensions.py
+++ b/bot/exts/utils/extensions.py
@@ -1,5 +1,4 @@
import functools
-import logging
import typing as t
from enum import Enum
@@ -11,10 +10,11 @@ from bot import exts
from bot.bot import Bot
from bot.constants import Emojis, MODERATION_ROLES, Roles, URLs
from bot.converters import Extension
+from bot.log import get_logger
from bot.pagination import LinePaginator
from bot.utils.extensions import EXTENSIONS
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
UNLOAD_BLACKLIST = {f"{exts.__name__}.utils.extensions", f"{exts.__name__}.moderation.modlog"}
diff --git a/bot/exts/utils/internal.py b/bot/exts/utils/internal.py
index 5d2cd7611..879735945 100644
--- a/bot/exts/utils/internal.py
+++ b/bot/exts/utils/internal.py
@@ -1,6 +1,5 @@
import contextlib
import inspect
-import logging
import pprint
import re
import textwrap
@@ -15,9 +14,10 @@ from discord.ext.commands import Cog, Context, group, has_any_role, is_owner
from bot.bot import Bot
from bot.constants import DEBUG_MODE, Roles
+from bot.log import get_logger
from bot.utils import find_nth_occurrence, send_to_paste_service
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
class Internal(Cog):
diff --git a/bot/exts/utils/ping.py b/bot/exts/utils/ping.py
index cf0e3265e..43d371d87 100644
--- a/bot/exts/utils/ping.py
+++ b/bot/exts/utils/ping.py
@@ -32,7 +32,7 @@ class Latency(commands.Cog):
"""
# datetime.datetime objects do not have the "milliseconds" attribute.
# It must be converted to seconds before converting to milliseconds.
- bot_ping = (datetime.utcnow() - ctx.message.created_at).total_seconds() * 1000
+ bot_ping = (datetime.utcnow() - ctx.message.created_at.replace(tzinfo=None)).total_seconds() * 1000
if bot_ping <= 0:
bot_ping = "Your clock is out of sync, could not calculate ping."
else:
diff --git a/bot/exts/utils/reminders.py b/bot/exts/utils/reminders.py
index 95f3661af..3cb9307a9 100644
--- a/bot/exts/utils/reminders.py
+++ b/bot/exts/utils/reminders.py
@@ -1,4 +1,3 @@
-import logging
import random
import textwrap
import typing as t
@@ -10,11 +9,9 @@ from dateutil.parser import isoparse
from discord.ext.commands import Cog, Context, Greedy, group
from bot.bot import Bot
-from bot.constants import (
- Guild, Icons, MODERATION_ROLES, POSITIVE_REPLIES,
- Roles, STAFF_PARTNERS_COMMUNITY_ROLES
-)
+from bot.constants import Guild, Icons, MODERATION_ROLES, POSITIVE_REPLIES, Roles, STAFF_PARTNERS_COMMUNITY_ROLES
from bot.converters import Duration, UnambiguousUser
+from bot.log import get_logger
from bot.pagination import LinePaginator
from bot.utils import scheduling
from bot.utils.checks import has_any_role_check, has_no_roles_check
@@ -24,7 +21,7 @@ from bot.utils.messages import send_denial
from bot.utils.scheduling import Scheduler
from bot.utils.time import TimestampFormats, discord_timestamp
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
LOCK_NAMESPACE = "reminder"
WHITELISTED_CHANNELS = Guild.reminder_whitelist
@@ -118,7 +115,7 @@ class Reminders(Cog):
if await has_no_roles_check(ctx, *STAFF_PARTNERS_COMMUNITY_ROLES):
return False, "members/roles"
elif await has_no_roles_check(ctx, *MODERATION_ROLES):
- return all(isinstance(mention, discord.Member) for mention in mentions), "roles"
+ return all(isinstance(mention, (discord.User, discord.Member)) for mention in mentions), "roles"
else:
return True, ""
diff --git a/bot/exts/utils/snekbox.py b/bot/exts/utils/snekbox.py
index 5fb10a25b..fbfc58d0b 100644
--- a/bot/exts/utils/snekbox.py
+++ b/bot/exts/utils/snekbox.py
@@ -1,7 +1,6 @@
import asyncio
import contextlib
import datetime
-import logging
import re
import textwrap
from functools import partial
@@ -14,10 +13,11 @@ from discord.ext.commands import Cog, Context, command, guild_only
from bot.bot import Bot
from bot.constants import Categories, Channels, Roles, URLs
from bot.decorators import redirect_output
+from bot.log import get_logger
from bot.utils import scheduling, send_to_paste_service
from bot.utils.messages import wait_for_deletion
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
ESCAPE_REGEX = re.compile("[`\u202E\u200B]{3,}")
FORMATTED_CODE_REGEX = re.compile(
diff --git a/bot/exts/utils/utils.py b/bot/exts/utils/utils.py
index 0139a6ad3..f69bab781 100644
--- a/bot/exts/utils/utils.py
+++ b/bot/exts/utils/utils.py
@@ -1,5 +1,4 @@
import difflib
-import logging
import re
import unicodedata
from typing import Tuple, Union
@@ -12,11 +11,12 @@ from bot.bot import Bot
from bot.constants import Channels, MODERATION_ROLES, Roles, STAFF_PARTNERS_COMMUNITY_ROLES
from bot.converters import Snowflake
from bot.decorators import in_whitelist
+from bot.log import get_logger
from bot.pagination import LinePaginator
from bot.utils import messages
from bot.utils.time import time_since
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
ZEN_OF_PYTHON = """\
Beautiful is better than ugly.
diff --git a/bot/log.py b/bot/log.py
index 4e20c005e..b3cecdcf2 100644
--- a/bot/log.py
+++ b/bot/log.py
@@ -3,6 +3,7 @@ import os
import sys
from logging import Logger, handlers
from pathlib import Path
+from typing import Optional, TYPE_CHECKING, cast
import coloredlogs
import sentry_sdk
@@ -14,11 +15,38 @@ from bot import constants
TRACE_LEVEL = 5
+if TYPE_CHECKING:
+ LoggerClass = Logger
+else:
+ LoggerClass = logging.getLoggerClass()
+
+
+class CustomLogger(LoggerClass):
+ """Custom implementation of the `Logger` class with an added `trace` method."""
+
+ def trace(self, msg: str, *args, **kwargs) -> None:
+ """
+ Log 'msg % args' with severity 'TRACE'.
+
+ To pass exception information, use the keyword argument exc_info with
+ a true value, e.g.
+
+ logger.trace("Houston, we have an %s", "interesting problem", exc_info=1)
+ """
+ if self.isEnabledFor(TRACE_LEVEL):
+ self.log(TRACE_LEVEL, msg, *args, **kwargs)
+
+
+def get_logger(name: Optional[str] = None) -> CustomLogger:
+ """Utility to make mypy recognise that logger is of type `CustomLogger`."""
+ return cast(CustomLogger, logging.getLogger(name))
+
+
def setup() -> None:
"""Set up loggers."""
logging.TRACE = TRACE_LEVEL
logging.addLevelName(TRACE_LEVEL, "TRACE")
- Logger.trace = _monkeypatch_trace
+ logging.setLoggerClass(CustomLogger)
format_string = "%(asctime)s | %(name)s | %(levelname)s | %(message)s"
log_format = logging.Formatter(format_string)
@@ -28,7 +56,7 @@ def setup() -> None:
file_handler = handlers.RotatingFileHandler(log_file, maxBytes=5242880, backupCount=7, encoding="utf8")
file_handler.setFormatter(log_format)
- root_log = logging.getLogger()
+ root_log = get_logger()
root_log.addHandler(file_handler)
if "COLOREDLOGS_LEVEL_STYLES" not in os.environ:
@@ -42,16 +70,16 @@ def setup() -> None:
if "COLOREDLOGS_LOG_FORMAT" not in os.environ:
coloredlogs.DEFAULT_LOG_FORMAT = format_string
- coloredlogs.install(level=logging.TRACE, logger=root_log, stream=sys.stdout)
+ coloredlogs.install(level=TRACE_LEVEL, logger=root_log, stream=sys.stdout)
root_log.setLevel(logging.DEBUG if constants.DEBUG_MODE else logging.INFO)
- logging.getLogger("discord").setLevel(logging.WARNING)
- logging.getLogger("websockets").setLevel(logging.WARNING)
- logging.getLogger("chardet").setLevel(logging.WARNING)
- logging.getLogger("async_rediscache").setLevel(logging.WARNING)
+ get_logger("discord").setLevel(logging.WARNING)
+ get_logger("websockets").setLevel(logging.WARNING)
+ get_logger("chardet").setLevel(logging.WARNING)
+ get_logger("async_rediscache").setLevel(logging.WARNING)
# Set back to the default of INFO even if asyncio's debug mode is enabled.
- logging.getLogger("asyncio").setLevel(logging.INFO)
+ get_logger("asyncio").setLevel(logging.INFO)
_set_trace_loggers()
@@ -73,19 +101,6 @@ def setup_sentry() -> None:
)
-def _monkeypatch_trace(self: logging.Logger, msg: str, *args, **kwargs) -> None:
- """
- Log 'msg % args' with severity 'TRACE'.
-
- To pass exception information, use the keyword argument exc_info with
- a true value, e.g.
-
- logger.trace("Houston, we have an %s", "interesting problem", exc_info=1)
- """
- if self.isEnabledFor(TRACE_LEVEL):
- self._log(TRACE_LEVEL, msg, args, **kwargs)
-
-
def _set_trace_loggers() -> None:
"""
Set loggers to the trace level according to the value from the BOT_TRACE_LOGGERS env var.
@@ -101,13 +116,13 @@ def _set_trace_loggers() -> None:
level_filter = constants.Bot.trace_loggers
if level_filter:
if level_filter.startswith("*"):
- logging.getLogger().setLevel(logging.TRACE)
+ get_logger().setLevel(TRACE_LEVEL)
elif level_filter.startswith("!"):
- logging.getLogger().setLevel(logging.TRACE)
+ get_logger().setLevel(TRACE_LEVEL)
for logger_name in level_filter.strip("!,").split(","):
- logging.getLogger(logger_name).setLevel(logging.DEBUG)
+ get_logger(logger_name).setLevel(logging.DEBUG)
else:
for logger_name in level_filter.strip(",").split(","):
- logging.getLogger(logger_name).setLevel(logging.TRACE)
+ get_logger(logger_name).setLevel(TRACE_LEVEL)
diff --git a/bot/monkey_patches.py b/bot/monkey_patches.py
index 4dbdb5eab..e56a19da2 100644
--- a/bot/monkey_patches.py
+++ b/bot/monkey_patches.py
@@ -1,10 +1,11 @@
-import logging
from datetime import datetime, timedelta
from discord import Forbidden, http
from discord.ext import commands
-log = logging.getLogger(__name__)
+from bot.log import get_logger
+
+log = get_logger(__name__)
class Command(commands.Command):
diff --git a/bot/pagination.py b/bot/pagination.py
index 26caa7db0..8f4353eb1 100644
--- a/bot/pagination.py
+++ b/bot/pagination.py
@@ -1,5 +1,4 @@
import asyncio
-import logging
import typing as t
from contextlib import suppress
from functools import partial
@@ -9,6 +8,7 @@ from discord.abc import User
from discord.ext.commands import Context, Paginator
from bot import constants
+from bot.log import get_logger
from bot.utils import messages
FIRST_EMOJI = "\u23EE" # [:track_previous:]
@@ -19,7 +19,7 @@ DELETE_EMOJI = constants.Emojis.trashcan # [:trashcan:]
PAGINATION_EMOJI = (FIRST_EMOJI, LEFT_EMOJI, RIGHT_EMOJI, LAST_EMOJI, DELETE_EMOJI)
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
class EmptyPaginatorEmbedError(Exception):
diff --git a/bot/resources/tags/async-await.md b/bot/resources/tags/async-await.md
index ff71ace07..01ab28fe3 100644
--- a/bot/resources/tags/async-await.md
+++ b/bot/resources/tags/async-await.md
@@ -2,27 +2,26 @@
Python provides the ability to run multiple tasks and coroutines simultaneously with the use of the `asyncio` library, which is included in the Python standard library.
-This works by running these coroutines in an event loop, where the context of which coroutine is being run is switches periodically to allow all of them to run, giving the appearance of running at the same time. This is different to using threads or processes in that all code is run in the main process and thread, although it is possible to run coroutines in threads.
+This works by running these coroutines in an event loop, where the context of the running coroutine switches periodically to allow all other coroutines to run, thus giving the appearance of running at the same time. This is different to using threads or processes in that all code runs in the main process and thread, although it is possible to run coroutines in other threads.
To call an async function we can either `await` it, or run it in an event loop which we get from `asyncio`.
-To create a coroutine that can be used with asyncio we need to define a function using the async keyword:
+To create a coroutine that can be used with asyncio we need to define a function using the `async` keyword:
```py
async def main():
await something_awaitable()
```
-Which means we can call `await something_awaitable()` directly from within the function. If this were a non-async function this would have raised an exception like: `SyntaxError: 'await' outside async function`
+Which means we can call `await something_awaitable()` directly from within the function. If this were a non-async function, it would raise the exception `SyntaxError: 'await' outside async function`
-To run the top level async function from outside of the event loop we can get an event loop from `asyncio`, and then use that loop to run the function:
+To run the top level async function from outside the event loop we need to use [`asyncio.run()`](https://docs.python.org/3/library/asyncio-task.html#asyncio.run), like this:
```py
-from asyncio import get_event_loop
+import asyncio
async def main():
await something_awaitable()
-loop = get_event_loop()
-loop.run_until_complete(main())
+asyncio.run(main())
```
-Note that in the `run_until_complete()` where we appear to be calling `main()`, this does not execute the code in `main`, rather it returns a `coroutine` object which is then handled and run by the event loop via `run_until_complete()`.
+Note that in the `asyncio.run()`, where we appear to be calling `main()`, this does not execute the code in `main`. Rather, it creates and returns a new `coroutine` object (i.e `main() is not main()`) which is then handled and run by the event loop via `asyncio.run()`.
To learn more about asyncio and its use, see the [asyncio documentation](https://docs.python.org/3/library/asyncio.html).
diff --git a/bot/resources/tags/contribute.md b/bot/resources/tags/contribute.md
new file mode 100644
index 000000000..070975646
--- /dev/null
+++ b/bot/resources/tags/contribute.md
@@ -0,0 +1,12 @@
+**Contribute to Python Discord's Open Source Projects**
+Looking to contribute to Open Source Projects for the first time? Want to add a feature or fix a bug on the bots on this server? We have on-going projects that people can contribute to, even if you've never contributed to open source before!
+
+**Projects to Contribute to**
+• [Sir Lancebot](https://github.com/python-discord/sir-lancebot) - our fun, beginner-friendly bot
+• [Python](https://github.com/python-discord/bot) - our utility & moderation bot
+• [Site](https://github.com/python-discord/site) - resources, guides, and more
+
+**Where to start**
+1. Read our [contributing guidelines](https://pythondiscord.com/pages/guides/pydis-guides/contributing/)
+2. Chat with us in <#635950537262759947> if you're ready to jump in or have any questions
+3. Open an issue or ask to be assigned to an issue to work on
diff --git a/bot/resources/tags/traceback.md b/bot/resources/tags/traceback.md
index e770fa86d..321737aac 100644
--- a/bot/resources/tags/traceback.md
+++ b/bot/resources/tags/traceback.md
@@ -1,4 +1,4 @@
-Please provide a full traceback to your exception in order for us to identify your issue.
+Please provide the full traceback for your exception in order to help us identify your issue.
A full traceback could look like:
```py
@@ -6,13 +6,13 @@ Traceback (most recent call last):
File "tiny", line 3, in
do_something()
File "tiny", line 2, in do_something
- a = 6 / 0
-ZeroDivisionError: integer division or modulo by zero
+ a = 6 / b
+ZeroDivisionError: division by zero
```
The best way to read your traceback is bottom to top.
-• Identify the exception raised (e.g. ZeroDivisionError)
-• Make note of the line number, and navigate there in your program.
-• Try to understand why the error occurred.
+• Identify the exception raised (in this case `ZeroDivisionError`)
+• Make note of the line number (in this case `2`), and navigate there in your program.
+• Try to understand why the error occurred (in this case because `b` is `0`).
-To read more about exceptions and errors, please refer to the [PyDis Wiki](https://pythondiscord.com/pages/asking-good-questions/#examining-tracebacks) or the [official Python tutorial.](https://docs.python.org/3.7/tutorial/errors.html)
+To read more about exceptions and errors, please refer to the [PyDis Wiki](https://pythondiscord.com/pages/guides/pydis-guides/asking-good-questions/#examining-tracebacks) or the [official Python tutorial](https://docs.python.org/3.7/tutorial/errors.html).
diff --git a/bot/resources/tags/windows-path.md b/bot/resources/tags/windows-path.md
index da8edf685..b2b0da029 100644
--- a/bot/resources/tags/windows-path.md
+++ b/bot/resources/tags/windows-path.md
@@ -1,30 +1,17 @@
**PATH on Windows**
-If you have installed Python but you forgot to check the *Add Python to PATH* option during the installation you may still be able to access your installation with ease.
+If you have installed Python but forgot to check the `Add Python to PATH` option during the installation, you may still be able to access your installation with ease.
-If you did not uncheck the option to install the Python launcher then you will find a `py` command on your system. If you want to be able to open your Python installation by running `python` then your best option is to re-install Python.
+If you did not uncheck the option to install the `py launcher`, then you'll instead have a `py` command which can be used in the same way. If you want to be able to access your Python installation via the `python` command, then your best option is to re-install Python (remembering to tick the `Add Python to PATH` checkbox).
-Otherwise, you can access your install using the `py` command in Command Prompt. Where you may type something with the `python` command like:
-```
-C:\Users\Username> python3 my_application_file.py
-```
-
-You can achieve the same result using the `py` command like this:
-```
-C:\Users\Username> py -3 my_application_file.py
-```
-
-You can pass any options to the Python interpreter after you specify a version, for example, to install a Python module using `pip` you can run:
-```
-C:\Users\Username> py -3 -m pip install numpy
-```
+You can pass any options to the Python interpreter, e.g. to install the `[numpy](https://pypi.org/project/numpy/)` module from PyPI you can run `py -3 -m pip install numpy` or `python -m pip install numpy`.
-You can also access different versions of Python using the version flag, like so:
+You can also access different versions of Python using the version flag of the `py` command, like so:
```
C:\Users\Username> py -3.7
... Python 3.7 starts ...
C:\Users\Username> py -3.6
-... Python 3.6 stars ...
+... Python 3.6 starts ...
C:\Users\Username> py -2
... Python 2 (any version installed) starts ...
```
diff --git a/bot/resources/tags/xy-problem.md b/bot/resources/tags/xy-problem.md
index b77bd27e8..8c508f18c 100644
--- a/bot/resources/tags/xy-problem.md
+++ b/bot/resources/tags/xy-problem.md
@@ -1,7 +1,7 @@
**xy-problem**
-Asking about your attempted solution rather than your actual problem.
+The XY problem can be summarised as asking about your attempted solution, rather than your actual problem.
Often programmers will get distracted with a potential solution they've come up with, and will try asking for help getting it to work. However, it's possible this solution either wouldn't work as they expect, or there's a much better solution instead.
-For more information and examples: http://xyproblem.info/
+For more information and examples, see http://xyproblem.info/
diff --git a/bot/resources/tags/ytdl.md b/bot/resources/tags/ytdl.md
index f96b7f853..68a0a0cdb 100644
--- a/bot/resources/tags/ytdl.md
+++ b/bot/resources/tags/ytdl.md
@@ -1,4 +1,4 @@
-Per [Python Discord's Rule 5](https://pythondiscord.com/pages/rules), we are unable to assist with questions related to youtube-dl, pytube, or other YouTube video downloaders as their usage violates YouTube's Terms of Service.
+Per [Python Discord's Rule 5](https://pythondiscord.com/pages/rules), we are unable to assist with questions related to youtube-dl, pytube, or other YouTube video downloaders, as their usage violates YouTube's Terms of Service.
For reference, this usage is covered by the following clauses in [YouTube's TOS](https://www.youtube.com/static?gl=GB&template=terms), as of 2021-03-17:
```
diff --git a/bot/resources/tags/zip.md b/bot/resources/tags/zip.md
index 6b05f0282..6f3157f71 100644
--- a/bot/resources/tags/zip.md
+++ b/bot/resources/tags/zip.md
@@ -3,7 +3,7 @@ The zip function allows you to iterate through multiple iterables simultaneously
```py
letters = 'abc'
numbers = [1, 2, 3]
-# zip(letters, numbers) --> [('a', 1), ('b', 2), ('c', 3)]
+# list(zip(letters, numbers)) --> [('a', 1), ('b', 2), ('c', 3)]
for letter, number in zip(letters, numbers):
print(letter, number)
```
diff --git a/bot/rules/discord_emojis.py b/bot/rules/discord_emojis.py
index 41faf7ee8..d979ac5e7 100644
--- a/bot/rules/discord_emojis.py
+++ b/bot/rules/discord_emojis.py
@@ -4,7 +4,6 @@ from typing import Dict, Iterable, List, Optional, Tuple
from discord import Member, Message
from emoji import demojize
-
DISCORD_EMOJI_RE = re.compile(r"<:\w+:\d+>|:\w+:")
CODE_BLOCK_RE = re.compile(r"```.*?```", flags=re.DOTALL)
diff --git a/bot/rules/links.py b/bot/rules/links.py
index ec75a19c5..c46b783c5 100644
--- a/bot/rules/links.py
+++ b/bot/rules/links.py
@@ -3,7 +3,6 @@ from typing import Dict, Iterable, List, Optional, Tuple
from discord import Member, Message
-
LINK_RE = re.compile(r"(https?://[^\s]+)")
diff --git a/bot/utils/channel.py b/bot/utils/channel.py
index 6d2356679..b9e234857 100644
--- a/bot/utils/channel.py
+++ b/bot/utils/channel.py
@@ -1,12 +1,11 @@
-import logging
-
import discord
import bot
from bot import constants
from bot.constants import Categories
+from bot.log import get_logger
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
def is_help_channel(channel: discord.TextChannel) -> bool:
diff --git a/bot/utils/checks.py b/bot/utils/checks.py
index 3d0c8a50c..e7f2cfbda 100644
--- a/bot/utils/checks.py
+++ b/bot/utils/checks.py
@@ -1,23 +1,15 @@
import datetime
-import logging
from typing import Callable, Container, Iterable, Optional, Union
from discord.ext.commands import (
- BucketType,
- CheckFailure,
- Cog,
- Command,
- CommandOnCooldown,
- Context,
- Cooldown,
- CooldownMapping,
- NoPrivateMessage,
- has_any_role,
+ BucketType, CheckFailure, Cog, Command, CommandOnCooldown, Context, Cooldown, CooldownMapping, NoPrivateMessage,
+ has_any_role
)
from bot import constants
+from bot.log import get_logger
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
class ContextCheckFailure(CheckFailure):
@@ -134,7 +126,7 @@ def cooldown_with_role_bypass(rate: int, per: float, type: BucketType = BucketTy
bypass = set(bypass_roles)
# this handles the actual cooldown logic
- buckets = CooldownMapping(Cooldown(rate, per, type))
+ buckets = CooldownMapping(Cooldown(rate, per), type)
# will be called after the command has been parse but before it has been invoked, ensures that
# the cooldown won't be updated if the user screws up their input to the command
@@ -149,7 +141,7 @@ def cooldown_with_role_bypass(rate: int, per: float, type: BucketType = BucketTy
bucket = buckets.get_bucket(ctx.message)
retry_after = bucket.update_rate_limit(current)
if retry_after:
- raise CommandOnCooldown(bucket, retry_after)
+ raise CommandOnCooldown(bucket, retry_after, type)
def wrapper(command: Command) -> Command:
# NOTE: this could be changed if a subclass of Command were to be used. I didn't see the need for it
diff --git a/bot/utils/function.py b/bot/utils/function.py
index 9bc44e753..55115d7d3 100644
--- a/bot/utils/function.py
+++ b/bot/utils/function.py
@@ -2,11 +2,12 @@
import functools
import inspect
-import logging
import types
import typing as t
-log = logging.getLogger(__name__)
+from bot.log import get_logger
+
+log = get_logger(__name__)
Argument = t.Union[int, str]
BoundArgs = t.OrderedDict[str, t.Any]
diff --git a/bot/utils/lock.py b/bot/utils/lock.py
index ec6f92cd4..c039a4f25 100644
--- a/bot/utils/lock.py
+++ b/bot/utils/lock.py
@@ -1,6 +1,5 @@
import asyncio
import inspect
-import logging
import types
from collections import defaultdict
from functools import partial
@@ -8,10 +7,11 @@ from typing import Any, Awaitable, Callable, Hashable, Union
from weakref import WeakValueDictionary
from bot.errors import LockedResourceError
+from bot.log import get_logger
from bot.utils import function
from bot.utils.function import command_wraps
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
__lock_dicts = defaultdict(WeakValueDictionary)
_IdCallableReturn = Union[Hashable, Awaitable[Hashable]]
diff --git a/bot/utils/members.py b/bot/utils/members.py
index 302fe6d63..77ddf1696 100644
--- a/bot/utils/members.py
+++ b/bot/utils/members.py
@@ -1,9 +1,10 @@
-import logging
import typing as t
import discord
-log = logging.getLogger(__name__)
+from bot.log import get_logger
+
+log = get_logger(__name__)
async def get_or_fetch_member(guild: discord.Guild, member_id: int) -> t.Optional[discord.Member]:
diff --git a/bot/utils/messages.py b/bot/utils/messages.py
index abeb04021..e55c07062 100644
--- a/bot/utils/messages.py
+++ b/bot/utils/messages.py
@@ -1,5 +1,4 @@
import asyncio
-import logging
import random
import re
from functools import partial
@@ -11,9 +10,10 @@ from discord.ext.commands import Context
import bot
from bot.constants import Emojis, MODERATION_ROLES, NEGATIVE_REPLIES
+from bot.log import get_logger
from bot.utils import scheduling
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
def reaction_check(
@@ -121,7 +121,7 @@ async def send_attachments(
"""
webhook_send_kwargs = {
'username': message.author.display_name,
- 'avatar_url': message.author.avatar_url,
+ 'avatar_url': message.author.display_avatar.url,
}
webhook_send_kwargs.update(kwargs)
webhook_send_kwargs['username'] = sub_clyde(webhook_send_kwargs['username'])
diff --git a/bot/utils/scheduling.py b/bot/utils/scheduling.py
index bb83b5c0d..7b4c8e2de 100644
--- a/bot/utils/scheduling.py
+++ b/bot/utils/scheduling.py
@@ -1,11 +1,12 @@
import asyncio
import contextlib
import inspect
-import logging
import typing as t
from datetime import datetime
from functools import partial
+from bot.log import get_logger
+
class Scheduler:
"""
@@ -27,7 +28,7 @@ class Scheduler:
def __init__(self, name: str):
self.name = name
- self._log = logging.getLogger(f"{__name__}.{name}")
+ self._log = get_logger(f"{__name__}.{name}")
self._scheduled_tasks: t.Dict[t.Hashable, asyncio.Task] = {}
def __contains__(self, task_id: t.Hashable) -> bool:
@@ -187,5 +188,5 @@ def _log_task_exception(task: asyncio.Task, *, suppressed_exceptions: t.Tuple[t.
exception = task.exception()
# Log the exception if one exists.
if exception and not isinstance(exception, suppressed_exceptions):
- log = logging.getLogger(__name__)
+ log = get_logger(__name__)
log.error(f"Error in task {task.get_name()} {id(task)}!", exc_info=exception)
diff --git a/bot/utils/services.py b/bot/utils/services.py
index db9c93d0f..439c8d500 100644
--- a/bot/utils/services.py
+++ b/bot/utils/services.py
@@ -1,12 +1,12 @@
-import logging
from typing import Optional
from aiohttp import ClientConnectorError
import bot
from bot.constants import URLs
+from bot.log import get_logger
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
FAILED_REQUEST_ATTEMPTS = 3
diff --git a/bot/utils/webhooks.py b/bot/utils/webhooks.py
index 66f82ec66..9c916b63a 100644
--- a/bot/utils/webhooks.py
+++ b/bot/utils/webhooks.py
@@ -1,12 +1,12 @@
-import logging
from typing import Optional
import discord
from discord import Embed
+from bot.log import get_logger
from bot.utils.messages import sub_clyde
-log = logging.getLogger(__name__)
+log = get_logger(__name__)
async def send_webhook(
diff --git a/config-default.yml b/config-default.yml
index 70e972086..90f84bf9b 100644
--- a/config-default.yml
+++ b/config-default.yml
@@ -248,15 +248,13 @@ guild:
- *ADMIN_SPAM
- *MODS
- # Modlog cog ignores events which occur in these channels
+ # Modlog cog explicitly ignores events which occur in these channels.
+ # This is on top of implicitly ignoring events in channels that the mod team cannot view.
modlog_blacklist:
- - *ADMINS
- - *ADMINS_VOICE
- *ATTACH_LOG
- *MESSAGE_LOG
- *MOD_LOG
- *STAFF_VOICE
- - *DEV_CORE_VOTING
reminder_whitelist:
- *BOT_CMD
diff --git a/poetry.lock b/poetry.lock
index 81b51b8da..16c599bd1 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -140,14 +140,14 @@ testing = ["pytest (>=4.6)", "pytest-flake8", "pytest-cov", "pytest-black (>=0.3
[[package]]
name = "beautifulsoup4"
-version = "4.9.3"
+version = "4.10.0"
description = "Screen-scraping library"
category = "main"
optional = false
-python-versions = "*"
+python-versions = ">3.0.0"
[package.dependencies]
-soupsieve = {version = ">1.2", markers = "python_version >= \"3.0\""}
+soupsieve = ">1.2"
[package.extras]
html5lib = ["html5lib"]
@@ -155,7 +155,7 @@ lxml = ["lxml"]
[[package]]
name = "certifi"
-version = "2021.5.30"
+version = "2021.10.8"
description = "Python package for providing Mozilla's CA Bundle."
category = "main"
optional = false
@@ -163,7 +163,7 @@ python-versions = "*"
[[package]]
name = "cffi"
-version = "1.14.6"
+version = "1.15.0"
description = "Foreign Function Interface for Python calling C code."
category = "main"
optional = false
@@ -190,7 +190,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]]
name = "charset-normalizer"
-version = "2.0.4"
+version = "2.0.7"
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
category = "dev"
optional = false
@@ -264,22 +264,26 @@ murmur = ["mmh3"]
[[package]]
name = "discord.py"
-version = "1.7.3"
+version = "2.0.0a0"
description = "A Python wrapper for the Discord API"
category = "main"
optional = false
-python-versions = ">=3.5.3"
+python-versions = ">=3.8.0"
[package.dependencies]
aiohttp = ">=3.6.0,<3.8.0"
[package.extras]
-docs = ["sphinx (==3.0.3)", "sphinxcontrib-trio (==1.1.2)", "sphinxcontrib-websupport"]
+docs = ["sphinx (==4.0.2)", "sphinxcontrib-trio (==1.1.2)", "sphinxcontrib-websupport"]
+speed = ["orjson (>=3.5.4)"]
voice = ["PyNaCl (>=1.3.0,<1.5)"]
+[package.source]
+type = "url"
+url = "https://github.com/Rapptz/discord.py/archive/45d498c1b76deaf3b394d17ccf56112fa691d160.zip"
[[package]]
name = "distlib"
-version = "0.3.2"
+version = "0.3.3"
description = "Distribution utilities"
category = "dev"
optional = false
@@ -317,13 +321,14 @@ testing = ["pre-commit"]
[[package]]
name = "fakeredis"
-version = "1.6.0"
+version = "1.6.1"
description = "Fake implementation of redis API for testing purposes."
category = "main"
optional = false
python-versions = ">=3.5"
[package.dependencies]
+packaging = "*"
redis = "<3.6.0"
six = ">=1.12"
sortedcontainers = "*"
@@ -345,11 +350,15 @@ sgmllib3k = "*"
[[package]]
name = "filelock"
-version = "3.0.12"
+version = "3.3.1"
description = "A platform independent file lock."
category = "dev"
optional = false
-python-versions = "*"
+python-versions = ">=3.6"
+
+[package.extras]
+docs = ["furo (>=2021.8.17b43)", "sphinx (>=4.1)", "sphinx-autodoc-typehints (>=1.12)"]
+testing = ["covdefaults (>=1.2.0)", "coverage (>=4)", "pytest (>=4)", "pytest-cov", "pytest-timeout (>=1.4.2)"]
[[package]]
name = "flake8"
@@ -366,14 +375,14 @@ pyflakes = ">=2.3.0,<2.4.0"
[[package]]
name = "flake8-annotations"
-version = "2.6.2"
+version = "2.7.0"
description = "Flake8 Type Annotation Checks"
category = "dev"
optional = false
-python-versions = ">=3.6.1,<4.0.0"
+python-versions = ">=3.6.2,<4.0.0"
[package.dependencies]
-flake8 = ">=3.7,<4.0"
+flake8 = ">=3.7,<5.0"
[[package]]
name = "flake8-bugbear"
@@ -403,15 +412,20 @@ flake8 = ">=3"
pydocstyle = ">=2.1"
[[package]]
-name = "flake8-import-order"
-version = "0.18.1"
-description = "Flake8 and pylama plugin that checks the ordering of import statements."
+name = "flake8-isort"
+version = "4.1.1"
+description = "flake8 plugin that integrates isort ."
category = "dev"
optional = false
python-versions = "*"
[package.dependencies]
-pycodestyle = "*"
+flake8 = ">=3.2.1,<5"
+isort = ">=4.3.5,<6"
+testfixtures = ">=6.8.0,<7"
+
+[package.extras]
+test = ["pytest-cov"]
[[package]]
name = "flake8-polyfill"
@@ -437,14 +451,14 @@ flake8 = "*"
[[package]]
name = "flake8-tidy-imports"
-version = "4.4.1"
+version = "4.5.0"
description = "A flake8 plugin that helps you write tidier imports."
category = "dev"
optional = false
python-versions = ">=3.6"
[package.dependencies]
-flake8 = ">=3.8.0,<4"
+flake8 = ">=3.8.0,<5"
[[package]]
name = "flake8-todo"
@@ -467,18 +481,18 @@ python-versions = ">=3.6"
[[package]]
name = "humanfriendly"
-version = "9.2"
+version = "10.0"
description = "Human friendly output for text interfaces using Python"
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[package.dependencies]
-pyreadline = {version = "*", markers = "sys_platform == \"win32\""}
+pyreadline3 = {version = "*", markers = "sys_platform == \"win32\" and python_version >= \"3.8\""}
[[package]]
name = "identify"
-version = "2.2.13"
+version = "2.3.0"
description = "File identification library for Python"
category = "dev"
optional = false
@@ -489,7 +503,7 @@ license = ["editdistance-s"]
[[package]]
name = "idna"
-version = "3.2"
+version = "3.3"
description = "Internationalized Domain Names in Applications (IDNA)"
category = "main"
optional = false
@@ -504,6 +518,20 @@ optional = false
python-versions = "*"
[[package]]
+name = "isort"
+version = "5.9.3"
+description = "A Python utility / library to sort Python imports."
+category = "dev"
+optional = false
+python-versions = ">=3.6.1,<4.0"
+
+[package.extras]
+pipfile_deprecated_finder = ["pipreqs", "requirementslib"]
+requirements_deprecated_finder = ["pipreqs", "pip-api"]
+colors = ["colorama (>=0.4.3,<0.5.0)"]
+plugins = ["setuptools"]
+
+[[package]]
name = "lxml"
version = "4.6.3"
description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API."
@@ -539,7 +567,7 @@ python-versions = "*"
[[package]]
name = "more-itertools"
-version = "8.8.0"
+version = "8.10.0"
description = "More routines for operating on iterables, beyond itertools"
category = "main"
optional = false
@@ -555,7 +583,7 @@ python-versions = ">=3.5"
[[package]]
name = "multidict"
-version = "5.1.0"
+version = "5.2.0"
description = "multidict implementation"
category = "main"
optional = false
@@ -581,7 +609,7 @@ python-versions = ">=3.5"
name = "packaging"
version = "21.0"
description = "Core utilities for Python packages"
-category = "dev"
+category = "main"
optional = false
python-versions = ">=3.6"
@@ -613,7 +641,7 @@ flake8-polyfill = ">=1.0.2,<2"
[[package]]
name = "pip-licenses"
-version = "3.5.2"
+version = "3.5.3"
description = "Dump the software license list of Python packages installed with pip."
category = "dev"
optional = false
@@ -627,7 +655,7 @@ test = ["docutils", "pytest-cov", "pytest-pycodestyle", "pytest-runner"]
[[package]]
name = "platformdirs"
-version = "2.2.0"
+version = "2.4.0"
description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
category = "dev"
optional = false
@@ -639,18 +667,19 @@ test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock
[[package]]
name = "pluggy"
-version = "0.13.1"
+version = "1.0.0"
description = "plugin and hook calling mechanisms for python"
category = "dev"
optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+python-versions = ">=3.6"
[package.extras]
dev = ["pre-commit", "tox"]
+testing = ["pytest", "pytest-benchmark"]
[[package]]
name = "pre-commit"
-version = "2.14.0"
+version = "2.15.0"
description = "A framework for managing and maintaining multi-language pre-commit hooks."
category = "dev"
optional = false
@@ -747,21 +776,21 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
name = "pyparsing"
version = "2.4.7"
description = "Python parsing module"
-category = "dev"
+category = "main"
optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
[[package]]
-name = "pyreadline"
-version = "2.1"
-description = "A python implmementation of GNU readline."
+name = "pyreadline3"
+version = "3.3"
+description = "A python implementation of GNU readline."
category = "main"
optional = false
python-versions = "*"
[[package]]
name = "pytest"
-version = "6.2.4"
+version = "6.2.5"
description = "pytest: simple powerful testing with Python"
category = "dev"
optional = false
@@ -773,7 +802,7 @@ attrs = ">=19.2.0"
colorama = {version = "*", markers = "sys_platform == \"win32\""}
iniconfig = "*"
packaging = "*"
-pluggy = ">=0.12,<1.0.0a1"
+pluggy = ">=0.12,<2.0"
py = ">=1.8.2"
toml = "*"
@@ -873,11 +902,14 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
[[package]]
name = "rapidfuzz"
-version = "1.5.0"
+version = "1.7.1"
description = "rapid fuzzy string matching"
category = "main"
optional = false
-python-versions = ">=3.5"
+python-versions = ">=2.7"
+
+[package.extras]
+full = ["numpy"]
[[package]]
name = "redis"
@@ -918,7 +950,7 @@ use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"]
[[package]]
name = "sentry-sdk"
-version = "1.3.1"
+version = "1.4.3"
description = "Python client for Sentry (https://sentry.io)"
category = "main"
optional = false
@@ -1007,6 +1039,19 @@ psutil = ">=5.7.2,<6.0.0"
toml = ">=0.10.0,<0.11.0"
[[package]]
+name = "testfixtures"
+version = "6.18.3"
+description = "A collection of helpers and mock objects for unit tests and doc tests."
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.extras]
+build = ["setuptools-git", "wheel", "twine"]
+docs = ["sphinx", "zope.component", "sybil", "twisted", "mock", "django (<2)", "django"]
+test = ["pytest (>=3.6)", "pytest-cov", "pytest-django", "zope.component", "sybil", "twisted", "mock", "django (<2)", "django"]
+
+[[package]]
name = "toml"
version = "0.10.2"
description = "Python Library for Tom's Obvious, Minimal Language"
@@ -1016,7 +1061,7 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
[[package]]
name = "typing-extensions"
-version = "3.10.0.0"
+version = "3.10.0.2"
description = "Backported and Experimental Type Hints for Python 3.5+"
category = "main"
optional = false
@@ -1024,7 +1069,7 @@ python-versions = "*"
[[package]]
name = "urllib3"
-version = "1.26.6"
+version = "1.26.7"
description = "HTTP library with thread-safe connection pooling, file post, and more."
category = "main"
optional = false
@@ -1037,7 +1082,7 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
[[package]]
name = "virtualenv"
-version = "20.7.2"
+version = "20.8.1"
description = "Virtual Python Environment builder"
category = "dev"
optional = false
@@ -1056,7 +1101,7 @@ testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)",
[[package]]
name = "yarl"
-version = "1.6.3"
+version = "1.7.0"
description = "Yet another URL library"
category = "main"
optional = false
@@ -1069,7 +1114,7 @@ multidict = ">=4.0"
[metadata]
lock-version = "1.1"
python-versions = "3.9.*"
-content-hash = "ceddbb2621849f480f736985d71f37cebefd08a9b38bc3943a6f72706258b6ee"
+content-hash = "e37923739c35ef349d57e324579acfe304cc7e6fc20ddc54205fc89f171ae94f"
[metadata.files]
aio-pika = [
@@ -1152,60 +1197,64 @@ attrs = [
{file = "backports.entry_points_selectable-1.1.0.tar.gz", hash = "sha256:988468260ec1c196dab6ae1149260e2f5472c9110334e5d51adcb77867361f6a"},
]
beautifulsoup4 = [
- {file = "beautifulsoup4-4.9.3-py2-none-any.whl", hash = "sha256:4c98143716ef1cb40bf7f39a8e3eec8f8b009509e74904ba3a7b315431577e35"},
- {file = "beautifulsoup4-4.9.3-py3-none-any.whl", hash = "sha256:fff47e031e34ec82bf17e00da8f592fe7de69aeea38be00523c04623c04fb666"},
- {file = "beautifulsoup4-4.9.3.tar.gz", hash = "sha256:84729e322ad1d5b4d25f805bfa05b902dd96450f43842c4e99067d5e1369eb25"},
+ {file = "beautifulsoup4-4.10.0-py3-none-any.whl", hash = "sha256:9a315ce70049920ea4572a4055bc4bd700c940521d36fc858205ad4fcde149bf"},
+ {file = "beautifulsoup4-4.10.0.tar.gz", hash = "sha256:c23ad23c521d818955a4151a67d81580319d4bf548d3d49f4223ae041ff98891"},
]
certifi = [
- {file = "certifi-2021.5.30-py2.py3-none-any.whl", hash = "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8"},
- {file = "certifi-2021.5.30.tar.gz", hash = "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee"},
+ {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"},
+ {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"},
]
cffi = [
- {file = "cffi-1.14.6-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:22b9c3c320171c108e903d61a3723b51e37aaa8c81255b5e7ce102775bd01e2c"},
- {file = "cffi-1.14.6-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:f0c5d1acbfca6ebdd6b1e3eded8d261affb6ddcf2186205518f1428b8569bb99"},
- {file = "cffi-1.14.6-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:99f27fefe34c37ba9875f224a8f36e31d744d8083e00f520f133cab79ad5e819"},
- {file = "cffi-1.14.6-cp27-cp27m-win32.whl", hash = "sha256:55af55e32ae468e9946f741a5d51f9896da6b9bf0bbdd326843fec05c730eb20"},
- {file = "cffi-1.14.6-cp27-cp27m-win_amd64.whl", hash = "sha256:7bcac9a2b4fdbed2c16fa5681356d7121ecabf041f18d97ed5b8e0dd38a80224"},
- {file = "cffi-1.14.6-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:ed38b924ce794e505647f7c331b22a693bee1538fdf46b0222c4717b42f744e7"},
- {file = "cffi-1.14.6-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:e22dcb48709fc51a7b58a927391b23ab37eb3737a98ac4338e2448bef8559b33"},
- {file = "cffi-1.14.6-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:aedb15f0a5a5949ecb129a82b72b19df97bbbca024081ed2ef88bd5c0a610534"},
- {file = "cffi-1.14.6-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:48916e459c54c4a70e52745639f1db524542140433599e13911b2f329834276a"},
- {file = "cffi-1.14.6-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f627688813d0a4140153ff532537fbe4afea5a3dffce1f9deb7f91f848a832b5"},
- {file = "cffi-1.14.6-cp35-cp35m-win32.whl", hash = "sha256:f0010c6f9d1a4011e429109fda55a225921e3206e7f62a0c22a35344bfd13cca"},
- {file = "cffi-1.14.6-cp35-cp35m-win_amd64.whl", hash = "sha256:57e555a9feb4a8460415f1aac331a2dc833b1115284f7ded7278b54afc5bd218"},
- {file = "cffi-1.14.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e8c6a99be100371dbb046880e7a282152aa5d6127ae01783e37662ef73850d8f"},
- {file = "cffi-1.14.6-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:19ca0dbdeda3b2615421d54bef8985f72af6e0c47082a8d26122adac81a95872"},
- {file = "cffi-1.14.6-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d950695ae4381ecd856bcaf2b1e866720e4ab9a1498cba61c602e56630ca7195"},
- {file = "cffi-1.14.6-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9dc245e3ac69c92ee4c167fbdd7428ec1956d4e754223124991ef29eb57a09d"},
- {file = "cffi-1.14.6-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a8661b2ce9694ca01c529bfa204dbb144b275a31685a075ce123f12331be790b"},
- {file = "cffi-1.14.6-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b315d709717a99f4b27b59b021e6207c64620790ca3e0bde636a6c7f14618abb"},
- {file = "cffi-1.14.6-cp36-cp36m-win32.whl", hash = "sha256:80b06212075346b5546b0417b9f2bf467fea3bfe7352f781ffc05a8ab24ba14a"},
- {file = "cffi-1.14.6-cp36-cp36m-win_amd64.whl", hash = "sha256:a9da7010cec5a12193d1af9872a00888f396aba3dc79186604a09ea3ee7c029e"},
- {file = "cffi-1.14.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4373612d59c404baeb7cbd788a18b2b2a8331abcc84c3ba40051fcd18b17a4d5"},
- {file = "cffi-1.14.6-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:f10afb1004f102c7868ebfe91c28f4a712227fe4cb24974350ace1f90e1febbf"},
- {file = "cffi-1.14.6-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:fd4305f86f53dfd8cd3522269ed7fc34856a8ee3709a5e28b2836b2db9d4cd69"},
- {file = "cffi-1.14.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d6169cb3c6c2ad50db5b868db6491a790300ade1ed5d1da29289d73bbe40b56"},
- {file = "cffi-1.14.6-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d4b68e216fc65e9fe4f524c177b54964af043dde734807586cf5435af84045c"},
- {file = "cffi-1.14.6-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33791e8a2dc2953f28b8d8d300dde42dd929ac28f974c4b4c6272cb2955cb762"},
- {file = "cffi-1.14.6-cp37-cp37m-win32.whl", hash = "sha256:0c0591bee64e438883b0c92a7bed78f6290d40bf02e54c5bf0978eaf36061771"},
- {file = "cffi-1.14.6-cp37-cp37m-win_amd64.whl", hash = "sha256:8eb687582ed7cd8c4bdbff3df6c0da443eb89c3c72e6e5dcdd9c81729712791a"},
- {file = "cffi-1.14.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ba6f2b3f452e150945d58f4badd92310449876c4c954836cfb1803bdd7b422f0"},
- {file = "cffi-1.14.6-cp38-cp38-manylinux1_i686.whl", hash = "sha256:64fda793737bc4037521d4899be780534b9aea552eb673b9833b01f945904c2e"},
- {file = "cffi-1.14.6-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:9f3e33c28cd39d1b655ed1ba7247133b6f7fc16fa16887b120c0c670e35ce346"},
- {file = "cffi-1.14.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26bb2549b72708c833f5abe62b756176022a7b9a7f689b571e74c8478ead51dc"},
- {file = "cffi-1.14.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb687a11f0a7a1839719edd80f41e459cc5366857ecbed383ff376c4e3cc6afd"},
- {file = "cffi-1.14.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d2ad4d668a5c0645d281dcd17aff2be3212bc109b33814bbb15c4939f44181cc"},
- {file = "cffi-1.14.6-cp38-cp38-win32.whl", hash = "sha256:487d63e1454627c8e47dd230025780e91869cfba4c753a74fda196a1f6ad6548"},
- {file = "cffi-1.14.6-cp38-cp38-win_amd64.whl", hash = "sha256:c33d18eb6e6bc36f09d793c0dc58b0211fccc6ae5149b808da4a62660678b156"},
- {file = "cffi-1.14.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:06c54a68935738d206570b20da5ef2b6b6d92b38ef3ec45c5422c0ebaf338d4d"},
- {file = "cffi-1.14.6-cp39-cp39-manylinux1_i686.whl", hash = "sha256:f174135f5609428cc6e1b9090f9268f5c8935fddb1b25ccb8255a2d50de6789e"},
- {file = "cffi-1.14.6-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f3ebe6e73c319340830a9b2825d32eb6d8475c1dac020b4f0aa774ee3b898d1c"},
- {file = "cffi-1.14.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c8d896becff2fa653dc4438b54a5a25a971d1f4110b32bd3068db3722c80202"},
- {file = "cffi-1.14.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4922cd707b25e623b902c86188aca466d3620892db76c0bdd7b99a3d5e61d35f"},
- {file = "cffi-1.14.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c9e005e9bd57bc987764c32a1bee4364c44fdc11a3cc20a40b93b444984f2b87"},
- {file = "cffi-1.14.6-cp39-cp39-win32.whl", hash = "sha256:eb9e2a346c5238a30a746893f23a9535e700f8192a68c07c0258e7ece6ff3728"},
- {file = "cffi-1.14.6-cp39-cp39-win_amd64.whl", hash = "sha256:818014c754cd3dba7229c0f5884396264d51ffb87ec86e927ef0be140bfdb0d2"},
- {file = "cffi-1.14.6.tar.gz", hash = "sha256:c9a875ce9d7fe32887784274dd533c57909b7b1dcadcc128a2ac21331a9765dd"},
+ {file = "cffi-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c2502a1a03b6312837279c8c1bd3ebedf6c12c4228ddbad40912d671ccc8a962"},
+ {file = "cffi-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:23cfe892bd5dd8941608f93348c0737e369e51c100d03718f108bf1add7bd6d0"},
+ {file = "cffi-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:41d45de54cd277a7878919867c0f08b0cf817605e4eb94093e7516505d3c8d14"},
+ {file = "cffi-1.15.0-cp27-cp27m-win32.whl", hash = "sha256:4a306fa632e8f0928956a41fa8e1d6243c71e7eb59ffbd165fc0b41e316b2474"},
+ {file = "cffi-1.15.0-cp27-cp27m-win_amd64.whl", hash = "sha256:e7022a66d9b55e93e1a845d8c9eba2a1bebd4966cd8bfc25d9cd07d515b33fa6"},
+ {file = "cffi-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:14cd121ea63ecdae71efa69c15c5543a4b5fbcd0bbe2aad864baca0063cecf27"},
+ {file = "cffi-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d4d692a89c5cf08a8557fdeb329b82e7bf609aadfaed6c0d79f5a449a3c7c023"},
+ {file = "cffi-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0104fb5ae2391d46a4cb082abdd5c69ea4eab79d8d44eaaf79f1b1fd806ee4c2"},
+ {file = "cffi-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:91ec59c33514b7c7559a6acda53bbfe1b283949c34fe7440bcf917f96ac0723e"},
+ {file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f5c7150ad32ba43a07c4479f40241756145a1f03b43480e058cfd862bf5041c7"},
+ {file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:00c878c90cb53ccfaae6b8bc18ad05d2036553e6d9d1d9dbcf323bbe83854ca3"},
+ {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abb9a20a72ac4e0fdb50dae135ba5e77880518e742077ced47eb1499e29a443c"},
+ {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a5263e363c27b653a90078143adb3d076c1a748ec9ecc78ea2fb916f9b861962"},
+ {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f54a64f8b0c8ff0b64d18aa76675262e1700f3995182267998c31ae974fbc382"},
+ {file = "cffi-1.15.0-cp310-cp310-win32.whl", hash = "sha256:c21c9e3896c23007803a875460fb786118f0cdd4434359577ea25eb556e34c55"},
+ {file = "cffi-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:5e069f72d497312b24fcc02073d70cb989045d1c91cbd53979366077959933e0"},
+ {file = "cffi-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:64d4ec9f448dfe041705426000cc13e34e6e5bb13736e9fd62e34a0b0c41566e"},
+ {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2756c88cbb94231c7a147402476be2c4df2f6078099a6f4a480d239a8817ae39"},
+ {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b96a311ac60a3f6be21d2572e46ce67f09abcf4d09344c49274eb9e0bf345fc"},
+ {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75e4024375654472cc27e91cbe9eaa08567f7fbdf822638be2814ce059f58032"},
+ {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:59888172256cac5629e60e72e86598027aca6bf01fa2465bdb676d37636573e8"},
+ {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:27c219baf94952ae9d50ec19651a687b826792055353d07648a5695413e0c605"},
+ {file = "cffi-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:4958391dbd6249d7ad855b9ca88fae690783a6be9e86df65865058ed81fc860e"},
+ {file = "cffi-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:f6f824dc3bce0edab5f427efcfb1d63ee75b6fcb7282900ccaf925be84efb0fc"},
+ {file = "cffi-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:06c48159c1abed75c2e721b1715c379fa3200c7784271b3c46df01383b593636"},
+ {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c2051981a968d7de9dd2d7b87bcb9c939c74a34626a6e2f8181455dd49ed69e4"},
+ {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:fd8a250edc26254fe5b33be00402e6d287f562b6a5b2152dec302fa15bb3e997"},
+ {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91d77d2a782be4274da750752bb1650a97bfd8f291022b379bb8e01c66b4e96b"},
+ {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:45db3a33139e9c8f7c09234b5784a5e33d31fd6907800b316decad50af323ff2"},
+ {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:263cc3d821c4ab2213cbe8cd8b355a7f72a8324577dc865ef98487c1aeee2bc7"},
+ {file = "cffi-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:17771976e82e9f94976180f76468546834d22a7cc404b17c22df2a2c81db0c66"},
+ {file = "cffi-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:3415c89f9204ee60cd09b235810be700e993e343a408693e80ce7f6a40108029"},
+ {file = "cffi-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4238e6dab5d6a8ba812de994bbb0a79bddbdf80994e4ce802b6f6f3142fcc880"},
+ {file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0808014eb713677ec1292301ea4c81ad277b6cdf2fdd90fd540af98c0b101d20"},
+ {file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:57e9ac9ccc3101fac9d6014fba037473e4358ef4e89f8e181f8951a2c0162024"},
+ {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b6c2ea03845c9f501ed1313e78de148cd3f6cad741a75d43a29b43da27f2e1e"},
+ {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:10dffb601ccfb65262a27233ac273d552ddc4d8ae1bf93b21c94b8511bffe728"},
+ {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:786902fb9ba7433aae840e0ed609f45c7bcd4e225ebb9c753aa39725bb3e6ad6"},
+ {file = "cffi-1.15.0-cp38-cp38-win32.whl", hash = "sha256:da5db4e883f1ce37f55c667e5c0de439df76ac4cb55964655906306918e7363c"},
+ {file = "cffi-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:181dee03b1170ff1969489acf1c26533710231c58f95534e3edac87fff06c443"},
+ {file = "cffi-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:45e8636704eacc432a206ac7345a5d3d2c62d95a507ec70d62f23cd91770482a"},
+ {file = "cffi-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:31fb708d9d7c3f49a60f04cf5b119aeefe5644daba1cd2a0fe389b674fd1de37"},
+ {file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6dc2737a3674b3e344847c8686cf29e500584ccad76204efea14f451d4cc669a"},
+ {file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74fdfdbfdc48d3f47148976f49fab3251e550a8720bebc99bf1483f5bfb5db3e"},
+ {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffaa5c925128e29efbde7301d8ecaf35c8c60ffbcd6a1ffd3a552177c8e5e796"},
+ {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f7d084648d77af029acb79a0ff49a0ad7e9d09057a9bf46596dac9514dc07df"},
+ {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef1f279350da2c586a69d32fc8733092fd32cc8ac95139a00377841f59a3f8d8"},
+ {file = "cffi-1.15.0-cp39-cp39-win32.whl", hash = "sha256:2a23af14f408d53d5e6cd4e3d9a24ff9e05906ad574822a10563efcef137979a"},
+ {file = "cffi-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:3773c4d81e6e818df2efbc7dd77325ca0dcb688116050fb2b3011218eda36139"},
+ {file = "cffi-1.15.0.tar.gz", hash = "sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954"},
]
cfgv = [
{file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"},
@@ -1216,8 +1265,8 @@ chardet = [
{file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"},
]
charset-normalizer = [
- {file = "charset-normalizer-2.0.4.tar.gz", hash = "sha256:f23667ebe1084be45f6ae0538e4a5a865206544097e4e8bbcacf42cd02a348f3"},
- {file = "charset_normalizer-2.0.4-py3-none-any.whl", hash = "sha256:0c8911edd15d19223366a194a513099a302055a962bca2cec0f54b8b63175d8b"},
+ {file = "charset-normalizer-2.0.7.tar.gz", hash = "sha256:e019de665e2bcf9c2b64e2e5aa025fa991da8720daa3c1138cadd2fd1856aed0"},
+ {file = "charset_normalizer-2.0.7-py3-none-any.whl", hash = "sha256:f7af805c321bfa1ce6714c51f254e0d5bb5e5834039bc17db7ebe3a4cec9492b"},
]
colorama = [
{file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
@@ -1289,13 +1338,10 @@ deepdiff = [
{file = "deepdiff-4.3.2-py3-none-any.whl", hash = "sha256:59fc1e3e7a28dd0147b0f2b00e3e27181f0f0ef4286b251d5f214a5bcd9a9bc4"},
{file = "deepdiff-4.3.2.tar.gz", hash = "sha256:91360be1d9d93b1d9c13ae9c5048fa83d9cff17a88eb30afaa0d7ff2d0fee17d"},
]
-"discord.py" = [
- {file = "discord.py-1.7.3-py3-none-any.whl", hash = "sha256:c6f64db136de0e18e090f6752ea68bdd4ab0a61b82dfe7acecefa22d6477bb0c"},
- {file = "discord.py-1.7.3.tar.gz", hash = "sha256:462cd0fe307aef8b29cbfa8dd613e548ae4b2cb581d46da9ac0d46fb6ea19408"},
-]
+"discord.py" = []
distlib = [
- {file = "distlib-0.3.2-py2.py3-none-any.whl", hash = "sha256:23e223426b28491b1ced97dc3bbe183027419dfc7982b4fa2f05d5f3ff10711c"},
- {file = "distlib-0.3.2.zip", hash = "sha256:106fef6dc37dd8c0e2c0a60d3fca3e77460a48907f335fa28420463a6f799736"},
+ {file = "distlib-0.3.3-py2.py3-none-any.whl", hash = "sha256:c8b54e8454e5bf6237cc84c20e8264c3e991e824ef27e8f1e81049867d861e31"},
+ {file = "distlib-0.3.3.zip", hash = "sha256:d982d0751ff6eaaab5e2ec8e691d949ee80eddf01a62eaa96ddb11531fe16b05"},
]
docopt = [
{file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"},
@@ -1308,24 +1354,24 @@ execnet = [
{file = "execnet-1.9.0.tar.gz", hash = "sha256:8f694f3ba9cc92cab508b152dcfe322153975c29bda272e2fd7f3f00f36e47c5"},
]
fakeredis = [
- {file = "fakeredis-1.6.0-py3-none-any.whl", hash = "sha256:3449b306f3a85102b28f8180c24722ef966fcb1e3c744758b6f635ec80321a5c"},
- {file = "fakeredis-1.6.0.tar.gz", hash = "sha256:11ccfc9769d718d37e45b382e64a6ba02586b622afa0371a6bd85766d72255f3"},
+ {file = "fakeredis-1.6.1-py3-none-any.whl", hash = "sha256:5eb1516f1fe1813e9da8f6c482178fc067af09f53de587ae03887ef5d9d13024"},
+ {file = "fakeredis-1.6.1.tar.gz", hash = "sha256:0d06a9384fb79da9f2164ce96e34eb9d4e2ea46215070805ea6fd3c174590b47"},
]
feedparser = [
{file = "feedparser-6.0.8-py3-none-any.whl", hash = "sha256:1b7f57841d9cf85074deb316ed2c795091a238adb79846bc46dccdaf80f9c59a"},
{file = "feedparser-6.0.8.tar.gz", hash = "sha256:5ce0410a05ab248c8c7cfca3a0ea2203968ee9ff4486067379af4827a59f9661"},
]
filelock = [
- {file = "filelock-3.0.12-py3-none-any.whl", hash = "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"},
- {file = "filelock-3.0.12.tar.gz", hash = "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59"},
+ {file = "filelock-3.3.1-py3-none-any.whl", hash = "sha256:2b5eb3589e7fdda14599e7eb1a50e09b4cc14f34ed98b8ba56d33bfaafcbef2f"},
+ {file = "filelock-3.3.1.tar.gz", hash = "sha256:34a9f35f95c441e7b38209775d6e0337f9a3759f3565f6c5798f19618527c76f"},
]
flake8 = [
{file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"},
{file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"},
]
flake8-annotations = [
- {file = "flake8-annotations-2.6.2.tar.gz", hash = "sha256:0d6cd2e770b5095f09689c9d84cc054c51b929c41a68969ea1beb4b825cac515"},
- {file = "flake8_annotations-2.6.2-py3-none-any.whl", hash = "sha256:d10c4638231f8a50c0a597c4efce42bd7b7d85df4f620a0ddaca526138936a4f"},
+ {file = "flake8-annotations-2.7.0.tar.gz", hash = "sha256:52e53c05b0c06cac1c2dec192ea2c36e85081238add3bd99421d56f574b9479b"},
+ {file = "flake8_annotations-2.7.0-py3-none-any.whl", hash = "sha256:3edfbbfb58e404868834fe6ec3eaf49c139f64f0701259f707d043185545151e"},
]
flake8-bugbear = [
{file = "flake8-bugbear-20.11.1.tar.gz", hash = "sha256:528020129fea2dea33a466b9d64ab650aa3e5f9ffc788b70ea4bc6cf18283538"},
@@ -1335,9 +1381,9 @@ 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"},
]
-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-isort = [
+ {file = "flake8-isort-4.1.1.tar.gz", hash = "sha256:d814304ab70e6e58859bc5c3e221e2e6e71c958e7005239202fee19c24f82717"},
+ {file = "flake8_isort-4.1.1-py3-none-any.whl", hash = "sha256:c4e8b6dcb7be9b71a02e6e5d4196cefcef0f3447be51e82730fb336fff164949"},
]
flake8-polyfill = [
{file = "flake8-polyfill-1.0.2.tar.gz", hash = "sha256:e44b087597f6da52ec6393a709e7108b2905317d0c0b744cdca6208e670d8eda"},
@@ -1348,8 +1394,8 @@ flake8-string-format = [
{file = "flake8_string_format-0.3.0-py2.py3-none-any.whl", hash = "sha256:812ff431f10576a74c89be4e85b8e075a705be39bc40c4b4278b5b13e2afa9af"},
]
flake8-tidy-imports = [
- {file = "flake8-tidy-imports-4.4.1.tar.gz", hash = "sha256:c18b3351b998787db071e766e318da1f0bd9d5cecc69c4022a69e7aa2efb2c51"},
- {file = "flake8_tidy_imports-4.4.1-py3-none-any.whl", hash = "sha256:631a1ba9daaedbe8bb53f6086c5a92b390e98371205259e0e311a378df8c3dc8"},
+ {file = "flake8-tidy-imports-4.5.0.tar.gz", hash = "sha256:ac637961d0f319012d099e49619f8c928e3221f74e00fe6eb89513bc64c40adb"},
+ {file = "flake8_tidy_imports-4.5.0-py3-none-any.whl", hash = "sha256:87eed94ae6a2fda6a5918d109746feadf1311e0eb8274ab7a7920f6db00a41c9"},
]
flake8-todo = [
{file = "flake8-todo-0.7.tar.gz", hash = "sha256:6e4c5491ff838c06fe5a771b0e95ee15fc005ca57196011011280fc834a85915"},
@@ -1398,21 +1444,25 @@ hiredis = [
{file = "hiredis-2.0.0.tar.gz", hash = "sha256:81d6d8e39695f2c37954d1011c0480ef7cf444d4e3ae24bc5e89ee5de360139a"},
]
humanfriendly = [
- {file = "humanfriendly-9.2-py2.py3-none-any.whl", hash = "sha256:332da98c24cc150efcc91b5508b19115209272bfdf4b0764a56795932f854271"},
- {file = "humanfriendly-9.2.tar.gz", hash = "sha256:f7dba53ac7935fd0b4a2fc9a29e316ddd9ea135fb3052d3d0279d10c18ff9c48"},
+ {file = "humanfriendly-10.0-py2.py3-none-any.whl", hash = "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477"},
+ {file = "humanfriendly-10.0.tar.gz", hash = "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc"},
]
identify = [
- {file = "identify-2.2.13-py2.py3-none-any.whl", hash = "sha256:7199679b5be13a6b40e6e19ea473e789b11b4e3b60986499b1f589ffb03c217c"},
- {file = "identify-2.2.13.tar.gz", hash = "sha256:7bc6e829392bd017236531963d2d937d66fc27cadc643ac0aba2ce9f26157c79"},
+ {file = "identify-2.3.0-py2.py3-none-any.whl", hash = "sha256:d1e82c83d063571bb88087676f81261a4eae913c492dafde184067c584bc7c05"},
+ {file = "identify-2.3.0.tar.gz", hash = "sha256:fd08c97f23ceee72784081f1ce5125c8f53a02d3f2716dde79a6ab8f1039fea5"},
]
idna = [
- {file = "idna-3.2-py3-none-any.whl", hash = "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a"},
- {file = "idna-3.2.tar.gz", hash = "sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3"},
+ {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"},
+ {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"},
]
iniconfig = [
{file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
{file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"},
]
+isort = [
+ {file = "isort-5.9.3-py3-none-any.whl", hash = "sha256:e17d6e2b81095c9db0a03a8025a957f334d6ea30b26f9ec70805411e5c7c81f2"},
+ {file = "isort-5.9.3.tar.gz", hash = "sha256:9c2ea1e62d871267b78307fe511c0838ba0da28698c5732d54e2790bf3ba9899"},
+]
lxml = [
{file = "lxml-4.6.3-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:df7c53783a46febb0e70f6b05df2ba104610f2fb0d27023409734a3ecbb78fb2"},
{file = "lxml-4.6.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:1b7584d421d254ab86d4f0b13ec662a9014397678a7c4265a02a6d7c2b18a75f"},
@@ -1470,51 +1520,86 @@ mccabe = [
{file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"},
]
more-itertools = [
- {file = "more-itertools-8.8.0.tar.gz", hash = "sha256:83f0308e05477c68f56ea3a888172c78ed5d5b3c282addb67508e7ba6c8f813a"},
- {file = "more_itertools-8.8.0-py3-none-any.whl", hash = "sha256:2cf89ec599962f2ddc4d568a05defc40e0a587fbc10d5989713638864c36be4d"},
+ {file = "more-itertools-8.10.0.tar.gz", hash = "sha256:1debcabeb1df793814859d64a81ad7cb10504c24349368ccf214c664c474f41f"},
+ {file = "more_itertools-8.10.0-py3-none-any.whl", hash = "sha256:56ddac45541718ba332db05f464bebfb0768110111affd27f66e0051f276fa43"},
]
mslex = [
{file = "mslex-0.3.0-py2.py3-none-any.whl", hash = "sha256:380cb14abf8fabf40e56df5c8b21a6d533dc5cbdcfe42406bbf08dda8f42e42a"},
{file = "mslex-0.3.0.tar.gz", hash = "sha256:4a1ac3f25025cad78ad2fe499dd16d42759f7a3801645399cce5c404415daa97"},
]
multidict = [
- {file = "multidict-5.1.0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:b7993704f1a4b204e71debe6095150d43b2ee6150fa4f44d6d966ec356a8d61f"},
- {file = "multidict-5.1.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:9dd6e9b1a913d096ac95d0399bd737e00f2af1e1594a787e00f7975778c8b2bf"},
- {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:f21756997ad8ef815d8ef3d34edd98804ab5ea337feedcd62fb52d22bf531281"},
- {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:1ab820665e67373de5802acae069a6a05567ae234ddb129f31d290fc3d1aa56d"},
- {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:9436dc58c123f07b230383083855593550c4d301d2532045a17ccf6eca505f6d"},
- {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:830f57206cc96ed0ccf68304141fec9481a096c4d2e2831f311bde1c404401da"},
- {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:2e68965192c4ea61fff1b81c14ff712fc7dc15d2bd120602e4a3494ea6584224"},
- {file = "multidict-5.1.0-cp36-cp36m-win32.whl", hash = "sha256:2f1a132f1c88724674271d636e6b7351477c27722f2ed789f719f9e3545a3d26"},
- {file = "multidict-5.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:3a4f32116f8f72ecf2a29dabfb27b23ab7cdc0ba807e8459e59a93a9be9506f6"},
- {file = "multidict-5.1.0-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:46c73e09ad374a6d876c599f2328161bcd95e280f84d2060cf57991dec5cfe76"},
- {file = "multidict-5.1.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:018132dbd8688c7a69ad89c4a3f39ea2f9f33302ebe567a879da8f4ca73f0d0a"},
- {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:4b186eb7d6ae7c06eb4392411189469e6a820da81447f46c0072a41c748ab73f"},
- {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:3a041b76d13706b7fff23b9fc83117c7b8fe8d5fe9e6be45eee72b9baa75f348"},
- {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:051012ccee979b2b06be928a6150d237aec75dd6bf2d1eeeb190baf2b05abc93"},
- {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:6a4d5ce640e37b0efcc8441caeea8f43a06addace2335bd11151bc02d2ee31f9"},
- {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:5cf3443199b83ed9e955f511b5b241fd3ae004e3cb81c58ec10f4fe47c7dce37"},
- {file = "multidict-5.1.0-cp37-cp37m-win32.whl", hash = "sha256:f200755768dc19c6f4e2b672421e0ebb3dd54c38d5a4f262b872d8cfcc9e93b5"},
- {file = "multidict-5.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:05c20b68e512166fddba59a918773ba002fdd77800cad9f55b59790030bab632"},
- {file = "multidict-5.1.0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:54fd1e83a184e19c598d5e70ba508196fd0bbdd676ce159feb412a4a6664f952"},
- {file = "multidict-5.1.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:0e3c84e6c67eba89c2dbcee08504ba8644ab4284863452450520dad8f1e89b79"},
- {file = "multidict-5.1.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:dc862056f76443a0db4509116c5cd480fe1b6a2d45512a653f9a855cc0517456"},
- {file = "multidict-5.1.0-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:0e929169f9c090dae0646a011c8b058e5e5fb391466016b39d21745b48817fd7"},
- {file = "multidict-5.1.0-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:d81eddcb12d608cc08081fa88d046c78afb1bf8107e6feab5d43503fea74a635"},
- {file = "multidict-5.1.0-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:585fd452dd7782130d112f7ddf3473ffdd521414674c33876187e101b588738a"},
- {file = "multidict-5.1.0-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:37e5438e1c78931df5d3c0c78ae049092877e5e9c02dd1ff5abb9cf27a5914ea"},
- {file = "multidict-5.1.0-cp38-cp38-win32.whl", hash = "sha256:07b42215124aedecc6083f1ce6b7e5ec5b50047afa701f3442054373a6deb656"},
- {file = "multidict-5.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:929006d3c2d923788ba153ad0de8ed2e5ed39fdbe8e7be21e2f22ed06c6783d3"},
- {file = "multidict-5.1.0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:b797515be8743b771aa868f83563f789bbd4b236659ba52243b735d80b29ed93"},
- {file = "multidict-5.1.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:d5c65bdf4484872c4af3150aeebe101ba560dcfb34488d9a8ff8dbcd21079647"},
- {file = "multidict-5.1.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b47a43177a5e65b771b80db71e7be76c0ba23cc8aa73eeeb089ed5219cdbe27d"},
- {file = "multidict-5.1.0-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:806068d4f86cb06af37cd65821554f98240a19ce646d3cd24e1c33587f313eb8"},
- {file = "multidict-5.1.0-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:46dd362c2f045095c920162e9307de5ffd0a1bfbba0a6e990b344366f55a30c1"},
- {file = "multidict-5.1.0-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:ace010325c787c378afd7f7c1ac66b26313b3344628652eacd149bdd23c68841"},
- {file = "multidict-5.1.0-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:ecc771ab628ea281517e24fd2c52e8f31c41e66652d07599ad8818abaad38cda"},
- {file = "multidict-5.1.0-cp39-cp39-win32.whl", hash = "sha256:fc13a9524bc18b6fb6e0dbec3533ba0496bbed167c56d0aabefd965584557d80"},
- {file = "multidict-5.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:7df80d07818b385f3129180369079bd6934cf70469f99daaebfac89dca288359"},
- {file = "multidict-5.1.0.tar.gz", hash = "sha256:25b4e5f22d3a37ddf3effc0710ba692cfc792c2b9edfb9c05aefe823256e84d5"},
+ {file = "multidict-5.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3822c5894c72e3b35aae9909bef66ec83e44522faf767c0ad39e0e2de11d3b55"},
+ {file = "multidict-5.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:28e6d883acd8674887d7edc896b91751dc2d8e87fbdca8359591a13872799e4e"},
+ {file = "multidict-5.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b61f85101ef08cbbc37846ac0e43f027f7844f3fade9b7f6dd087178caedeee7"},
+ {file = "multidict-5.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9b668c065968c5979fe6b6fa6760bb6ab9aeb94b75b73c0a9c1acf6393ac3bf"},
+ {file = "multidict-5.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:517d75522b7b18a3385726b54a081afd425d4f41144a5399e5abd97ccafdf36b"},
+ {file = "multidict-5.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1b4ac3ba7a97b35a5ccf34f41b5a8642a01d1e55454b699e5e8e7a99b5a3acf5"},
+ {file = "multidict-5.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:df23c83398715b26ab09574217ca21e14694917a0c857e356fd39e1c64f8283f"},
+ {file = "multidict-5.2.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e58a9b5cc96e014ddf93c2227cbdeca94b56a7eb77300205d6e4001805391747"},
+ {file = "multidict-5.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f76440e480c3b2ca7f843ff8a48dc82446b86ed4930552d736c0bac507498a52"},
+ {file = "multidict-5.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cfde464ca4af42a629648c0b0d79b8f295cf5b695412451716531d6916461628"},
+ {file = "multidict-5.2.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:0fed465af2e0eb6357ba95795d003ac0bdb546305cc2366b1fc8f0ad67cc3fda"},
+ {file = "multidict-5.2.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:b70913cbf2e14275013be98a06ef4b412329fe7b4f83d64eb70dce8269ed1e1a"},
+ {file = "multidict-5.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a5635bcf1b75f0f6ef3c8a1ad07b500104a971e38d3683167b9454cb6465ac86"},
+ {file = "multidict-5.2.0-cp310-cp310-win32.whl", hash = "sha256:77f0fb7200cc7dedda7a60912f2059086e29ff67cefbc58d2506638c1a9132d7"},
+ {file = "multidict-5.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:9416cf11bcd73c861267e88aea71e9fcc35302b3943e45e1dbb4317f91a4b34f"},
+ {file = "multidict-5.2.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:fd77c8f3cba815aa69cb97ee2b2ef385c7c12ada9c734b0f3b32e26bb88bbf1d"},
+ {file = "multidict-5.2.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98ec9aea6223adf46999f22e2c0ab6cf33f5914be604a404f658386a8f1fba37"},
+ {file = "multidict-5.2.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e5283c0a00f48e8cafcecadebfa0ed1dac8b39e295c7248c44c665c16dc1138b"},
+ {file = "multidict-5.2.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5f79c19c6420962eb17c7e48878a03053b7ccd7b69f389d5831c0a4a7f1ac0a1"},
+ {file = "multidict-5.2.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e4a67f1080123de76e4e97a18d10350df6a7182e243312426d508712e99988d4"},
+ {file = "multidict-5.2.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:94b117e27efd8e08b4046c57461d5a114d26b40824995a2eb58372b94f9fca02"},
+ {file = "multidict-5.2.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:2e77282fd1d677c313ffcaddfec236bf23f273c4fba7cdf198108f5940ae10f5"},
+ {file = "multidict-5.2.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:116347c63ba049c1ea56e157fa8aa6edaf5e92925c9b64f3da7769bdfa012858"},
+ {file = "multidict-5.2.0-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:dc3a866cf6c13d59a01878cd806f219340f3e82eed514485e094321f24900677"},
+ {file = "multidict-5.2.0-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:ac42181292099d91217a82e3fa3ce0e0ddf3a74fd891b7c2b347a7f5aa0edded"},
+ {file = "multidict-5.2.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:f0bb0973f42ffcb5e3537548e0767079420aefd94ba990b61cf7bb8d47f4916d"},
+ {file = "multidict-5.2.0-cp36-cp36m-win32.whl", hash = "sha256:ea21d4d5104b4f840b91d9dc8cbc832aba9612121eaba503e54eaab1ad140eb9"},
+ {file = "multidict-5.2.0-cp36-cp36m-win_amd64.whl", hash = "sha256:e6453f3cbeb78440747096f239d282cc57a2997a16b5197c9bc839099e1633d0"},
+ {file = "multidict-5.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d3def943bfd5f1c47d51fd324df1e806d8da1f8e105cc7f1c76a1daf0f7e17b0"},
+ {file = "multidict-5.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35591729668a303a02b06e8dba0eb8140c4a1bfd4c4b3209a436a02a5ac1de11"},
+ {file = "multidict-5.2.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8cacda0b679ebc25624d5de66c705bc53dcc7c6f02a7fb0f3ca5e227d80422"},
+ {file = "multidict-5.2.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:baf1856fab8212bf35230c019cde7c641887e3fc08cadd39d32a421a30151ea3"},
+ {file = "multidict-5.2.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a43616aec0f0d53c411582c451f5d3e1123a68cc7b3475d6f7d97a626f8ff90d"},
+ {file = "multidict-5.2.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:25cbd39a9029b409167aa0a20d8a17f502d43f2efebfe9e3ac019fe6796c59ac"},
+ {file = "multidict-5.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0a2cbcfbea6dc776782a444db819c8b78afe4db597211298dd8b2222f73e9cd0"},
+ {file = "multidict-5.2.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:3d2d7d1fff8e09d99354c04c3fd5b560fb04639fd45926b34e27cfdec678a704"},
+ {file = "multidict-5.2.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:a37e9a68349f6abe24130846e2f1d2e38f7ddab30b81b754e5a1fde32f782b23"},
+ {file = "multidict-5.2.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:637c1896497ff19e1ee27c1c2c2ddaa9f2d134bbb5e0c52254361ea20486418d"},
+ {file = "multidict-5.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:9815765f9dcda04921ba467957be543423e5ec6a1136135d84f2ae092c50d87b"},
+ {file = "multidict-5.2.0-cp37-cp37m-win32.whl", hash = "sha256:8b911d74acdc1fe2941e59b4f1a278a330e9c34c6c8ca1ee21264c51ec9b67ef"},
+ {file = "multidict-5.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:380b868f55f63d048a25931a1632818f90e4be71d2081c2338fcf656d299949a"},
+ {file = "multidict-5.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e7d81ce5744757d2f05fc41896e3b2ae0458464b14b5a2c1e87a6a9d69aefaa8"},
+ {file = "multidict-5.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d1d55cdf706ddc62822d394d1df53573d32a7a07d4f099470d3cb9323b721b6"},
+ {file = "multidict-5.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a4771d0d0ac9d9fe9e24e33bed482a13dfc1256d008d101485fe460359476065"},
+ {file = "multidict-5.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da7d57ea65744d249427793c042094c4016789eb2562576fb831870f9c878d9e"},
+ {file = "multidict-5.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cdd68778f96216596218b4e8882944d24a634d984ee1a5a049b300377878fa7c"},
+ {file = "multidict-5.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ecc99bce8ee42dcad15848c7885197d26841cb24fa2ee6e89d23b8993c871c64"},
+ {file = "multidict-5.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:067150fad08e6f2dd91a650c7a49ba65085303fcc3decbd64a57dc13a2733031"},
+ {file = "multidict-5.2.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:78c106b2b506b4d895ddc801ff509f941119394b89c9115580014127414e6c2d"},
+ {file = "multidict-5.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e6c4fa1ec16e01e292315ba76eb1d012c025b99d22896bd14a66628b245e3e01"},
+ {file = "multidict-5.2.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b227345e4186809d31f22087d0265655114af7cda442ecaf72246275865bebe4"},
+ {file = "multidict-5.2.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:06560fbdcf22c9387100979e65b26fba0816c162b888cb65b845d3def7a54c9b"},
+ {file = "multidict-5.2.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:7878b61c867fb2df7a95e44b316f88d5a3742390c99dfba6c557a21b30180cac"},
+ {file = "multidict-5.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:246145bff76cc4b19310f0ad28bd0769b940c2a49fc601b86bfd150cbd72bb22"},
+ {file = "multidict-5.2.0-cp38-cp38-win32.whl", hash = "sha256:c30ac9f562106cd9e8071c23949a067b10211917fdcb75b4718cf5775356a940"},
+ {file = "multidict-5.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:f19001e790013ed580abfde2a4465388950728861b52f0da73e8e8a9418533c0"},
+ {file = "multidict-5.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c1ff762e2ee126e6f1258650ac641e2b8e1f3d927a925aafcfde943b77a36d24"},
+ {file = "multidict-5.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bd6c9c50bf2ad3f0448edaa1a3b55b2e6866ef8feca5d8dbec10ec7c94371d21"},
+ {file = "multidict-5.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fc66d4016f6e50ed36fb39cd287a3878ffcebfa90008535c62e0e90a7ab713ae"},
+ {file = "multidict-5.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9acb76d5f3dd9421874923da2ed1e76041cb51b9337fd7f507edde1d86535d6"},
+ {file = "multidict-5.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dfc924a7e946dd3c6360e50e8f750d51e3ef5395c95dc054bc9eab0f70df4f9c"},
+ {file = "multidict-5.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:32fdba7333eb2351fee2596b756d730d62b5827d5e1ab2f84e6cbb287cc67fe0"},
+ {file = "multidict-5.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:b9aad49466b8d828b96b9e3630006234879c8d3e2b0a9d99219b3121bc5cdb17"},
+ {file = "multidict-5.2.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:93de39267c4c676c9ebb2057e98a8138bade0d806aad4d864322eee0803140a0"},
+ {file = "multidict-5.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f9bef5cff994ca3026fcc90680e326d1a19df9841c5e3d224076407cc21471a1"},
+ {file = "multidict-5.2.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:5f841c4f14331fd1e36cbf3336ed7be2cb2a8f110ce40ea253e5573387db7621"},
+ {file = "multidict-5.2.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:38ba256ee9b310da6a1a0f013ef4e422fca30a685bcbec86a969bd520504e341"},
+ {file = "multidict-5.2.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:3bc3b1621b979621cee9f7b09f024ec76ec03cc365e638126a056317470bde1b"},
+ {file = "multidict-5.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6ee908c070020d682e9b42c8f621e8bb10c767d04416e2ebe44e37d0f44d9ad5"},
+ {file = "multidict-5.2.0-cp39-cp39-win32.whl", hash = "sha256:1c7976cd1c157fa7ba5456ae5d31ccdf1479680dc9b8d8aa28afabc370df42b8"},
+ {file = "multidict-5.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:c9631c642e08b9fff1c6255487e62971d8b8e821808ddd013d8ac058087591ac"},
+ {file = "multidict-5.2.0.tar.gz", hash = "sha256:0dd1c93edb444b33ba2274b66f63def8a327d607c6c790772f448a53b6ea59ce"},
]
nodeenv = [
{file = "nodeenv-1.6.0-py2.py3-none-any.whl", hash = "sha256:621e6b7076565ddcacd2db0294c0381e01fd28945ab36bcf00f41c5daf63bef7"},
@@ -1536,20 +1621,20 @@ pep8-naming = [
{file = "pep8_naming-0.12.1-py2.py3-none-any.whl", hash = "sha256:4a8daeaeb33cfcde779309fc0c9c0a68a3bbe2ad8a8308b763c5068f86eb9f37"},
]
pip-licenses = [
- {file = "pip-licenses-3.5.2.tar.gz", hash = "sha256:c5e984f461b34ad04dafa151d0048eb9d049e3d6439966c6440bb6b53ad077b6"},
- {file = "pip_licenses-3.5.2-py3-none-any.whl", hash = "sha256:62deafc82d5dccea1a4cab55172706e02f228abcd67f4d53e382fcb1497e9b62"},
+ {file = "pip-licenses-3.5.3.tar.gz", hash = "sha256:f44860e00957b791c6c6005a3328f2d5eaeee96ddb8e7d87d4b0aa25b02252e4"},
+ {file = "pip_licenses-3.5.3-py3-none-any.whl", hash = "sha256:59c148d6a03784bf945d232c0dc0e9de4272a3675acaa0361ad7712398ca86ba"},
]
platformdirs = [
- {file = "platformdirs-2.2.0-py3-none-any.whl", hash = "sha256:4666d822218db6a262bdfdc9c39d21f23b4cfdb08af331a81e92751daf6c866c"},
- {file = "platformdirs-2.2.0.tar.gz", hash = "sha256:632daad3ab546bd8e6af0537d09805cec458dce201bccfe23012df73332e181e"},
+ {file = "platformdirs-2.4.0-py3-none-any.whl", hash = "sha256:8868bbe3c3c80d42f20156f22e7131d2fb321f5bc86a2a345375c6481a67021d"},
+ {file = "platformdirs-2.4.0.tar.gz", hash = "sha256:367a5e80b3d04d2428ffa76d33f124cf11e8fff2acdaa9b43d545f5c7d661ef2"},
]
pluggy = [
- {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"},
- {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"},
+ {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.14.0-py2.py3-none-any.whl", hash = "sha256:ec3045ae62e1aa2eecfb8e86fa3025c2e3698f77394ef8d2011ce0aedd85b2d4"},
- {file = "pre_commit-2.14.0.tar.gz", hash = "sha256:2386eeb4cf6633712c7cc9ede83684d53c8cafca6b59f79c738098b51c6d206c"},
+ {file = "pre_commit-2.15.0-py2.py3-none-any.whl", hash = "sha256:a4ed01000afcb484d9eb8d504272e642c4c4099bbad3a6b27e519bd6a3e928a6"},
+ {file = "pre_commit-2.15.0.tar.gz", hash = "sha256:3c25add78dbdfb6a28a651780d5c311ac40dd17f160eb3954a0c59da40a505a7"},
]
psutil = [
{file = "psutil-5.8.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:0066a82f7b1b37d334e68697faba68e5ad5e858279fd6351c8ca6024e8d6ba64"},
@@ -1643,14 +1728,13 @@ pyparsing = [
{file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"},
{file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"},
]
-pyreadline = [
- {file = "pyreadline-2.1.win-amd64.exe", hash = "sha256:9ce5fa65b8992dfa373bddc5b6e0864ead8f291c94fbfec05fbd5c836162e67b"},
- {file = "pyreadline-2.1.win32.exe", hash = "sha256:65540c21bfe14405a3a77e4c085ecfce88724743a4ead47c66b84defcf82c32e"},
- {file = "pyreadline-2.1.zip", hash = "sha256:4530592fc2e85b25b1a9f79664433da09237c1a270e4d78ea5aa3a2c7229e2d1"},
+pyreadline3 = [
+ {file = "pyreadline3-3.3-py3-none-any.whl", hash = "sha256:0003fd0079d152ecbd8111202c5a7dfa6a5569ffd65b235e45f3c2ecbee337b4"},
+ {file = "pyreadline3-3.3.tar.gz", hash = "sha256:ff3b5a1ac0010d0967869f723e687d42cabc7dccf33b14934c92aa5168d260b3"},
]
pytest = [
- {file = "pytest-6.2.4-py3-none-any.whl", hash = "sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890"},
- {file = "pytest-6.2.4.tar.gz", hash = "sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b"},
+ {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"},
+ {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"},
]
pytest-cov = [
{file = "pytest-cov-2.12.1.tar.gz", hash = "sha256:261ceeb8c227b726249b376b8526b600f38667ee314f910353fa318caa01f4d7"},
@@ -1708,67 +1792,57 @@ pyyaml = [
{file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"},
]
rapidfuzz = [
- {file = "rapidfuzz-1.5.0-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:670a330e90e962de5823e01e8ae1b8903af788325fbce1ef3fd5ece4d22e0ba4"},
- {file = "rapidfuzz-1.5.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:079afafa6e6b00ee799e16d9fc6c6522132cbd7742a7a9e78bd301321e1b5ad6"},
- {file = "rapidfuzz-1.5.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:26cb066e79c9867d313450514bb70124d392ac457640c4ec090d29eb68b75541"},
- {file = "rapidfuzz-1.5.0-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:542fbe8fb4403af36bfffd53e42cb1ff3f8d969a046208373d004804072b744c"},
- {file = "rapidfuzz-1.5.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:407a5c4d2af813e803b828b004f8686300baf298e9bf90b3388a568b1637a8dc"},
- {file = "rapidfuzz-1.5.0-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:662b4021951ac9edb9a0d026820529e891cea69c11f280188c5b80fefe6ee257"},
- {file = "rapidfuzz-1.5.0-cp35-cp35m-manylinux2014_ppc64le.whl", hash = "sha256:03c97beb1c7ce5cb1d12bbb8eb87777e9a5fad23216dab78d6850cafdd3ecaf1"},
- {file = "rapidfuzz-1.5.0-cp35-cp35m-manylinux2014_s390x.whl", hash = "sha256:eaafa0349d47850ed2c3ae121b62e078a63daf1d533b1cd43fca0c675a85a025"},
- {file = "rapidfuzz-1.5.0-cp35-cp35m-win32.whl", hash = "sha256:f0b7e15209208ee74bc264b97e111a3c73e19336eda7255c406e56cc6fbbd384"},
- {file = "rapidfuzz-1.5.0-cp35-cp35m-win_amd64.whl", hash = "sha256:0679af3d85082dcb27e75ea30c5047dbcc99340f38490c7d4769ae16909c246a"},
- {file = "rapidfuzz-1.5.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:3a3ef319fd1162e7e38bf11259d86fc6ea3885d2abae6359e5b4dafad62592db"},
- {file = "rapidfuzz-1.5.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:60ea1cee33a5a847aeac91a35865c6f7f35a87613df282bda2e7f984e91526f5"},
- {file = "rapidfuzz-1.5.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:2ba6ffe8ac66dbeae91a0b2cb50f4836ec16920f58746eaf46ff3e9c4f9c0ad8"},
- {file = "rapidfuzz-1.5.0-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:7c101bafb27436affcaa14c631e2bf99d6a7a7860a201ce17ee98447c9c0e7f4"},
- {file = "rapidfuzz-1.5.0-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:a8f3f374b4e8e80516b955a1da6364c526d480311a5c6be48264cf7dc06d2fba"},
- {file = "rapidfuzz-1.5.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:f2fe161526cce52eae224c2af9ae1b9c475ae3e1001fe76024603b290bc8f719"},
- {file = "rapidfuzz-1.5.0-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:8b086b2f70571c9bf16ead5f65976414f8e75a1c680220a839b8ddf005743060"},
- {file = "rapidfuzz-1.5.0-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:814cd474c31db0383c69eed5b457571f63521f38829955c842b141b4835f067f"},
- {file = "rapidfuzz-1.5.0-cp36-cp36m-win32.whl", hash = "sha256:0a901aa223a4b051846cb828c33967a6f9c66b8fe0ba7e2a4dc70f6612006988"},
- {file = "rapidfuzz-1.5.0-cp36-cp36m-win_amd64.whl", hash = "sha256:f03a5fa9fe38d7f8d566bff0b66600f488d56700469bf1e5e36078f4b58290b6"},
- {file = "rapidfuzz-1.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:122b7c25792eb27ca59ab23623a922a7290d881d296556d0c23da63ed1691cd5"},
- {file = "rapidfuzz-1.5.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:73509dbfcf556233d62683aed0e5f23282ec7138eeedc3ecda2938ad8e8c969d"},
- {file = "rapidfuzz-1.5.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:6e8c4fd87361699e0cf5cf7ff075e4cd70a2698e9f914368f0c3e198c77c755c"},
- {file = "rapidfuzz-1.5.0-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d627ec73d324d804af4c95909e2fa30b0e59f7efaf69264e553a0e498034404b"},
- {file = "rapidfuzz-1.5.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:c57f3b74942ae0d0869336e613cbd0760de61a462ff441095eb5fca6575cf964"},
- {file = "rapidfuzz-1.5.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:075b8bf76dd4bbc9ccb5177806c9867424d365898415433bf88e7b8e88dc4dfe"},
- {file = "rapidfuzz-1.5.0-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:8049a500b431724d283ddf97d67fe48aa67b4523d617a203c22fd9da3a496223"},
- {file = "rapidfuzz-1.5.0-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:a2d84fde07c32514758d283dd1227453db3ed5372a3e9eae85d0c29b2953f252"},
- {file = "rapidfuzz-1.5.0-cp37-cp37m-win32.whl", hash = "sha256:0e35b9b92a955018ebd09d4d9d70f8e81a0106fe1ed04bc82e3a05166cd04ea5"},
- {file = "rapidfuzz-1.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:8ae7bf62f0382d13e9b36babc897742bac5e7ee04b4e5e94cd67085bfccfd2fd"},
- {file = "rapidfuzz-1.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:466d9c644fa235278ef376eefb1fc4382107b07764fbc3c7280533ad9ce49bb4"},
- {file = "rapidfuzz-1.5.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:d04a8465738363d0b9ee39abb3b289e1198d1f3cbc98bc43b8e21ec8e0b21774"},
- {file = "rapidfuzz-1.5.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:2c1ce8e8419ac8462289a6e021b8802701ea0f111ebde7607ba3c9588c3d6f30"},
- {file = "rapidfuzz-1.5.0-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:f44564a29e96af0925e68733859d8247a692968034e1b37407d9cfa746d3a853"},
- {file = "rapidfuzz-1.5.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:d2d1bea50f54387bc1e82b93f6e3a433084e0fa538a7ada8e4d4d7200bae4b83"},
- {file = "rapidfuzz-1.5.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:b409f0f86a316b6132253258185c7b011e779ed2170d1ad83c79515fea7d78c8"},
- {file = "rapidfuzz-1.5.0-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:bf5a6f4f2eb44f32271e9c2d1e46b657764dbd1b933dd84d7c0433eab48741f8"},
- {file = "rapidfuzz-1.5.0-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:bbdee2e3c2cee9c59e1d1a3f351760a1b510e96379d14ba2fa2484a79f56d0ea"},
- {file = "rapidfuzz-1.5.0-cp38-cp38-win32.whl", hash = "sha256:575a0eceaf84632f2014fd55a42a0621e448115adf6fcbc2b0e5c7ae1c18b501"},
- {file = "rapidfuzz-1.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:cd6603b94e2a3d56d143a5100f8f3c1d29ad8f5416bdc2a25b079f96eee3c306"},
- {file = "rapidfuzz-1.5.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3fa261479e3828eff1f3d0265def8d0d893f2e2f90692d5dae96b3f4ae44d69e"},
- {file = "rapidfuzz-1.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7a386fe0aad7e89b5017768492ea085d241c32f6dc5a6774b0a309d28f61e720"},
- {file = "rapidfuzz-1.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68156a67d541bb4584cb31e366fb7de9326f5b77ed07f9882e9b9aaa40b2e5b8"},
- {file = "rapidfuzz-1.5.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:b62b2a2d2532d357d1b970107a90e85305bdd8e302995dd251f67a19495033f5"},
- {file = "rapidfuzz-1.5.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:190b48ba8e3fbcb1cfc522300dbd6a007f50c13cd71002c95bd3946a63b749f6"},
- {file = "rapidfuzz-1.5.0-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:51f9ac3316e713b4a10554a4d6b75fe6f802dd9b4073082cc98968ace6377cac"},
- {file = "rapidfuzz-1.5.0-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:e00198aa7ca8408616d9821501ff90157c429c952d55a2a53987a9b064f73d49"},
- {file = "rapidfuzz-1.5.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:5784c24e2de539064d8d5ce3f68756630b54fc33af31e054373a65bbed68823a"},
- {file = "rapidfuzz-1.5.0-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:712a4d510c466d6ca75138dad53a1cbd8db0da4bbfa5fc431fcebb0a426e5323"},
- {file = "rapidfuzz-1.5.0-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:2647e00e2211ed741aecb4e676461b7202ce46d536c3439ede911b088432b7a4"},
- {file = "rapidfuzz-1.5.0-cp39-cp39-win32.whl", hash = "sha256:0b77ca0dacb129e878c2583295b76e12da890bd091115417d23b4049b02c2566"},
- {file = "rapidfuzz-1.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:dec0d429d117ffd7df1661e5f6ca56bfb6806e117be0b75b5d414df43aa4b6d5"},
- {file = "rapidfuzz-1.5.0-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a533d17d177d11b7c177c849adb728035621462f6ce2baaeb9cf1f42ba3e326c"},
- {file = "rapidfuzz-1.5.0-pp36-pypy36_pp73-manylinux1_x86_64.whl", hash = "sha256:ac9a2d5a47a4a4eab060882a162d3626889abdec69f899a59fe7b9e01ce122c9"},
- {file = "rapidfuzz-1.5.0-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:0e6e2f02bb67a35d75a5613509bb49f0050c0ec4471a9af14da3ad5488d6d5ff"},
- {file = "rapidfuzz-1.5.0-pp36-pypy36_pp73-win32.whl", hash = "sha256:8c61ced6729146e695ecad403165bf3a07e60b8e8a18df91962b3abf72aae6d5"},
- {file = "rapidfuzz-1.5.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:360415125e967d8682291f00bcea311c738101e0aee4cb90e5572d7e54483f0d"},
- {file = "rapidfuzz-1.5.0-pp37-pypy37_pp73-manylinux1_x86_64.whl", hash = "sha256:2fb9d47fc16a2e8f5e900c8334d823a7307148ea764321f861b876f85a880d57"},
- {file = "rapidfuzz-1.5.0-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:2134ac91e8951d42c9a7de131d767580b8ac50820475221024e5bd63577a376f"},
- {file = "rapidfuzz-1.5.0-pp37-pypy37_pp73-win32.whl", hash = "sha256:04c4fd372e858f25e0898ba27b5bb7ed8dc528b0915b7aa02d20237e9cdd4feb"},
- {file = "rapidfuzz-1.5.0.tar.gz", hash = "sha256:141ee381c16f7e58640ef1f1dbf76beb953d248297a7165f7ba25d81ac1161c7"},
+ {file = "rapidfuzz-1.7.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1ca9888e867aed2bb8d51571270e5f8393d718bb189fe1a7c0b047b8fd72bad3"},
+ {file = "rapidfuzz-1.7.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:f336cd32a2a72eb9d7694618c9065ef3a2af330ab7e54bc0ec69d3b2eb08080e"},
+ {file = "rapidfuzz-1.7.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:76124767ac3d3213a1aad989f80b156b225defef8addc825a5b631d3164c3213"},
+ {file = "rapidfuzz-1.7.1-cp27-cp27m-win32.whl", hash = "sha256:c1090deb95e5369fff47c223c0ed3472644efc56817e288ebeaaa34822a1235c"},
+ {file = "rapidfuzz-1.7.1-cp27-cp27m-win_amd64.whl", hash = "sha256:83f94c89e8f16679e0def3c7afa6c9ba477d837fd01250d6a1e3fea12267ce24"},
+ {file = "rapidfuzz-1.7.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:cdd5962bd009b1457e280b5619d312cd6305b5b8afeff6c27869f98fee839c36"},
+ {file = "rapidfuzz-1.7.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:2940960e212b66f00fc58f9b4a13e6f80221141dcbaee9c51f97e0a1f30ff1ab"},
+ {file = "rapidfuzz-1.7.1-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:5ed4304a91043d27b92fe9af5eb87d1586548da6d03cbda5bbc98b00fee227cb"},
+ {file = "rapidfuzz-1.7.1-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:be18495bd84bf2bd3e888270a3cd4dea868ff4b9b8ec6e540f0e195cda554140"},
+ {file = "rapidfuzz-1.7.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:d5779e6f548b6f3edfbdfbeeda4158286684dcb2bae3515ce68c510ea48e1b4d"},
+ {file = "rapidfuzz-1.7.1-cp35-cp35m-win32.whl", hash = "sha256:80d780c4f6da08eb6801489df54fdbdc5ef2b882bd73f9585ef6e0cf09f1690d"},
+ {file = "rapidfuzz-1.7.1-cp35-cp35m-win_amd64.whl", hash = "sha256:3b205c63b8606c2b8595ba8403a8c3ebd39de9f7f44631a2f651f3efe106ae9a"},
+ {file = "rapidfuzz-1.7.1-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8f96588a8a7d021debb4c60d82b15a80995daa99159bbeddd8a37f68f75ee06c"},
+ {file = "rapidfuzz-1.7.1-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b8139116a937691dde17f27aafe774647808339305f4683b3a6d9bae6518aa2a"},
+ {file = "rapidfuzz-1.7.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba574801c8410cc1f2d690ef65f898f6a660bba22ec8213e0f34dd0f0590bc71"},
+ {file = "rapidfuzz-1.7.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2d5194e3cb638af0cc7c02daa61cef07e332fd3f790ec113006302131be9afa6"},
+ {file = "rapidfuzz-1.7.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dd9d8eaae888b966422cbcba954390a63b4933d8c513ea0056fd6e42d421d08"},
+ {file = "rapidfuzz-1.7.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3725c61b9cf57b6b7a765b92046e7d9e5ccce845835b523954b410a70dc32692"},
+ {file = "rapidfuzz-1.7.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e417961e5ca450d6c7448accc5a7e4e9ab0dd3c63729f76215d5e672785920fc"},
+ {file = "rapidfuzz-1.7.1-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:26d756284c8c6274b5d558e759415bfb4016fcdf168159b34702c346875d8cc0"},
+ {file = "rapidfuzz-1.7.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4887766f0dcc5df43fe4315df4b3c642829e06dc60d5bcb5e682fb76657e8ed1"},
+ {file = "rapidfuzz-1.7.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ec0a29671d59998b97998b757ab1c636dd3b7721eda41746ae897abe709681a9"},
+ {file = "rapidfuzz-1.7.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dff55750fecd8c0f07bc199e48427c86873be2d0e6a3a80df98972847287f5d3"},
+ {file = "rapidfuzz-1.7.1-cp37-cp37m-win32.whl", hash = "sha256:e113f741bb18b0ddd14d714d80ce9c6d5322724f3023b920708e82491e7aef28"},
+ {file = "rapidfuzz-1.7.1-cp37-cp37m-win_amd64.whl", hash = "sha256:ef20654be0aed240ee44c98ce02639c37422adc3e144d28c4b6d3da043d9fd20"},
+ {file = "rapidfuzz-1.7.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:9e27eb57745a4d2a390b056f6f490b712c2f54250c5d2c794dd76062065a8aef"},
+ {file = "rapidfuzz-1.7.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:de2b0ebb67ee0b78973141dba91f574a325a3425664dbdbad37fd7aca7b28cab"},
+ {file = "rapidfuzz-1.7.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:88c65d91dcd3c0595112d16555536c60ac5bcab1a43e517e155a242a39525057"},
+ {file = "rapidfuzz-1.7.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:afd525a9b593cc1099f0210e116bcb4d9fc5585728d7bd929e6a4133dacd2d59"},
+ {file = "rapidfuzz-1.7.1-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e6d77f104a8d67c01ae4248ced6f0d4ef05e63931afdf49c20decf962318877f"},
+ {file = "rapidfuzz-1.7.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7db9d6ad0ab80e9e0f66f157b8e31b1d04ce5fa767b936ca1c212b98092572b1"},
+ {file = "rapidfuzz-1.7.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0195c57f4beea0e7691594f59faf62a4be3c818c1955a8b9b712f37adc479d2d"},
+ {file = "rapidfuzz-1.7.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9ffca8c8b74d12cd36c051e9befa7c4eb2d34624ce71f22dbfc659af15bf4a1e"},
+ {file = "rapidfuzz-1.7.1-cp38-cp38-win32.whl", hash = "sha256:234cb75aa1e21cabad6a8c0718f84e2bfafdd4756b5232d5739545f97e343e59"},
+ {file = "rapidfuzz-1.7.1-cp38-cp38-win_amd64.whl", hash = "sha256:058977e93ab736071fcd8828fc6289ec026e9ca4a19f2a0967f9260e63910da8"},
+ {file = "rapidfuzz-1.7.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9d02bb0724326826b1884cc9b9d9fd97ac352c18213f45e465a39ef069a33115"},
+ {file = "rapidfuzz-1.7.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:212d6fa5b824aaa49a921c81d7cdc1d079b3545a30563ae14dc88e17918e76bf"},
+ {file = "rapidfuzz-1.7.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a0cd8117deba10e2a1d6dccb6ff44a4c737adda3048dc45860c5f53cf64db14f"},
+ {file = "rapidfuzz-1.7.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:61faa47b6b5d5a0cbe9fa6369df44d3f9435c4cccdb4d38d9de437f18b69dc4d"},
+ {file = "rapidfuzz-1.7.1-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1daa756be52a7ee60d553ba667cda3a188ee811c92a9c21df43a4cdadb1eb8ca"},
+ {file = "rapidfuzz-1.7.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c98ac10782dadf507e922963c8b8456a79151b4f10dbb08cfc86c1572db366dc"},
+ {file = "rapidfuzz-1.7.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:358d80061ca107df6c3e1f67fa7af0f94a62827cb9c44ac09a16e78b38f7c3d5"},
+ {file = "rapidfuzz-1.7.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a5f90fc31d54fcd74a97d175892555786a8214a3cff43077463915b8a45a191d"},
+ {file = "rapidfuzz-1.7.1-cp39-cp39-win32.whl", hash = "sha256:55dffdcdccea6f077a4f09164039411f01f621633be5883c58ceaf94f007a688"},
+ {file = "rapidfuzz-1.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:d712a7f680d2074b587650f81865ca838c04fcc6b77c9d2d742de0853aaa24ce"},
+ {file = "rapidfuzz-1.7.1-pp27-pypy_73-macosx_10_9_x86_64.whl", hash = "sha256:729d73a8db5a2b444a19d4aa2be009b2e628d207d7c754f6d280e3c6a59b94cb"},
+ {file = "rapidfuzz-1.7.1-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:a1cabbc645395b6175cad79164d9ec621866a004b476e44cac534020b9f6bddb"},
+ {file = "rapidfuzz-1.7.1-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ae697294f456f7f76e5bd30db5a65e8b855e7e09f9a65e144efa1e2c5009553c"},
+ {file = "rapidfuzz-1.7.1-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e8ae51c1cf1f034f15216fec2e1eef658c8b3a9cbdcc1a053cc7133ede9d616d"},
+ {file = "rapidfuzz-1.7.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:dccc072f2a0eeb98d46a79427ef793836ebc5184b1fe544b34607be10705ddc3"},
+ {file = "rapidfuzz-1.7.1.tar.gz", hash = "sha256:99495c679174b2a02641f7dc2364a208135cacca77fc4825a86efbfe1e23b0ff"},
]
redis = [
{file = "redis-3.5.3-py2.py3-none-any.whl", hash = "sha256:432b788c4530cfe16d8d943a09d40ca6c16149727e4afe8c2c9d5580c59d9f24"},
@@ -1822,8 +1896,8 @@ requests = [
{file = "requests-2.26.0.tar.gz", hash = "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"},
]
sentry-sdk = [
- {file = "sentry-sdk-1.3.1.tar.gz", hash = "sha256:ebe99144fa9618d4b0e7617e7929b75acd905d258c3c779edcd34c0adfffe26c"},
- {file = "sentry_sdk-1.3.1-py2.py3-none-any.whl", hash = "sha256:f33d34c886d0ba24c75ea8885a8b3a172358853c7cbde05979fc99c29ef7bc52"},
+ {file = "sentry-sdk-1.4.3.tar.gz", hash = "sha256:b9844751e40710e84a457c5bc29b21c383ccb2b63d76eeaad72f7f1c808c8828"},
+ {file = "sentry_sdk-1.4.3-py2.py3-none-any.whl", hash = "sha256:c091cc7115ff25fe3a0e410dbecd7a996f81a3f6137d2272daef32d6c3cfa6dc"},
]
sgmllib3k = [
{file = "sgmllib3k-1.0.0.tar.gz", hash = "sha256:7868fb1c8bfa764c1ac563d3cf369c381d1325d36124933a726f29fcdaa812e9"},
@@ -1852,59 +1926,98 @@ taskipy = [
{file = "taskipy-1.7.0-py3-none-any.whl", hash = "sha256:9e284c10898e9dee01a3e72220b94b192b1daa0f560271503a6df1da53d03844"},
{file = "taskipy-1.7.0.tar.gz", hash = "sha256:960e480b1004971e76454ecd1a0484e640744a30073a1069894a311467f85ed8"},
]
+testfixtures = [
+ {file = "testfixtures-6.18.3-py2.py3-none-any.whl", hash = "sha256:6ddb7f56a123e1a9339f130a200359092bd0a6455e31838d6c477e8729bb7763"},
+ {file = "testfixtures-6.18.3.tar.gz", hash = "sha256:2600100ae96ffd082334b378e355550fef8b4a529a6fa4c34f47130905c7426d"},
+]
toml = [
{file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
{file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
]
typing-extensions = [
- {file = "typing_extensions-3.10.0.0-py2-none-any.whl", hash = "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497"},
- {file = "typing_extensions-3.10.0.0-py3-none-any.whl", hash = "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84"},
- {file = "typing_extensions-3.10.0.0.tar.gz", hash = "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342"},
+ {file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"},
+ {file = "typing_extensions-3.10.0.2-py3-none-any.whl", hash = "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"},
+ {file = "typing_extensions-3.10.0.2.tar.gz", hash = "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e"},
]
urllib3 = [
- {file = "urllib3-1.26.6-py2.py3-none-any.whl", hash = "sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4"},
- {file = "urllib3-1.26.6.tar.gz", hash = "sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f"},
+ {file = "urllib3-1.26.7-py2.py3-none-any.whl", hash = "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"},
+ {file = "urllib3-1.26.7.tar.gz", hash = "sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece"},
]
virtualenv = [
- {file = "virtualenv-20.7.2-py2.py3-none-any.whl", hash = "sha256:e4670891b3a03eb071748c569a87cceaefbf643c5bac46d996c5a45c34aa0f06"},
- {file = "virtualenv-20.7.2.tar.gz", hash = "sha256:9ef4e8ee4710826e98ff3075c9a4739e2cb1040de6a2a8d35db0055840dc96a0"},
+ {file = "virtualenv-20.8.1-py2.py3-none-any.whl", hash = "sha256:10062e34c204b5e4ec5f62e6ef2473f8ba76513a9a617e873f1f8fb4a519d300"},
+ {file = "virtualenv-20.8.1.tar.gz", hash = "sha256:bcc17f0b3a29670dd777d6f0755a4c04f28815395bca279cdcb213b97199a6b8"},
]
yarl = [
- {file = "yarl-1.6.3-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:0355a701b3998dcd832d0dc47cc5dedf3874f966ac7f870e0f3a6788d802d434"},
- {file = "yarl-1.6.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:bafb450deef6861815ed579c7a6113a879a6ef58aed4c3a4be54400ae8871478"},
- {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:547f7665ad50fa8563150ed079f8e805e63dd85def6674c97efd78eed6c224a6"},
- {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:63f90b20ca654b3ecc7a8d62c03ffa46999595f0167d6450fa8383bab252987e"},
- {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:97b5bdc450d63c3ba30a127d018b866ea94e65655efaf889ebeabc20f7d12406"},
- {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:d8d07d102f17b68966e2de0e07bfd6e139c7c02ef06d3a0f8d2f0f055e13bb76"},
- {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:15263c3b0b47968c1d90daa89f21fcc889bb4b1aac5555580d74565de6836366"},
- {file = "yarl-1.6.3-cp36-cp36m-win32.whl", hash = "sha256:b5dfc9a40c198334f4f3f55880ecf910adebdcb2a0b9a9c23c9345faa9185721"},
- {file = "yarl-1.6.3-cp36-cp36m-win_amd64.whl", hash = "sha256:b2e9a456c121e26d13c29251f8267541bd75e6a1ccf9e859179701c36a078643"},
- {file = "yarl-1.6.3-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:ce3beb46a72d9f2190f9e1027886bfc513702d748047b548b05dab7dfb584d2e"},
- {file = "yarl-1.6.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:2ce4c621d21326a4a5500c25031e102af589edb50c09b321049e388b3934eec3"},
- {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:d26608cf178efb8faa5ff0f2d2e77c208f471c5a3709e577a7b3fd0445703ac8"},
- {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:4c5bcfc3ed226bf6419f7a33982fb4b8ec2e45785a0561eb99274ebbf09fdd6a"},
- {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:4736eaee5626db8d9cda9eb5282028cc834e2aeb194e0d8b50217d707e98bb5c"},
- {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:68dc568889b1c13f1e4745c96b931cc94fdd0defe92a72c2b8ce01091b22e35f"},
- {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:7356644cbed76119d0b6bd32ffba704d30d747e0c217109d7979a7bc36c4d970"},
- {file = "yarl-1.6.3-cp37-cp37m-win32.whl", hash = "sha256:00d7ad91b6583602eb9c1d085a2cf281ada267e9a197e8b7cae487dadbfa293e"},
- {file = "yarl-1.6.3-cp37-cp37m-win_amd64.whl", hash = "sha256:69ee97c71fee1f63d04c945f56d5d726483c4762845400a6795a3b75d56b6c50"},
- {file = "yarl-1.6.3-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:e46fba844f4895b36f4c398c5af062a9808d1f26b2999c58909517384d5deda2"},
- {file = "yarl-1.6.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:31ede6e8c4329fb81c86706ba8f6bf661a924b53ba191b27aa5fcee5714d18ec"},
- {file = "yarl-1.6.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:fcbb48a93e8699eae920f8d92f7160c03567b421bc17362a9ffbbd706a816f71"},
- {file = "yarl-1.6.3-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:72a660bdd24497e3e84f5519e57a9ee9220b6f3ac4d45056961bf22838ce20cc"},
- {file = "yarl-1.6.3-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:324ba3d3c6fee56e2e0b0d09bf5c73824b9f08234339d2b788af65e60040c959"},
- {file = "yarl-1.6.3-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:e6b5460dc5ad42ad2b36cca524491dfcaffbfd9c8df50508bddc354e787b8dc2"},
- {file = "yarl-1.6.3-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:6d6283d8e0631b617edf0fd726353cb76630b83a089a40933043894e7f6721e2"},
- {file = "yarl-1.6.3-cp38-cp38-win32.whl", hash = "sha256:9ede61b0854e267fd565e7527e2f2eb3ef8858b301319be0604177690e1a3896"},
- {file = "yarl-1.6.3-cp38-cp38-win_amd64.whl", hash = "sha256:f0b059678fd549c66b89bed03efcabb009075bd131c248ecdf087bdb6faba24a"},
- {file = "yarl-1.6.3-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:329412812ecfc94a57cd37c9d547579510a9e83c516bc069470db5f75684629e"},
- {file = "yarl-1.6.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:c49ff66d479d38ab863c50f7bb27dee97c6627c5fe60697de15529da9c3de724"},
- {file = "yarl-1.6.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f040bcc6725c821a4c0665f3aa96a4d0805a7aaf2caf266d256b8ed71b9f041c"},
- {file = "yarl-1.6.3-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:d5c32c82990e4ac4d8150fd7652b972216b204de4e83a122546dce571c1bdf25"},
- {file = "yarl-1.6.3-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:d597767fcd2c3dc49d6eea360c458b65643d1e4dbed91361cf5e36e53c1f8c96"},
- {file = "yarl-1.6.3-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:8aa3decd5e0e852dc68335abf5478a518b41bf2ab2f330fe44916399efedfae0"},
- {file = "yarl-1.6.3-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:73494d5b71099ae8cb8754f1df131c11d433b387efab7b51849e7e1e851f07a4"},
- {file = "yarl-1.6.3-cp39-cp39-win32.whl", hash = "sha256:5b883e458058f8d6099e4420f0cc2567989032b5f34b271c0827de9f1079a424"},
- {file = "yarl-1.6.3-cp39-cp39-win_amd64.whl", hash = "sha256:4953fb0b4fdb7e08b2f3b3be80a00d28c5c8a2056bb066169de00e6501b986b6"},
- {file = "yarl-1.6.3.tar.gz", hash = "sha256:8a9066529240171b68893d60dca86a763eae2139dd42f42106b03cf4b426bf10"},
+ {file = "yarl-1.7.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e35d8230e4b08d86ea65c32450533b906a8267a87b873f2954adeaecede85169"},
+ {file = "yarl-1.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eb4b3f277880c314e47720b4b6bb2c85114ab3c04c5442c9bc7006b3787904d8"},
+ {file = "yarl-1.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c7015dcedb91d90a138eebdc7e432aec8966e0147ab2a55f2df27b1904fa7291"},
+ {file = "yarl-1.7.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb3e478175e15e00d659fb0354a6a8db71a7811a2a5052aed98048bc972e5d2b"},
+ {file = "yarl-1.7.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8b8c409aa3a7966647e7c1c524846b362a6bcbbe120bf8a176431f940d2b9a2e"},
+ {file = "yarl-1.7.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b22ea41c7e98170474a01e3eded1377d46b2dfaef45888a0005c683eaaa49285"},
+ {file = "yarl-1.7.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a7dfc46add4cfe5578013dbc4127893edc69fe19132d2836ff2f6e49edc5ecd6"},
+ {file = "yarl-1.7.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:82ff6f85f67500a4f74885d81659cd270eb24dfe692fe44e622b8a2fd57e7279"},
+ {file = "yarl-1.7.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f3cd2158b2ed0fb25c6811adfdcc47224efe075f2d68a750071dacc03a7a66e4"},
+ {file = "yarl-1.7.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:59c0f13f9592820c51280d1cf811294d753e4a18baf90f0139d1dc93d4b6fc5f"},
+ {file = "yarl-1.7.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:7f7655ad83d1a8afa48435a449bf2f3009293da1604f5dd95b5ddcf5f673bd69"},
+ {file = "yarl-1.7.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:aa9f0d9b62d15182341b3e9816582f46182cab91c1a57b2d308b9a3c4e2c4f78"},
+ {file = "yarl-1.7.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fdd1b90c225a653b1bd1c0cae8edf1957892b9a09c8bf7ee6321eeb8208eac0f"},
+ {file = "yarl-1.7.0-cp310-cp310-win32.whl", hash = "sha256:7c8d0bb76eabc5299db203e952ec55f8f4c53f08e0df4285aac8c92bd9e12675"},
+ {file = "yarl-1.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:622a36fa779efb4ff9eff5fe52730ff17521431379851a31e040958fc251670c"},
+ {file = "yarl-1.7.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:3d461b7a8e139b9e4b41f62eb417ffa0b98d1c46d4caf14c845e6a3b349c0bb1"},
+ {file = "yarl-1.7.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81cfacdd1e40bc931b5519499342efa388d24d262c30a3d31187bfa04f4a7001"},
+ {file = "yarl-1.7.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:821b978f2152be7695d4331ef0621d207aedf9bbd591ba23a63412a3efc29a01"},
+ {file = "yarl-1.7.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b64bd24c8c9a487f4a12260dc26732bf41028816dbf0c458f17864fbebdb3131"},
+ {file = "yarl-1.7.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:98c9ddb92b60a83c21be42c776d3d9d5ec632a762a094c41bda37b7dfbd2cd83"},
+ {file = "yarl-1.7.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a532d75ca74431c053a88a802e161fb3d651b8bf5821a3440bc3616e38754583"},
+ {file = "yarl-1.7.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:053e09817eafb892e94e172d05406c1b3a22a93bc68f6eff5198363a3d764459"},
+ {file = "yarl-1.7.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:98c51f02d542945d306c8e934aa2c1e66ba5e9c1c86b5bf37f3a51c8a747067e"},
+ {file = "yarl-1.7.0-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:15ec41a5a5fdb7bace6d7b16701f9440007a82734f69127c0fbf6d87e10f4a1e"},
+ {file = "yarl-1.7.0-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:a7f08819dba1e1255d6991ed37448a1bf4b1352c004bcd899b9da0c47958513d"},
+ {file = "yarl-1.7.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:8e3ffab21db0542ffd1887f3b9575ddd58961f2cf61429cb6458afc00c4581e0"},
+ {file = "yarl-1.7.0-cp36-cp36m-win32.whl", hash = "sha256:50127634f519b2956005891507e3aa4ac345f66a7ea7bbc2d7dcba7401f41898"},
+ {file = "yarl-1.7.0-cp36-cp36m-win_amd64.whl", hash = "sha256:36ec44f15193f6d5288d42ebb8e751b967ebdfb72d6830983838d45ab18edb4f"},
+ {file = "yarl-1.7.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:ec1b5a25a25c880c976d0bb3d107def085bb08dbb3db7f4442e0a2b980359d24"},
+ {file = "yarl-1.7.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b36f5a63c891f813c6f04ef19675b382efc190fd5ce7e10ab19386d2548bca06"},
+ {file = "yarl-1.7.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:38173b8c3a29945e7ecade9a3f6ff39581eee8201338ee6a2c8882db5df3e806"},
+ {file = "yarl-1.7.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ba402f32184f0b405fb281b93bd0d8ab7e3257735b57b62a6ed2e94cdf4fe50"},
+ {file = "yarl-1.7.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:be52bc5208d767cdd8308a9e93059b3b36d1e048fecbea0e0346d0d24a76adc0"},
+ {file = "yarl-1.7.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:08c2044a956f4ef30405f2f433ce77f1f57c2c773bf81ae43201917831044d5a"},
+ {file = "yarl-1.7.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:484d61c047c45670ef5967653a1d0783e232c54bf9dd786a7737036828fa8d54"},
+ {file = "yarl-1.7.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b7de92a4af85cfcaf4081f8aa6165b1d63ee5de150af3ee85f954145f93105a7"},
+ {file = "yarl-1.7.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:376e41775aab79c5575534924a386c8e0f1a5d91db69fc6133fd27a489bcaf10"},
+ {file = "yarl-1.7.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:8a8b10d0e7bac154f959b709fcea593cda527b234119311eb950096653816a86"},
+ {file = "yarl-1.7.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:f46cd4c43e6175030e2a56def8f1d83b64e6706eeb2bb9ab0ef4756f65eab23f"},
+ {file = "yarl-1.7.0-cp37-cp37m-win32.whl", hash = "sha256:b28cfb46140efe1a6092b8c5c4994a1fe70dc83c38fbcea4992401e0c6fb9cce"},
+ {file = "yarl-1.7.0-cp37-cp37m-win_amd64.whl", hash = "sha256:9624154ec9c02a776802da1086eed7f5034bd1971977f5146233869c2ac80297"},
+ {file = "yarl-1.7.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:69945d13e1bbf81784a9bc48824feb9cd66491e6a503d4e83f6cd7c7cc861361"},
+ {file = "yarl-1.7.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:46a742ed9e363bd01be64160ce7520e92e11989bd4cb224403cfd31c101cc83d"},
+ {file = "yarl-1.7.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cb4ff1ac7cb4500f43581b3f4cbd627d702143aa6be1fdc1fa3ebffaf4dc1be5"},
+ {file = "yarl-1.7.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ad51e17cd65ea3debb0e10f0120cf8dd987c741fe423ed2285087368090b33d"},
+ {file = "yarl-1.7.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7e37786ea89a5d3ffbbf318ea9790926f8dfda83858544f128553c347ad143c6"},
+ {file = "yarl-1.7.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c63c1e208f800daad71715786bfeb1cecdc595d87e2e9b1cd234fd6e597fd71d"},
+ {file = "yarl-1.7.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:91cbe24300c11835ef186436363352b3257db7af165e0a767f4f17aa25761388"},
+ {file = "yarl-1.7.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e510dbec7c59d32eaa61ffa48173d5e3d7170a67f4a03e8f5e2e9e3971aca622"},
+ {file = "yarl-1.7.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3def6e681cc02397e5d8141ee97b41d02932b2bcf0fb34532ad62855eab7c60e"},
+ {file = "yarl-1.7.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:263c81b94e6431942b27f6f671fa62f430a0a5c14bb255f2ab69eeb9b2b66ff7"},
+ {file = "yarl-1.7.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:e78c91faefe88d601ddd16e3882918dbde20577a2438e2320f8239c8b7507b8f"},
+ {file = "yarl-1.7.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:22b2430c49713bfb2f0a0dd4a8d7aab218b28476ba86fd1c78ad8899462cbcf2"},
+ {file = "yarl-1.7.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2e7ad9db939082f5d0b9269cfd92c025cb8f2fbbb1f1b9dc5a393c639db5bd92"},
+ {file = "yarl-1.7.0-cp38-cp38-win32.whl", hash = "sha256:3a31e4a8dcb1beaf167b7e7af61b88cb961b220db8d3ba1c839723630e57eef7"},
+ {file = "yarl-1.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:d579957439933d752358c6a300c93110f84aae67b63dd0c19dde6ecbf4056f6b"},
+ {file = "yarl-1.7.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:87721b549505a546eb003252185103b5ec8147de6d3ad3714d148a5a67b6fe53"},
+ {file = "yarl-1.7.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a1fa866fa24d9f4108f9e58ea8a2135655419885cdb443e36b39a346e1181532"},
+ {file = "yarl-1.7.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1d3b8449dfedfe94eaff2b77954258b09b24949f6818dfa444b05dbb05ae1b7e"},
+ {file = "yarl-1.7.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db2372e350794ce8b9f810feb094c606b7e0e4aa6807141ac4fadfe5ddd75bb0"},
+ {file = "yarl-1.7.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a06d9d0b9a97fa99b84fee71d9dd11e69e21ac8a27229089f07b5e5e50e8d63c"},
+ {file = "yarl-1.7.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a3455c2456d6307bcfa80bc1157b8603f7d93573291f5bdc7144489ca0df4628"},
+ {file = "yarl-1.7.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:d30d67e3486aea61bb2cbf7cf81385364c2e4f7ce7469a76ed72af76a5cdfe6b"},
+ {file = "yarl-1.7.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c18a4b286e8d780c3a40c31d7b79836aa93b720f71d5743f20c08b7e049ca073"},
+ {file = "yarl-1.7.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d54c925396e7891666cabc0199366ca55b27d003393465acef63fd29b8b7aa92"},
+ {file = "yarl-1.7.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:64773840952de17851a1c7346ad7f71688c77e74248d1f0bc230e96680f84028"},
+ {file = "yarl-1.7.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:acbf1756d9dc7cd0ae943d883be72e84e04396f6c2ff93a6ddeca929d562039f"},
+ {file = "yarl-1.7.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:2e48f27936aa838939c798f466c851ba4ae79e347e8dfce43b009c64b930df12"},
+ {file = "yarl-1.7.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1beef4734ca1ad40a9d8c6b20a76ab46e3a2ed09f38561f01e4aa2ea82cafcef"},
+ {file = "yarl-1.7.0-cp39-cp39-win32.whl", hash = "sha256:8ee78c9a5f3c642219d4607680a4693b59239c27a3aa608b64ef79ddc9698039"},
+ {file = "yarl-1.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:d750503682605088a14d29a4701548c15c510da4f13c8b17409c4097d5b04c52"},
+ {file = "yarl-1.7.0.tar.gz", hash = "sha256:8e7ebaf62e19c2feb097ffb7c94deb0f0c9fab52590784c8cd679d30ab009162"},
]
diff --git a/pyproject.toml b/pyproject.toml
index 4431a41c5..e227ffaa6 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -7,6 +7,7 @@ license = "MIT"
[tool.poetry.dependencies]
python = "3.9.*"
+"discord.py" = {url = "https://github.com/Rapptz/discord.py/archive/45d498c1b76deaf3b394d17ccf56112fa691d160.zip"}
aio-pika = "~=6.1"
aiodns = "~=2.0"
aiohttp = "~=3.7"
@@ -17,7 +18,6 @@ beautifulsoup4 = "~=4.9"
colorama = { version = "~=0.4.3", markers = "sys_platform == 'win32'" }
coloredlogs = "~=14.0"
deepdiff = "~=4.0"
-"discord.py" = "~=1.7.3"
emoji = "~=0.6"
feedparser = "~=6.0.2"
rapidfuzz = "~=1.4"
@@ -38,10 +38,10 @@ flake8 = "~=3.8"
flake8-annotations = "~=2.0"
flake8-bugbear = "~=20.1"
flake8-docstrings = "~=1.4"
-flake8-import-order = "~=0.18"
flake8-string-format = "~=0.2"
flake8-tidy-imports = "~=4.0"
flake8-todo = "~=0.7"
+flake8-isort = "~=4.0"
pep8-naming = "~=0.9"
pre-commit = "~=2.1"
taskipy = "~=1.7.0"
@@ -66,8 +66,17 @@ test = "pytest -n auto --cov-report= --cov --ff"
retest = "pytest -n auto --cov-report= --cov --lf"
html = "coverage html"
report = "coverage report"
+isort = "isort ."
[tool.coverage.run]
branch = true
source_pkgs = ["bot"]
source = ["tests"]
+
+[tool.isort]
+multi_line_output = 6
+order_by_type = false
+case_sensitive = true
+combine_as_imports = true
+line_length = 120
+atomic = true
diff --git a/tests/__init__.py b/tests/__init__.py
index 2228110ad..c2b9d12dc 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -1,5 +1,6 @@
import logging
+from bot.log import get_logger
-log = logging.getLogger()
+log = get_logger()
log.setLevel(logging.CRITICAL)
diff --git a/tests/base.py b/tests/base.py
index d99b9ac31..5e304ea9d 100644
--- a/tests/base.py
+++ b/tests/base.py
@@ -6,6 +6,7 @@ from typing import Dict
import discord
from discord.ext import commands
+from bot.log import get_logger
from tests import helpers
@@ -42,7 +43,7 @@ class LoggingTestsMixin:
manager when we're testing under the assumption that no log records will be emitted.
"""
if not isinstance(logger, logging.Logger):
- logger = logging.getLogger(logger)
+ logger = get_logger(logger)
if level:
level = logging._nameToLevel.get(level, level)
@@ -102,4 +103,4 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase):
with self.assertRaises(commands.MissingPermissions) as cm:
await cmd.can_run(ctx)
- self.assertCountEqual(permissions.keys(), cm.exception.missing_perms)
+ self.assertCountEqual(permissions.keys(), cm.exception.missing_permissions)
diff --git a/tests/bot/exts/backend/sync/test_base.py b/tests/bot/exts/backend/sync/test_base.py
index 3ad9db9c3..9dc46005b 100644
--- a/tests/bot/exts/backend/sync/test_base.py
+++ b/tests/bot/exts/backend/sync/test_base.py
@@ -1,7 +1,6 @@
import unittest
from unittest import mock
-
from bot.api import ResponseCodeError
from bot.exts.backend.sync._syncers import Syncer
from tests import helpers
diff --git a/tests/bot/exts/backend/test_error_handler.py b/tests/bot/exts/backend/test_error_handler.py
index 2b0549b98..462f718e6 100644
--- a/tests/bot/exts/backend/test_error_handler.py
+++ b/tests/bot/exts/backend/test_error_handler.py
@@ -107,7 +107,7 @@ class ErrorHandlerTests(unittest.IsolatedAsyncioTestCase):
"""Should send error with `ctx.send` when error is `CommandOnCooldown`."""
self.ctx.reset_mock()
cog = ErrorHandler(self.bot)
- error = errors.CommandOnCooldown(10, 9)
+ error = errors.CommandOnCooldown(10, 9, type=None)
self.assertIsNone(await cog.on_command_error(self.ctx, error))
self.ctx.send.assert_awaited_once_with(error)
diff --git a/tests/bot/exts/events/test_code_jams.py b/tests/bot/exts/events/test_code_jams.py
index b9ee1e363..0856546af 100644
--- a/tests/bot/exts/events/test_code_jams.py
+++ b/tests/bot/exts/events/test_code_jams.py
@@ -8,8 +8,8 @@ from bot.constants import Roles
from bot.exts.events import code_jams
from bot.exts.events.code_jams import _channels, _cog
from tests.helpers import (
- MockAttachment, MockBot, MockCategoryChannel, MockContext,
- MockGuild, MockMember, MockRole, MockTextChannel, autospec
+ MockAttachment, MockBot, MockCategoryChannel, MockContext, MockGuild, MockMember, MockRole, MockTextChannel,
+ autospec
)
TEST_CSV = b"""\
diff --git a/tests/bot/exts/filters/test_token_remover.py b/tests/bot/exts/filters/test_token_remover.py
index 05e790723..4db27269a 100644
--- a/tests/bot/exts/filters/test_token_remover.py
+++ b/tests/bot/exts/filters/test_token_remover.py
@@ -26,7 +26,7 @@ class TokenRemoverTests(unittest.IsolatedAsyncioTestCase):
self.msg.guild.get_member.return_value.bot = False
self.msg.guild.get_member.return_value.__str__.return_value = "Woody"
self.msg.author.__str__ = MagicMock(return_value=self.msg.author.name)
- self.msg.author.avatar_url_as.return_value = "picture-lemon.png"
+ self.msg.author.display_avatar.url = "picture-lemon.png"
def test_extract_user_id_valid(self):
"""Should consider user IDs valid if they decode into an integer ID."""
@@ -376,7 +376,7 @@ class TokenRemoverTests(unittest.IsolatedAsyncioTestCase):
colour=Colour(constants.Colours.soft_red),
title="Token removed!",
text=log_msg + "\n" + userid_log_message,
- thumbnail=self.msg.author.avatar_url_as.return_value,
+ thumbnail=self.msg.author.display_avatar.url,
channel_id=constants.Channels.mod_alerts,
ping_everyone=True,
)
diff --git a/tests/bot/exts/info/test_information.py b/tests/bot/exts/info/test_information.py
index d8250befb..4b50c3fd9 100644
--- a/tests/bot/exts/info/test_information.py
+++ b/tests/bot/exts/info/test_information.py
@@ -84,7 +84,7 @@ class InformationCogTests(unittest.IsolatedAsyncioTestCase):
self.assertEqual(dummy_embed.fields[0].value, str(dummy_role.id))
self.assertEqual(dummy_embed.fields[1].value, f"#{dummy_role.colour.value:0>6x}")
- self.assertEqual(dummy_embed.fields[2].value, "0.63 0.48 218")
+ self.assertEqual(dummy_embed.fields[2].value, "0.65 0.64 242")
self.assertEqual(dummy_embed.fields[3].value, "1")
self.assertEqual(dummy_embed.fields[4].value, "10")
self.assertEqual(dummy_embed.fields[5].value, "0")
@@ -435,10 +435,9 @@ class UserEmbedTests(unittest.IsolatedAsyncioTestCase):
ctx = helpers.MockContext()
user = helpers.MockMember(id=217, colour=0)
- user.avatar_url_as.return_value = "avatar url"
+ user.display_avatar.url = "avatar url"
embed = await self.cog.create_user_embed(ctx, user)
- user.avatar_url_as.assert_called_once_with(static_format="png")
self.assertEqual(embed.thumbnail.url, "avatar url")
diff --git a/tests/bot/exts/moderation/test_incidents.py b/tests/bot/exts/moderation/test_incidents.py
index 8304af1c0..cfe0c4b03 100644
--- a/tests/bot/exts/moderation/test_incidents.py
+++ b/tests/bot/exts/moderation/test_incidents.py
@@ -4,7 +4,7 @@ import logging
import typing as t
import unittest
from unittest import mock
-from unittest.mock import AsyncMock, MagicMock, call, patch
+from unittest.mock import AsyncMock, MagicMock, Mock, call, patch
import aiohttp
import discord
@@ -14,15 +14,8 @@ from bot.constants import Colours
from bot.exts.moderation import incidents
from bot.utils.messages import format_user
from tests.helpers import (
- MockAsyncWebhook,
- MockAttachment,
- MockBot,
- MockMember,
- MockMessage,
- MockReaction,
- MockRole,
- MockTextChannel,
- MockUser,
+ MockAsyncWebhook, MockAttachment, MockBot, MockMember, MockMessage, MockReaction, MockRole, MockTextChannel,
+ MockUser
)
@@ -398,7 +391,7 @@ class TestArchive(TestIncidents):
# Define our own `incident` to be archived
incident = MockMessage(
content="this is an incident",
- author=MockUser(name="author_name", avatar_url="author_avatar"),
+ author=MockUser(name="author_name", display_avatar=Mock(url="author_avatar")),
id=123,
)
built_embed = MagicMock(discord.Embed, id=123) # We patch `make_embed` to return this
diff --git a/tests/bot/exts/moderation/test_silence.py b/tests/bot/exts/moderation/test_silence.py
index 59a5893ef..92ce3418a 100644
--- a/tests/bot/exts/moderation/test_silence.py
+++ b/tests/bot/exts/moderation/test_silence.py
@@ -12,14 +12,7 @@ from discord import PermissionOverwrite
from bot.constants import Channels, Guild, MODERATION_ROLES, Roles
from bot.exts.moderation import silence
from tests.helpers import (
- MockBot,
- MockContext,
- MockGuild,
- MockMember,
- MockRole,
- MockTextChannel,
- MockVoiceChannel,
- autospec
+ MockBot, MockContext, MockGuild, MockMember, MockRole, MockTextChannel, MockVoiceChannel, autospec
)
redis_session = None
@@ -438,7 +431,13 @@ class SilenceTests(unittest.IsolatedAsyncioTestCase):
asyncio.run(self.cog._async_init()) # Populate instance attributes.
self.text_channel = MockTextChannel()
- self.text_overwrite = PermissionOverwrite(send_messages=True, add_reactions=False)
+ self.text_overwrite = PermissionOverwrite(
+ send_messages=True,
+ add_reactions=False,
+ create_private_threads=True,
+ create_public_threads=False,
+ send_messages_in_threads=True
+ )
self.text_channel.overwrites_for.return_value = self.text_overwrite
self.voice_channel = MockVoiceChannel()
@@ -509,9 +508,39 @@ class SilenceTests(unittest.IsolatedAsyncioTestCase):
async def test_skipped_already_silenced(self):
"""Permissions were not set and `False` was returned for an already silenced channel."""
subtests = (
- (False, MockTextChannel(), PermissionOverwrite(send_messages=False, add_reactions=False)),
- (True, MockTextChannel(), PermissionOverwrite(send_messages=True, add_reactions=True)),
- (True, MockTextChannel(), PermissionOverwrite(send_messages=False, add_reactions=False)),
+ (
+ False,
+ MockTextChannel(),
+ PermissionOverwrite(
+ send_messages=False,
+ add_reactions=False,
+ create_private_threads=False,
+ create_public_threads=False,
+ send_messages_in_threads=False
+ )
+ ),
+ (
+ True,
+ MockTextChannel(),
+ PermissionOverwrite(
+ send_messages=True,
+ add_reactions=True,
+ create_private_threads=True,
+ create_public_threads=True,
+ send_messages_in_threads=True
+ )
+ ),
+ (
+ True,
+ MockTextChannel(),
+ PermissionOverwrite(
+ send_messages=False,
+ add_reactions=False,
+ create_private_threads=False,
+ create_public_threads=False,
+ send_messages_in_threads=False
+ )
+ ),
(False, MockVoiceChannel(), PermissionOverwrite(connect=False, speak=False)),
(True, MockVoiceChannel(), PermissionOverwrite(connect=True, speak=True)),
(True, MockVoiceChannel(), PermissionOverwrite(connect=False, speak=False)),
@@ -559,11 +588,16 @@ class SilenceTests(unittest.IsolatedAsyncioTestCase):
await self.cog._set_silence_overwrites(self.text_channel)
new_overwrite_dict = dict(self.text_overwrite)
- # Remove 'send_messages' & 'add_reactions' keys because they were changed by the method.
- del prev_overwrite_dict['send_messages']
- del prev_overwrite_dict['add_reactions']
- del new_overwrite_dict['send_messages']
- del new_overwrite_dict['add_reactions']
+ # Remove related permission keys because they were changed by the method.
+ for perm_name in (
+ "send_messages",
+ "add_reactions",
+ "create_private_threads",
+ "create_public_threads",
+ "send_messages_in_threads"
+ ):
+ del prev_overwrite_dict[perm_name]
+ del new_overwrite_dict[perm_name]
self.assertDictEqual(prev_overwrite_dict, new_overwrite_dict)
@@ -601,7 +635,10 @@ class SilenceTests(unittest.IsolatedAsyncioTestCase):
async def test_cached_previous_overwrites(self):
"""Channel's previous overwrites were cached."""
- overwrite_json = '{"send_messages": true, "add_reactions": false}'
+ overwrite_json = (
+ '{"send_messages": true, "add_reactions": false, "create_private_threads": true, '
+ '"create_public_threads": false, "send_messages_in_threads": true}'
+ )
await self.cog._set_silence_overwrites(self.text_channel)
self.cog.previous_overwrites.set.assert_awaited_once_with(self.text_channel.id, overwrite_json)
diff --git a/tests/bot/test_converters.py b/tests/bot/test_converters.py
index 6e3a6b898..ef6c8e19e 100644
--- a/tests/bot/test_converters.py
+++ b/tests/bot/test_converters.py
@@ -6,13 +6,7 @@ from unittest.mock import MagicMock, patch
from dateutil.relativedelta import relativedelta
from discord.ext.commands import BadArgument
-from bot.converters import (
- Duration,
- HushDurationConverter,
- ISODateTime,
- PackageName,
- TagNameConverter,
-)
+from bot.converters import Duration, HushDurationConverter, ISODateTime, PackageName, TagNameConverter
class ConverterTests(unittest.IsolatedAsyncioTestCase):
diff --git a/tests/bot/utils/test_checks.py b/tests/bot/utils/test_checks.py
index 883465e0b..4ae11d5d3 100644
--- a/tests/bot/utils/test_checks.py
+++ b/tests/bot/utils/test_checks.py
@@ -32,6 +32,7 @@ class ChecksTests(unittest.IsolatedAsyncioTestCase):
async def test_has_no_roles_check_without_guild(self):
"""`has_no_roles_check` should return `False` when `Context.guild` is None."""
self.ctx.channel = MagicMock(DMChannel)
+ self.ctx.guild = None
self.assertFalse(await checks.has_no_roles_check(self.ctx))
async def test_has_no_roles_check_returns_false_with_unwanted_role(self):
diff --git a/tests/helpers.py b/tests/helpers.py
index 83b9b2363..9d4988d23 100644
--- a/tests/helpers.py
+++ b/tests/helpers.py
@@ -39,7 +39,7 @@ class HashableMixin(discord.mixins.EqualityComparable):
class ColourMixin:
- """A mixin for Mocks that provides the aliasing of color->colour like discord.py does."""
+ """A mixin for Mocks that provides the aliasing of (accent_)color->(accent_)colour like discord.py does."""
@property
def color(self) -> discord.Colour:
@@ -49,6 +49,14 @@ class ColourMixin:
def color(self, color: discord.Colour) -> None:
self.colour = color
+ @property
+ def accent_color(self) -> discord.Colour:
+ return self.accent_colour
+
+ @accent_color.setter
+ def accent_color(self, color: discord.Colour) -> None:
+ self.accent_colour = color
+
class CustomMockMixin:
"""
@@ -242,7 +250,13 @@ class MockMember(CustomMockMixin, unittest.mock.Mock, ColourMixin, HashableMixin
# Create a User instance to get a realistic Mock of `discord.User`
-user_instance = discord.User(data=unittest.mock.MagicMock(), state=unittest.mock.MagicMock())
+_user_data_mock = collections.defaultdict(unittest.mock.MagicMock, {
+ "accent_color": 0
+})
+user_instance = discord.User(
+ data=unittest.mock.MagicMock(get=unittest.mock.Mock(side_effect=_user_data_mock.get)),
+ state=unittest.mock.MagicMock()
+)
class MockUser(CustomMockMixin, unittest.mock.Mock, ColourMixin, HashableMixin):
@@ -428,7 +442,12 @@ message_instance = discord.Message(state=state, channel=channel, data=message_da
# Create a Context instance to get a realistic MagicMock of `discord.ext.commands.Context`
-context_instance = Context(message=unittest.mock.MagicMock(), prefix=unittest.mock.MagicMock())
+context_instance = Context(
+ message=unittest.mock.MagicMock(),
+ prefix="$",
+ bot=MockBot(),
+ view=None
+)
context_instance.invoked_from_error_handler = None
@@ -537,7 +556,7 @@ class MockReaction(CustomMockMixin, unittest.mock.MagicMock):
self.__str__.return_value = str(self.emoji)
-webhook_instance = discord.Webhook(data=unittest.mock.MagicMock(), adapter=unittest.mock.MagicMock())
+webhook_instance = discord.Webhook(data=unittest.mock.MagicMock(), session=unittest.mock.MagicMock())
class MockAsyncWebhook(CustomMockMixin, unittest.mock.MagicMock):
diff --git a/tests/test_base.py b/tests/test_base.py
index a7db4bf3e..365805a71 100644
--- a/tests/test_base.py
+++ b/tests/test_base.py
@@ -1,8 +1,7 @@
import logging
-import unittest
import unittest.mock
-
+from bot.log import get_logger
from tests.base import LoggingTestsMixin, _CaptureLogHandler
@@ -15,7 +14,7 @@ class LoggingTestCaseTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
- cls.log = logging.getLogger(__name__)
+ cls.log = get_logger(__name__)
def test_assert_not_logs_does_not_raise_with_no_logs(self):
"""Test if LoggingTestCase.assertNotLogs does not raise when no logs were emitted."""
@@ -56,15 +55,15 @@ class LoggingTestCaseTests(unittest.TestCase):
def test_logging_test_case_works_with_logger_instance(self):
"""Test if the LoggingTestCase captures logging for provided logger."""
- log = logging.getLogger("new_logger")
+ log = get_logger("new_logger")
with self.assertRaises(AssertionError):
with LoggingTestCase.assertNotLogs(self, logger=log):
log.info("Hello, this should raise an AssertionError")
def test_logging_test_case_respects_alternative_logger(self):
"""Test if LoggingTestCase only checks the provided logger."""
- log_one = logging.getLogger("log one")
- log_two = logging.getLogger("log two")
+ log_one = get_logger("log one")
+ log_two = get_logger("log two")
with LoggingTestCase.assertNotLogs(self, logger=log_one):
log_two.info("Hello, this should not raise an AssertionError")
diff --git a/tox.ini b/tox.ini
index b8293a3b6..9472c32f9 100644
--- a/tox.ini
+++ b/tox.ini
@@ -5,7 +5,7 @@ import-order-style=pycharm
application_import_names=bot,tests
exclude=.cache,.venv,.git,constants.py
ignore=
- B311,W503,E226,S311,T000
+ B311,W503,E226,S311,T000,E731
# Missing Docstrings
D100,D104,D105,D107,
# Docstring Whitespace