Skip to content

Commit

Permalink
Merge pull request #2616 from Exirel/mypy-option-disallow-incomplete-…
Browse files Browse the repository at this point in the history
…defs

mypy: add --disallow-incomplete-defs option
  • Loading branch information
dgw authored Oct 18, 2024
2 parents c7693ca + 5cf930d commit 0e5c976
Show file tree
Hide file tree
Showing 18 changed files with 170 additions and 154 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ lint-style:
flake8 sopel/ test/

lint-type:
mypy --check-untyped-defs sopel
mypy --check-untyped-defs --disallow-incomplete-defs sopel

.PHONY: test test_norecord test_novcr vcr_rerecord
test:
Expand Down
66 changes: 34 additions & 32 deletions sopel/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@
from types import MappingProxyType
from typing import (
Any,
Callable,
Optional,
Sequence,
TYPE_CHECKING,
TypeVar,
Union,
Expand All @@ -36,6 +38,8 @@

if TYPE_CHECKING:
from collections.abc import Iterable, Mapping

from sopel.plugins.handlers import AbstractPluginHandler
from sopel.trigger import PreTrigger


Expand Down Expand Up @@ -182,7 +186,6 @@ def hostmask(self) -> Optional[str]:
:return: the bot's current hostmask if the bot is connected and in
a least one channel; ``None`` otherwise
:rtype: Optional[str]
"""
if not self.users or self.nick not in self.users:
# bot must be connected and in at least one channel
Expand All @@ -198,11 +201,11 @@ def plugins(self) -> Mapping[str, plugins.handlers.AbstractPluginHandler]:
"""
return MappingProxyType(self._plugins)

def has_channel_privilege(self, channel, privilege) -> bool:
def has_channel_privilege(self, channel: str, privilege: int) -> bool:
"""Tell if the bot has a ``privilege`` level or above in a ``channel``.
:param str channel: a channel the bot is in
:param int privilege: privilege level to check
:param channel: a channel the bot is in
:param privilege: privilege level to check
:raise ValueError: when the channel is unknown
This method checks the bot's privilege level in a channel, i.e. if it
Expand Down Expand Up @@ -339,10 +342,10 @@ def post_setup(self) -> None:

# plugins management

def reload_plugin(self, name) -> None:
def reload_plugin(self, name: str) -> None:
"""Reload a plugin.
:param str name: name of the plugin to reload
:param name: name of the plugin to reload
:raise plugins.exceptions.PluginNotRegistered: when there is no
``name`` plugin registered
Expand Down Expand Up @@ -391,45 +394,49 @@ def reload_plugins(self) -> None:

# TODO: deprecate both add_plugin and remove_plugin; see #2425

def add_plugin(self, plugin, callables, jobs, shutdowns, urls) -> None:
def add_plugin(
self,
plugin: AbstractPluginHandler,
callables: Sequence[Callable],
jobs: Sequence[Callable],
shutdowns: Sequence[Callable],
urls: Sequence[Callable],
) -> None:
"""Add a loaded plugin to the bot's registry.
:param plugin: loaded plugin to add
:type plugin: :class:`sopel.plugins.handlers.AbstractPluginHandler`
:param callables: an iterable of callables from the ``plugin``
:type callables: :term:`iterable`
:param jobs: an iterable of functions from the ``plugin`` that are
periodically invoked
:type jobs: :term:`iterable`
:param shutdowns: an iterable of functions from the ``plugin`` that
should be called on shutdown
:type shutdowns: :term:`iterable`
:param urls: an iterable of functions from the ``plugin`` to call when
matched against a URL
:type urls: :term:`iterable`
"""
self._plugins[plugin.name] = plugin
self.register_callables(callables)
self.register_jobs(jobs)
self.register_shutdowns(shutdowns)
self.register_urls(urls)

def remove_plugin(self, plugin, callables, jobs, shutdowns, urls) -> None:
def remove_plugin(
self,
plugin: AbstractPluginHandler,
callables: Sequence[Callable],
jobs: Sequence[Callable],
shutdowns: Sequence[Callable],
urls: Sequence[Callable],
) -> None:
"""Remove a loaded plugin from the bot's registry.
:param plugin: loaded plugin to remove
:type plugin: :class:`sopel.plugins.handlers.AbstractPluginHandler`
:param callables: an iterable of callables from the ``plugin``
:type callables: :term:`iterable`
:param jobs: an iterable of functions from the ``plugin`` that are
periodically invoked
:type jobs: :term:`iterable`
:param shutdowns: an iterable of functions from the ``plugin`` that
should be called on shutdown
:type shutdowns: :term:`iterable`
:param urls: an iterable of functions from the ``plugin`` to call when
matched against a URL
:type urls: :term:`iterable`
"""
name = plugin.name
if not self.has_plugin(name):
Expand Down Expand Up @@ -993,12 +1000,11 @@ def on_scheduler_error(
self,
scheduler: plugin_jobs.Scheduler,
exc: BaseException,
):
) -> None:
"""Called when the Job Scheduler fails.
:param scheduler: the job scheduler that errored
:type scheduler: :class:`sopel.plugins.jobs.Scheduler`
:param Exception exc: the raised exception
:param exc: the raised exception
.. seealso::
Expand All @@ -1011,14 +1017,12 @@ def on_job_error(
scheduler: plugin_jobs.Scheduler,
job: tools_jobs.Job,
exc: BaseException,
):
) -> None:
"""Called when a job from the Job Scheduler fails.
:param scheduler: the job scheduler responsible for the errored ``job``
:type scheduler: :class:`sopel.plugins.jobs.Scheduler`
:param job: the Job that errored
:type job: :class:`sopel.tools.jobs.Job`
:param Exception exc: the raised exception
:param exc: the raised exception
.. seealso::
Expand All @@ -1030,13 +1034,11 @@ def error(
self,
trigger: Optional[Trigger] = None,
exception: Optional[BaseException] = None,
):
) -> None:
"""Called internally when a plugin causes an error.
:param trigger: the ``Trigger``\\ing line (if available)
:type trigger: :class:`sopel.trigger.Trigger`
:param Exception exception: the exception raised by the error (if
available)
:param trigger: the IRC line that caused the error (if available)
:param exception: the exception raised by the error (if available)
"""
message = 'Unexpected error'
if exception:
Expand All @@ -1056,7 +1058,7 @@ def error(
def _host_blocked(self, host: str) -> bool:
"""Check if a hostname is blocked.
:param str host: the hostname to check
:param host: the hostname to check
"""
bad_masks = self.config.core.host_blocks
for bad_mask in bad_masks:
Expand All @@ -1071,7 +1073,7 @@ def _host_blocked(self, host: str) -> bool:
def _nick_blocked(self, nick: str) -> bool:
"""Check if a nickname is blocked.
:param str nick: the nickname to check
:param nick: the nickname to check
"""
bad_nicks = self.config.core.nick_blocks
for bad_nick in bad_nicks:
Expand Down
3 changes: 1 addition & 2 deletions sopel/builtins/calc.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,7 @@ def c(bot, trigger):
# Account for the silly non-Anglophones and their silly radix point.
eqn = trigger.group(2).replace(',', '.')
try:
result = eval_equation(eqn)
result = "{:.10g}".format(result)
result = "{:.10g}".format(eval_equation(eqn))
except eval_equation.Error as err:
bot.reply("Can't process expression: {}".format(str(err)))
return
Expand Down
2 changes: 1 addition & 1 deletion sopel/builtins/dice.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ def _roll_dice(dice_match: re.Match[str]) -> DicePouch:
@plugin.example(".roll 2d10+3", user_help=True)
@plugin.example(".roll 1d6", user_help=True)
@plugin.output_prefix('[dice] ')
def roll(bot: SopelWrapper, trigger: Trigger):
def roll(bot: SopelWrapper, trigger: Trigger) -> None:
"""Rolls dice and reports the result.
The dice roll follows this format: XdY[vZ][+N][#COMMENT]
Expand Down
16 changes: 8 additions & 8 deletions sopel/builtins/safety.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ class SafetySection(types.StaticSection):
"""Optional hosts-file formatted domain blocklist to use instead of StevenBlack's."""


def configure(settings: Config):
def configure(settings: Config) -> None:
"""
| name | example | purpose |
| ---- | ------- | ------- |
Expand Down Expand Up @@ -90,7 +90,7 @@ def configure(settings: Config):
)


def setup(bot: Sopel):
def setup(bot: Sopel) -> None:
bot.settings.define_section("safety", SafetySection)

if bot.settings.safety.default_mode is None:
Expand Down Expand Up @@ -166,7 +166,7 @@ def download_domain_list(bot: Sopel, path: str) -> bool:
return True


def update_local_cache(bot: Sopel, init: bool = False):
def update_local_cache(bot: Sopel, init: bool = False) -> None:
"""Download the current malware domain list and load it into memory.
:param init: Load the file even if it's unchanged
Expand Down Expand Up @@ -202,7 +202,7 @@ def update_local_cache(bot: Sopel, init: bool = False):
bot.memory[SAFETY_CACHE_LOCAL_KEY] = unsafe_domains


def shutdown(bot: Sopel):
def shutdown(bot: Sopel) -> None:
bot.memory.pop(SAFETY_CACHE_KEY, None)
bot.memory.pop(SAFETY_CACHE_LOCAL_KEY, None)
bot.memory.pop(SAFETY_CACHE_LOCK_KEY, None)
Expand All @@ -211,7 +211,7 @@ def shutdown(bot: Sopel):
@plugin.rule(r'(?u).*(https?://\S+).*')
@plugin.priority('high')
@plugin.output_prefix(PLUGIN_OUTPUT_PREFIX)
def url_handler(bot: SopelWrapper, trigger: Trigger):
def url_handler(bot: SopelWrapper, trigger: Trigger) -> None:
"""Checks for malicious URLs."""
mode = bot.db.get_channel_value(
trigger.sender,
Expand Down Expand Up @@ -365,7 +365,7 @@ def virustotal_lookup(
@plugin.example(".virustotal https://malware.wicar.org/")
@plugin.example(".virustotal hxxps://malware.wicar.org/")
@plugin.output_prefix("[safety][VirusTotal] ")
def vt_command(bot: SopelWrapper, trigger: Trigger):
def vt_command(bot: SopelWrapper, trigger: Trigger) -> None:
"""Look up VT results on demand."""
if not bot.settings.safety.vt_api_key:
bot.reply("Sorry, I don't have a VirusTotal API key configured.")
Expand Down Expand Up @@ -421,7 +421,7 @@ def vt_command(bot: SopelWrapper, trigger: Trigger):
@plugin.command('safety')
@plugin.example(".safety on")
@plugin.output_prefix(PLUGIN_OUTPUT_PREFIX)
def toggle_safety(bot: SopelWrapper, trigger: Trigger):
def toggle_safety(bot: SopelWrapper, trigger: Trigger) -> None:
"""Set safety setting for channel."""
if not trigger.admin and bot.channels[trigger.sender].privileges[trigger.nick] < plugin.OP:
bot.reply('Only channel operators can change safety settings')
Expand Down Expand Up @@ -455,7 +455,7 @@ def toggle_safety(bot: SopelWrapper, trigger: Trigger):
# Clean the cache every day
# Code above also calls this if there are too many cache entries
@plugin.interval(24 * 60 * 60)
def _clean_cache(bot: Sopel):
def _clean_cache(bot: Sopel) -> None:
"""Cleans up old entries in URL safety cache."""

update_local_cache(bot)
Expand Down
30 changes: 21 additions & 9 deletions sopel/builtins/units.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,34 +9,40 @@
from __future__ import annotations

import re
from typing import Pattern, TYPE_CHECKING

from sopel import plugin


if TYPE_CHECKING:
from sopel.bot import SopelWrapper
from sopel.trigger import Trigger


PLUGIN_OUTPUT_PREFIX = '[units] '

find_temp = re.compile(r'(-?[0-9]*\.?[0-9]*)[ °]*(K|C|F)', re.IGNORECASE)
find_length = re.compile(r'([0-9]*\.?[0-9]*)[ ]*(mile[s]?|mi|inch|in|foot|feet|ft|yard[s]?|yd|(?:milli|centi|kilo|)meter[s]?|[mkc]?m|ly|light-year[s]?|au|astronomical unit[s]?|parsec[s]?|pc)', re.IGNORECASE)
find_mass = re.compile(r'([0-9]*\.?[0-9]*)[ ]*(lb|lbm|pound[s]?|ounce|oz|(?:kilo|)gram(?:me|)[s]?|[k]?g)', re.IGNORECASE)


def f_to_c(temp):
def f_to_c(temp: float) -> float:
return (float(temp) - 32) * 5 / 9


def c_to_k(temp):
def c_to_k(temp: float) -> float:
return temp + 273.15


def c_to_f(temp):
def c_to_f(temp: float) -> float:
return (9.0 / 5.0 * temp + 32)


def k_to_c(temp):
def k_to_c(temp: float) -> float:
return temp - 273.15


def _extract_source(pattern, trigger) -> tuple[str, ...]:
def _extract_source(pattern: Pattern, trigger: Trigger) -> tuple[str, ...]:
match = pattern.match(trigger.group(2))
if match:
return match.groups()
Expand All @@ -49,7 +55,7 @@ def _extract_source(pattern, trigger) -> tuple[str, ...]:
@plugin.example('.temp 100C', '100.00°C = 212.00°F = 373.15K')
@plugin.example('.temp 100K', '-173.15°C = -279.67°F = 100.00K')
@plugin.output_prefix(PLUGIN_OUTPUT_PREFIX)
def temperature(bot, trigger):
def temperature(bot: SopelWrapper, trigger: Trigger) -> int | None:
"""Convert temperatures"""
try:
source = _extract_source(find_temp, trigger)
Expand All @@ -71,14 +77,16 @@ def temperature(bot, trigger):

if kelvin <= 0:
bot.reply("Physically impossible temperature.")
return
return None

bot.say("{:.2f}°C = {:.2f}°F = {:.2f}K".format(
celsius,
fahrenheit,
kelvin,
))

return None


@plugin.command('length', 'distance')
@plugin.example('.distance 3m', '3.00m = 9 feet, 10.11 inches')
Expand All @@ -92,7 +100,7 @@ def temperature(bot, trigger):
@plugin.example('.length 3 au', '448793612.10km = 278867421.71 miles')
@plugin.example('.length 3 parsec', '92570329129020.20km = 57520535754731.61 miles')
@plugin.output_prefix(PLUGIN_OUTPUT_PREFIX)
def distance(bot, trigger):
def distance(bot: SopelWrapper, trigger: Trigger) -> int | None:
"""Convert distances"""
try:
source = _extract_source(find_length, trigger)
Expand Down Expand Up @@ -160,10 +168,12 @@ def distance(bot, trigger):

bot.say('{} = {}'.format(metric_part, stupid_part))

return None


@plugin.command('weight', 'mass')
@plugin.output_prefix(PLUGIN_OUTPUT_PREFIX)
def mass(bot, trigger):
def mass(bot: SopelWrapper, trigger: Trigger) -> int | None:
"""Convert mass"""
try:
source = _extract_source(find_mass, trigger)
Expand Down Expand Up @@ -199,3 +209,5 @@ def mass(bot, trigger):
stupid_part = '{:.2f} oz'.format(ounce)

bot.say('{} = {}'.format(metric_part, stupid_part))

return None
Loading

0 comments on commit 0e5c976

Please sign in to comment.