ruff: Fix RUF012 Mutable class attributes should be annotated with typing.ClassVar.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
This commit is contained in:
Anders Kaseorg 2023-11-01 17:19:47 -07:00
parent b37708b96e
commit f26b861f51
25 changed files with 83 additions and 76 deletions

View file

@ -12,7 +12,7 @@ import urllib.request
from collections import OrderedDict from collections import OrderedDict
from concurrent.futures import ThreadPoolExecutor from concurrent.futures import ThreadPoolExecutor
from io import BytesIO from io import BytesIO
from typing import Any, Dict, List, Match, Optional, Set, Tuple, Type, Union from typing import Any, ClassVar, Dict, List, Match, Optional, Set, Tuple, Type, Union
import nio import nio
from nio.responses import ( from nio.responses import (
@ -52,7 +52,7 @@ class MatrixToZulip:
Matrix -> Zulip Matrix -> Zulip
""" """
non_formatted_messages: Dict[Type[nio.Event], str] = { non_formatted_messages: ClassVar[Dict[Type[nio.Event], str]] = {
nio.StickerEvent: "sticker", nio.StickerEvent: "sticker",
} }

View file

@ -5,7 +5,7 @@ import sys
from contextlib import contextmanager from contextlib import contextmanager
from subprocess import PIPE, Popen from subprocess import PIPE, Popen
from tempfile import mkdtemp from tempfile import mkdtemp
from typing import Any, Awaitable, Callable, Iterator, List from typing import Any, Awaitable, Callable, Final, Iterator, List
from unittest import TestCase, mock from unittest import TestCase, mock
import nio import nio
@ -209,13 +209,13 @@ class MatrixBridgeMatrixToZulipTests(TestCase):
class MatrixBridgeZulipToMatrixTests(TestCase): class MatrixBridgeZulipToMatrixTests(TestCase):
room = mock.MagicMock() room = mock.MagicMock()
valid_zulip_config = dict( valid_zulip_config: Final = dict(
stream="some stream", stream="some stream",
topic="some topic", topic="some topic",
email="some@email", email="some@email",
bridges={("some stream", "some topic"): room}, bridges={("some stream", "some topic"): room},
) )
valid_msg = dict( valid_msg: Final = dict(
sender_email="John@Smith.smith", # must not be equal to config:email sender_email="John@Smith.smith", # must not be equal to config:email
sender_id=42, sender_id=42,
type="stream", # Can only mirror Zulip streams type="stream", # Can only mirror Zulip streams

View file

@ -32,10 +32,10 @@ sa_family_t = c_ushort
class sockaddr(Structure): class sockaddr(Structure):
_fields_ = [ _fields_ = (
("sa_family", sa_family_t), ("sa_family", sa_family_t),
("sa_data", c_char * 14), ("sa_data", c_char * 14),
] )
# --- glibc/inet/netinet/in.h --- # --- glibc/inet/netinet/in.h ---
@ -45,34 +45,30 @@ in_addr_t = c_uint32
class in_addr(Structure): class in_addr(Structure):
_fields_ = [ _fields_ = (("s_addr", in_addr_t),)
("s_addr", in_addr_t),
]
class sockaddr_in(Structure): class sockaddr_in(Structure):
_fields_ = [ _fields_ = (
("sin_family", sa_family_t), ("sin_family", sa_family_t),
("sin_port", in_port_t), ("sin_port", in_port_t),
("sin_addr", in_addr), ("sin_addr", in_addr),
("sin_zero", c_uint8 * 8), ("sin_zero", c_uint8 * 8),
] )
class in6_addr(Structure): class in6_addr(Structure):
_fields_ = [ _fields_ = (("s6_addr", c_uint8 * 16),)
("s6_addr", c_uint8 * 16),
]
class sockaddr_in6(Structure): class sockaddr_in6(Structure):
_fields_ = [ _fields_ = (
("sin6_family", sa_family_t), ("sin6_family", sa_family_t),
("sin6_port", in_port_t), ("sin6_port", in_port_t),
("sin6_flowinfo", c_uint32), ("sin6_flowinfo", c_uint32),
("sin6_addr", in6_addr), ("sin6_addr", in6_addr),
("sin6_scope_id", c_uint32), ("sin6_scope_id", c_uint32),
] )
# --- glibc/stdlib/stdlib.h --- # --- glibc/stdlib/stdlib.h ---
@ -93,32 +89,32 @@ ZNotice_Kind_t = c_int
class _ZTimeval(Structure): class _ZTimeval(Structure):
_fields_ = [ _fields_ = (
("tv_sec", c_int), ("tv_sec", c_int),
("tv_usec", c_int), ("tv_usec", c_int),
] )
class ZUnique_Id_t(Structure): class ZUnique_Id_t(Structure):
_fields_ = [ _fields_ = (
("zuid_addr", in_addr), ("zuid_addr", in_addr),
("tv", _ZTimeval), ("tv", _ZTimeval),
] )
ZChecksum_t = c_uint ZChecksum_t = c_uint
class _ZSenderSockaddr(Union): class _ZSenderSockaddr(Union):
_fields_ = [ _fields_ = (
("sa", sockaddr), ("sa", sockaddr),
("ip4", sockaddr_in), ("ip4", sockaddr_in),
("ip6", sockaddr_in6), ("ip6", sockaddr_in6),
] )
class ZNotice_t(Structure): class ZNotice_t(Structure):
_fields_ = [ _fields_ = (
("z_packet", c_char_p), ("z_packet", c_char_p),
("z_version", c_char_p), ("z_version", c_char_p),
("z_kind", ZNotice_Kind_t), ("z_kind", ZNotice_Kind_t),
@ -147,15 +143,15 @@ class ZNotice_t(Structure):
("z_message_len", c_int), ("z_message_len", c_int),
("z_num_hdr_fields", c_uint), ("z_num_hdr_fields", c_uint),
("z_hdr_fields", POINTER(c_char_p)), ("z_hdr_fields", POINTER(c_char_p)),
] )
class ZSubscription_t(Structure): class ZSubscription_t(Structure):
_fields_ = [ _fields_ = (
("zsub_recipient", c_char_p), ("zsub_recipient", c_char_p),
("zsub_class", c_char_p), ("zsub_class", c_char_p),
("zsub_classinst", c_char_p), ("zsub_classinst", c_char_p),
] )
Code_t = c_int Code_t = c_int

View file

@ -14,6 +14,7 @@ import tempfile
import textwrap import textwrap
import time import time
from ctypes import POINTER, byref, c_char, c_int, c_ushort from ctypes import POINTER, byref, c_char, c_int, c_ushort
from enum import Enum, auto
from pathlib import Path from pathlib import Path
from queue import Queue from queue import Queue
from threading import Thread from threading import Thread
@ -29,8 +30,10 @@ from zulip import RandomExponentialBackoff
DEFAULT_SITE = "https://api.zulip.com" DEFAULT_SITE = "https://api.zulip.com"
class States: class States(Enum):
Startup, ZulipToZephyr, ZephyrToZulip = list(range(3)) Startup = auto()
ZulipToZephyr = auto()
ZephyrToZulip = auto()
CURRENT_STATE = States.Startup CURRENT_STATE = States.Startup

View file

@ -1,3 +1,4 @@
from typing import Final
from unittest.mock import patch from unittest.mock import patch
from requests.exceptions import ConnectionError from requests.exceptions import ConnectionError
@ -8,7 +9,7 @@ from zulip_bots.test_lib import BotTestCase, DefaultTests, StubBotHandler
class TestBeeminderBot(BotTestCase, DefaultTests): class TestBeeminderBot(BotTestCase, DefaultTests):
bot_name = "beeminder" bot_name = "beeminder"
normal_config = {"auth_token": "XXXXXX", "username": "aaron", "goalname": "goal"} normal_config: Final = {"auth_token": "XXXXXX", "username": "aaron", "goalname": "goal"}
help_message = """ help_message = """
You can add datapoints towards your beeminder goals \ You can add datapoints towards your beeminder goals \

View file

@ -5,7 +5,7 @@ from zulip_bots.game_handler import GameAdapter
class ConnectFourMessageHandler: class ConnectFourMessageHandler:
tokens = [":blue_circle:", ":red_circle:"] tokens = (":blue_circle:", ":red_circle:")
def parse_board(self, board: Any) -> str: def parse_board(self, board: Any) -> str:
# Header for the top of the board # Header for the top of the board

View file

@ -1,4 +1,4 @@
from typing import Dict, List from typing import Dict, Final, List
from typing_extensions import override from typing_extensions import override
@ -99,7 +99,7 @@ class TestConnectFourBot(BotTestCase, DefaultTests):
The first player to get 4 in a row wins!\n Good Luck!", The first player to get 4 in a row wins!\n Good Luck!",
) )
blank_board = [ blank_board: Final = [
[0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0],
@ -108,7 +108,7 @@ The first player to get 4 in a row wins!\n Good Luck!",
[0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0],
] ]
almost_win_board = [ almost_win_board: Final = [
[0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0],
@ -117,7 +117,7 @@ The first player to get 4 in a row wins!\n Good Luck!",
[1, -1, 0, 0, 0, 0, 0], [1, -1, 0, 0, 0, 0, 0],
] ]
almost_draw_board = [ almost_draw_board: Final = [
[1, -1, 1, -1, 1, -1, 0], [1, -1, 1, -1, 1, -1, 0],
[0, 0, 0, 0, 0, 0, 1], [0, 0, 0, 0, 0, 0, 1],
[0, 0, 0, 0, 0, 0, -1], [0, 0, 0, 0, 0, 0, -1],

View file

@ -1,3 +1,4 @@
from typing import Final
from unittest.mock import patch from unittest.mock import patch
from zulip_bots.bots.dropbox_share.test_util import ( from zulip_bots.bots.dropbox_share.test_util import (
@ -75,7 +76,7 @@ def get_help() -> str:
class TestDropboxBot(BotTestCase, DefaultTests): class TestDropboxBot(BotTestCase, DefaultTests):
bot_name = "dropbox_share" bot_name = "dropbox_share"
config_info = {"access_token": "1234567890"} config_info: Final = {"access_token": "1234567890"}
def test_bot_responds_to_empty_message(self): def test_bot_responds_to_empty_message(self):
with self.mock_config_info(self.config_info): with self.mock_config_info(self.config_info):

View file

@ -1,3 +1,4 @@
from typing import Final
from unittest.mock import patch from unittest.mock import patch
from requests.exceptions import ConnectionError from requests.exceptions import ConnectionError
@ -7,9 +8,9 @@ from zulip_bots.test_lib import BotTestCase, DefaultTests
class TestFlockBot(BotTestCase, DefaultTests): class TestFlockBot(BotTestCase, DefaultTests):
bot_name = "flock" bot_name = "flock"
normal_config = {"token": "12345"} normal_config: Final = {"token": "12345"}
message_config = {"token": "12345", "text": "Ricky: test message", "to": "u:somekey"} message_config: Final = {"token": "12345", "text": "Ricky: test message", "to": "u:somekey"}
help_message = """ help_message = """
You can send messages to any Flock user associated with your account from Zulip. You can send messages to any Flock user associated with your account from Zulip.

View file

@ -8,13 +8,13 @@ from zulip_bots.lib import BotHandler
class FrontHandler: class FrontHandler:
FRONT_API = "https://api2.frontapp.com/conversations/{}" FRONT_API = "https://api2.frontapp.com/conversations/{}"
COMMANDS = [ COMMANDS = (
("archive", "Archive a conversation."), ("archive", "Archive a conversation."),
("delete", "Delete a conversation."), ("delete", "Delete a conversation."),
("spam", "Mark a conversation as spam."), ("spam", "Mark a conversation as spam."),
("open", "Restore a conversation."), ("open", "Restore a conversation."),
("comment <text>", "Leave a comment."), ("comment <text>", "Leave a comment."),
] )
CNV_ID_REGEXP = "cnv_(?P<id>[0-9a-z]+)" CNV_ID_REGEXP = "cnv_(?P<id>[0-9a-z]+)"
COMMENT_PREFIX = "comment " COMMENT_PREFIX = "comment "

View file

@ -4,7 +4,7 @@ from zulip_bots.game_handler import BadMoveError, GameAdapter
class GameHandlerBotMessageHandler: class GameHandlerBotMessageHandler:
tokens = [":blue_circle:", ":red_circle:"] tokens = (":blue_circle:", ":red_circle:")
def parse_board(self, board: Any) -> str: def parse_board(self, board: Any) -> str:
return "foo" return "foo"

View file

@ -1,13 +1,13 @@
import copy import copy
from typing import Any, Dict, List, Tuple from typing import Any, Dict, Final, List, Tuple
from zulip_bots.game_handler import BadMoveError, GameAdapter from zulip_bots.game_handler import BadMoveError, GameAdapter
class GameOfFifteenModel: class GameOfFifteenModel:
final_board = [[0, 1, 2], [3, 4, 5], [6, 7, 8]] final_board: Final = [[0, 1, 2], [3, 4, 5], [6, 7, 8]]
initial_board = [[8, 7, 6], [5, 4, 3], [2, 1, 0]] initial_board: Final = [[8, 7, 6], [5, 4, 3], [2, 1, 0]]
def __init__(self, board: Any = None) -> None: def __init__(self, board: Any = None) -> None:
if board is not None: if board is not None:
@ -81,7 +81,7 @@ class GameOfFifteenModel:
class GameOfFifteenMessageHandler: class GameOfFifteenMessageHandler:
tiles = { tiles: Final = {
"0": ":grey_question:", "0": ":grey_question:",
"1": ":one:", "1": ":one:",
"2": ":two:", "2": ":two:",

View file

@ -1,4 +1,4 @@
from typing import Dict, List, Tuple from typing import Dict, Final, List, Tuple
from zulip_bots.bots.game_of_fifteen.game_of_fifteen import GameOfFifteenModel from zulip_bots.bots.game_of_fifteen.game_of_fifteen import GameOfFifteenModel
from zulip_bots.game_handler import BadMoveError from zulip_bots.game_handler import BadMoveError
@ -68,7 +68,7 @@ class TestGameOfFifteenBot(BotTestCase, DefaultTests):
"To make a move, type @-mention `move <tile1> <tile2> ...`", "To make a move, type @-mention `move <tile1> <tile2> ...`",
) )
winning_board = [[0, 1, 2], [3, 4, 5], [6, 7, 8]] winning_board: Final = [[0, 1, 2], [3, 4, 5], [6, 7, 8]]
def test_game_of_fifteen_logic(self) -> None: def test_game_of_fifteen_logic(self) -> None:
def confirm_available_moves( def confirm_available_moves(

View file

@ -1,3 +1,5 @@
from typing import Final
from typing_extensions import override from typing_extensions import override
from zulip_bots.test_file_utils import get_bot_message_handler from zulip_bots.test_file_utils import get_bot_message_handler
@ -6,8 +8,8 @@ from zulip_bots.test_lib import BotTestCase, DefaultTests, StubBotHandler
class TestGithubDetailBot(BotTestCase, DefaultTests): class TestGithubDetailBot(BotTestCase, DefaultTests):
bot_name = "github_detail" bot_name = "github_detail"
mock_config = {"owner": "zulip", "repo": "zulip"} mock_config: Final = {"owner": "zulip", "repo": "zulip"}
empty_config = {"owner": "", "repo": ""} empty_config: Final = {"owner": "", "repo": ""}
# Overrides default test_bot_usage(). # Overrides default test_bot_usage().
@override @override

View file

@ -1,12 +1,12 @@
# See readme.md for instructions on running this code. # See readme.md for instructions on running this code.
from typing import Dict from typing import Dict, Final
from zulip_bots.lib import BotHandler, use_storage from zulip_bots.lib import BotHandler, use_storage
class IncrementorHandler: class IncrementorHandler:
META = { META: Final = {
"name": "Incrementor", "name": "Incrementor",
"description": "Example bot to test the update_message() function.", "description": "Example bot to test the update_message() function.",
} }

View file

@ -1,22 +1,24 @@
from typing import Final
from zulip_bots.test_lib import BotTestCase, DefaultTests from zulip_bots.test_lib import BotTestCase, DefaultTests
class TestJiraBot(BotTestCase, DefaultTests): class TestJiraBot(BotTestCase, DefaultTests):
bot_name = "jira" bot_name = "jira"
MOCK_CONFIG_INFO = { MOCK_CONFIG_INFO: Final = {
"username": "example@example.com", "username": "example@example.com",
"password": "qwerty!123", "password": "qwerty!123",
"domain": "example.atlassian.net", "domain": "example.atlassian.net",
} }
MOCK_SCHEME_CONFIG_INFO = { MOCK_SCHEME_CONFIG_INFO: Final = {
"username": "example@example.com", "username": "example@example.com",
"password": "qwerty!123", "password": "qwerty!123",
"domain": "http://example.atlassian.net", "domain": "http://example.atlassian.net",
} }
MOCK_DISPLAY_CONFIG_INFO = { MOCK_DISPLAY_CONFIG_INFO: Final = {
"username": "example@example.com", "username": "example@example.com",
"password": "qwerty!123", "password": "qwerty!123",
"domain": "example.atlassian.net", "domain": "example.atlassian.net",

View file

@ -1,4 +1,4 @@
from typing import Any, List from typing import Any, Final, List
from zulip_bots.game_handler import GameAdapter, SamePlayerMoveError from zulip_bots.game_handler import GameAdapter, SamePlayerMoveError
@ -52,7 +52,7 @@ class MerelsModel:
class MerelsMessageHandler: class MerelsMessageHandler:
tokens = [":o_button:", ":cross_mark_button:"] tokens = (":o_button:", ":cross_mark_button:")
def parse_board(self, board: Any) -> str: def parse_board(self, board: Any) -> str:
return board return board
@ -73,7 +73,7 @@ class MerelsHandler(GameAdapter):
"@mention-bot". "@mention-bot".
""" """
META = { META: Final = {
"name": "merels", "name": "merels",
"description": "Lets you play merels against any player.", "description": "Lets you play merels against any player.",
} }

View file

@ -1,5 +1,5 @@
import logging import logging
from typing import Dict, Optional from typing import Dict, Final, Optional
import requests import requests
@ -18,7 +18,7 @@ class StackOverflowHandler:
the same stream that it was called from. the same stream that it was called from.
""" """
META = { META: Final = {
"name": "StackOverflow", "name": "StackOverflow",
"description": "Searches Stack Overflow for a query and returns the top 3 articles.", "description": "Searches Stack Overflow for a query and returns the top 3 articles.",
} }

View file

@ -1,6 +1,6 @@
import copy import copy
import random import random
from typing import Any, List, Tuple from typing import Any, Final, List, Tuple
from typing_extensions import override from typing_extensions import override
@ -15,7 +15,7 @@ class TicTacToeModel:
smarter = True smarter = True
# If smarter is True, the computer will do some extra thinking - it'll be harder for the user. # If smarter is True, the computer will do some extra thinking - it'll be harder for the user.
triplets = [ triplets: Final = [
[(0, 0), (0, 1), (0, 2)], # Row 1 [(0, 0), (0, 1), (0, 2)], # Row 1
[(1, 0), (1, 1), (1, 2)], # Row 2 [(1, 0), (1, 1), (1, 2)], # Row 2
[(2, 0), (2, 1), (2, 2)], # Row 3 [(2, 0), (2, 1), (2, 2)], # Row 3
@ -26,7 +26,7 @@ class TicTacToeModel:
[(0, 2), (1, 1), (2, 0)], # Diagonal 2 [(0, 2), (1, 1), (2, 0)], # Diagonal 2
] ]
initial_board = [[0, 0, 0], [0, 0, 0], [0, 0, 0]] initial_board: Final = [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
def __init__(self, board: Any = None) -> None: def __init__(self, board: Any = None) -> None:
if board is not None: if board is not None:
@ -203,7 +203,7 @@ class TicTacToeModel:
class TicTacToeMessageHandler: class TicTacToeMessageHandler:
tokens = [":x:", ":o:"] tokens = (":x:", ":o:")
def parse_row(self, row: Tuple[int, int], row_num: int) -> str: def parse_row(self, row: Tuple[int, int], row_num: int) -> str:
"""Takes the row passed in as a list and returns it as a string.""" """Takes the row passed in as a list and returns it as a string."""
@ -248,7 +248,7 @@ class TicTacToeHandler(GameAdapter):
"@mention-bot". "@mention-bot".
""" """
META = { META: Final = {
"name": "TicTacToe", "name": "TicTacToe",
"description": "Lets you play Tic-tac-toe against a computer.", "description": "Lets you play Tic-tac-toe against a computer.",
} }

View file

@ -1,3 +1,4 @@
from typing import Final
from unittest.mock import patch from unittest.mock import patch
from zulip_bots.test_file_utils import get_bot_message_handler, read_bot_fixture_data from zulip_bots.test_file_utils import get_bot_message_handler, read_bot_fixture_data
@ -6,7 +7,7 @@ from zulip_bots.test_lib import BotTestCase, DefaultTests, StubBotHandler
class TestTwitpostBot(BotTestCase, DefaultTests): class TestTwitpostBot(BotTestCase, DefaultTests):
bot_name = "twitpost" bot_name = "twitpost"
mock_config = { mock_config: Final = {
"consumer_key": "abcdefghijklmnopqrstuvwxy", "consumer_key": "abcdefghijklmnopqrstuvwxy",
"consumer_secret": "aabbccddeeffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyy", "consumer_secret": "aabbccddeeffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyy",
"access_token": "123456789012345678-ABCDefgh1234afdsa678lKj6gHhslsi", "access_token": "123456789012345678-ABCDefgh1234afdsa678lKj6gHhslsi",

View file

@ -2,13 +2,13 @@
import os import os
import re import re
from typing import Any, Dict, List, Set, Tuple, Union from typing import Any, Dict, Final, List, Set, Tuple, Union
from zulip_bots.lib import BotHandler from zulip_bots.lib import BotHandler
class VirtualFsHandler: class VirtualFsHandler:
META = { META: Final = {
"name": "VirtualFs", "name": "VirtualFs",
"description": "Provides a simple, permanent file system to store and retrieve strings.", "description": "Provides a simple, permanent file system to store and retrieve strings.",
} }

View file

@ -1,5 +1,5 @@
import logging import logging
from typing import Dict from typing import Dict, Final
import requests import requests
@ -20,7 +20,7 @@ class WikipediaHandler:
kind of external issue tracker as well. kind of external issue tracker as well.
""" """
META = { META: Final = {
"name": "Wikipedia", "name": "Wikipedia",
"description": "Searches Wikipedia for a term and returns the top 3 articles.", "description": "Searches Wikipedia for a term and returns the top 3 articles.",
} }

View file

@ -1,4 +1,4 @@
from typing import Any, Dict, Optional from typing import Any, Dict, Final, Optional
from unittest.mock import patch from unittest.mock import patch
from typing_extensions import override from typing_extensions import override
@ -10,13 +10,13 @@ from zulip_bots.test_lib import BotTestCase, DefaultTests, StubBotHandler
class TestWitaiBot(BotTestCase, DefaultTests): class TestWitaiBot(BotTestCase, DefaultTests):
bot_name = "witai" bot_name = "witai"
MOCK_CONFIG_INFO = { MOCK_CONFIG_INFO: Final = {
"token": "12345678", "token": "12345678",
"handler_location": "/Users/abcd/efgh", "handler_location": "/Users/abcd/efgh",
"help_message": "Qwertyuiop!", "help_message": "Qwertyuiop!",
} }
MOCK_WITAI_RESPONSE = { MOCK_WITAI_RESPONSE: Final = {
"_text": "What is your favorite food?", "_text": "What is your favorite food?",
"entities": {"intent": [{"confidence": 1.0, "value": "favorite_food"}]}, "entities": {"intent": [{"confidence": 1.0, "value": "favorite_food"}]},
} }

View file

@ -1,6 +1,6 @@
import logging import logging
import random import random
from typing import Dict, Optional from typing import Dict, Final, Optional
import requests import requests
@ -18,7 +18,7 @@ class XkcdHandler:
commands. commands.
""" """
META = { META: Final = {
"name": "XKCD", "name": "XKCD",
"description": "Fetches comic strips from https://xkcd.com.", "description": "Fetches comic strips from https://xkcd.com.",
} }

View file

@ -1,4 +1,4 @@
from typing import Dict from typing import Dict, Final
from unittest.mock import patch from unittest.mock import patch
from requests.exceptions import ConnectionError, HTTPError from requests.exceptions import ConnectionError, HTTPError
@ -10,7 +10,7 @@ from zulip_bots.test_lib import BotTestCase, DefaultTests, StubBotHandler
class TestYoutubeBot(BotTestCase, DefaultTests): class TestYoutubeBot(BotTestCase, DefaultTests):
bot_name = "youtube" bot_name = "youtube"
normal_config: Dict[str, str] = { normal_config: Final[Dict[str, str]] = {
"key": "12345678", "key": "12345678",
"number_of_results": "5", "number_of_results": "5",
"video_region": "US", "video_region": "US",