From 0f20f15c26b357dc35a56a6f9cdccc46176c4f4d Mon Sep 17 00:00:00 2001 From: Jakub Kuczys Date: Fri, 11 Nov 2022 15:12:54 +0100 Subject: [PATCH 01/16] Stop using deprecated set-output GH Actions command (#5876) --- .github/workflows/scripts/bump_version.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/.github/workflows/scripts/bump_version.py b/.github/workflows/scripts/bump_version.py index 5aab114a192..aefacf1ef60 100644 --- a/.github/workflows/scripts/bump_version.py +++ b/.github/workflows/scripts/bump_version.py @@ -1,13 +1,20 @@ import os import re import sys -from typing import Match +from typing import Any, Match import redbot +GITHUB_OUTPUT = os.environ["GITHUB_OUTPUT"] + + +def set_output(name: str, value: Any) -> None: + with open(GITHUB_OUTPUT, "a", encoding="utf-8") as fp: + fp.write(f"{name}={value}\n") + if int(os.environ.get("JUST_RETURN_VERSION", 0)): - print(f"::set-output name=version::{redbot._VERSION}") + set_output("version", redbot._VERSION) sys.exit(0) @@ -17,7 +24,7 @@ def repl(match: Match[str]) -> str: global version_info - print(f"::set-output name=old_version::{match.group('version')}") + set_output("old_version", match.group("version")) new_stable_version = os.environ.get("NEW_STABLE_VERSION", "auto") if new_stable_version == "auto": @@ -49,4 +56,4 @@ def repl(match: Match[str]) -> str: with open("redbot/__init__.py", "w", encoding="utf-8", newline="\n") as fp: fp.write(new_contents) -print(f"::set-output name=new_version::{version_info}") +set_output("new_version", version_info) From 51fa3f502e7874a4df86006b6ed59302177621ab Mon Sep 17 00:00:00 2001 From: Jakub Kuczys Date: Fri, 11 Nov 2022 15:37:49 +0100 Subject: [PATCH 02/16] Remove `bordered()` utility function (#5692) --- docs/changelog_3_4_0.rst | 2 +- redbot/core/events.py | 2 +- redbot/core/utils/chat_formatting.py | 65 ---------------------------- tests/core/test_utils.py | 56 ------------------------ 4 files changed, 2 insertions(+), 123 deletions(-) diff --git a/docs/changelog_3_4_0.rst b/docs/changelog_3_4_0.rst index 5764d6eb746..2811feba4aa 100644 --- a/docs/changelog_3_4_0.rst +++ b/docs/changelog_3_4_0.rst @@ -1580,5 +1580,5 @@ Miscellaneous - Updated features list in ``[p]serverinfo`` with the latest changes from Discord (:issue:`4116`) - Simple version of ``[p]serverinfo`` now shows info about more detailed ``[p]serverinfo 1`` (:issue:`4121`) - ``[p]set nickname``, ``[p]set serverprefix``, ``[p]streamalert``, and ``[p]streamset`` commands now can be run by users with permissions related to the actions they're making (:issue:`4109`) -- `bordered()` now uses ``+`` for corners if keyword argument ``ascii_border`` is set to `True` (:issue:`4097`) +- ``bordered()`` now uses ``+`` for corners if keyword argument ``ascii_border`` is set to `True` (:issue:`4097`) - Fixed timestamp storage in few places in Red (:issue:`4017`) diff --git a/redbot/core/events.py b/redbot/core/events.py index 8aaed3e9083..beb1eababdb 100644 --- a/redbot/core/events.py +++ b/redbot/core/events.py @@ -31,7 +31,7 @@ fetch_latest_red_version_info, send_to_owners_with_prefix_replaced, ) -from .utils.chat_formatting import inline, bordered, format_perms_list, humanize_timedelta +from .utils.chat_formatting import inline, format_perms_list, humanize_timedelta import rich from rich import box diff --git a/redbot/core/utils/chat_formatting.py b/redbot/core/utils/chat_formatting.py index 8a97c42f4ed..a36fb761df0 100644 --- a/redbot/core/utils/chat_formatting.py +++ b/redbot/core/utils/chat_formatting.py @@ -200,71 +200,6 @@ def spoiler(text: str, escape_formatting: bool = True) -> str: return f"||{escape(text, formatting=escape_formatting)}||" -def bordered(*columns: Sequence[str], ascii_border: bool = False) -> str: - """Get two blocks of text inside borders. - - Note - ---- - This will only work with a monospaced font. - - Parameters - ---------- - *columns : `sequence` of `str` - The columns of text, each being a list of lines in that column. - ascii_border : bool - Whether or not the border should be pure ASCII. - - Returns - ------- - str - The bordered text. - - """ - borders = { - "TL": "+" if ascii_border else "┌", # Top-left - "TR": "+" if ascii_border else "┐", # Top-right - "BL": "+" if ascii_border else "└", # Bottom-left - "BR": "+" if ascii_border else "┘", # Bottom-right - "HZ": "-" if ascii_border else "─", # Horizontal - "VT": "|" if ascii_border else "│", # Vertical - } - - sep = " " * 4 # Separator between boxes - widths = tuple(max(len(row) for row in column) + 9 for column in columns) # width of each col - colsdone = [False] * len(columns) # whether or not each column is done - lines = [sep.join("{TL}" + "{HZ}" * width + "{TR}" for width in widths)] - - for line in itertools.zip_longest(*columns): - row = [] - for colidx, column in enumerate(line): - width = widths[colidx] - done = colsdone[colidx] - if column is None: - if not done: - # bottom border of column - column = "{HZ}" * width - row.append("{BL}" + column + "{BR}") - colsdone[colidx] = True # mark column as done - else: - # leave empty - row.append(" " * (width + 2)) - else: - column += " " * (width - len(column)) # append padded spaces - row.append("{VT}" + column + "{VT}") - - lines.append(sep.join(row)) - - final_row = [] - for width, done in zip(widths, colsdone): - if not done: - final_row.append("{BL}" + "{HZ}" * width + "{BR}") - else: - final_row.append(" " * (width + 2)) - lines.append(sep.join(final_row)) - - return "\n".join(lines).format(**borders) - - def pagify( text: str, delims: Sequence[str] = ["\n"], diff --git a/tests/core/test_utils.py b/tests/core/test_utils.py index 2bfe0590068..69d22efd4d3 100644 --- a/tests/core/test_utils.py +++ b/tests/core/test_utils.py @@ -1,9 +1,7 @@ import asyncio import pytest import random -import textwrap from redbot.core.utils import ( - chat_formatting, bounded_gather, bounded_gather_iter, deduplicate_iterables, @@ -11,60 +9,6 @@ ) -def test_bordered_symmetrical(): - expected = textwrap.dedent( - """\ - ┌──────────────┐ ┌─────────────┐ - │one │ │four │ - │two │ │five │ - │three │ │six │ - └──────────────┘ └─────────────┘""" - ) - col1, col2 = ["one", "two", "three"], ["four", "five", "six"] - assert chat_formatting.bordered(col1, col2) == expected - - -def test_bordered_asymmetrical(): - expected = textwrap.dedent( - """\ - ┌──────────────┐ ┌──────────────┐ - │one │ │four │ - │two │ │five │ - │three │ │six │ - └──────────────┘ │seven │ - └──────────────┘""" - ) - col1, col2 = ["one", "two", "three"], ["four", "five", "six", "seven"] - assert chat_formatting.bordered(col1, col2) == expected - - -def test_bordered_asymmetrical_2(): - expected = textwrap.dedent( - """\ - ┌──────────────┐ ┌─────────────┐ - │one │ │five │ - │two │ │six │ - │three │ └─────────────┘ - │four │ - └──────────────┘ """ - ) - col1, col2 = ["one", "two", "three", "four"], ["five", "six"] - assert chat_formatting.bordered(col1, col2) == expected - - -def test_bordered_ascii(): - expected = textwrap.dedent( - """\ - +--------------+ +-------------+ - |one | |four | - |two | |five | - |three | |six | - +--------------+ +-------------+""" - ) - col1, col2 = ["one", "two", "three"], ["four", "five", "six"] - assert chat_formatting.bordered(col1, col2, ascii_border=True) == expected - - def test_deduplicate_iterables(): expected = [1, 2, 3, 4, 5] inputs = [[1, 2, 1], [3, 1, 2, 4], [5, 1, 2]] From 86aed377693fb03bda594a261c63a22f0f45d15d Mon Sep 17 00:00:00 2001 From: Kowlin <10947836+Kowlin@users.noreply.github.com> Date: Sat, 12 Nov 2022 01:59:51 +0100 Subject: [PATCH 03/16] Fix HTTPException in set api's send_modal (#5860) Co-authored-by: Flame442 <34169552+Flame442@users.noreply.github.com> --- redbot/core/utils/views.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/redbot/core/utils/views.py b/redbot/core/utils/views.py index f1d1b7e63a3..eeeb7bec74e 100644 --- a/redbot/core/utils/views.py +++ b/redbot/core/utils/views.py @@ -274,7 +274,7 @@ class SetApiModal(discord.ui.Modal): This Modal can either be used standalone with its own ``discord.ui.View`` for custom implementations, or created via ``SetApiView`` - to have an easy to implemement secure way of setting API keys. + to have an easy to implement secure way of setting API keys. Parameters ---------- @@ -299,12 +299,10 @@ def __init__( self.default_keys = list(default_keys.keys()) self.default_keys_fmt = self._format_keys(default_keys) + _placeholder_token = "client_id YOUR_CLIENT_ID\nclient_secret YOUR_CLIENT_SECRET" _placeholder_service = "service" if self.default_service is not None: _placeholder_service = self.default_service - _placeholder_token = "client_id YOUR_CLIENT_ID\nclient_secret YOUR_CLIENT_SECRET" - if self.default_keys_fmt is not None: - _placeholder_token = self.default_keys_fmt self.title = _("Set API Keys") self.keys_label = _("Keys and tokens") From e864924acbea3734d5ed114d3fba337ddce3d3a8 Mon Sep 17 00:00:00 2001 From: Ankur Gupta <52248401+Ankur391@users.noreply.github.com> Date: Sun, 13 Nov 2022 05:14:13 +0530 Subject: [PATCH 04/16] Harrypotter questions (#5887) Co-authored-by: Flame442 <34169552+Flame442@users.noreply.github.com> Co-authored-by: Lemon Rose <78662983+japandotorg@users.noreply.github.com> --- .../cogs/trivia/data/lists/harrypotter.yaml | 98 ++++++++++++++++++- 1 file changed, 97 insertions(+), 1 deletion(-) diff --git a/redbot/cogs/trivia/data/lists/harrypotter.yaml b/redbot/cogs/trivia/data/lists/harrypotter.yaml index 284f500c4e1..c51450f0b9d 100644 --- a/redbot/cogs/trivia/data/lists/harrypotter.yaml +++ b/redbot/cogs/trivia/data/lists/harrypotter.yaml @@ -1,4 +1,4 @@ -AUTHOR: JennJenn +AUTHOR: JennJenn & Unknown A bezoar is a stone taken from the stomach of a ...?: - goat A wizard who can transform himself into an animal at will is called what?: @@ -513,3 +513,99 @@ Why did the Ministry stop using owls for interroffice memos?: - droppings - owl droppings - the droppings +Who stopped Harry and Ron getting into Hogwarts their second year?: +- Dobby +Lumos is the spell that produces light from the user’s wand. What spell turns it off?: +- Nox +How old was the kid that Dudley and his gang beat up that "deserved it"?: +- 10 +- ten +What magical creatures attacked Harry and Dudley in Little Whinging?: +- Dementors +- Dementor +What magical creature can Harry see pulling the Hogwarts carriages that has not been able to see prior to this year?: +- Thestral +What quill does Umbridge give to Harry Potter during detention?: +- Black quill +- Blood quill +- Black +- Blood +After becoming High Inquisitor at Hogwarts, which teacher does Umbridge fire first?: +- Sybill Trelawney +- Sybill +- Professor Trelawney +What is the first spell that Harry instructs Dumbledore's Army to learn?: +- Expelliarmus +Which Azkaban escapee is responsible for the torture of Neville's parents?: +- Bellatrix Lestrange +- Bellatrix +- Lestrange +During the Battle of the Department of Mysteries, which member of the Order of the Phoenix is slain by Bellatrix Lestrange?: +- Sirius Black +- Sirius +How do Harry and the Weasleys travel to the Quidditch World Cup?: +- Portkey +- Port key +What is the name of Barty Crouch’s house elf?: +- Winky +In Harry Potter and goblet of fire, Which team wins the Quidditch World Cup?: +- Ireland +Which reporter shows up to write articles about the progression of the Triwizard Tournament?: +- Rita Skeeters +Who was impersonating Mad-Eye Moody throughout the Goblet of Fire?: +- Barty Crouch Jr. +- Barty Crouch Jr +- Barty Crouch Junior +What is one of the conditions that are necessary in order to hatch a dragon's egg in captivity?: +- Fire +What is the cost of the silver unicorn horns that Harry examines in the Apothecary?: +- 21 Gallons +- Twenty one Gallons +What gift did Mr. Weasley give to Percy when he was made a prefect?: +- An owl +- owl +How tall is the troll that Harry Potter and Ron Weasley find in the girls' bathroom at Hogwarts?: +- 12 feet +- Twelve feet +- 12ft +Who is the new Defense Against the Dark Arts teacher at Hogwarts in The Chamber of Secrets?: +- Gilderoy Lockhart +- Lockhart +Who slipped Ginny Weasley a diary at the beginning of Harry Potter and the Chamber of Secrets?: +- Lucius Malfoy +- Lucius +Who was the first to be Petrified during Harry Potter’s second year at Hogwarts?: +- Mrs. Norris +- Norris +What is the name of the ghost that haunts on of the girls bathrooms at Hogwarts?: +- Moaning Myrtle +- Myrtle +What do wizards call people from magical families with no magical powers?: +- Squibs +Which character in the Chamber of Secrets was framed for first opening the Chamber of Secrets?: +- Rubeus Hagrid +- Hagrid +Who is the Heir of Slytherin?: +- Tom Riddle +- Voldemort +As a horrific side effect of some polyjuice potion, Hermione gets turned into what creature?: +- cat +Which character was taken into the Chamber of Secrets by the Basilisk?: +- Ginny +- Ginny Weasley +Who accompanied Harry, Ron and Hermione in the train car to Hogwarts?: +- Remus Lupin +- Prof. Lupin +- Lupin +- Professor Lupin +After the Weasleys won the Daily Prophet Grand Prize Galleon Draw, where did they travel?: +- Egypt +How many secret passages are shown on the Marauder's Map?: +- seven +- 7 +How many hours into the past do Harry and Hermione travel in an attempt to rescue Sirius and Buckbeak?: +- 3 +- Three +How many muggles did Peter Pettigrew kill when he faked his own death?: +- 12 +- Twelve \ No newline at end of file From d5cdebcd765506e24725b087fa0a7b3b3ef7d332 Mon Sep 17 00:00:00 2001 From: Visne <39844191+Visne@users.noreply.github.com> Date: Sun, 13 Nov 2022 00:49:33 +0100 Subject: [PATCH 05/16] Improve topography trivia (#5572) Co-authored-by: Flame442 <34169552+Flame442@users.noreply.github.com> --- .../cogs/trivia/data/lists/worldcapitals.yaml | 2 +- redbot/cogs/trivia/data/lists/worldflags.yaml | 72 ++++++++++--------- redbot/cogs/trivia/data/lists/worldmap.yaml | 52 +++++++++----- 3 files changed, 74 insertions(+), 52 deletions(-) diff --git a/redbot/cogs/trivia/data/lists/worldcapitals.yaml b/redbot/cogs/trivia/data/lists/worldcapitals.yaml index 2ab804a8181..dc68a2f3e81 100644 --- a/redbot/cogs/trivia/data/lists/worldcapitals.yaml +++ b/redbot/cogs/trivia/data/lists/worldcapitals.yaml @@ -134,7 +134,7 @@ What is the capital of Eritrea?: - Asmara What is the capital of Estonia?: - Tallinn -What is the capital of eSwatini (Swaziland)?: +What is the capital of Eswatini?: - Mbabane What is the capital of Ethiopia?: - Addis Ababa diff --git a/redbot/cogs/trivia/data/lists/worldflags.yaml b/redbot/cogs/trivia/data/lists/worldflags.yaml index 21ea0f98162..160927dc8c0 100644 --- a/redbot/cogs/trivia/data/lists/worldflags.yaml +++ b/redbot/cogs/trivia/data/lists/worldflags.yaml @@ -10,6 +10,7 @@ What country is represented by this flag? https://i.imgur.com/1KBFTgX.png: - Sierra Leone What country is represented by this flag? https://i.imgur.com/1VsVFYK.png: - Maldives +- The Maldives What country is represented by this flag? https://i.imgur.com/1dQ8NBz.png: - Brazil What country is represented by this flag? https://i.imgur.com/bRZ8gai.png: @@ -21,8 +22,8 @@ What country is represented by this flag? https://i.imgur.com/222q836.png: What country is represented by this flag? https://i.imgur.com/2Wxbjv5.png: - South Korea What country is represented by this flag? https://i.imgur.com/3H3Lfat.png: -- Republic of the Philippines - Philippines +- The Philippines What country is represented by this flag? https://i.imgur.com/3KXMyBS.png: - Georgia What country is represented by this flag? https://i.imgur.com/3LN2uSA.png: @@ -45,10 +46,15 @@ What country is represented by this flag? https://i.imgur.com/5NbPMRh.png: - Belarus What country is represented by this flag? https://i.imgur.com/5TEduqP.png: - United Kingdom +- The United Kingdom - UK -- GB -- Great Britain +- U.K. +- The UK +- The U.K. - Britain +- Great Britain +- GB +- G.B. What country is represented by this flag? https://i.imgur.com/6UkAfly.png: - Kenya What country is represented by this flag? https://i.imgur.com/7qSsp7Z.png: @@ -59,7 +65,9 @@ What country is represented by this flag? https://i.imgur.com/8F5aqVG.png: - Germany What country is represented by this flag? https://i.imgur.com/8LFhQVn.png: - North Korea -- People's Republic of Korea +- Democratic People's Republic of Korea +- DPRK +- D.P.R.K. What country is represented by this flag? https://i.imgur.com/8OzbswS.png: - Armenia What country is represented by this flag? https://i.imgur.com/T91TSYR.png: @@ -86,7 +94,6 @@ What country is represented by this flag? https://i.imgur.com/BgdKIb2.png: - Mozambique What country is represented by this flag? https://i.imgur.com/BqwHKYv.png: - Malta -- Hvid Rød What country is represented by this flag? https://i.imgur.com/C3kWeQw.png: - Bahrain What country is represented by this flag? https://i.imgur.com/HzktfX0.png: @@ -129,6 +136,7 @@ What country is represented by this flag? https://i.imgur.com/KxMALzR.png: - Serbia What country is represented by this flag? https://i.imgur.com/Kytf2Mm.png: - Romania +- Chad What country is represented by this flag? https://i.imgur.com/LHZxJCE.png: - Azerbaijan What country is represented by this flag? https://i.imgur.com/LPm5b84.png: @@ -141,7 +149,7 @@ What country is represented by this flag? https://i.imgur.com/LgwBULL.png: - Canada What country is represented by this flag? https://i.imgur.com/Ls07jJq.png: - Democratic Republic of the Congo -- Dem. Rep. of Congo +- Democratic Republic of Congo - DRC - D.R.C. What country is represented by this flag? https://i.imgur.com/Ma1ivs0.png: @@ -174,6 +182,7 @@ What country is represented by this flag? https://i.imgur.com/QpwAZCH.png: What country is represented by this flag? https://i.imgur.com/R36hcy9.png: - Cote d'Ivoire - Ivory Coast +- Cote dIvoire # For the lazy people What country is represented by this flag? https://i.imgur.com/RLgBmmx.png: - Finland What country is represented by this flag? https://i.imgur.com/RTVaeeT.png: @@ -203,8 +212,6 @@ What country is represented by this flag? https://i.imgur.com/V0y3m1h.png: - Malawi What country is represented by this flag? https://i.imgur.com/V3IdT58.png: - Slovenia -What country is represented by this flag? https://i.imgur.com/VZLff6p.png: -- Chad What country is represented by this flag? https://i.imgur.com/Vbr5Tam.png: - Panama What country is represented by this flag? https://i.imgur.com/VeiCyLB.png: @@ -215,6 +222,7 @@ What country is represented by this flag? https://i.imgur.com/WJYeKvC.png: - Ukraine What country is represented by this flag? https://i.imgur.com/WSCunIE.png: - Vatican City +- The Vatican - Vatican What country is represented by this flag? https://i.imgur.com/WjFxXYM.png: - Iran @@ -234,7 +242,6 @@ What country is represented by this flag? https://i.imgur.com/Yvry0ap.png: - C.A.R. What country is represented by this flag? https://i.imgur.com/ZZJK5He.png: - Republic of the Congo -- Congo - Republic of Congo - ROC - R.O.C. @@ -243,16 +250,15 @@ What country is represented by this flag? https://i.imgur.com/ZnhOID0.png: What country is represented by this flag? https://i.imgur.com/Zphmsnu.png: - San Marino What country is represented by this flag? https://i.imgur.com/b9STlvA.png: +- Federated States of Micronesia - Micronesia +- FSM What country is represented by this flag? https://i.imgur.com/bMd1cb8.png: - Saint Vincent and the Grenadines +- St. Vincent and the Grenadines - St Vincent and the Grenadines -- St Vincent -- Grenadines What country is represented by this flag? https://i.imgur.com/bRF5q3c.png: - Antigua and Barbuda -- Antigua -- Barbuda What country is represented by this flag? https://i.imgur.com/bWDivLK.png: - Taiwan - Republic of China @@ -263,8 +269,8 @@ What country is represented by this flag? https://i.imgur.com/biZHNNQ.png: What country is represented by this flag? https://i.imgur.com/bjtyCp5.png: - Mauritius What country is represented by this flag? https://i.imgur.com/chOrWHr.png: -- Indonesia - Monaco +- Indonesia What country is represented by this flag? https://i.imgur.com/d8hPELu.png: - Latvia What country is represented by this flag? https://i.imgur.com/dBo8TPh.png: @@ -279,8 +285,7 @@ What country is represented by this flag? https://i.imgur.com/dmlTGO4.png: - Nicaragua What country is represented by this flag? https://i.imgur.com/duJRErG.png: - Saint Kitts and Nevis -- Saint Kitts -- St Kitts +- St. Kitts and Nevis - St Kitts and Nevis What country is represented by this flag? https://i.imgur.com/eDocaIL.png: - Sudan @@ -304,7 +309,6 @@ What country is represented by this flag? https://i.imgur.com/hbJ4J2A.png: - Nigeria What country is represented by this flag? https://i.imgur.com/hiNRJUm.png: - South Africa -- Republic of South Africa What country is represented by this flag? https://i.imgur.com/hr9515B.png: - Equatorial Guinea What country is represented by this flag? https://i.imgur.com/hy3crgu.png: @@ -316,10 +320,16 @@ What country is represented by this flag? https://i.imgur.com/iO3sAPy.png: What country is represented by this flag? https://i.imgur.com/iT92sev.png: - United States of America - USA -- U.S.A +- U.S.A. +- United States - US - U.S. -- United States +- The United States of America +- The USA +- The U.S.A. +- The United States +- The US +- The U.S. What country is represented by this flag? https://i.imgur.com/ins1BsE.png: - Laos What country is represented by this flag? https://i.imgur.com/j7Ig3Bu.png: @@ -330,9 +340,6 @@ What country is represented by this flag? https://i.imgur.com/jKRkoe5.png: - Paraguay What country is represented by this flag? https://i.imgur.com/jXpxd9Q.png: - Trinidad and Tobago -- Trinidad & Tobago -- Trinidad -- Tobago What country is represented by this flag? https://i.imgur.com/jwBaTxq.png: - Tunisia What country is represented by this flag? https://i.imgur.com/jwR0mTj.png: @@ -347,13 +354,14 @@ What country is represented by this flag? https://i.imgur.com/kxgws8z.png: - Burundi What country is represented by this flag? https://i.imgur.com/l6TGY4c.png: - Bosnia and Herzegovina +- Bosnia-Herzegovina +- Bosnia Herzegovina - Bosnia -- Herzegovina What country is represented by this flag? https://i.imgur.com/li2uTFU.png: +- East-Timor - Timor-Leste -- Timor -- Timor Leste - East Timor +- Timor Leste What country is represented by this flag? https://i.imgur.com/lkJ72mO.png: - Gabon What country is represented by this flag? https://i.imgur.com/mAgkfeH.png: @@ -376,6 +384,7 @@ What country is represented by this flag? https://i.imgur.com/oMLG5ri.png: - Mongolia What country is represented by this flag? https://i.imgur.com/pATL0sO.png: - Saint Lucia +- St. Lucia - St Lucia What country is represented by this flag? https://i.imgur.com/qGrb426.png: - Uganda @@ -384,10 +393,8 @@ What country is represented by this flag? https://i.imgur.com/qNyJQ5d.png: - Netherlands - Holland What country is represented by this flag? https://i.imgur.com/qZEuijY.png: -- Ireland - Republic of Ireland -- ROI -- R.O.I. +- Ireland # Technically wrong, but extremely common usage What country is represented by this flag? https://i.imgur.com/qtDaw2f.png: - Western Sahara What country is represented by this flag? https://i.imgur.com/qvcmPb7.png: @@ -410,6 +417,9 @@ What country is represented by this flag? https://i.imgur.com/uW7bNGe.png: - United Arab Emirates - UAE - U.A.E. +- The United Arab Emirates +- The UAE +- The U.A.E. What country is represented by this flag? https://i.imgur.com/uZ5WnBU.png: - Angola What country is represented by this flag? https://i.imgur.com/uaJ6QQD.png: @@ -428,16 +438,12 @@ What country is represented by this flag? https://i.imgur.com/wKXsrWD.png: - Afghanistan What country is represented by this flag? https://i.imgur.com/weIW0md.png: - Palau -- Palaos Bandera -- Bandera de Palaos What country is represented by this flag? https://i.imgur.com/wr0KhwF.png: - Cyprus What country is represented by this flag? https://i.imgur.com/wwxWE9U.png: - Solomon Islands -- Solomon What country is represented by this flag? https://i.imgur.com/xhVuaYj.png: -- eSwatini -- Swaziland +- Eswatini What country is represented by this flag? https://i.imgur.com/y5PJwXq.png: - Papua New Guinea What country is represented by this flag? https://i.imgur.com/y7JWUm0.png: diff --git a/redbot/cogs/trivia/data/lists/worldmap.yaml b/redbot/cogs/trivia/data/lists/worldmap.yaml index 40e85531554..e1b2ab43c7b 100644 --- a/redbot/cogs/trivia/data/lists/worldmap.yaml +++ b/redbot/cogs/trivia/data/lists/worldmap.yaml @@ -5,7 +5,8 @@ What country is highlighted on this map? https://i.imgur.com/0iRnWAj.png: - Papua New Guinea What country is highlighted on this map? https://i.imgur.com/0oN9MFH.png: - Central African Republic -- Car +- CAR +- C.A.R. What country is highlighted on this map? https://i.imgur.com/14nAY83.png: - Mongolia What country is highlighted on this map? https://i.imgur.com/15fLQVC.png: @@ -25,6 +26,7 @@ What country is highlighted on this map? https://i.imgur.com/21wXDZ6.png: What country is highlighted on this map? https://i.imgur.com/2K0IRgL.png: - Bosnia and Herzegovina - Bosnia-Herzegovina +- Bosnia Herzegovina - Bosnia What country is highlighted on this map? https://i.imgur.com/2uGKX9L.png: - Palau @@ -44,7 +46,9 @@ What country is highlighted on this map? https://i.imgur.com/3vcsCuu.png: - United Arab Emirates - UAE - U.A.E. -- U.A.E +- The United Arab Emirates +- The UAE +- The U.A.E. What country is highlighted on this map? https://i.imgur.com/3xcUQ1X.png: - Saint Kitts and Nevis - St. Kitts and Nevis @@ -63,12 +67,15 @@ What country is highlighted on this map? https://i.imgur.com/5WjbgUb.png: - Saudi Arabia What country is highlighted on this map? https://i.imgur.com/5el1VgN.png: - United Kingdom +- The United Kingdom - UK - U.K. -- U.K +- The UK +- The U.K. - Britain -- The United Kingdom of Great Britain and Northern Ireland -- the uk +- Great Britain +- GB +- G.B. What country is highlighted on this map? https://i.imgur.com/5m63w4b.png: - Italy What country is highlighted on this map? https://i.imgur.com/68B62BL.png: @@ -86,13 +93,14 @@ What country is highlighted on this map? https://i.imgur.com/7DXx3lu.png: What country is highlighted on this map? https://i.imgur.com/7OqhNc4.png: - Peru What country is highlighted on this map? https://i.imgur.com/7XrAS6L.png: -- Swaziland +- Eswatini What country is highlighted on this map? https://i.imgur.com/837JSrU.png: - Kosovo What country is highlighted on this map? https://i.imgur.com/8KwkBwG.png: - Tuvalu What country is highlighted on this map? https://i.imgur.com/8Q88Bgd.png: -- Ireland +- Republic of Ireland +- Ireland # Technically wrong, but extremely common usage What country is highlighted on this map? https://i.imgur.com/91UkD8N.png: - Djibouti What country is highlighted on this map? https://i.imgur.com/9RWwaqH.png: @@ -142,6 +150,8 @@ What country is highlighted on this map? https://i.imgur.com/GvlNpbw.png: What country is highlighted on this map? https://i.imgur.com/Gxo6qKW.png: - North Korea - Democratic People's Republic of Korea +- DPRK +- D.P.R.K. What country is highlighted on this map? https://i.imgur.com/J0RBGiH.png: - Türkiye - Turkiye @@ -159,6 +169,7 @@ What country is highlighted on this map? https://i.imgur.com/MQAQnR3.png: - Venezuela What country is highlighted on this map? https://i.imgur.com/Mc08OmO.png: - Taiwan +- Republic of China What country is highlighted on this map? https://i.imgur.com/Msr7M5X.png: - Suriname What country is highlighted on this map? https://i.imgur.com/NSIImmk.png: @@ -196,7 +207,8 @@ What country is highlighted on this map? https://i.imgur.com/RzNq1Xy.png: - Malaysia What country is highlighted on this map? https://i.imgur.com/S9DpuSt.png: - Vatican City -- Holy See +- The Vatican +- Vatican What country is highlighted on this map? https://i.imgur.com/S9Gs60x.png: - Jordan What country is highlighted on this map? https://i.imgur.com/SoraDd9.png: @@ -216,12 +228,18 @@ What country is highlighted on this map? https://i.imgur.com/UTGGdgf.png: What country is highlighted on this map? https://i.imgur.com/UammPKQ.png: - Jamaica What country is highlighted on this map? https://i.imgur.com/UghsBw6.png: +- United States of America - USA - U.S.A. -- U.S.A -- United States of America -- America - United States +- US +- U.S. +- The United States of America +- The USA +- The U.S.A. +- The United States +- The US +- The U.S. What country is highlighted on this map? https://i.imgur.com/W0OhBRP.png: - Tanzania What country is highlighted on this map? https://i.imgur.com/W9z0vCV.png: @@ -313,6 +331,7 @@ What country is highlighted on this map? https://i.imgur.com/gr5BLLs.png: - Chad What country is highlighted on this map? https://i.imgur.com/gxGbZhB.png: - China +- People's Republic of China What country is highlighted on this map? https://i.imgur.com/h63XZgg.png: - Bolivia What country is highlighted on this map? https://i.imgur.com/h8nW7MC.png: @@ -344,7 +363,7 @@ What country is highlighted on this map? https://i.imgur.com/kBdjnEv.png: What country is highlighted on this map? https://i.imgur.com/kHtsSx9.png: - Cote d'Ivoire - Ivory Coast -- Cote Divoire +- Cote dIvoire # For the lazy people What country is highlighted on this map? https://i.imgur.com/lHMAntb.png: - Pakistan What country is highlighted on this map? https://i.imgur.com/lIIYtBD.png: @@ -359,7 +378,6 @@ What country is highlighted on this map? https://i.imgur.com/mM0RJG0.png: - Croatia What country is highlighted on this map? https://i.imgur.com/mUXpeOu.png: - Germany -- Deutschland What country is highlighted on this map? https://i.imgur.com/mWfKGSn.png: - Burkina Faso What country is highlighted on this map? https://i.imgur.com/mYOwWRb.png: @@ -380,9 +398,6 @@ What country is highlighted on this map? https://i.imgur.com/pQmzZ6S.png: What country is highlighted on this map? https://i.imgur.com/pZXybV8.png: - Morocco What country is highlighted on this map? https://i.imgur.com/ph52UhZ.png: -- St Vincent -- St. Vincent -- Saint Vincent - Saint Vincent and the Grenadines - St. Vincent and the Grenadines - St Vincent and the Grenadines @@ -405,6 +420,7 @@ What country is highlighted on this map? https://i.imgur.com/qqKx7hp.png: What country is highlighted on this map? https://i.imgur.com/qssRZBv.png: - Federated States of Micronesia - Micronesia +- FSM What country is highlighted on this map? https://i.imgur.com/rGAxHB7.png: - Barbados What country is highlighted on this map? https://i.imgur.com/rRTyhTj.png: @@ -419,8 +435,8 @@ What country is highlighted on this map? https://i.imgur.com/sU7T64y.png: What country is highlighted on this map? https://i.imgur.com/tX9BQ5V.png: - Oman What country is highlighted on this map? https://i.imgur.com/tv8zqCC.png: -- Republic of Congo - Republic of the Congo +- Republic of Congo What country is highlighted on this map? https://i.imgur.com/twTqP40.png: - Ethiopia What country is highlighted on this map? https://i.imgur.com/uazYsHm.png: @@ -439,7 +455,7 @@ What country is highlighted on this map? https://i.imgur.com/yF1LWYJ.png: - Israel What country is highlighted on this map? https://i.imgur.com/zChCrog.png: - Maldives -- Republic of Maldives +- The Maldives What country is highlighted on this map? https://i.imgur.com/zKslICy.png: - Cameroon What country is highlighted on this map? https://i.imgur.com/zRYwJsw.png: From 6023f9015cc01b4d53c016020be737efad24687d Mon Sep 17 00:00:00 2001 From: Jakub Kuczys Date: Sun, 13 Nov 2022 23:50:02 +0100 Subject: [PATCH 06/16] Restrict first/last character in instance name (#5680) Co-authored-by: Flame442 <34169552+Flame442@users.noreply.github.com> --- redbot/setup.py | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/redbot/setup.py b/redbot/setup.py index 44d8438e037..b4af36a804a 100644 --- a/redbot/setup.py +++ b/redbot/setup.py @@ -125,12 +125,23 @@ def get_storage_type(backend: Optional[str], *, interactive: bool): def get_name(name: str) -> str: - INSTANCE_NAME_RE = re.compile(r"[A-Za-z0-9_\.\-]*") + INSTANCE_NAME_RE = re.compile( + r""" + [a-z0-9] # starts with letter or digit + (?: + (?!.*[_\.\-]{2}) # ensure no consecutive dots, hyphens, or underscores + [a-z0-9_\.\-]* # match allowed characters + [a-z0-9] # ensure string ends with letter or digit + )? # optional to allow strings of length 1 + """, + re.VERBOSE | re.IGNORECASE, + ) if name: if INSTANCE_NAME_RE.fullmatch(name) is None: print( - "ERROR: Instance names can only include characters A-z, numbers, " - "underscores (_) and periods (.)." + "ERROR: Instance names need to start and end with a letter or a number" + " and can only include characters A-z, numbers," + " and non-consecutive underscores (_) and periods (.)." ) sys.exit(1) return name @@ -139,14 +150,18 @@ def get_name(name: str) -> str: print( "Please enter a name for your instance," " it will be used to run your bot from here on out.\n" - "This name is case-sensitive and should only include characters" - " A-z, numbers, underscores (_) and periods (.)." + "This name is case-sensitive, needs to start and end with a letter or a number" + " and should only include characters A-z, numbers," + " and non-consecutive underscores (_) and periods (.)." ) name = input("> ") - if INSTANCE_NAME_RE.fullmatch(name) is None: + if not name: + pass + elif INSTANCE_NAME_RE.fullmatch(name) is None: print( - "ERROR: Instance names can only include characters A-z, numbers, " - "underscores (_) and periods (.)." + "ERROR: Instance names need to start and end with a letter or a number" + " and can only include characters A-z, numbers," + " and non-consecutive underscores (_) and periods (.)." ) name = "" elif "-" in name and not confirm( From 0580213cb6dbb6805a72cdf3b7a4439a2dbfff06 Mon Sep 17 00:00:00 2001 From: TrustyJAID Date: Tue, 15 Nov 2022 21:56:56 -0700 Subject: [PATCH 07/16] Fix `[p]helpset usemenus disable` (#5907) --- redbot/core/core_commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/redbot/core/core_commands.py b/redbot/core/core_commands.py index 25f772f198c..fb3c632b253 100644 --- a/redbot/core/core_commands.py +++ b/redbot/core/core_commands.py @@ -3760,7 +3760,7 @@ async def helpset_usemenus( if use_menus == "reactions": msg = _("Help will use reaction menus.") await ctx.bot._config.help.use_menus.set(1) - if use_menus == "disabled": + if use_menus == "disable": msg = _("Help will not use menus.") await ctx.bot._config.help.use_menus.set(0) From 4574f13ad5def2006ab7d45a5bd12d191e85fefe Mon Sep 17 00:00:00 2001 From: Jakub Kuczys Date: Thu, 17 Nov 2022 19:58:24 +0100 Subject: [PATCH 08/16] Fix flaky CI caused by flake8's flakiness (GitHub migration) (#5910) --- .github/workflows/lint_python.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint_python.yaml b/.github/workflows/lint_python.yaml index ea0ff7679c2..1cfa71aa8b3 100644 --- a/.github/workflows/lint_python.yaml +++ b/.github/workflows/lint_python.yaml @@ -20,7 +20,7 @@ jobs: - uses: actions/setup-python@v4 with: python-version: "3.8" - - run: "python -m pip install git+https://github.com/pycqa/pyflakes@1911c20#egg=pyflakes git+https://github.com/pycqa/pycodestyle@d219c68#egg=pycodestyle git+https://gitlab.com/pycqa/flake8@3.7.9#egg=flake8" + - run: "python -m pip install git+https://github.com/pycqa/pyflakes@1911c20#egg=pyflakes git+https://github.com/pycqa/pycodestyle@d219c68#egg=pycodestyle git+https://github.com/pycqa/flake8@3.7.9#egg=flake8" name: Install Flake8 - run: "python -m flake8 . --count --select=E9,F7,F82 --show-source" name: Flake8 Linting From 66f906c274df55fd0d6998eb9698ff5608c25897 Mon Sep 17 00:00:00 2001 From: Jakub Kuczys Date: Thu, 17 Nov 2022 20:16:01 +0100 Subject: [PATCH 09/16] Update CODEOWNERS file with my new username (#5911) --- .github/CODEOWNERS | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 81c38df881b..830d67f64ed 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,25 +1,25 @@ # Cogs /redbot/cogs/audio/** @aikaterna @PredaaA -/redbot/cogs/downloader/* @jack1142 +/redbot/cogs/downloader/* @Jackenmen /redbot/cogs/streams/* @palmtree5 /redbot/cogs/mutes/* @TrustyJAID # Docs - Install and update guides -/docs/install_guides/** @jack1142 -/docs/update_red.rst @jack1142 +/docs/install_guides/** @Jackenmen +/docs/update_red.rst @Jackenmen # Docs - Version guarantees -/docs/version_guarantees.rst @jack1142 +/docs/version_guarantees.rst @Jackenmen # Trivia Lists /redbot/cogs/trivia/data/lists/whosthatpokemon*.yaml @aikaterna # Tests -/redbot/pytest/downloader* @jack1142 -/tests/cogs/downloader/* @jack1142 +/redbot/pytest/downloader* @Jackenmen +/tests/cogs/downloader/* @Jackenmen # Schemas -/schema/* @jack1142 +/schema/* @Jackenmen # CI /.travis.yml @Kowlin From 1fbd6d854b0902c0f52efb123ad50c6c19433e6f Mon Sep 17 00:00:00 2001 From: Ryan <50505980+ryan5453@users.noreply.github.com> Date: Thu, 17 Nov 2022 17:22:44 -0500 Subject: [PATCH 10/16] [General] Fix [p]lmgtfy URL generation (#5909) Co-authored-by: Jakub Kuczys --- redbot/cogs/general/general.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/redbot/cogs/general/general.py b/redbot/cogs/general/general.py index 1d43bb0ad34..1f22a100f4a 100644 --- a/redbot/cogs/general/general.py +++ b/redbot/cogs/general/general.py @@ -220,7 +220,7 @@ async def stopwatch(self, ctx): async def lmgtfy(self, ctx, *, search_terms: str): """Create a lmgtfy link.""" search_terms = escape(urllib.parse.quote_plus(search_terms), mass_mentions=True) - await ctx.send("https://lmgtfy.app/?q={}".format(search_terms)) + await ctx.send("https://lmgtfy.app/?q={}&s=g".format(search_terms)) @commands.command(hidden=True) @commands.guild_only() From ed4f36a52998e94c08fb752f9e75c86f9f115251 Mon Sep 17 00:00:00 2001 From: Kreusada <67752638+Kreusada@users.noreply.github.com> Date: Thu, 17 Nov 2022 23:11:56 +0000 Subject: [PATCH 11/16] Add serverlock warning to `[p]invite` (#5898) --- redbot/core/core_commands.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/redbot/core/core_commands.py b/redbot/core/core_commands.py index fb3c632b253..98cfa232f63 100644 --- a/redbot/core/core_commands.py +++ b/redbot/core/core_commands.py @@ -62,6 +62,7 @@ humanize_timedelta, inline, pagify, + warning, ) from .commands import CommandConverter, CogConverter from .commands.requires import PrivilegeLevel @@ -1477,8 +1478,16 @@ async def invite(self, ctx): **Example:** - `[p]invite` """ + message = await self.bot.get_invite_url() + if (admin := self.bot.get_cog("Admin")) and await admin.config.serverlocked(): + message += "\n\n" + warning( + _( + "This bot is currently **serverlocked**, meaning that it is locked " + "to its current servers and will leave any server it joins." + ) + ) try: - await ctx.author.send(await self.bot.get_invite_url()) + await ctx.author.send(message) await ctx.tick() except discord.errors.Forbidden: await ctx.send( From 6c8b6eb71c5d1ca59d46ba68575ea287d6c36a12 Mon Sep 17 00:00:00 2001 From: Flame442 <34169552+Flame442@users.noreply.github.com> Date: Tue, 22 Nov 2022 19:37:20 -0500 Subject: [PATCH 12/16] [CustomCom] Fix [p]cc cooldown docstring & docs (#5914) --- docs/cog_guides/customcommands.rst | 4 ++-- redbot/cogs/customcom/customcom.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/cog_guides/customcommands.rst b/docs/cog_guides/customcommands.rst index c4c3ee32f69..c6ba9794419 100644 --- a/docs/cog_guides/customcommands.rst +++ b/docs/cog_guides/customcommands.rst @@ -80,8 +80,8 @@ Examples: **Arguments:** - ```` The custom command to check or set the cooldown. -- ```` The number of seconds to wait before allowing the command to be invoked again. If omitted, will instead return the current cooldown settings. -- ```` The group to apply the cooldown on. Defaults to per member. Valid choices are server, guild, user, and member. +- ``[cooldown]`` The number of seconds to wait before allowing the command to be invoked again. If omitted, will instead return the current cooldown settings. +- ``[per]`` The group to apply the cooldown on. Defaults to per member. Valid choices are server / guild, user / member, and channel. .. _customcommands-command-customcom-create: diff --git a/redbot/cogs/customcom/customcom.py b/redbot/cogs/customcom/customcom.py index 4f14627761e..6865075a56d 100644 --- a/redbot/cogs/customcom/customcom.py +++ b/redbot/cogs/customcom/customcom.py @@ -454,8 +454,8 @@ async def cc_cooldown( **Arguments:** - `` The custom command to check or set the cooldown. - - `` The number of seconds to wait before allowing the command to be invoked again. If omitted, will instead return the current cooldown settings. - - `` The group to apply the cooldown on. Defaults to per member. Valid choices are server, guild, user, and member. + - `[cooldown]` The number of seconds to wait before allowing the command to be invoked again. If omitted, will instead return the current cooldown settings. + - `[per]` The group to apply the cooldown on. Defaults to per member. Valid choices are server / guild, user / member, and channel. """ if cooldown is None: try: From bbb15924b969eb6fa170b3603556b5bcb9830a0f Mon Sep 17 00:00:00 2001 From: TrustyJAID Date: Thu, 24 Nov 2022 14:05:07 -0700 Subject: [PATCH 13/16] Allow send_interactive to upload a file containing all content (#5902) Co-authored-by: Flame442 <34169552+Flame442@users.noreply.github.com> --- redbot/core/commands/context.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/redbot/core/commands/context.py b/redbot/core/commands/context.py index 02d87239d17..ed354dedc28 100644 --- a/redbot/core/commands/context.py +++ b/redbot/core/commands/context.py @@ -9,7 +9,7 @@ from discord.ext.commands import Context as DPYContext from .requires import PermState -from ..utils.chat_formatting import box +from ..utils.chat_formatting import box, text_to_file from ..utils.predicates import MessagePredicate from ..utils import can_user_react_in, common_filters @@ -150,7 +150,11 @@ async def react_quietly( return True async def send_interactive( - self, messages: Iterable[str], box_lang: str = None, timeout: int = 15 + self, + messages: Iterable[str], + box_lang: str = None, + timeout: int = 15, + join_character: str = "", ) -> List[discord.Message]: """Send multiple messages interactively. @@ -168,6 +172,9 @@ async def send_interactive( timeout : int How long the user has to respond to the prompt before it times out. After timing out, the bot deletes its prompt message. + join_character : str + The character used to join all the messages when the file output + is selected. """ messages = tuple(messages) @@ -189,13 +196,14 @@ async def send_interactive( is_are = "are" query = await self.send( "There {} still {} message{} remaining. " - "Type `more` to continue." + "Type `more` to continue or `file` to upload all contents as a file." "".format(is_are, n_remaining, plural) ) + pred = MessagePredicate.lower_contained_in(("more", "file"), self) try: resp = await self.bot.wait_for( "message", - check=MessagePredicate.lower_equal_to("more", self), + check=pred, timeout=timeout, ) except asyncio.TimeoutError: @@ -211,6 +219,9 @@ async def send_interactive( # or channel is a DM with contextlib.suppress(discord.HTTPException): await query.delete() + if pred.result == 1: + await self.send(file=text_to_file(join_character.join(messages))) + break return ret async def embed_colour(self): From b018a76b615694efc4c4e87fe2470b755328b2b6 Mon Sep 17 00:00:00 2001 From: AntonioNarra <53577215+AntonioNarra@users.noreply.github.com> Date: Thu, 1 Dec 2022 18:58:11 -0500 Subject: [PATCH 14/16] Core - #5891 Recovering Server Prefixes (#5918) --- redbot/core/core_commands.py | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/redbot/core/core_commands.py b/redbot/core/core_commands.py index 98cfa232f63..0593afed87e 100644 --- a/redbot/core/core_commands.py +++ b/redbot/core/core_commands.py @@ -3351,13 +3351,16 @@ async def _set_ownernotifications_listdestinations(self, ctx: commands.Context): # -- End Set Ownernotifications Commands -- ### @_set.command(name="showsettings") - async def _set_showsettings(self, ctx: commands.Context): + async def _set_showsettings(self, ctx: commands.Context, server: discord.Guild = None): """ - Show the current settings for [botname]. + Show the current settings for [botname]. Accepts optional guild parameter if its prefix must be recovered. """ - if ctx.guild: - guild_data = await ctx.bot._config.guild(ctx.guild).all() - guild = ctx.guild + if server is None: + server = ctx.guild + + if server: + guild_data = await ctx.bot._config.guild(server).all() + guild = server admin_role_ids = guild_data["admin_role"] admin_role_names = [r.name for r in guild.roles if r.id in admin_role_ids] admin_roles_str = ( @@ -3367,9 +3370,9 @@ async def _set_showsettings(self, ctx: commands.Context): mod_role_names = [r.name for r in guild.roles if r.id in mod_role_ids] mod_roles_str = humanize_list(mod_role_names) if mod_role_names else _("Not Set.") - guild_locale = await i18n.get_locale_from_guild(self.bot, ctx.guild) + guild_locale = await i18n.get_locale_from_guild(self.bot, server) guild_regional_format = ( - await i18n.get_regional_format_from_guild(self.bot, ctx.guild) or guild_locale + await i18n.get_regional_format_from_guild(self.bot, server) or guild_locale ) guild_settings = _( @@ -3386,7 +3389,7 @@ async def _set_showsettings(self, ctx: commands.Context): else: guild_settings = "" - prefixes = await ctx.bot._prefix_cache.get_prefixes(ctx.guild) + prefixes = await ctx.bot._prefix_cache.get_prefixes(server) global_data = await ctx.bot._config.all() locale = global_data["locale"] regional_format = global_data["regional_format"] or locale @@ -3599,8 +3602,9 @@ async def _set_prefix(self, ctx: commands.Context, *prefixes: str): @_set.command(name="serverprefix", aliases=["serverprefixes"]) @checks.admin_or_permissions(manage_guild=True) - @commands.guild_only() - async def _set_serverprefix(self, ctx: commands.Context, *prefixes: str): + async def _set_serverprefix( + self, ctx: commands.Context, server: Optional[discord.Guild], *prefixes: str + ): """ Sets [botname]'s server prefix(es). @@ -3613,12 +3617,16 @@ async def _set_serverprefix(self, ctx: commands.Context, *prefixes: str): - `[p]set serverprefix "! "` - Quotes are needed to use spaces in prefixes. - `[p]set serverprefix "@[botname] "` - This uses a mention as the prefix. - `[p]set serverprefix ! ? .` - Sets multiple prefixes. + - `[p]set serverprefix "Red - Discord Bot" ? - Sets the prefix for a specific server. Quotes are needed to use spaces in the server name. **Arguments:** - `[prefixes...]` - The prefixes the bot will respond to on this server. Leave blank to clear server prefixes. """ + if server is None: + server = ctx.guild + if not prefixes: - await ctx.bot.set_prefixes(guild=ctx.guild, prefixes=[]) + await ctx.bot.set_prefixes(guild=server, prefixes=[]) await ctx.send(_("Server prefixes have been reset.")) return if any(prefix.startswith("/") for prefix in prefixes): @@ -3630,7 +3638,7 @@ async def _set_serverprefix(self, ctx: commands.Context, *prefixes: str): await ctx.send(_("You cannot have a prefix longer than 25 characters.")) return prefixes = sorted(prefixes, reverse=True) - await ctx.bot.set_prefixes(guild=ctx.guild, prefixes=prefixes) + await ctx.bot.set_prefixes(guild=server, prefixes=prefixes) if len(prefixes) == 1: await ctx.send(_("Server prefix set.")) else: From 72172ff1cbf50f74a88dfc2376fdd234301f4ea7 Mon Sep 17 00:00:00 2001 From: Kowlin <10947836+Kowlin@users.noreply.github.com> Date: Fri, 2 Dec 2022 19:40:15 +0100 Subject: [PATCH 15/16] Bump discord.py to version 2.1.0 (#5920) --- redbot/cogs/mod/kickban.py | 10 ++++++---- setup.cfg | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/redbot/cogs/mod/kickban.py b/redbot/cogs/mod/kickban.py index d14720a7b48..89ba9af4f65 100644 --- a/redbot/cogs/mod/kickban.py +++ b/redbot/cogs/mod/kickban.py @@ -187,7 +187,7 @@ async def ban_user( else: username = user.name if hasattr(user, "name") else "Unknown" try: - await guild.ban(user, reason=audit_reason, delete_message_days=days) + await guild.ban(user, reason=audit_reason, delete_message_seconds=days * 86400) log.info( "{}({}) {}ned {}({}), deleting {} days worth of messages.".format( author.name, author.id, ban_type, username, user.id, str(days) @@ -538,7 +538,9 @@ def remove_processed(ids): banned.append(user_id) else: try: - await guild.ban(user, reason=audit_reason, delete_message_days=days) + await guild.ban( + user, reason=audit_reason, delete_message_seconds=days * 86400 + ) log.info("{}({}) hackbanned {}".format(author.name, author.id, user_id)) except discord.NotFound: errors[user_id] = _("User with ID {user_id} not found").format( @@ -647,7 +649,7 @@ async def tempban( audit_reason = get_audit_reason(author, reason, shorten=True) try: - await guild.ban(member, reason=audit_reason, delete_message_days=days) + await guild.ban(member, reason=audit_reason, delete_message_seconds=days * 86400) except discord.Forbidden: await ctx.send(_("I can't do that for some reason.")) except discord.HTTPException: @@ -706,7 +708,7 @@ async def softban(self, ctx: commands.Context, member: discord.Member, *, reason except discord.HTTPException: msg = None try: - await guild.ban(member, reason=audit_reason, delete_message_days=1) + await guild.ban(member, reason=audit_reason, delete_message_seconds=86400) except discord.errors.Forbidden: await ctx.send(_("My role is not high enough to softban that user.")) if msg is not None: diff --git a/setup.cfg b/setup.cfg index 7bb1233f9b3..93b7bc625d4 100644 --- a/setup.cfg +++ b/setup.cfg @@ -51,7 +51,7 @@ install_requires = colorama==0.4.4 commonmark==0.9.1 contextlib2==21.6.0 - discord.py==2.0.1 + discord.py==2.1.0 distro==1.6.0; sys_platform == "linux" fuzzywuzzy==0.18.0 idna==3.2 From f7c14b432108665b68716141d023b57fb7eead0a Mon Sep 17 00:00:00 2001 From: Jakub Kuczys Date: Fri, 9 Dec 2022 18:50:37 +0100 Subject: [PATCH 16/16] Modernize packaging-related things in Red (#5924) --- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/run_pip_compile.yaml | 84 +++++++++ .../workflows/scripts/compile_requirements.py | 35 ++++ .../workflows/scripts/merge_requirements.py | 134 ++++++++++++++ .github/workflows/tests.yml | 4 +- .readthedocs.yml | 2 +- MANIFEST.in | 6 + Makefile | 2 +- .../_includes/install-and-setup-red-unix.rst | 4 +- docs/install_guides/windows.rst | 4 +- make.bat | 2 +- make.ps1 | 2 +- pyproject.toml | 60 ++++-- redbot/__main__.py | 5 +- redbot/core/events.py | 152 +++++++++------- redbot/core/utils/_internal_utils.py | 6 +- redbot/launcher.py | 1 - requirements/base.in | 23 +++ requirements/base.txt | 88 +++++++++ requirements/extra-doc.in | 6 + requirements/extra-doc.txt | 46 +++++ requirements/extra-postgres.in | 3 + requirements/extra-postgres.txt | 2 + requirements/extra-style.in | 3 + requirements/extra-style.txt | 12 ++ requirements/extra-test.in | 6 + requirements/extra-test.txt | 33 ++++ setup.cfg | 146 --------------- setup.py | 63 ++++++- tests/core/test_version.py | 14 +- tools/bumpdeps.py | 171 ------------------ tools/dev-requirements.txt | 3 +- tools/primary_deps.ini | 46 ----- tox.ini | 7 +- 34 files changed, 701 insertions(+), 476 deletions(-) create mode 100644 .github/workflows/run_pip_compile.yaml create mode 100644 .github/workflows/scripts/compile_requirements.py create mode 100644 .github/workflows/scripts/merge_requirements.py create mode 100644 requirements/base.in create mode 100644 requirements/base.txt create mode 100644 requirements/extra-doc.in create mode 100644 requirements/extra-doc.txt create mode 100644 requirements/extra-postgres.in create mode 100644 requirements/extra-postgres.txt create mode 100644 requirements/extra-style.in create mode 100644 requirements/extra-style.txt create mode 100644 requirements/extra-test.in create mode 100644 requirements/extra-test.txt delete mode 100644 setup.cfg delete mode 100755 tools/bumpdeps.py delete mode 100644 tools/primary_deps.ini diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 6ec7bc5666a..cf4681d86ef 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -26,7 +26,7 @@ jobs: - name: Install dependencies run: | - python -m pip install -U pip setuptools wheel + python -m pip install -U pip wheel python -m pip install -e .[all] # Set the `CODEQL-PYTHON` environment variable to the Python executable # that includes the dependencies diff --git a/.github/workflows/run_pip_compile.yaml b/.github/workflows/run_pip_compile.yaml new file mode 100644 index 00000000000..47a47083fa1 --- /dev/null +++ b/.github/workflows/run_pip_compile.yaml @@ -0,0 +1,84 @@ +name: Generate requirements files with pip-compile. + +on: + workflow_dispatch: + +jobs: + generate_requirements: + name: Generate requirements files for ${{ matrix.os }} platform. + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: + - ubuntu-latest + - windows-latest + - macos-latest + steps: + - name: Checkout the repository. + uses: actions/checkout@v3 + + - name: Set up Python 3.8. + uses: actions/setup-python@v4 + with: + python-version: '3.8' + + - name: Install dependencies + run: | + python -m pip install -U pip + python -m pip install -U pip-tools + + - name: Generate requirements files. + id: compile_requirements + run: | + python .github/workflows/scripts/compile_requirements.py + + - name: Upload requirements files. + uses: actions/upload-artifact@v3 + with: + name: ${{ steps.compile_requirements.outputs.sys_platform }} + path: requirements/${{ steps.compile_requirements.outputs.sys_platform }}-*.txt + + merge_requirements: + name: Merge requirements files. + needs: generate_requirements + runs-on: ubuntu-latest + steps: + - name: Checkout the repository. + uses: actions/checkout@v3 + + - name: Set up Python 3.8. + uses: actions/setup-python@v4 + with: + python-version: '3.8' + + - name: Install dependencies + run: | + python -m pip install -U "packaging>=22.0" + + - name: Download Windows requirements. + uses: actions/download-artifact@v3 + with: + name: win32 + path: requirements + - name: Download Linux requirements. + uses: actions/download-artifact@v3 + with: + name: linux + path: requirements + - name: Download macOS requirements. + uses: actions/download-artifact@v3 + with: + name: darwin + path: requirements + + - name: Merge requirements files. + run: | + python .github/workflows/scripts/merge_requirements.py + + - name: Upload merged requirements files. + uses: actions/upload-artifact@v3 + with: + name: merged + path: | + requirements/base.txt + requirements/extra-*.txt diff --git a/.github/workflows/scripts/compile_requirements.py b/.github/workflows/scripts/compile_requirements.py new file mode 100644 index 00000000000..37aab01b84c --- /dev/null +++ b/.github/workflows/scripts/compile_requirements.py @@ -0,0 +1,35 @@ +import os +import shutil +import subprocess +import sys +from pathlib import Path + + +GITHUB_OUTPUT = os.environ["GITHUB_OUTPUT"] +REQUIREMENTS_FOLDER = Path(__file__).parents[3].absolute() / "requirements" +os.chdir(REQUIREMENTS_FOLDER) + + +def pip_compile(name: str) -> None: + subprocess.check_call( + ( + sys.executable, + "-m", + "piptools", + "compile", + "--upgrade", + "--verbose", + f"{name}.in", + "--output-file", + f"{sys.platform}-{name}.txt", + ) + ) + + +pip_compile("base") +shutil.copyfile(f"{sys.platform}-base.txt", "base.txt") +for file in REQUIREMENTS_FOLDER.glob("extra-*.in"): + pip_compile(file.stem) + +with open(GITHUB_OUTPUT, "a", encoding="utf-8") as fp: + fp.write(f"sys_platform={sys.platform}\n") diff --git a/.github/workflows/scripts/merge_requirements.py b/.github/workflows/scripts/merge_requirements.py new file mode 100644 index 00000000000..4ad2822b207 --- /dev/null +++ b/.github/workflows/scripts/merge_requirements.py @@ -0,0 +1,134 @@ +import os +from pathlib import Path +from typing import List, TextIO + +from packaging.markers import Marker +from packaging.requirements import Requirement + + +REQUIREMENTS_FOLDER = Path(__file__).parents[3].absolute() / "requirements" +os.chdir(REQUIREMENTS_FOLDER) + + +class RequirementData: + def __init__(self, requirement_string: str) -> None: + self.req = Requirement(requirement_string) + self.comments = set() + + @property + def name(self) -> str: + return self.req.name + + @property + def marker(self) -> Marker: + return self.req.marker + + @marker.setter + def marker(self, value: Marker) -> None: + self.req.marker = value + + +def get_requirements(fp: TextIO) -> List[RequirementData]: + requirements = [] + + current = None + for line in fp.read().splitlines(): + annotation_prefix = " # " + if line.startswith(annotation_prefix) and current is not None: + source = line[len(annotation_prefix) :].strip() + if source == "via": + continue + via_prefix = "via " + if source.startswith(via_prefix): + source = source[len(via_prefix) :] + current.comments.add(source) + elif line and not line.startswith(("#", " ")): + current = RequirementData(line) + requirements.append(current) + + return requirements + + +names = ["base"] +names.extend(file.stem for file in REQUIREMENTS_FOLDER.glob("extra-*.in")) +base_requirements = [] + +for name in names: + # {req_name: {sys_platform: RequirementData} + input_data = {} + all_platforms = set() + for file in REQUIREMENTS_FOLDER.glob(f"*-{name}.txt"): + platform_name = file.stem.split("-", maxsplit=1)[0] + all_platforms.add(platform_name) + with file.open(encoding="utf-8") as fp: + requirements = get_requirements(fp) + + for req in requirements: + platforms = input_data.setdefault(req.name, {}) + platforms[platform_name] = req + + output = base_requirements if name == "base" else [] + for req_name, platforms in input_data.items(): + req = next(iter(platforms.values())) + for other_req in platforms.values(): + if req.req != other_req.req: + raise RuntimeError(f"Incompatible requirements for {req_name}.") + + req.comments.update(other_req.comments) + + base_req = next( + (base_req for base_req in base_requirements if base_req.name == req.name), None + ) + if base_req is not None: + old_base_marker = base_req.marker + old_req_marker = req.marker + req.marker = base_req.marker = None + if base_req.req != req.req: + raise RuntimeError(f"Incompatible requirements for {req_name}.") + + base_req.marker = old_base_marker + req.marker = old_req_marker + if base_req.marker is None or base_req.marker == req.marker: + continue + + if len(platforms) == len(all_platforms): + output.append(req) + continue + elif len(platforms) < len(all_platforms - platforms.keys()): + platform_marker = " or ".join( + f"sys_platform == '{platform}'" for platform in platforms + ) + else: + platform_marker = " and ".join( + f"sys_platform != '{platform}'" for platform in all_platforms - platforms.keys() + ) + + new_marker = ( + f"({req.marker}) and ({platform_marker})" + if req.marker is not None + else platform_marker + ) + req.marker = Marker(new_marker) + if base_req is not None and base_req.marker == req.marker: + continue + + output.append(req) + + output.sort(key=lambda req: (req.marker is not None, req.name)) + with open(f"{name}.txt", "w+", encoding="utf-8") as fp: + for req in output: + fp.write(str(req.req)) + fp.write("\n") + comments = sorted(req.comments) + + if len(comments) == 1: + source = comments[0] + fp.write(" # via ") + fp.write(source) + fp.write("\n") + else: + fp.write(" # via\n") + for source in comments: + fp.write(" # ") + fp.write(source) + fp.write("\n") diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a34ad0b7214..4783b8b7c0f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -46,7 +46,7 @@ jobs: - name: Install tox run: | python -m pip install --upgrade pip - pip install tox + pip install 'tox<4' - name: Tox test env: TOXENV: ${{ matrix.tox_env }} @@ -82,7 +82,7 @@ jobs: - name: Install tox run: | python -m pip install --upgrade pip - pip install tox + pip install 'tox<4' - name: Tox test env: TOXENV: postgres diff --git a/.readthedocs.yml b/.readthedocs.yml index d180ab09a80..477d6aec40c 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -9,4 +9,4 @@ python: - method: pip path: . extra_requirements: - - docs + - doc diff --git a/MANIFEST.in b/MANIFEST.in index 62ccfa1ef3f..10bddfaa1c4 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,6 +2,12 @@ include LICENSE recursive-include redbot *.LICENSE +# include requirements files +include requirements/base.in +include requirements/base.txt +include requirements/extra-*.in +include requirements/extra-*.txt + # include locale files recursive-include redbot locales/*.po diff --git a/Makefile b/Makefile index a094ae28c4d..0f92df923e4 100644 --- a/Makefile +++ b/Makefile @@ -52,7 +52,7 @@ bumpdeps: # Development environment newenv: $(PYTHON) -m venv --clear .venv - .venv/bin/pip install -U pip setuptools wheel + .venv/bin/pip install -U pip wheel $(MAKE) syncenv syncenv: .venv/bin/pip install -Ur ./tools/dev-requirements.txt diff --git a/docs/install_guides/_includes/install-and-setup-red-unix.rst b/docs/install_guides/_includes/install-and-setup-red-unix.rst index 25b403678be..5e4a18b01ac 100644 --- a/docs/install_guides/_includes/install-and-setup-red-unix.rst +++ b/docs/install_guides/_includes/install-and-setup-red-unix.rst @@ -9,7 +9,7 @@ To install without additional config backend support: .. prompt:: bash :prompts: (redenv) $ - python -m pip install -U pip setuptools wheel + python -m pip install -U pip wheel python -m pip install -U Red-DiscordBot Or, to install with PostgreSQL support: @@ -17,7 +17,7 @@ Or, to install with PostgreSQL support: .. prompt:: bash :prompts: (redenv) $ - python -m pip install -U pip setuptools wheel + python -m pip install -U pip wheel python -m pip install -U "Red-DiscordBot[postgres]" diff --git a/docs/install_guides/windows.rst b/docs/install_guides/windows.rst index d6ce2fe1c13..d35a00890d1 100644 --- a/docs/install_guides/windows.rst +++ b/docs/install_guides/windows.rst @@ -134,7 +134,7 @@ Run **one** of the following set of commands, depending on what extras you want .. prompt:: batch :prompts: (redenv) C:\\> - python -m pip install -U pip setuptools wheel + python -m pip install -U pip wheel python -m pip install -U Red-DiscordBot * With PostgreSQL support: @@ -142,7 +142,7 @@ Run **one** of the following set of commands, depending on what extras you want .. prompt:: batch :prompts: (redenv) C:\\> - python -m pip install -U pip setuptools wheel + python -m pip install -U pip wheel python -m pip install -U Red-DiscordBot[postgres] -------------------------- diff --git a/make.bat b/make.bat index 2204b0172ab..7f3d046b516 100644 --- a/make.bat +++ b/make.bat @@ -24,7 +24,7 @@ goto:eof :newenv py -3.8 -m venv --clear .venv -"%~dp0.venv\Scripts\python" -m pip install -U pip setuptools wheel +"%~dp0.venv\Scripts\python" -m pip install -U pip wheel goto syncenv :syncenv diff --git a/make.ps1 b/make.ps1 index d960a626ef9..370a21837f1 100644 --- a/make.ps1 +++ b/make.ps1 @@ -56,7 +56,7 @@ function stylediff() { function newenv() { py -3.8 -m venv --clear .venv - & $PSScriptRoot\.venv\Scripts\python.exe -m pip install -U pip setuptools + & $PSScriptRoot\.venv\Scripts\python.exe -m pip install -U pip wheel syncenv } diff --git a/pyproject.toml b/pyproject.toml index f3ec923f7e7..08529574eb5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,14 +1,52 @@ [build-system] - requires = ["setuptools", "wheel"] - build-backend = "setuptools.build_meta" +requires = ["setuptools>=64", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "Red-DiscordBot" +description = "A highly customisable Discord bot" +readme = "README.md" +authors = [{ name = "Cog Creators" }] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Framework :: AsyncIO", + "Intended Audience :: Developers", + "Intended Audience :: End Users/Desktop", + "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", + "Natural Language :: English", + "Operating System :: MacOS :: MacOS X", + "Operating System :: Microsoft :: Windows", + "Operating System :: POSIX :: Linux", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Topic :: Communications :: Chat", +] +dynamic = ["version", "requires-python", "dependencies", "optional-dependencies"] + +[project.urls] +"Homepage" = "https://github.com/Cog-Creators/Red-DiscordBot" +"Discord Server" = "https://discord.gg/red" +"Documentation" = "https://docs.discord.red" +"Donate on Patreon" = "https://www.patreon.com/Red_Devs" +"Issue Tracker" = "https://github.com/Cog-Creators/Red-DiscordBot/issues" +"Source Code" = "https://github.com/Cog-Creators/Red-DiscordBot" + +[project.scripts] +redbot = "redbot.__main__:main" +redbot-setup = "redbot.setup:run_cli" +redbot-launcher = "redbot.launcher:main" + +[project.entry-points.pytest11] +red-discordbot = "redbot.pytest" [tool.black] - line-length = 99 - required-version = '22.1.0' - target-version = ['py38'] - include = '\.py$' - force-exclude = ''' - /( - redbot\/vendored - )/ - ''' +line-length = 99 +required-version = '22.1.0' +target-version = ['py38'] +include = '\.py$' +force-exclude = ''' +/( + redbot\/vendored +)/ +''' diff --git a/redbot/__main__.py b/redbot/__main__.py index 745b85fa767..f9de75e0357 100644 --- a/redbot/__main__.py +++ b/redbot/__main__.py @@ -10,7 +10,6 @@ import logging import os import pip -import pkg_resources import platform import shutil import signal @@ -335,7 +334,9 @@ async def run_bot(red: Red, cli_flags: Namespace) -> None: # `sys.path`, you must invoke the appropriate methods on the `working_set` instance # to keep it in sync." # Source: https://setuptools.readthedocs.io/en/latest/pkg_resources.html#workingset-objects - pkg_resources.working_set.add_entry(str(LIB_PATH)) + pkg_resources = sys.modules.get("pkg_resources") + if pkg_resources is not None: + pkg_resources.working_set.add_entry(str(LIB_PATH)) sys.meta_path.insert(0, SharedLibImportWarner()) if cli_flags.token: diff --git a/redbot/core/events.py b/redbot/core/events.py index beb1eababdb..69cb64e6868 100644 --- a/redbot/core/events.py +++ b/redbot/core/events.py @@ -6,11 +6,12 @@ import logging import traceback from datetime import datetime, timedelta, timezone +from typing import Tuple import aiohttp import discord -import pkg_resources -from pkg_resources import DistributionNotFound +import importlib.metadata +from packaging.requirements import Requirement from redbot.core import data_manager from redbot.core.commands import RedHelpFormatter, HelpSettings @@ -54,6 +55,88 @@ _ = Translator(__name__, __file__) +def get_outdated_red_messages(pypi_version: str, py_version_req: str) -> Tuple[str, str]: + outdated_red_message = _( + "Your Red instance is out of date! {} is the current version, however you are using {}!" + ).format(pypi_version, red_version) + rich_outdated_message = ( + f"[red]Outdated version![/red]\n" + f"[red]!!![/red]Version [cyan]{pypi_version}[/] is available, " + f"but you're using [cyan]{red_version}[/][red]!!![/red]" + ) + current_python = platform.python_version() + extra_update = _( + "\n\nWhile the following command should work in most scenarios as it is " + "based on your current OS, environment, and Python version, " + "**we highly recommend you to read the update docs at <{docs}> and " + "make sure there is nothing else that " + "needs to be done during the update.**" + ).format(docs="https://docs.discord.red/en/stable/update_red.html") + + if not expected_version(current_python, py_version_req): + extra_update += _( + "\n\nYou have Python `{py_version}` and this update " + "requires `{req_py}`; you cannot simply run the update command.\n\n" + "You will need to follow the update instructions in our docs above, " + "if you still need help updating after following the docs go to our " + "#support channel in " + ).format(py_version=current_python, req_py=py_version_req) + outdated_red_message += extra_update + return outdated_red_message, rich_outdated_message + + red_dist = importlib.metadata.distribution("Red-DiscordBot") + installed_extras = red_dist.metadata.get_all("Provides-Extra") + installed_extras.remove("dev") + installed_extras.remove("all") + distributions = {} + for req_str in red_dist.requires: + req = Requirement(req_str) + if req.marker is None or req.marker.evaluate(): + continue + for extra in reversed(installed_extras): + if not req.marker.evaluate({"extra": extra}): + continue + + # Check that the requirement is met. + # This is a bit simplified for our purposes and does not check + # whether the requirements of our requirements are met as well. + # This could potentially be an issue if we'll ever depend on + # a dependency's extra in our extra when we already depend on that + # in our base dependencies. However, considering that right now, all + # our dependencies are also fully pinned, this should not ever matter. + if req.name in distributions: + dist = distributions[req.name] + else: + try: + dist = importlib.metadata.distribution(req.name) + except importlib.metadata.PackageNotFoundError: + installed_extras.remove(extra) + dist = None + distributions[req.name] = dist + if dist is None or not req.specifier.contains(dist.version, prereleases=True): + installed_extras.remove(extra) + + if installed_extras: + package_extras = f"[{','.join(installed_extras)}]" + else: + package_extras = "" + + extra_update += _( + "\n\nTo update your bot, first shutdown your bot" + " then open a window of {console} (Not as admin) and run the following:" + "{command_1}\n" + "Once you've started up your bot again, we recommend that" + " you update any installed 3rd-party cogs with this command in Discord:" + "{command_2}" + ).format( + console=_("Command Prompt") if platform.system() == "Windows" else _("Terminal"), + command_1=f'```"{sys.executable}" -m pip install -U "Red-DiscordBot{package_extras}"```', + command_2=f"```[p]cog update```", + ) + outdated_red_message += extra_update + return outdated_red_message, rich_outdated_message + + def init_events(bot, cli_flags): @bot.event async def on_connect(): @@ -74,7 +157,6 @@ async def on_ready(): prefixes = cli_flags.prefix or (await bot._config.prefix()) lang = await bot._config.locale() - red_pkg = pkg_resources.get_distribution("Red-DiscordBot") dpy_version = discord.__version__ table_general_info = Table(show_edge=False, show_header=False, box=box.MINIMAL) @@ -97,69 +179,9 @@ async def on_ready(): pypi_version, py_version_req = await fetch_latest_red_version_info() outdated = pypi_version and pypi_version > red_version_info if outdated: - outdated_red_message = _( - "Your Red instance is out of date! {} is the current " - "version, however you are using {}!" - ).format(pypi_version, red_version) - rich_outdated_message = ( - f"[red]Outdated version![/red]\n" - f"[red]!!![/red]Version [cyan]{pypi_version}[/] is available, " - f"but you're using [cyan]{red_version}[/][red]!!![/red]" + outdated_red_message, rich_outdated_message = get_outdated_red_messages( + pypi_version, py_version_req ) - current_python = platform.python_version() - extra_update = _( - "\n\nWhile the following command should work in most scenarios as it is " - "based on your current OS, environment, and Python version, " - "**we highly recommend you to read the update docs at <{docs}> and " - "make sure there is nothing else that " - "needs to be done during the update.**" - ).format(docs="https://docs.discord.red/en/stable/update_red.html") - if expected_version(current_python, py_version_req): - installed_extras = [] - for extra, reqs in red_pkg._dep_map.items(): - if extra is None or extra in {"dev", "all"}: - continue - try: - pkg_resources.require(req.name for req in reqs) - except pkg_resources.DistributionNotFound: - pass - else: - installed_extras.append(extra) - - if installed_extras: - package_extras = f"[{','.join(installed_extras)}]" - else: - package_extras = "" - - extra_update += _( - "\n\nTo update your bot, first shutdown your " - "bot then open a window of {console} (Not as admin) and " - "run the following:\n\n" - ).format( - console=_("Command Prompt") - if platform.system() == "Windows" - else _("Terminal") - ) - extra_update += ( - '```"{python}" -m pip install -U Red-DiscordBot{package_extras}```'.format( - python=sys.executable, package_extras=package_extras - ) - ) - extra_update += _( - "\nOnce you've started up your bot again, if you have any 3rd-party cogs" - " installed we then highly recommend you update them with this command" - " in Discord: `[p]cog update`" - ) - - else: - extra_update += _( - "\n\nYou have Python `{py_version}` and this update " - "requires `{req_py}`; you cannot simply run the update command.\n\n" - "You will need to follow the update instructions in our docs above, " - "if you still need help updating after following the docs go to our " - "#support channel in " - ).format(py_version=current_python, req_py=py_version_req) - outdated_red_message += extra_update rich_console = rich.get_console() rich_console.print(INTRO, style="red", markup=False, highlight=False) diff --git a/redbot/core/utils/_internal_utils.py b/redbot/core/utils/_internal_utils.py index d6c1ed381e0..8407fdedfe8 100644 --- a/redbot/core/utils/_internal_utils.py +++ b/redbot/core/utils/_internal_utils.py @@ -31,7 +31,7 @@ import aiohttp import discord -import pkg_resources +from packaging.requirements import Requirement from fuzzywuzzy import fuzz, process from rich.progress import ProgressColumn from rich.progress_bar import ProgressBar @@ -316,8 +316,8 @@ async def preprocessor(bot: Red, destination: discord.abc.Messageable, content: def expected_version(current: str, expected: str) -> bool: - # `pkg_resources` needs a regular requirement string, so "x" serves as requirement's name here - return current in pkg_resources.Requirement.parse(f"x{expected}") + # Requirement needs a regular requirement string, so "x" serves as requirement's name here + return Requirement(f"x{expected}").specifier.contains(current, prereleases=True) async def fetch_latest_red_version_info() -> Tuple[Optional[VersionInfo], Optional[str]]: diff --git a/redbot/launcher.py b/redbot/launcher.py index f7382078ca7..f726590a5f5 100644 --- a/redbot/launcher.py +++ b/redbot/launcher.py @@ -12,7 +12,6 @@ import asyncio import aiohttp -import pkg_resources from redbot import MIN_PYTHON_VERSION from redbot.setup import ( basic_setup, diff --git a/requirements/base.in b/requirements/base.in new file mode 100644 index 00000000000..53bb09b0e7f --- /dev/null +++ b/requirements/base.in @@ -0,0 +1,23 @@ +aiohttp +aiohttp-json-rpc +aiosqlite +appdirs +apsw-wheels +babel +click +colorama +discord.py +fuzzywuzzy +markdown +packaging +psutil +python-dateutil +python-Levenshtein-wheels +PyNaCl +PyYAML +Red-Commons +Red-Lavalink>=0.11.0rc1 +rich +schema +distro; sys_platform == "linux" +uvloop; sys_platform != "win32" and platform_python_implementation == "CPython" diff --git a/requirements/base.txt b/requirements/base.txt new file mode 100644 index 00000000000..e69fa803bae --- /dev/null +++ b/requirements/base.txt @@ -0,0 +1,88 @@ +aiohttp==3.7.4.post0 + # via + # -r base.in + # aiohttp-json-rpc + # discord-py + # red-lavalink +aiohttp-json-rpc==0.13.3 + # via -r base.in +aiosqlite==0.17.0 + # via -r base.in +appdirs==1.4.4 + # via -r base.in +apsw-wheels==3.36.0.post1 + # via -r base.in +async-timeout==3.0.1 + # via aiohttp +attrs==21.2.0 + # via aiohttp +babel==2.9.1 + # via -r base.in +cffi==1.14.6 + # via pynacl +chardet==4.0.0 + # via aiohttp +click==8.0.1 + # via -r base.in +colorama==0.4.4 + # via + # -r base.in + # click +commonmark==0.9.1 + # via rich +contextlib2==21.6.0 + # via schema +discord-py==2.1.0 + # via + # -r base.in + # red-lavalink +fuzzywuzzy==0.18.0 + # via -r base.in +idna==3.2 + # via yarl +markdown==3.3.4 + # via -r base.in +multidict==5.1.0 + # via + # aiohttp + # yarl +packaging==22.0 + # via -r base.in +psutil==5.8.0 + # via -r base.in +pycparser==2.20 + # via cffi +pygments==2.10.0 + # via rich +pynacl==1.4.0 + # via -r base.in +python-dateutil==2.8.2 + # via -r base.in +python-levenshtein-wheels==0.13.2 + # via -r base.in +pytz==2021.1 + # via babel +pyyaml==5.4.1 + # via -r base.in +red-commons==1.0.0 + # via + # -r base.in + # red-lavalink +red-lavalink==0.11.0rc1 + # via -r base.in +rich==10.9.0 + # via -r base.in +schema==0.7.4 + # via -r base.in +six==1.16.0 + # via python-dateutil +typing-extensions==3.10.0.2 + # via rich +yarl==1.6.3 + # via + # -r base.in + # aiohttp +distro==1.6.0; sys_platform == "linux" + # via -r base.in +uvloop==0.16.0; sys_platform != "win32" and platform_python_implementation == "CPython" + # via -r base.in diff --git a/requirements/extra-doc.in b/requirements/extra-doc.in new file mode 100644 index 00000000000..2f14a30c11c --- /dev/null +++ b/requirements/extra-doc.in @@ -0,0 +1,6 @@ +-c base.txt + +Sphinx +sphinx-prompt +sphinx_rtd_theme +sphinxcontrib-trio diff --git a/requirements/extra-doc.txt b/requirements/extra-doc.txt new file mode 100644 index 00000000000..eed9cb2e351 --- /dev/null +++ b/requirements/extra-doc.txt @@ -0,0 +1,46 @@ +alabaster==0.7.12 + # via sphinx +certifi==2021.5.30 + # via requests +charset-normalizer==2.0.4 + # via requests +docutils==0.16 + # via + # sphinx + # sphinx-rtd-theme +imagesize==1.2.0 + # via sphinx +jinja2==3.0.1 + # via sphinx +markupsafe==2.0.1 + # via jinja2 +requests==2.26.0 + # via sphinx +snowballstemmer==2.1.0 + # via sphinx +sphinx==4.1.2 + # via + # -r extra-doc.in + # sphinx-prompt + # sphinx-rtd-theme + # sphinxcontrib-trio +sphinx-prompt==1.5.0 + # via -r extra-doc.in +sphinx-rtd-theme==0.5.2 + # via -r extra-doc.in +sphinxcontrib-applehelp==1.0.2 + # via sphinx +sphinxcontrib-devhelp==1.0.2 + # via sphinx +sphinxcontrib-htmlhelp==2.0.0 + # via sphinx +sphinxcontrib-jsmath==1.0.1 + # via sphinx +sphinxcontrib-qthelp==1.0.3 + # via sphinx +sphinxcontrib-serializinghtml==1.1.5 + # via sphinx +sphinxcontrib-trio==1.1.2 + # via -r extra-doc.in +urllib3==1.26.6 + # via requests diff --git a/requirements/extra-postgres.in b/requirements/extra-postgres.in new file mode 100644 index 00000000000..af77be5d57c --- /dev/null +++ b/requirements/extra-postgres.in @@ -0,0 +1,3 @@ +-c base.txt + +asyncpg diff --git a/requirements/extra-postgres.txt b/requirements/extra-postgres.txt new file mode 100644 index 00000000000..945c075af2b --- /dev/null +++ b/requirements/extra-postgres.txt @@ -0,0 +1,2 @@ +asyncpg==0.24.0 + # via -r extra-postgres.in diff --git a/requirements/extra-style.in b/requirements/extra-style.in new file mode 100644 index 00000000000..72cc61bd1d8 --- /dev/null +++ b/requirements/extra-style.in @@ -0,0 +1,3 @@ +-c base.txt + +black diff --git a/requirements/extra-style.txt b/requirements/extra-style.txt new file mode 100644 index 00000000000..dde309b4de9 --- /dev/null +++ b/requirements/extra-style.txt @@ -0,0 +1,12 @@ +black==22.1.0 + # via -r extra-style.in +mypy-extensions==0.4.3 + # via black +pathspec==0.9.0 + # via black +regex==2021.8.28 + # via black +toml==0.10.2 + # via black +typed-ast==1.4.3 + # via black diff --git a/requirements/extra-test.in b/requirements/extra-test.in new file mode 100644 index 00000000000..b726962fd8e --- /dev/null +++ b/requirements/extra-test.in @@ -0,0 +1,6 @@ +-c base.txt + +pylint +pytest +pytest-asyncio +pytest-mock diff --git a/requirements/extra-test.txt b/requirements/extra-test.txt new file mode 100644 index 00000000000..ea453cbf318 --- /dev/null +++ b/requirements/extra-test.txt @@ -0,0 +1,33 @@ +astroid==2.7.3 + # via pylint +iniconfig==1.1.1 + # via pytest +isort==5.9.3 + # via pylint +lazy-object-proxy==1.6.0 + # via astroid +mccabe==0.6.1 + # via pylint +platformdirs==2.3.0 + # via pylint +pluggy==1.0.0 + # via pytest +py==1.10.0 + # via pytest +pylint==2.10.2 + # via -r extra-test.in +pytest==6.2.5 + # via + # -r extra-test.in + # pytest-asyncio + # pytest-mock +pytest-asyncio==0.15.1 + # via -r extra-test.in +pytest-mock==3.6.1 + # via -r extra-test.in +toml==0.10.2 + # via + # pylint + # pytest +wrapt==1.12.1 + # via astroid diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 93b7bc625d4..00000000000 --- a/setup.cfg +++ /dev/null @@ -1,146 +0,0 @@ -[metadata] -name = Red-DiscordBot -description = A highly customisable Discord bot -license = GPL-3.0 -long_description = file: README.md -long_description_content_type = text/markdown; charset=UTF-8; variant=GFM -author = Cog-Creators -author_email = cogcreators@gmail.com -url = https://github.com/Cog-Creators/Red-DiscordBot -project_urls = - Discord Server = https://discord.gg/red - Documentation = https://docs.discord.red - Donate on Patreon = https://www.patreon.com/Red_Devs - Issue Tracker = https://github.com/Cog-Creators/Red-DiscordBot/issues - Source Code = https://github.com/Cog-Creators/Red-DiscordBot -classifiers = - # List at https://pypi.org/pypi?%3Aaction=list_classifiers - Development Status :: 5 - Production/Stable - Framework :: AsyncIO - Intended Audience :: Developers - Intended Audience :: End Users/Desktop - License :: OSI Approved :: GNU General Public License v3 (GPLv3) - Natural Language :: English - Operating System :: MacOS :: MacOS X - Operating System :: Microsoft :: Windows - Operating System :: POSIX :: Linux - Programming Language :: Python :: 3 :: Only - Programming Language :: Python :: 3.8 - Programming Language :: Python :: 3.9 - Topic :: Communications :: Chat -license_files = - LICENSE - redbot/**/*.LICENSE - -[options] -packages = find_namespace: -python_requires = >=3.8.1,<3.10 -include_package_data = True -install_requires = - aiohttp==3.7.4.post0 - aiohttp-json-rpc==0.13.3 - aiosqlite==0.17.0 - appdirs==1.4.4 - apsw-wheels==3.36.0.post1 - async-timeout==3.0.1 - attrs==21.2.0 - Babel==2.9.1 - cffi==1.14.6 - chardet==4.0.0 - click==8.0.1 - colorama==0.4.4 - commonmark==0.9.1 - contextlib2==21.6.0 - discord.py==2.1.0 - distro==1.6.0; sys_platform == "linux" - fuzzywuzzy==0.18.0 - idna==3.2 - Markdown==3.3.4 - multidict==5.1.0 - psutil==5.8.0 - pycparser==2.20 - Pygments==2.10.0 - PyNaCl==1.4.0 - python-dateutil==2.8.2 - python-Levenshtein-wheels==0.13.2 - pytz==2021.1 - PyYAML==5.4.1 - Red-Commons==1.0.0 - Red-Lavalink==0.11.0rc1 - rich==10.9.0 - schema==0.7.4 - six==1.16.0 - typing-extensions==3.10.0.2 - uvloop==0.16.0; sys_platform != "win32" and platform_python_implementation == "CPython" - yarl==1.6.3 - -[options.extras_require] -docs = - alabaster==0.7.12 - certifi==2021.5.30 - charset-normalizer==2.0.4 - docutils==0.16 - imagesize==1.2.0 - Jinja2==3.0.1 - MarkupSafe==2.0.1 - packaging==21.0 - pyparsing==2.4.7 - requests==2.26.0 - snowballstemmer==2.1.0 - Sphinx==4.1.2 - sphinx-prompt==1.5.0 - sphinx-rtd-theme==0.5.2 - sphinxcontrib-applehelp==1.0.2 - sphinxcontrib-devhelp==1.0.2 - sphinxcontrib-htmlhelp==2.0.0 - sphinxcontrib-jsmath==1.0.1 - sphinxcontrib-qthelp==1.0.3 - sphinxcontrib-serializinghtml==1.1.5 - sphinxcontrib-trio==1.1.2 - urllib3==1.26.6 -postgres = - asyncpg==0.24.0 -style = - black==22.1.0 - mypy-extensions==0.4.3 - pathspec==0.9.0 - regex==2021.8.28 - toml==0.10.2 - typed-ast==1.4.3 -test = - astroid==2.7.3 - iniconfig==1.1.1 - isort==5.9.3 - lazy-object-proxy==1.6.0 - mccabe==0.6.1 - packaging==21.0 - platformdirs==2.3.0 - pluggy==1.0.0 - py==1.10.0 - pylint==2.10.2 - pyparsing==2.4.7 - pytest==6.2.5 - pytest-asyncio==0.15.1 - pytest-mock==3.6.1 - toml==0.10.2 - wrapt==1.12.1 -all = - %(postgres)s -dev = - %(all)s - %(docs)s - %(style)s - %(test)s - -[options.entry_points] -console_scripts = - redbot=redbot.__main__:main - redbot-setup=redbot.setup:run_cli - redbot-launcher=redbot.launcher:main -pytest11 = - red-discordbot=redbot.pytest - -[options.packages.find] -include = - redbot - redbot.* diff --git a/setup.py b/setup.py index 0a6e94a2375..58d1b8f9fe1 100644 --- a/setup.py +++ b/setup.py @@ -1,17 +1,64 @@ import os import sys -from setuptools import setup +from pathlib import Path + +from setuptools import find_namespace_packages, setup + +ROOT_FOLDER = Path(__file__).parent.absolute() +REQUIREMENTS_FOLDER = ROOT_FOLDER / "requirements" # Since we're importing `redbot` package, we have to ensure that it's in sys.path. -sys.path.insert(0, os.path.abspath(os.path.dirname(__file__))) +sys.path.insert(0, str(ROOT_FOLDER)) from redbot import VersionInfo version, _ = VersionInfo._get_version(ignore_installed=True) -if os.getenv("TOX_RED", False) and sys.version_info >= (3, 10): - # We want to be able to test Python versions that we do not support yet. - setup(python_requires=">=3.8.1", version=version) -else: - # Metadata and options defined in setup.cfg - setup(version=version) + +def get_requirements(fp): + return [ + line.strip() + for line in fp.read().splitlines() + if line.strip() and not line.strip().startswith("#") + ] + + +def extras_combined(*extra_names): + return list( + { + req + for extra_name, extra_reqs in extras_require.items() + if not extra_names or extra_name in extra_names + for req in extra_reqs + } + ) + + +with open(REQUIREMENTS_FOLDER / "base.txt", encoding="utf-8") as fp: + install_requires = get_requirements(fp) + +extras_require = {} +for file in REQUIREMENTS_FOLDER.glob("extra-*.txt"): + with file.open(encoding="utf-8") as fp: + extras_require[file.stem[len("extra-") :]] = get_requirements(fp) + +extras_require["dev"] = extras_combined() +extras_require["all"] = extras_combined("postgres") + + +python_requires = ">=3.8.1" +if not os.getenv("TOX_RED", False) or sys.version_info < (3, 10): + python_requires += ",<3.10" + +# Metadata and options defined in pyproject.toml +setup( + version=version, + python_requires=python_requires, + # TODO: use [tool.setuptools.dynamic] table once this feature gets out of beta + install_requires=install_requires, + extras_require=extras_require, + # TODO: use [project] table once PEP 639 gets accepted + license_files=["LICENSE", "redbot/**/*.LICENSE"], + # TODO: use [tool.setuptools.packages] table once this feature gets out of beta + packages=find_namespace_packages(include=["redbot", "redbot.*"]), +) diff --git a/tests/core/test_version.py b/tests/core/test_version.py index 06242cd27b8..12c55c74901 100644 --- a/tests/core/test_version.py +++ b/tests/core/test_version.py @@ -1,7 +1,7 @@ import importlib.metadata -import pkg_resources import os import sys +from packaging.requirements import Requirement import pytest @@ -55,9 +55,9 @@ def test_python_version_has_lower_bound(): requires_python = importlib.metadata.metadata("Red-DiscordBot")["Requires-Python"] assert requires_python is not None - # `pkg_resources` needs a regular requirement string, so "x" serves as requirement's name here - req = pkg_resources.Requirement.parse(f"x{requires_python}") - assert any(op in (">", ">=") for op, version in req.specs) + # Requirement needs a regular requirement string, so "x" serves as requirement's name here + req = Requirement(f"x{requires_python}") + assert any(spec.operator in (">", ">=") for spec in req.specifier) @pytest.mark.skipif( @@ -72,6 +72,6 @@ def test_python_version_has_upper_bound(): requires_python = importlib.metadata.metadata("Red-DiscordBot")["Requires-Python"] assert requires_python is not None - # `pkg_resources` needs a regular requirement string, so "x" serves as requirement's name here - req = pkg_resources.Requirement.parse(f"x{requires_python}") - assert any(op in ("<", "<=") for op, version in req.specs) + # Requirement needs a regular requirement string, so "x" serves as requirement's name here + req = Requirement(f"x{requires_python}") + assert any(spec.operator in ("<", "<=") for spec in req.specifier) diff --git a/tools/bumpdeps.py b/tools/bumpdeps.py deleted file mode 100755 index b6b38d0d048..00000000000 --- a/tools/bumpdeps.py +++ /dev/null @@ -1,171 +0,0 @@ -#!/usr/bin/env python3.8 -"""Script to bump pinned dependencies in setup.cfg. - -This script aims to help update our list of pinned primary and -secondary dependencies in *setup.cfg*, using the unpinned primary -dependencies listed in *primary_deps.ini*. - -This script will not work when run on Windows. - -What this script does ---------------------- -It prints to stdout all primary and secondary dependencies for Red, -pinned to the latest possible version, within the constraints specified -in ``primary_deps.ini``. The output should be suitable for copying and -pasting into ``setup.cfg``. PEP 508 markers are preserved. - -How this script works ---------------------- -Overview: -1. Primary dependencies are read from primary_deps.ini using -setuptools' config parser. -2. A clean virtual environment is created in a temporary directory. -3. Core primary dependencies are passed to the ``pip install`` command -for that virtual environment. -4. Pinned primary dependencies are obtained by reading the output of -``pip freeze`` in that virtual environment, and any PEP 508 markers -shown with the requirement in ``primary_deps.ini`` are preserved. -5. Steps 2-4 are repeated for each extra requirement, but care is taken -not to duplicate core dependencies (primary or secondary) in the final -pinned extra dependencies. - -This script makes use of the *packaging* library to parse version -specifiers and environment markers. - -Known Limitations ------------------ -These limitations don't stop this script from being helpful, but -hopefully help explain in which situations some dependencies may need -to be listed manually in ``setup.cfg``. - -1. Whilst environment markers of any primary dependencies specified in -``primary_deps.ini`` are preserved in the output, they will not be -added to secondary dependencies. So for example, if some package -*dep1* has a dependency *dep2*, and *dep1* is listed as a primary -dependency in ``primary_deps.ini`` like follows:: - dep1; sys_platform == "linux" - -Then the output will look like this:: - dep1==1.1.1; sys_platform == "linux" - dep2==2.2.2 - -So even though ``dep1`` and its dependencies should only be installed on -Linux, in reality, its dependencies will be installed regardless. To -work around this, simply list the secondary dependencies in -``primary_deps.ini`` as well, with the environment markers. - -2. If a core requirement and an extra requirement have a common -sub-dependency, there is a chance the sub-dependency will have a version -conflict unless it is manually held back. This script will issue a -warning to stderr when it thinks this might be happening. - -3. Environment markers which exclude dependencies from the system -running this script will cause those dependencies to be excluded from -the output. So for example, if a dependency has the environment marker -``sys_platform == "darwin"``, and the script is being run on linux, then -this dependency will be ignored, and must be added to ``setup.cfg`` -manually. -""" -import shlex -import sys -import subprocess as sp -import tempfile -import textwrap -import venv -from pathlib import Path -from typing import Sequence, Iterable, Dict - -import packaging.requirements -import setuptools.config - -THIS_DIRECTORY = Path(__file__).parent -REQUIREMENTS_INI_PTH: Path = THIS_DIRECTORY / "primary_deps.ini" - -PIP_INSTALL_ARGS = ("install", "--upgrade") -PIP_FREEZE_ARGS = ("freeze", "--no-color") - - -def main() -> int: - if not REQUIREMENTS_INI_PTH.is_file(): - print("No primary_deps.ini found in the same directory as bumpdeps.py", file=sys.stderr) - return 1 - - primary_reqs_cfg = setuptools.config.read_configuration(str(REQUIREMENTS_INI_PTH)) - - print("[options]") - print("install_requires =") - core_primary_deps = primary_reqs_cfg["options"]["install_requires"] - full_core_reqs = get_all_reqs(core_primary_deps) - print(textwrap.indent("\n".join(map(str, full_core_reqs)), " " * 4)) - print() - - print("[options.extras_require]") - for extra, extra_primary_deps in primary_reqs_cfg["options"]["extras_require"].items(): - print(extra, "=") - full_extra_reqs = get_all_reqs( - extra_primary_deps, all_core_deps={r.name.lower(): r for r in full_core_reqs} - ) - print(textwrap.indent("\n".join(map(str, full_extra_reqs)), " " * 4)) - - return 0 - - -def get_all_reqs( - primary_deps: Iterable[str], all_core_deps: Dict[str, packaging.requirements.Requirement] = () -) -> Sequence[packaging.requirements.Requirement]: - reqs_dict = {r.name.lower(): r for r in map(packaging.requirements.Requirement, primary_deps)} - with tempfile.TemporaryDirectory() as tmpdir: - venv.create(tmpdir, system_site_packages=False, clear=True, with_pip=True) - tmpdir_pth = Path(tmpdir) - - pip_exe_pth = tmpdir_pth / "bin" / "pip" - - # Upgrade pip to latest version - sp.run((pip_exe_pth, *PIP_INSTALL_ARGS, "pip"), stdout=sp.DEVNULL, check=True) - - # Install the primary dependencies - sp.run( - (pip_exe_pth, *PIP_INSTALL_ARGS, *map(str, reqs_dict.values())), - stdout=sp.DEVNULL, - check=True, - ) - - # Get pinned primary+secondary dependencies from pip freeze - proc = sp.run( - (pip_exe_pth, *PIP_FREEZE_ARGS), stdout=sp.PIPE, check=True, encoding="utf-8" - ) - - # Return Requirement objects - ret = [] - for req_obj in map(packaging.requirements.Requirement, proc.stdout.strip().split("\n")): - dep_name = req_obj.name.lower() - # Don't include core dependencies if these are extra dependencies - if dep_name in all_core_deps: - if req_obj.specifier != all_core_deps[dep_name].specifier: - print( - f"[WARNING] {dep_name} is listed as both a core requirement and an extra " - f"requirement, and it's possible that their versions conflict!", - file=sys.stderr, - ) - continue - - # Preserve environment markers - if dep_name in reqs_dict: - req_obj.marker = reqs_dict[dep_name].marker - - ret.append(req_obj) - - return ret - - -if __name__ == "__main__": - try: - exit_code = main() - except sp.CalledProcessError as exc: - cmd = " ".join(map(lambda c: shlex.quote(str(c)), exc.cmd)) - print( - f"The following command failed with code {exc.returncode}:\n ", cmd, file=sys.stderr - ) - exit_code = 1 - - sys.exit(exit_code) diff --git a/tools/dev-requirements.txt b/tools/dev-requirements.txt index 962b6480a17..d99485829a4 100644 --- a/tools/dev-requirements.txt +++ b/tools/dev-requirements.txt @@ -1,3 +1,2 @@ -packaging -tox +tox<4 -e .[dev] diff --git a/tools/primary_deps.ini b/tools/primary_deps.ini deleted file mode 100644 index f0617fb0cf1..00000000000 --- a/tools/primary_deps.ini +++ /dev/null @@ -1,46 +0,0 @@ -# primary_deps.ini -# This file should list primary dependencies in terms of both core and -# extras, in setup.cfg format. A primary dependency is one which is -# used directly in Red, or otherwise is forced to be listed as a -# dependency. Version specifiers should be as liberal as possible. - -[options] -install_requires = - aiohttp - aiohttp-json-rpc - aiosqlite - appdirs - apsw-wheels - babel - click - colorama - discord.py - distro; sys_platform == "linux" - fuzzywuzzy - markdown - psutil - python-dateutil - python-Levenshtein-wheels - PyYAML - Red-Commons - Red-Lavalink - rich - schema - uvloop; sys_platform != "win32" and platform_python_implementation == "CPython" - PyNaCl - -[options.extras_require] -docs = - Sphinx - sphinx-prompt - sphinx_rtd_theme - sphinxcontrib-trio -postgres = - asyncpg -style = - black -test = - pylint - pytest - pytest-asyncio - pytest-mock diff --git a/tox.ini b/tox.ini index f613b080af7..14f1479c476 100644 --- a/tox.ini +++ b/tox.ini @@ -10,13 +10,14 @@ envlist = docs style skip_missing_interpreters = True +isolated_build = True [testenv] description = Run tests and basic automatic issue checking. whitelist_externals = pytest pylint -extras = voice, test +extras = test setenv = TOX_RED = 1 commands = @@ -28,7 +29,7 @@ commands = description = Run pytest with PostgreSQL backend whitelist_externals = pytest -extras = voice, test, postgres +extras = test, postgres setenv = TOX_RED = 1 RED_STORAGE_TYPE=postgres @@ -53,7 +54,7 @@ setenv = # Prioritise make.bat over any make.exe which might be on PATH PATHEXT=.BAT;.EXE basepython = python3.8 -extras = docs +extras = doc commands = sphinx-build -d "{toxworkdir}/docs_doctree" docs "{toxworkdir}/docs_out/html" -W --keep-going -bhtml sphinx-build -d "{toxworkdir}/docs_doctree" docs "{toxworkdir}/docs_out/doctest" -W --keep-going -bdoctest