diff --git a/.pylintrc b/.pylintrc index 0c31c99..b694ad2 100644 --- a/.pylintrc +++ b/.pylintrc @@ -13,7 +13,9 @@ disable= C0115, # missing-class-docstring C0116, # missing-function-docstring W0703, # broad-except + W0707, # raise-missing-from R0801, # duplicate-code + R0901, # too-many-ancestors R0902, # too-many-instance-attributes R0903, # too-few-public-methods E1136, # unsubscriptable-object (false positive with python 3.9) diff --git a/Makefile b/Makefile index f184cef..696c0b2 100644 --- a/Makefile +++ b/Makefile @@ -52,7 +52,18 @@ docker-start: # Blackify code .PHONY: black black: - $(PYTHON_PATH) -m black `git ls-files "*.py"` --line-length=79 && $(PYTHON_PATH) -m pylint tuxbot + $(PYTHON_PATH) -m black `git ls-files "*.py"` --line-length=79 + +.PHONY: lint +lint: + $(PYTHON_PATH) -m pylint tuxbot + +.PHONY: type +type: + $(PYTHON_PATH) -m mypy tuxbot + +.PHONY: style +style: black lint type # Translations .PHONY: xgettext diff --git a/tuxbot/__run__.py b/tuxbot/__run__.py index f015cfc..b20d4c1 100644 --- a/tuxbot/__run__.py +++ b/tuxbot/__run__.py @@ -110,9 +110,7 @@ def parse_cli_flags(args: list) -> Namespace: "--token", "-T", type=str, help="Run Tuxbot with passed token" ) - args = parser.parse_args(args) - - return args + return parser.parse_args(args) async def shutdown_handler(tux: Tux, signal_type, exit_code=None) -> None: diff --git a/tuxbot/cogs/Admin/config.py b/tuxbot/cogs/Admin/config.py index 0d23ddb..8702f08 100644 --- a/tuxbot/cogs/Admin/config.py +++ b/tuxbot/cogs/Admin/config.py @@ -1,3 +1,5 @@ +from typing import Dict + from structured_config import Structure HAS_MODELS = True @@ -7,4 +9,4 @@ class AdminConfig(Structure): pass -extra = {} +extra: Dict[str, Dict] = {} diff --git a/tuxbot/cogs/Crypto/config.py b/tuxbot/cogs/Crypto/config.py index 1fdee5c..1663bb8 100644 --- a/tuxbot/cogs/Crypto/config.py +++ b/tuxbot/cogs/Crypto/config.py @@ -1,3 +1,5 @@ +from typing import Dict + from structured_config import Structure HAS_MODELS = False @@ -7,4 +9,4 @@ class CryptoConfig(Structure): pass -extra = {} +extra: Dict[str, Dict] = {} diff --git a/tuxbot/cogs/Crypto/functions/extractor.py b/tuxbot/cogs/Crypto/functions/extractor.py index cef67f3..3503f14 100644 --- a/tuxbot/cogs/Crypto/functions/extractor.py +++ b/tuxbot/cogs/Crypto/functions/extractor.py @@ -12,8 +12,8 @@ async def extract( kwargs = data_parser(data) - if len(attachments) > 0: - file = attachments[0] + if attachments and attachments[0]: + file: Attachment = attachments[0] if file.size > max_size: raise ValueError diff --git a/tuxbot/cogs/Crypto/functions/parser.py b/tuxbot/cogs/Crypto/functions/parser.py index 5be0585..ce9f5d9 100644 --- a/tuxbot/cogs/Crypto/functions/parser.py +++ b/tuxbot/cogs/Crypto/functions/parser.py @@ -3,7 +3,10 @@ import re def data_parser(data: str) -> dict: output = { - "message": None, + "message": "", + "compressed": False, + "graphical": False, + "chars": tuple(), } if not data: @@ -22,10 +25,10 @@ def data_parser(data: str) -> dict: if "--chars" in data: regex = r"--chars=(\S\S)" - match = re.search(regex, data) - output["chars"] = tuple(match.group()[-2:]) - data = "".join(data.rsplit(match.group(), 1)) + if match := re.search(regex, data): + output["chars"] = tuple(match.group()[-2:]) + data = "".join(data.rsplit(match.group(), 1)) output["message"] = data.strip() diff --git a/tuxbot/cogs/Custom/config.py b/tuxbot/cogs/Custom/config.py index 2f8e6c4..2e74b41 100644 --- a/tuxbot/cogs/Custom/config.py +++ b/tuxbot/cogs/Custom/config.py @@ -1,3 +1,5 @@ +from typing import Dict + from structured_config import Structure HAS_MODELS = False @@ -7,4 +9,4 @@ class CustomConfig(Structure): pass -extra = {} +extra: Dict[str, Dict] = {} diff --git a/tuxbot/cogs/Custom/models/__init__.py b/tuxbot/cogs/Custom/models/__init__.py index e69de29..10b0fd7 100644 --- a/tuxbot/cogs/Custom/models/__init__.py +++ b/tuxbot/cogs/Custom/models/__init__.py @@ -0,0 +1 @@ +# pylint: disable=cyclic-import diff --git a/tuxbot/cogs/Dev/config.py b/tuxbot/cogs/Dev/config.py index b6d2bcc..4273450 100644 --- a/tuxbot/cogs/Dev/config.py +++ b/tuxbot/cogs/Dev/config.py @@ -1,3 +1,5 @@ +from typing import Dict + from structured_config import Structure HAS_MODELS = False @@ -7,4 +9,4 @@ class DevConfig(Structure): pass -extra = {} +extra: Dict[str, Dict] = {} diff --git a/tuxbot/cogs/Logs/config.py b/tuxbot/cogs/Logs/config.py index 4959470..6135724 100644 --- a/tuxbot/cogs/Logs/config.py +++ b/tuxbot/cogs/Logs/config.py @@ -1,3 +1,5 @@ +from typing import Dict + from structured_config import Structure, StrField HAS_MODELS = False @@ -12,7 +14,7 @@ class LogsConfig(Structure): sentryKey: str = StrField("") -extra = { +extra: Dict[str, Dict] = { "dm": { "type": str, "description": "URL of the webhook used for send DMs " diff --git a/tuxbot/cogs/Logs/functions/utils.py b/tuxbot/cogs/Logs/functions/utils.py index daa75f0..84453fa 100644 --- a/tuxbot/cogs/Logs/functions/utils.py +++ b/tuxbot/cogs/Logs/functions/utils.py @@ -1,4 +1,5 @@ from collections import Counter +from typing import Dict def sort_by(_events: Counter) -> dict[str, dict]: @@ -12,7 +13,7 @@ def sort_by(_events: Counter) -> dict[str, dict]: "voice", "other", ] - sorted_events = {m: {} for m in majors} + sorted_events: Dict[str, Dict] = {m: {} for m in majors} for event, count in _events: done = False diff --git a/tuxbot/cogs/Network/config.py b/tuxbot/cogs/Network/config.py index 51276d6..114e890 100644 --- a/tuxbot/cogs/Network/config.py +++ b/tuxbot/cogs/Network/config.py @@ -1,3 +1,5 @@ +from typing import Dict + from structured_config import Structure, StrField HAS_MODELS = False @@ -7,7 +9,7 @@ class NetworkConfig(Structure): ipinfoKey: str = StrField("") -extra = { +extra: Dict[str, Dict] = { "ipinfoKey": { "type": str, "description": "API Key for ipinfo.io (.iplocalise command)", diff --git a/tuxbot/cogs/Network/functions/converters.py b/tuxbot/cogs/Network/functions/converters.py index d480c3f..566a956 100644 --- a/tuxbot/cogs/Network/functions/converters.py +++ b/tuxbot/cogs/Network/functions/converters.py @@ -1,13 +1,5 @@ -import re - from discord.ext import commands -from tuxbot.cogs.Network.functions.exceptions import ( - InvalidIp, - InvalidDomain, - InvalidQueryType, -) - def _(x): return x diff --git a/tuxbot/cogs/Network/functions/utils.py b/tuxbot/cogs/Network/functions/utils.py index 118f478..87065d7 100644 --- a/tuxbot/cogs/Network/functions/utils.py +++ b/tuxbot/cogs/Network/functions/utils.py @@ -23,15 +23,15 @@ def _(x): async def get_ip(ip: str, inet: str = "", tmp: discord.Message = None) -> str: + _inet: Union[socket.AddressFamily, int] = 0 # pylint: disable=no-member + if inet == "6": - inet = socket.AF_INET6 + _inet = socket.AF_INET6 elif inet == "4": - inet = socket.AF_INET - else: - inet = 0 + _inet = socket.AF_INET try: - return socket.getaddrinfo(str(ip), None, inet)[1][4][0] + return socket.getaddrinfo(str(ip), None, _inet)[1][4][0] except socket.gaierror as e: if tmp: await tmp.delete() diff --git a/tuxbot/cogs/Polls/config.py b/tuxbot/cogs/Polls/config.py index e5f8b5c..23b4320 100644 --- a/tuxbot/cogs/Polls/config.py +++ b/tuxbot/cogs/Polls/config.py @@ -1,3 +1,5 @@ +from typing import Dict + from structured_config import Structure HAS_MODELS = True @@ -7,4 +9,4 @@ class PollsConfig(Structure): pass -extra = {} +extra: Dict[str, Dict] = {} diff --git a/tuxbot/cogs/Polls/functions/listeners.py b/tuxbot/cogs/Polls/functions/listeners.py index aafd8e6..3074232 100644 --- a/tuxbot/cogs/Polls/functions/listeners.py +++ b/tuxbot/cogs/Polls/functions/listeners.py @@ -49,8 +49,10 @@ async def _suggest_reaction_add( or (await self.bot.is_owner(discord.Object(pld.user_id))) or ( (channel := await self.bot.fetch_channel(pld.channel_id)) - .permissions_for(await channel.guild.fetch_member(pld.user_id)) - .administrator + # pylint: disable=used-before-assignment + .permissions_for( + await channel.guild.fetch_member(pld.user_id) + ).administrator ) ): diff --git a/tuxbot/cogs/Polls/models/polls.py b/tuxbot/cogs/Polls/models/polls.py index dfe82c4..6b1efff 100644 --- a/tuxbot/cogs/Polls/models/polls.py +++ b/tuxbot/cogs/Polls/models/polls.py @@ -13,7 +13,8 @@ class Poll(tortoise.Model): available_choices = fields.IntField() - choices: fields.ManyToManyRelation["Response"] = fields.ManyToManyField( + # noinspection PyUnresolvedReferences + choices: fields.ManyToManyRelation["Response"] = fields.ManyToManyField( # type: ignore "models.Response", related_name="choices" ) diff --git a/tuxbot/cogs/Utils/config.py b/tuxbot/cogs/Utils/config.py index 3f43e82..00ff323 100644 --- a/tuxbot/cogs/Utils/config.py +++ b/tuxbot/cogs/Utils/config.py @@ -1,3 +1,5 @@ +from typing import Dict + from structured_config import Structure HAS_MODELS = False @@ -7,4 +9,4 @@ class UtilsConfig(Structure): pass -extra = {} +extra: Dict[str, Dict] = {} diff --git a/tuxbot/core/i18n.py b/tuxbot/core/i18n.py index 9a8143e..07b2c8f 100644 --- a/tuxbot/core/i18n.py +++ b/tuxbot/core/i18n.py @@ -1,7 +1,7 @@ import logging import os from pathlib import Path -from typing import Callable, Union, Dict, List +from typing import Union, Dict, List, NoReturn, Any from babel.messages.pofile import read_po @@ -19,7 +19,7 @@ available_locales: Dict[str, List[str]] = { } -def find_locale(locale: str) -> str: +def find_locale(locale: str) -> Union[str, NoReturn]: """We suppose `locale` is in `_available_locales.values()`""" for key, val in available_locales.items(): @@ -40,10 +40,10 @@ def list_locales() -> str: def get_locale_name(locale: str) -> str: """Return the name of this `locale`""" - return available_locales.get(find_locale(locale))[0] + return available_locales[find_locale(locale)][0] -class Translator(Callable[[str], str]): +class Translator: """Class to load texts at init.""" def __init__(self, name: str, file_location: Union[Path, os.PathLike]): @@ -59,7 +59,7 @@ class Translator(Callable[[str], str]): """ self.cog_folder = Path(file_location).resolve().parent self.cog_name = name - self.translations = {} + self.translations: Dict[str, Any] = {} _translators.append(self) diff --git a/tuxbot/core/utils/functions/utils.py b/tuxbot/core/utils/functions/utils.py index a3670c7..08e15ba 100644 --- a/tuxbot/core/utils/functions/utils.py +++ b/tuxbot/core/utils/functions/utils.py @@ -1,5 +1,6 @@ import asyncio import functools +from typing import Dict import aiohttp from discord.ext import commands @@ -27,7 +28,7 @@ def typing(func): async def shorten(session, text: str, length: int) -> dict: - output = {"text": text[:length], "link": None} + output: Dict[str, str] = {"text": text[:length], "link": ""} if len(text) > length: output["text"] += "[...]" @@ -51,7 +52,7 @@ def replace_in_dict(value: dict, search: str, replace: str) -> dict: for k, v in value.items(): if isinstance(v, (str, bytes)): - v = v.replace(search, replace) + v = v.replace(search, replace) # type: ignore elif isinstance(v, list): v = replace_in_list(v, search, replace) elif isinstance(v, dict): @@ -67,7 +68,7 @@ def replace_in_list(value: list, search: str, replace: str) -> list: for v in value: if isinstance(v, (str, bytes)): - v = v.replace(search, replace) + v = v.replace(search, replace) # type: ignore elif isinstance(v, list): v = replace_in_list(v, search, replace) elif isinstance(v, dict): diff --git a/tuxbot/setup.py b/tuxbot/setup.py index bff52ff..7b37b04 100644 --- a/tuxbot/setup.py +++ b/tuxbot/setup.py @@ -138,7 +138,9 @@ def get_multiple( List[Union[str, int]] List containing user filled values. """ - prompt = IntPrompt if value_type is int else Prompt + prompt: Union[IntPrompt, Prompt] = ( + IntPrompt() if value_type is int else Prompt() + ) user_input = prompt.ask(question, console=console) @@ -167,11 +169,13 @@ def get_multiple( def get_extra(question: str, value_type: type) -> Union[str, int]: - prompt = IntPrompt if value_type is int else Prompt + prompt: Union[IntPrompt, Prompt] = ( + IntPrompt() if value_type is int else Prompt() + ) return prompt.ask(question, console=console) -def additional_config(cogs: str = "**"): +def additional_config(cogs: Union[str, list] = "**"): """Asking for additional configs in cogs. Returns @@ -185,7 +189,7 @@ def additional_config(cogs: str = "**"): cogs = sum(cogs, []) if len(cogs) == 0: - paths = Path("tuxbot/cogs").glob("**/config.py") + paths = list(Path("tuxbot/cogs").glob("**/config.py")) else: paths = [Path(f"tuxbot/cogs/{cog}/config.py") for cog in cogs] @@ -195,7 +199,7 @@ def additional_config(cogs: str = "**"): console.print(Rule(f"\nConfiguration for `{cog_name}` module")) mod = importlib.import_module(str(path).replace("/", ".")[:-3]) mod_config_type = getattr(mod, cog_name.capitalize() + "Config") - mod_extra = mod.extra + mod_extra = getattr(mod, "extra") mod_config = config.ConfigFile( str(cogs_data_path(cog_name) / "config.yaml"), @@ -362,9 +366,7 @@ def parse_cli_flags(args: list) -> Namespace: help="Check for update", ) - args = parser.parse_args(args) - - return args + return parser.parse_args(args) def setup() -> None: