diff options
author | 2024-01-31 14:45:38 +0100 | |
---|---|---|
committer | 2024-01-31 14:45:38 +0100 | |
commit | e5bab98908f57686a71cae91da13655d56be1f66 (patch) | |
tree | 84cfd957aedf843c26506abfd00045188389b856 | |
parent | Bump sentry-sdk from 1.39.2 to 1.40.0 (#2904) (diff) | |
parent | delete pagination tests (diff) |
Merge pull request #2879 from python-discord/use-botcore-paginator
Use `LinePaginator` from `bot-core`
-rw-r--r-- | bot/pagination.py | 372 | ||||
-rw-r--r-- | poetry.lock | 105 | ||||
-rw-r--r-- | pyproject.toml | 2 | ||||
-rw-r--r-- | tests/bot/test_pagination.py | 46 |
4 files changed, 85 insertions, 440 deletions
diff --git a/bot/pagination.py b/bot/pagination.py index fd9dd8af6..6b2f76715 100644 --- a/bot/pagination.py +++ b/bot/pagination.py @@ -1,190 +1,19 @@ -import asyncio -from contextlib import suppress -from functools import partial +from collections.abc import Sequence import discord -from discord.abc import User -from discord.ext.commands import Context, Paginator +from discord.ext.commands import Context +from pydis_core.utils.pagination import LinePaginator as _LinePaginator, PaginationEmojis -from bot import constants -from bot.log import get_logger -from bot.utils import messages +from bot.constants import Emojis -FIRST_EMOJI = "\u23EE" # [:track_previous:] -LEFT_EMOJI = "\u2B05" # [:arrow_left:] -RIGHT_EMOJI = "\u27A1" # [:arrow_right:] -LAST_EMOJI = "\u23ED" # [:track_next:] -DELETE_EMOJI = constants.Emojis.trashcan # [:trashcan:] -PAGINATION_EMOJI = (FIRST_EMOJI, LEFT_EMOJI, RIGHT_EMOJI, LAST_EMOJI, DELETE_EMOJI) - -log = get_logger(__name__) - - -class EmptyPaginatorEmbedError(Exception): - """Raised when attempting to paginate with empty contents.""" - - - -class LinePaginator(Paginator): +class LinePaginator(_LinePaginator): """ A class that aids in paginating code blocks for Discord messages. - Available attributes include: - * prefix: `str` - The prefix inserted to every page. e.g. three backticks. - * suffix: `str` - The suffix appended at the end of every page. e.g. three backticks. - * max_size: `int` - The maximum amount of codepoints allowed in a page. - * scale_to_size: `int` - The maximum amount of characters a single line can scale up to. - * max_lines: `int` - The maximum amount of lines allowed in a page. + See the super class's docs for more info. """ - def __init__( - self, - prefix: str = "```", - suffix: str = "```", - max_size: int = 4000, - scale_to_size: int = 4000, - max_lines: int | None = None, - linesep: str = "\n" - ) -> None: - """ - This function overrides the Paginator.__init__ from inside discord.ext.commands. - - It overrides in order to allow us to configure the maximum number of lines per page. - """ - # Embeds that exceed 4096 characters will result in an HTTPException - # (Discord API limit), so we've set a limit of 4000 - if max_size > 4000: - raise ValueError(f"max_size must be <= 4,000 characters. ({max_size} > 4000)") - - super().__init__( - prefix, - suffix, - max_size - len(suffix), - linesep - ) - - if scale_to_size < max_size: - raise ValueError(f"scale_to_size must be >= max_size. ({scale_to_size} < {max_size})") - - if scale_to_size > 4000: - raise ValueError(f"scale_to_size must be <= 4,000 characters. ({scale_to_size} > 4000)") - - self.scale_to_size = scale_to_size - len(suffix) - self.max_lines = max_lines - self._current_page = [prefix] - self._linecount = 0 - self._count = len(prefix) + 1 # prefix + newline - self._pages = [] - - def add_line(self, line: str = "", *, empty: bool = False) -> None: - """ - Adds a line to the current page. - - If a line on a page exceeds `max_size` characters, then `max_size` will go up to - `scale_to_size` for a single line before creating a new page for the overflow words. If it - is still exceeded, the excess characters are stored and placed on the next pages unti - there are none remaining (by word boundary). The line is truncated if `scale_to_size` is - still exceeded after attempting to continue onto the next page. - - In the case that the page already contains one or more lines and the new lines would cause - `max_size` to be exceeded, a new page is created. This is done in order to make a best - effort to avoid breaking up single lines across pages, while keeping the total length of the - page at a reasonable size. - - This function overrides the `Paginator.add_line` from inside `discord.ext.commands`. - - It overrides in order to allow us to configure the maximum number of lines per page. - """ - remaining_words = None - if len(line) > (max_chars := self.max_size - len(self.prefix) - 2): - if len(line) > self.scale_to_size: - line, remaining_words = self._split_remaining_words(line, max_chars) - if len(line) > self.scale_to_size: - log.debug("Could not continue to next page, truncating line.") - line = line[:self.scale_to_size] - - # Check if we should start a new page or continue the line on the current one - if self.max_lines is not None and self._linecount >= self.max_lines: - log.debug("max_lines exceeded, creating new page.") - self._new_page() - elif self._count + len(line) + 1 > self.max_size and self._linecount > 0: - log.debug("max_size exceeded on page with lines, creating new page.") - self._new_page() - - self._linecount += 1 - - self._count += len(line) + 1 - self._current_page.append(line) - - if empty: - self._current_page.append("") - self._count += 1 - - # Start a new page if there were any overflow words - if remaining_words: - self._new_page() - self.add_line(remaining_words) - - def _new_page(self) -> None: - """ - Internal: start a new page for the paginator. - - This closes the current page and resets the counters for the new page's line count and - character count. - """ - self._linecount = 0 - self._count = len(self.prefix) + 1 - self.close_page() - - def _split_remaining_words(self, line: str, max_chars: int) -> tuple[str, str | None]: - """ - Internal: split a line into two strings -- reduced_words and remaining_words. - - reduced_words: the remaining words in `line`, after attempting to remove all words that - exceed `max_chars` (rounding down to the nearest word boundary). - - remaining_words: the words in `line` which exceed `max_chars`. This value is None if - no words could be split from `line`. - - If there are any remaining_words, an ellipses is appended to reduced_words and a - continuation header is inserted before remaining_words to visually communicate the line - continuation. - - Return a tuple in the format (reduced_words, remaining_words). - """ - reduced_words = [] - remaining_words = [] - - # "(Continued)" is used on a line by itself to indicate the continuation of last page - continuation_header = "(Continued)\n-----------\n" - reduced_char_count = 0 - is_full = False - - for word in line.split(" "): - if not is_full: - if len(word) + reduced_char_count <= max_chars: - reduced_words.append(word) - reduced_char_count += len(word) + 1 - else: - # If reduced_words is empty, we were unable to split the words across pages - if not reduced_words: - return line, None - is_full = True - remaining_words.append(word) - else: - remaining_words.append(word) - - return ( - " ".join(reduced_words) + "..." if remaining_words else "", - continuation_header + " ".join(remaining_words) if remaining_words else None - ) - @classmethod async def paginate( cls, @@ -197,175 +26,36 @@ class LinePaginator(Paginator): max_size: int = 500, scale_to_size: int = 4000, empty: bool = True, - restrict_to_user: User | None = None, + restrict_to_user: discord.User | None = None, timeout: int = 300, - footer_text: str | None = None, - url: str | None = None, + footer_text: str | None = None, url: str | None = None, exception_on_empty_embed: bool = False, reply: bool = False, + allowed_roles: Sequence[int] | None = None, **kwargs ) -> discord.Message | None: """ Use a paginator and set of reactions to provide pagination over a set of lines. - The reactions are used to switch page, or to finish with pagination. - - When used, this will send a message using `ctx.send()` and apply a set of reactions to it. These reactions may - be used to change page, or to remove pagination from the message. - - Pagination will also be removed automatically if no reaction is added for five minutes (300 seconds). - - The interaction will be limited to `restrict_to_user` (ctx.author by default) or - to any user with a moderation role. - - Example: - >>> embed = discord.Embed() - >>> embed.set_author(name="Some Operation", url=url, icon_url=icon) - >>> await LinePaginator.paginate([line for line in lines], ctx, embed) - """ - paginator = cls(prefix=prefix, suffix=suffix, max_size=max_size, max_lines=max_lines, - scale_to_size=scale_to_size) - current_page = 0 - - if not restrict_to_user: - if isinstance(ctx, discord.Interaction): - restrict_to_user = ctx.user - else: - restrict_to_user = ctx.author - - if not lines: - if exception_on_empty_embed: - log.exception("Pagination asked for empty lines iterable") - raise EmptyPaginatorEmbedError("No lines to paginate") - - log.debug("No lines to add to paginator, adding '(nothing to display)' message") - lines.append("*(nothing to display)*") - - for line in lines: - try: - paginator.add_line(line, empty=empty) - except Exception: - log.exception(f"Failed to add line to paginator: '{line}'") - raise # Should propagate - else: - log.trace(f"Added line to paginator: '{line}'") - - log.debug(f"Paginator created with {len(paginator.pages)} pages") - - embed.description = paginator.pages[current_page] - - reference = ctx.message if reply else None - - if len(paginator.pages) <= 1: - if footer_text: - embed.set_footer(text=footer_text) - log.trace(f"Setting embed footer to '{footer_text}'") - - if url: - embed.url = url - log.trace(f"Setting embed url to '{url}'") - - log.debug("There's less than two pages, so we won't paginate - sending single page on its own") - - if isinstance(ctx, discord.Interaction): - return await ctx.response.send_message(embed=embed) - return await ctx.send(embed=embed, reference=reference) - - if footer_text: - embed.set_footer(text=f"{footer_text} (Page {current_page + 1}/{len(paginator.pages)})") - else: - embed.set_footer(text=f"Page {current_page + 1}/{len(paginator.pages)}") - log.trace(f"Setting embed footer to '{embed.footer.text}'") - - if url: - embed.url = url - log.trace(f"Setting embed url to '{url}'") - - log.debug("Sending first page to channel...") - - if isinstance(ctx, discord.Interaction): - await ctx.response.send_message(embed=embed) - message = await ctx.original_response() - else: - message = await ctx.send(embed=embed, reference=reference) - - log.debug("Adding emoji reactions to message...") - - for emoji in PAGINATION_EMOJI: - # Add all the applicable emoji to the message - log.trace(f"Adding reaction: {emoji!r}") - await message.add_reaction(emoji) - - check = partial( - messages.reaction_check, - message_id=message.id, - allowed_emoji=PAGINATION_EMOJI, - allowed_users=(restrict_to_user.id,), + Acts as a wrapper for the super class' `paginate` method to provide the pagination emojis by default. + + Consult the super class's `paginate` method for detailed information. + """ + return await super().paginate( + pagination_emojis=PaginationEmojis(delete=Emojis.trashcan), + lines=lines, + ctx=ctx, + embed=embed, + prefix=prefix, + suffix=suffix, + max_lines=max_lines, + max_size=max_size, + scale_to_size=scale_to_size, + empty=empty, + restrict_to_user=restrict_to_user, + timeout=timeout, + footer_text=footer_text, + url=url, + exception_on_empty_embed=exception_on_empty_embed, + reply=reply, + allowed_roles=allowed_roles ) - - while True: - try: - if isinstance(ctx, discord.Interaction): - reaction, user = await ctx.client.wait_for("reaction_add", timeout=timeout, check=check) - else: - reaction, user = await ctx.bot.wait_for("reaction_add", timeout=timeout, check=check) - log.trace(f"Got reaction: {reaction}") - except asyncio.TimeoutError: - log.debug("Timed out waiting for a reaction") - break # We're done, no reactions for the last 5 minutes - - if str(reaction.emoji) == DELETE_EMOJI: - log.debug("Got delete reaction") - return await message.delete() - if reaction.emoji in PAGINATION_EMOJI: - total_pages = len(paginator.pages) - try: - await message.remove_reaction(reaction.emoji, user) - except discord.HTTPException as e: - # Suppress if trying to act on an archived thread. - if e.code != 50083: - raise e - - if reaction.emoji == FIRST_EMOJI: - current_page = 0 - log.debug(f"Got first page reaction - changing to page 1/{total_pages}") - elif reaction.emoji == LAST_EMOJI: - current_page = len(paginator.pages) - 1 - log.debug(f"Got last page reaction - changing to page {current_page + 1}/{total_pages}") - elif reaction.emoji == LEFT_EMOJI: - if current_page <= 0: - log.debug("Got previous page reaction, but we're on the first page - ignoring") - continue - - current_page -= 1 - log.debug(f"Got previous page reaction - changing to page {current_page + 1}/{total_pages}") - elif reaction.emoji == RIGHT_EMOJI: - if current_page >= len(paginator.pages) - 1: - log.debug("Got next page reaction, but we're on the last page - ignoring") - continue - - current_page += 1 - log.debug(f"Got next page reaction - changing to page {current_page + 1}/{total_pages}") - - embed.description = paginator.pages[current_page] - - if footer_text: - embed.set_footer(text=f"{footer_text} (Page {current_page + 1}/{len(paginator.pages)})") - else: - embed.set_footer(text=f"Page {current_page + 1}/{len(paginator.pages)}") - - try: - await message.edit(embed=embed) - except discord.HTTPException as e: - if e.code == 50083: - # Trying to act on an archived thread, just ignore and abort - break - raise e - - log.debug("Ending pagination and clearing reactions.") - with suppress(discord.NotFound): - try: - await message.clear_reactions() - except discord.HTTPException as e: - # Suppress if trying to act on an archived thread. - if e.code != 50083: - raise e diff --git a/poetry.lock b/poetry.lock index c6d844b49..8eacd3a36 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. [[package]] name = "aiodns" @@ -601,13 +601,13 @@ testing = ["hatch", "pre-commit", "pytest", "tox"] [[package]] name = "fakeredis" -version = "2.20.1" +version = "2.21.0" description = "Python implementation of redis API, can be used for testing purposes." optional = false python-versions = ">=3.7,<4.0" files = [ - {file = "fakeredis-2.20.1-py3-none-any.whl", hash = "sha256:d1cb22ed76b574cbf807c2987ea82fc0bd3e7d68a7a1e3331dd202cc39d6b4e5"}, - {file = "fakeredis-2.20.1.tar.gz", hash = "sha256:a2a5ccfcd72dc90435c18cde284f8cdd0cb032eb67d59f3fed907cde1cbffbbd"}, + {file = "fakeredis-2.21.0-py3-none-any.whl", hash = "sha256:dcea37c57a1aaf39bed1227aea20de052fb19dc030ccac3f8a73324b2ec90cee"}, + {file = "fakeredis-2.21.0.tar.gz", hash = "sha256:1b3ff9c068e39c43725f2373b105228cd03e6a50fc79a5698e852b7601b1201b"}, ] [package.dependencies] @@ -616,9 +616,11 @@ redis = ">=4" sortedcontainers = ">=2,<3" [package.extras] -bf = ["pybloom-live (>=4.0,<5.0)"] +bf = ["pyprobables (>=0.6,<0.7)"] +cf = ["pyprobables (>=0.6,<0.7)"] json = ["jsonpath-ng (>=1.6,<2.0)"] lua = ["lupa (>=1.14,<3.0)"] +probabilistic = ["pyprobables (>=0.6,<0.7)"] [[package]] name = "feedparser" @@ -1203,28 +1205,28 @@ test = ["docutils", "mypy", "pytest-cov", "pytest-pycodestyle", "pytest-runner"] [[package]] name = "platformdirs" -version = "4.1.0" +version = "4.2.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." optional = false python-versions = ">=3.8" files = [ - {file = "platformdirs-4.1.0-py3-none-any.whl", hash = "sha256:11c8f37bcca40db96d8144522d925583bdb7a31f7b0e37e3ed4318400a8e2380"}, - {file = "platformdirs-4.1.0.tar.gz", hash = "sha256:906d548203468492d432bcb294d4bc2fff751bf84971fbb2c10918cc206ee420"}, + {file = "platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068"}, + {file = "platformdirs-4.2.0.tar.gz", hash = "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768"}, ] [package.extras] -docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] +docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] [[package]] name = "pluggy" -version = "1.3.0" +version = "1.4.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" files = [ - {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"}, - {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"}, + {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, + {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, ] [package.extras] @@ -1268,27 +1270,27 @@ tests = ["pytest", "pytest-cov", "pytest-lazy-fixture"] [[package]] name = "psutil" -version = "5.9.7" +version = "5.9.8" description = "Cross-platform lib for process and system monitoring in Python." optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" files = [ - {file = "psutil-5.9.7-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:0bd41bf2d1463dfa535942b2a8f0e958acf6607ac0be52265ab31f7923bcd5e6"}, - {file = "psutil-5.9.7-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:5794944462509e49d4d458f4dbfb92c47539e7d8d15c796f141f474010084056"}, - {file = "psutil-5.9.7-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:fe361f743cb3389b8efda21980d93eb55c1f1e3898269bc9a2a1d0bb7b1f6508"}, - {file = "psutil-5.9.7-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:e469990e28f1ad738f65a42dcfc17adaed9d0f325d55047593cb9033a0ab63df"}, - {file = "psutil-5.9.7-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:3c4747a3e2ead1589e647e64aad601981f01b68f9398ddf94d01e3dc0d1e57c7"}, - {file = "psutil-5.9.7-cp27-none-win32.whl", hash = "sha256:1d4bc4a0148fdd7fd8f38e0498639ae128e64538faa507df25a20f8f7fb2341c"}, - {file = "psutil-5.9.7-cp27-none-win_amd64.whl", hash = "sha256:4c03362e280d06bbbfcd52f29acd79c733e0af33d707c54255d21029b8b32ba6"}, - {file = "psutil-5.9.7-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ea36cc62e69a13ec52b2f625c27527f6e4479bca2b340b7a452af55b34fcbe2e"}, - {file = "psutil-5.9.7-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1132704b876e58d277168cd729d64750633d5ff0183acf5b3c986b8466cd0284"}, - {file = "psutil-5.9.7-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe8b7f07948f1304497ce4f4684881250cd859b16d06a1dc4d7941eeb6233bfe"}, - {file = "psutil-5.9.7-cp36-cp36m-win32.whl", hash = "sha256:b27f8fdb190c8c03914f908a4555159327d7481dac2f01008d483137ef3311a9"}, - {file = "psutil-5.9.7-cp36-cp36m-win_amd64.whl", hash = "sha256:44969859757f4d8f2a9bd5b76eba8c3099a2c8cf3992ff62144061e39ba8568e"}, - {file = "psutil-5.9.7-cp37-abi3-win32.whl", hash = "sha256:c727ca5a9b2dd5193b8644b9f0c883d54f1248310023b5ad3e92036c5e2ada68"}, - {file = "psutil-5.9.7-cp37-abi3-win_amd64.whl", hash = "sha256:f37f87e4d73b79e6c5e749440c3113b81d1ee7d26f21c19c47371ddea834f414"}, - {file = "psutil-5.9.7-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:032f4f2c909818c86cea4fe2cc407f1c0f0cde8e6c6d702b28b8ce0c0d143340"}, - {file = "psutil-5.9.7.tar.gz", hash = "sha256:3f02134e82cfb5d089fddf20bb2e03fd5cd52395321d1c8458a9e58500ff417c"}, + {file = "psutil-5.9.8-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:26bd09967ae00920df88e0352a91cff1a78f8d69b3ecabbfe733610c0af486c8"}, + {file = "psutil-5.9.8-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:05806de88103b25903dff19bb6692bd2e714ccf9e668d050d144012055cbca73"}, + {file = "psutil-5.9.8-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:611052c4bc70432ec770d5d54f64206aa7203a101ec273a0cd82418c86503bb7"}, + {file = "psutil-5.9.8-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:50187900d73c1381ba1454cf40308c2bf6f34268518b3f36a9b663ca87e65e36"}, + {file = "psutil-5.9.8-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:02615ed8c5ea222323408ceba16c60e99c3f91639b07da6373fb7e6539abc56d"}, + {file = "psutil-5.9.8-cp27-none-win32.whl", hash = "sha256:36f435891adb138ed3c9e58c6af3e2e6ca9ac2f365efe1f9cfef2794e6c93b4e"}, + {file = "psutil-5.9.8-cp27-none-win_amd64.whl", hash = "sha256:bd1184ceb3f87651a67b2708d4c3338e9b10c5df903f2e3776b62303b26cb631"}, + {file = "psutil-5.9.8-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:aee678c8720623dc456fa20659af736241f575d79429a0e5e9cf88ae0605cc81"}, + {file = "psutil-5.9.8-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cb6403ce6d8e047495a701dc7c5bd788add903f8986d523e3e20b98b733e421"}, + {file = "psutil-5.9.8-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d06016f7f8625a1825ba3732081d77c94589dca78b7a3fc072194851e88461a4"}, + {file = "psutil-5.9.8-cp36-cp36m-win32.whl", hash = "sha256:7d79560ad97af658a0f6adfef8b834b53f64746d45b403f225b85c5c2c140eee"}, + {file = "psutil-5.9.8-cp36-cp36m-win_amd64.whl", hash = "sha256:27cc40c3493bb10de1be4b3f07cae4c010ce715290a5be22b98493509c6299e2"}, + {file = "psutil-5.9.8-cp37-abi3-win32.whl", hash = "sha256:bc56c2a1b0d15aa3eaa5a60c9f3f8e3e565303b465dbf57a1b730e7a2b9844e0"}, + {file = "psutil-5.9.8-cp37-abi3-win_amd64.whl", hash = "sha256:8db4c1b57507eef143a15a6884ca10f7c73876cdf5d51e713151c1236a0e68cf"}, + {file = "psutil-5.9.8-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:d16bbddf0693323b8c6123dd804100241da461e41d6e332fb0ba6058f630f8c8"}, + {file = "psutil-5.9.8.tar.gz", hash = "sha256:6be126e3225486dff286a8fb9a06246a5253f4c7c53b475ea5f5ac934e64194c"}, ] [package.extras] @@ -1524,13 +1526,13 @@ python-dotenv = ">=0.21.0" [[package]] name = "pydis-core" -version = "10.5.1" +version = "10.7.0" description = "PyDis core provides core functionality and utility to the bots of the Python Discord community." optional = false python-versions = ">=3.10.dev0,<3.12.dev0" files = [ - {file = "pydis_core-10.5.1-py3-none-any.whl", hash = "sha256:25311e97a36b60a40b9d5f8d8e626fa121e4cb110d67a6111c9b9aba456845f9"}, - {file = "pydis_core-10.5.1.tar.gz", hash = "sha256:95d4ac24a35591b02cb8970f943ef8e045ac7c519e2ec4604586e002993e8c7e"}, + {file = "pydis_core-10.7.0-py3-none-any.whl", hash = "sha256:bd53daf83d4a073177d92591f3747884e5b9abd67d34fdf49766d5a9ebfd63f4"}, + {file = "pydis_core-10.7.0.tar.gz", hash = "sha256:e48d2ea429427229ee5d5bc9fc303688957f431a444db0609bb31eb46c8168dd"}, ] [package.dependencies] @@ -1643,13 +1645,13 @@ six = ">=1.5" [[package]] name = "python-dotenv" -version = "1.0.0" +version = "1.0.1" description = "Read key-value pairs from a .env file and set them as environment variables" optional = false python-versions = ">=3.8" files = [ - {file = "python-dotenv-1.0.0.tar.gz", hash = "sha256:a8df96034aae6d2d50a4ebe8216326c61c3eb64836776504fcca410e5937a3ba"}, - {file = "python_dotenv-1.0.0-py3-none-any.whl", hash = "sha256:f5971a9226b701070a4bf2c38c89e5a3f0d64de8debda981d1db98583009122a"}, + {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, + {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, ] [package.extras] @@ -1698,7 +1700,6 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, @@ -1978,18 +1979,17 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "requests-file" -version = "1.5.1" +version = "2.0.0" description = "File transport adapter for Requests" optional = false python-versions = "*" files = [ - {file = "requests-file-1.5.1.tar.gz", hash = "sha256:07d74208d3389d01c38ab89ef403af0cfec63957d53a0081d8eca738d0247d8e"}, - {file = "requests_file-1.5.1-py2.py3-none-any.whl", hash = "sha256:dfe5dae75c12481f68ba353183c53a65e6044c923e64c24b2209f6c7570ca953"}, + {file = "requests-file-2.0.0.tar.gz", hash = "sha256:20c5931629c558fda566cacc10cfe2cd502433e628f568c34c80d96a0cc95972"}, + {file = "requests_file-2.0.0-py2.py3-none-any.whl", hash = "sha256:3e493d390adb44aa102ebea827a48717336d5268968c370eaf19abaf5cae13bf"}, ] [package.dependencies] requests = ">=1.0.0" -six = "*" [[package]] name = "ruff" @@ -2193,13 +2193,13 @@ files = [ [[package]] name = "types-python-dateutil" -version = "2.8.19.14" +version = "2.8.19.20240106" description = "Typing stubs for python-dateutil" optional = false -python-versions = "*" +python-versions = ">=3.8" files = [ - {file = "types-python-dateutil-2.8.19.14.tar.gz", hash = "sha256:1f4f10ac98bb8b16ade9dbee3518d9ace017821d94b057a425b069f834737f4b"}, - {file = "types_python_dateutil-2.8.19.14-py3-none-any.whl", hash = "sha256:f977b8de27787639986b4e28963263fd0e5158942b3ecef91b9335c130cb1ce9"}, + {file = "types-python-dateutil-2.8.19.20240106.tar.gz", hash = "sha256:1f8db221c3b98e6ca02ea83a58371b22c374f42ae5bbdf186db9c9a76581459f"}, + {file = "types_python_dateutil-2.8.19.20240106-py3-none-any.whl", hash = "sha256:efbbdc54590d0f16152fa103c9879c7d4a00e82078f6e2cf01769042165acaa2"}, ] [[package]] @@ -2215,17 +2215,18 @@ files = [ [[package]] name = "urllib3" -version = "2.1.0" +version = "2.2.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" files = [ - {file = "urllib3-2.1.0-py3-none-any.whl", hash = "sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3"}, - {file = "urllib3-2.1.0.tar.gz", hash = "sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54"}, + {file = "urllib3-2.2.0-py3-none-any.whl", hash = "sha256:ce3711610ddce217e6d113a2732fafad960a03fd0318c91faa79481e35c11224"}, + {file = "urllib3-2.2.0.tar.gz", hash = "sha256:051d961ad0c62a94e50ecf1af379c3aba230c66c710493493560c0c223c49f20"}, ] [package.extras] brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] @@ -2251,13 +2252,13 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [[package]] name = "wcwidth" -version = "0.2.12" +version = "0.2.13" description = "Measures the displayed width of unicode strings in a terminal" optional = false python-versions = "*" files = [ - {file = "wcwidth-0.2.12-py2.py3-none-any.whl", hash = "sha256:f26ec43d96c8cbfed76a5075dac87680124fa84e0855195a6184da9c187f133c"}, - {file = "wcwidth-0.2.12.tar.gz", hash = "sha256:f01c104efdf57971bcb756f054dd58ddec5204dd15fa31d6503ea57947d97c02"}, + {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, + {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, ] [[package]] @@ -2366,4 +2367,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "3.11.*" -content-hash = "97613a59075c48ee34c0597f4b8d7e31ccf1480533c5157a44bd26bef3b639d4" +content-hash = "662bc7742828b70055cb7c9d89280cc1651117799e5ae94b87b1d1f8556cd5f9" diff --git a/pyproject.toml b/pyproject.toml index d50773899..e67e69498 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ license = "MIT" python = "3.11.*" # See https://bot-core.pythondiscord.com/ for docs. -pydis_core = { version = "10.5.1", extras = ["async-rediscache"] } +pydis_core = { version = "10.7.0", extras = ["async-rediscache"] } aiohttp = "3.9.3" arrow = "1.3.0" diff --git a/tests/bot/test_pagination.py b/tests/bot/test_pagination.py deleted file mode 100644 index cf23f1948..000000000 --- a/tests/bot/test_pagination.py +++ /dev/null @@ -1,46 +0,0 @@ -from unittest import TestCase - -from bot import pagination - - -class LinePaginatorTests(TestCase): - """Tests functionality of the `LinePaginator`.""" - - def setUp(self): - """Create a paginator for the test method.""" - self.paginator = pagination.LinePaginator(prefix="", suffix="", max_size=30, - scale_to_size=50) - - def test_add_line_works_on_small_lines(self): - """`add_line` should allow small lines to be added.""" - self.paginator.add_line("x" * (self.paginator.max_size - 3)) - # Note that the page isn't added to _pages until it's full. - self.assertEqual(len(self.paginator._pages), 0) - - def test_add_line_works_on_long_lines(self): - """After additional lines after `max_size` is exceeded should go on the next page.""" - self.paginator.add_line("x" * self.paginator.max_size) - self.assertEqual(len(self.paginator._pages), 0) - - # Any additional lines should start a new page after `max_size` is exceeded. - self.paginator.add_line("x") - self.assertEqual(len(self.paginator._pages), 1) - - def test_add_line_continuation(self): - """When `scale_to_size` is exceeded, remaining words should be split onto the next page.""" - self.paginator.add_line("zyz " * (self.paginator.scale_to_size//4 + 1)) - self.assertEqual(len(self.paginator._pages), 1) - - def test_add_line_no_continuation(self): - """If adding a new line to an existing page would exceed `max_size`, it should start a new - page rather than using continuation. - """ - self.paginator.add_line("z" * (self.paginator.max_size - 3)) - self.paginator.add_line("z") - self.assertEqual(len(self.paginator._pages), 1) - - def test_add_line_truncates_very_long_words(self): - """`add_line` should truncate if a single long word exceeds `scale_to_size`.""" - self.paginator.add_line("x" * (self.paginator.scale_to_size + 1)) - # Note: item at index 1 is the truncated line, index 0 is prefix - self.assertEqual(self.paginator._current_page[1], "x" * self.paginator.scale_to_size) |