Skip to content

Commit abe42ec

Browse files
committed
Release 8.0.1
1 parent 6ccd615 commit abe42ec

32 files changed

+703
-309
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ lint-style:
88
flake8 sopel/ test/
99

1010
lint-type:
11-
mypy --check-untyped-defs sopel
11+
mypy --check-untyped-defs --disallow-incomplete-defs sopel
1212

1313
.PHONY: test test_norecord test_novcr vcr_rerecord
1414
test:

NEWS

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,52 @@ This file is used to auto-generate the "Changelog" section of Sopel's website.
22
When adding new entries, follow the style guide in NEWS.spec.md to avoid
33
causing problems with the site build.
44

5+
Changes between 8.0.0 and 8.0.1
6+
===============================
7+
8+
Plugin changes
9+
--------------
10+
11+
* find:
12+
* Fixed double-bold formatting [[#2589][]]
13+
* Support escaping backslashes [[#2589][]]
14+
15+
Core changes
16+
------------
17+
18+
* Use distribution name to query version of entry-point plugins [[#2594][]]
19+
* Added plugin version number in `sopel-plugins show` output [[#2638][]]
20+
* Fixed loading folder-style plugins with relative imports [[#2633][]]
21+
* Fixed rate-limiting behavior for rules without a rate limit [[#2629][]]
22+
* `config.types.ChoiceAttribute` logs invalid values for debugging [[#2624][]]
23+
* Also remove null (`\x00`) in `irc.utils.safe()` function [[#2620][]]
24+
25+
Housekeeping changes
26+
--------------------
27+
28+
* Document advanced tip about arbitrarily scheduling code [[#2617][]]
29+
* Include `versionadded` notes for more methods in `irc.AbstractBot` [[#2642][]]
30+
* Start moving from `typing.Optional` to the `| None` convention [[#2642][]]
31+
* Minor updates to keep up with type-checking ecosystem [[#2614][], [#2628][]]
32+
* Start checking for incomplete type annotations [[#2616][]]
33+
* Added tests to `find` plugin [[#2589][]]
34+
* Fixed slowdown in `@plugin.example` tests with `repeat` enabled [[#2630][]]
35+
36+
[#2589]: https://github.com/sopel-irc/sopel/pull/2589
37+
[#2594]: https://github.com/sopel-irc/sopel/pull/2594
38+
[#2614]: https://github.com/sopel-irc/sopel/pull/2614
39+
[#2616]: https://github.com/sopel-irc/sopel/pull/2616
40+
[#2617]: https://github.com/sopel-irc/sopel/pull/2617
41+
[#2620]: https://github.com/sopel-irc/sopel/pull/2620
42+
[#2624]: https://github.com/sopel-irc/sopel/pull/2624
43+
[#2628]: https://github.com/sopel-irc/sopel/pull/2628
44+
[#2629]: https://github.com/sopel-irc/sopel/pull/2629
45+
[#2630]: https://github.com/sopel-irc/sopel/pull/2630
46+
[#2633]: https://github.com/sopel-irc/sopel/pull/2633
47+
[#2638]: https://github.com/sopel-irc/sopel/pull/2638
48+
[#2642]: https://github.com/sopel-irc/sopel/pull/2642
49+
50+
551
Changes between 7.1.9 and 8.0.0
652
===============================
753

docs/source/plugin/advanced.rst

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,45 @@ If something is not in here, feel free to ask about it on our IRC channel, or
1515
maybe open an issue with the solution if you devise one yourself.
1616

1717

18+
Running a function on a schedule
19+
================================
20+
21+
Sopel provides the :func:`@plugin.interval <sopel.plugin.interval>` decorator
22+
to run plugin callables periodically, but plugin developers semi-frequently ask
23+
how to run a function at the same time every day/week.
24+
25+
Integrating this kind of feature into Sopel's plugin API is trickier than one
26+
might think, and it's actually simpler to have plugins just use a library like
27+
`schedule`__ directly::
28+
29+
import schedule
30+
31+
from sopel import plugin
32+
33+
34+
def scheduled_message(bot):
35+
bot.say("This is the scheduled message.", "#channelname")
36+
37+
38+
def setup(bot):
39+
# schedule the message at midnight every day
40+
schedule.every().day.at('00:00').do(scheduled_message, bot=bot)
41+
42+
43+
@plugin.interval(60)
44+
def run_schedule(bot):
45+
schedule.run_pending()
46+
47+
As long as the ``bot`` is passed as an argument, the scheduled function can
48+
access config settings or any other attributes/properties it needs.
49+
50+
Multiple plugins all setting up their own checks with ``interval`` naturally
51+
creates *some* overhead, but it shouldn't be significant compared to all the
52+
other things happening inside a Sopel bot with numerous plugins.
53+
54+
.. __: https://pypi.org/project/schedule/
55+
56+
1857
Restricting commands to certain channels
1958
========================================
2059

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ namespaces = false
1111

1212
[project]
1313
name = "sopel"
14-
version = "8.0.0"
14+
version = "8.0.1"
1515
description = "Simple and extensible IRC bot"
1616
maintainers = [
1717
{ name="dgw" },

sopel/bot.py

Lines changed: 41 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
from types import MappingProxyType
1818
from typing import (
1919
Any,
20+
Callable,
2021
Optional,
22+
Sequence,
2123
TYPE_CHECKING,
2224
TypeVar,
2325
Union,
@@ -36,6 +38,8 @@
3638

3739
if TYPE_CHECKING:
3840
from collections.abc import Iterable, Mapping
41+
42+
from sopel.plugins.handlers import AbstractPluginHandler
3943
from sopel.trigger import PreTrigger
4044

4145

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

201-
def has_channel_privilege(self, channel, privilege) -> bool:
204+
def has_channel_privilege(self, channel: str, privilege: int) -> bool:
202205
"""Tell if the bot has a ``privilege`` level or above in a ``channel``.
203206
204-
:param str channel: a channel the bot is in
205-
:param int privilege: privilege level to check
207+
:param channel: a channel the bot is in
208+
:param privilege: privilege level to check
206209
:raise ValueError: when the channel is unknown
207210
208211
This method checks the bot's privilege level in a channel, i.e. if it
@@ -339,10 +342,10 @@ def post_setup(self) -> None:
339342

340343
# plugins management
341344

342-
def reload_plugin(self, name) -> None:
345+
def reload_plugin(self, name: str) -> None:
343346
"""Reload a plugin.
344347
345-
:param str name: name of the plugin to reload
348+
:param name: name of the plugin to reload
346349
:raise plugins.exceptions.PluginNotRegistered: when there is no
347350
``name`` plugin registered
348351
@@ -391,45 +394,49 @@ def reload_plugins(self) -> None:
391394

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

394-
def add_plugin(self, plugin, callables, jobs, shutdowns, urls) -> None:
397+
def add_plugin(
398+
self,
399+
plugin: AbstractPluginHandler,
400+
callables: Sequence[Callable],
401+
jobs: Sequence[Callable],
402+
shutdowns: Sequence[Callable],
403+
urls: Sequence[Callable],
404+
) -> None:
395405
"""Add a loaded plugin to the bot's registry.
396406
397407
:param plugin: loaded plugin to add
398-
:type plugin: :class:`sopel.plugins.handlers.AbstractPluginHandler`
399408
:param callables: an iterable of callables from the ``plugin``
400-
:type callables: :term:`iterable`
401409
:param jobs: an iterable of functions from the ``plugin`` that are
402410
periodically invoked
403-
:type jobs: :term:`iterable`
404411
:param shutdowns: an iterable of functions from the ``plugin`` that
405412
should be called on shutdown
406-
:type shutdowns: :term:`iterable`
407413
:param urls: an iterable of functions from the ``plugin`` to call when
408414
matched against a URL
409-
:type urls: :term:`iterable`
410415
"""
411416
self._plugins[plugin.name] = plugin
412417
self.register_callables(callables)
413418
self.register_jobs(jobs)
414419
self.register_shutdowns(shutdowns)
415420
self.register_urls(urls)
416421

417-
def remove_plugin(self, plugin, callables, jobs, shutdowns, urls) -> None:
422+
def remove_plugin(
423+
self,
424+
plugin: AbstractPluginHandler,
425+
callables: Sequence[Callable],
426+
jobs: Sequence[Callable],
427+
shutdowns: Sequence[Callable],
428+
urls: Sequence[Callable],
429+
) -> None:
418430
"""Remove a loaded plugin from the bot's registry.
419431
420432
:param plugin: loaded plugin to remove
421-
:type plugin: :class:`sopel.plugins.handlers.AbstractPluginHandler`
422433
:param callables: an iterable of callables from the ``plugin``
423-
:type callables: :term:`iterable`
424434
:param jobs: an iterable of functions from the ``plugin`` that are
425435
periodically invoked
426-
:type jobs: :term:`iterable`
427436
:param shutdowns: an iterable of functions from the ``plugin`` that
428437
should be called on shutdown
429-
:type shutdowns: :term:`iterable`
430438
:param urls: an iterable of functions from the ``plugin`` to call when
431439
matched against a URL
432-
:type urls: :term:`iterable`
433440
"""
434441
name = plugin.name
435442
if not self.has_plugin(name):
@@ -595,30 +602,26 @@ def rate_limit_info(
595602
if trigger.admin or rule.is_unblockable():
596603
return False, None
597604

605+
nick = trigger.nick
598606
is_channel = trigger.sender and not trigger.sender.is_nick()
599607
channel = trigger.sender if is_channel else None
600608

601609
at_time = trigger.time
602-
603-
user_metrics = rule.get_user_metrics(trigger.nick)
604-
channel_metrics = rule.get_channel_metrics(channel)
605-
global_metrics = rule.get_global_metrics()
606-
607-
if user_metrics.is_limited(at_time - rule.user_rate_limit):
610+
if rule.is_user_rate_limited(nick, at_time):
608611
template = rule.user_rate_template
609612
rate_limit_type = "user"
610613
rate_limit = rule.user_rate_limit
611-
metrics = user_metrics
612-
elif is_channel and channel_metrics.is_limited(at_time - rule.channel_rate_limit):
614+
metrics = rule.get_user_metrics(nick)
615+
elif channel and rule.is_channel_rate_limited(channel, at_time):
613616
template = rule.channel_rate_template
614617
rate_limit_type = "channel"
615618
rate_limit = rule.channel_rate_limit
616-
metrics = channel_metrics
617-
elif global_metrics.is_limited(at_time - rule.global_rate_limit):
619+
metrics = rule.get_channel_metrics(channel)
620+
elif rule.is_global_rate_limited(at_time):
618621
template = rule.global_rate_template
619622
rate_limit_type = "global"
620623
rate_limit = rule.global_rate_limit
621-
metrics = global_metrics
624+
metrics = rule.get_global_metrics()
622625
else:
623626
return False, None
624627

@@ -993,12 +996,11 @@ def on_scheduler_error(
993996
self,
994997
scheduler: plugin_jobs.Scheduler,
995998
exc: BaseException,
996-
):
999+
) -> None:
9971000
"""Called when the Job Scheduler fails.
9981001
9991002
:param scheduler: the job scheduler that errored
1000-
:type scheduler: :class:`sopel.plugins.jobs.Scheduler`
1001-
:param Exception exc: the raised exception
1003+
:param exc: the raised exception
10021004
10031005
.. seealso::
10041006
@@ -1011,14 +1013,12 @@ def on_job_error(
10111013
scheduler: plugin_jobs.Scheduler,
10121014
job: tools_jobs.Job,
10131015
exc: BaseException,
1014-
):
1016+
) -> None:
10151017
"""Called when a job from the Job Scheduler fails.
10161018
10171019
:param scheduler: the job scheduler responsible for the errored ``job``
1018-
:type scheduler: :class:`sopel.plugins.jobs.Scheduler`
10191020
:param job: the Job that errored
1020-
:type job: :class:`sopel.tools.jobs.Job`
1021-
:param Exception exc: the raised exception
1021+
:param exc: the raised exception
10221022
10231023
.. seealso::
10241024
@@ -1030,13 +1030,11 @@ def error(
10301030
self,
10311031
trigger: Optional[Trigger] = None,
10321032
exception: Optional[BaseException] = None,
1033-
):
1033+
) -> None:
10341034
"""Called internally when a plugin causes an error.
10351035
1036-
:param trigger: the ``Trigger``\\ing line (if available)
1037-
:type trigger: :class:`sopel.trigger.Trigger`
1038-
:param Exception exception: the exception raised by the error (if
1039-
available)
1036+
:param trigger: the IRC line that caused the error (if available)
1037+
:param exception: the exception raised by the error (if available)
10401038
"""
10411039
message = 'Unexpected error'
10421040
if exception:
@@ -1056,7 +1054,7 @@ def error(
10561054
def _host_blocked(self, host: str) -> bool:
10571055
"""Check if a hostname is blocked.
10581056
1059-
:param str host: the hostname to check
1057+
:param host: the hostname to check
10601058
"""
10611059
bad_masks = self.config.core.host_blocks
10621060
for bad_mask in bad_masks:
@@ -1071,7 +1069,7 @@ def _host_blocked(self, host: str) -> bool:
10711069
def _nick_blocked(self, nick: str) -> bool:
10721070
"""Check if a nickname is blocked.
10731071
1074-
:param str nick: the nickname to check
1072+
:param nick: the nickname to check
10751073
"""
10761074
bad_nicks = self.config.core.nick_blocks
10771075
for bad_nick in bad_nicks:

sopel/builtins/calc.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,7 @@ def c(bot, trigger):
4242
# Account for the silly non-Anglophones and their silly radix point.
4343
eqn = trigger.group(2).replace(',', '.')
4444
try:
45-
result = eval_equation(eqn)
46-
result = "{:.10g}".format(result)
45+
result = "{:.10g}".format(eval_equation(eqn))
4746
except eval_equation.Error as err:
4847
bot.reply("Can't process expression: {}".format(str(err)))
4948
return

sopel/builtins/dice.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ def _roll_dice(dice_match: re.Match[str]) -> DicePouch:
244244
@plugin.example(".roll 2d10+3", user_help=True)
245245
@plugin.example(".roll 1d6", user_help=True)
246246
@plugin.output_prefix('[dice] ')
247-
def roll(bot: SopelWrapper, trigger: Trigger):
247+
def roll(bot: SopelWrapper, trigger: Trigger) -> None:
248248
"""Rolls dice and reports the result.
249249
250250
The dice roll follows this format: XdY[vZ][+N][#COMMENT]

0 commit comments

Comments
 (0)