diff --git a/telegram/__init__.py b/telegram/__init__.py index 688fc9534..09cd5bbb0 100644 --- a/telegram/__init__.py +++ b/telegram/__init__.py @@ -18,6 +18,153 @@ # along with this program. If not, see [http://www.gnu.org/licenses/]. """A library that provides a Python interface to the Telegram Bot API""" +__author__ = 'devs@python-telegram-bot.org' + +__all__ = ( # Keep this alphabetically ordered + 'Animation', + 'Audio', + 'Bot', + 'bot_api_version', + 'BotCommand', + 'BotCommandScope', + 'BotCommandScopeAllChatAdministrators', + 'BotCommandScopeAllGroupChats', + 'BotCommandScopeAllPrivateChats', + 'BotCommandScopeChat', + 'BotCommandScopeChatAdministrators', + 'BotCommandScopeChatMember', + 'BotCommandScopeDefault', + 'CallbackGame', + 'CallbackQuery', + 'Chat', + 'ChatInviteLink', + 'ChatJoinRequest', + 'ChatLocation', + 'ChatMember', + 'ChatMemberOwner', + 'ChatMemberAdministrator', + 'ChatMemberMember', + 'ChatMemberRestricted', + 'ChatMemberLeft', + 'ChatMemberBanned', + 'ChatMemberUpdated', + 'ChatPermissions', + 'ChatPhoto', + 'ChosenInlineResult', + 'constants', + 'Contact', + 'Credentials', + 'DataCredentials', + 'Dice', + 'Document', + 'EncryptedCredentials', + 'EncryptedPassportElement', + 'error', + 'File', + 'FileCredentials', + 'ForceReply', + 'Game', + 'GameHighScore', + 'helpers', + 'IdDocumentData', + 'InlineKeyboardButton', + 'InlineKeyboardMarkup', + 'InlineQuery', + 'InlineQueryResult', + 'InlineQueryResultArticle', + 'InlineQueryResultAudio', + 'InlineQueryResultCachedAudio', + 'InlineQueryResultCachedDocument', + 'InlineQueryResultCachedGif', + 'InlineQueryResultCachedMpeg4Gif', + 'InlineQueryResultCachedPhoto', + 'InlineQueryResultCachedSticker', + 'InlineQueryResultCachedVideo', + 'InlineQueryResultCachedVoice', + 'InlineQueryResultContact', + 'InlineQueryResultDocument', + 'InlineQueryResultGame', + 'InlineQueryResultGif', + 'InlineQueryResultLocation', + 'InlineQueryResultMpeg4Gif', + 'InlineQueryResultPhoto', + 'InlineQueryResultVenue', + 'InlineQueryResultVideo', + 'InlineQueryResultVoice', + 'InputContactMessageContent', + 'InputFile', + 'InputInvoiceMessageContent', + 'InputLocationMessageContent', + 'InputMedia', + 'InputMediaAnimation', + 'InputMediaAudio', + 'InputMediaDocument', + 'InputMediaPhoto', + 'InputMediaVideo', + 'InputMessageContent', + 'InputTextMessageContent', + 'InputVenueMessageContent', + 'Invoice', + 'KeyboardButton', + 'KeyboardButtonPollType', + 'LabeledPrice', + 'Location', + 'LoginUrl', + 'MaskPosition', + 'Message', + 'MessageAutoDeleteTimerChanged', + 'MessageEntity', + 'MessageId', + 'OrderInfo', + 'PassportData', + 'PassportElementError', + 'PassportElementErrorDataField', + 'PassportElementErrorFile', + 'PassportElementErrorFiles', + 'PassportElementErrorFrontSide', + 'PassportElementErrorReverseSide', + 'PassportElementErrorSelfie', + 'PassportElementErrorTranslationFile', + 'PassportElementErrorTranslationFiles', + 'PassportElementErrorUnspecified', + 'PassportFile', + 'PersonalDetails', + 'PhotoSize', + 'Poll', + 'PollAnswer', + 'PollOption', + 'PreCheckoutQuery', + 'ProximityAlertTriggered', + 'ReplyKeyboardMarkup', + 'ReplyKeyboardRemove', + 'ReplyMarkup', + 'request', + 'ResidentialAddress', + 'SecureData', + 'SecureValue', + 'ShippingAddress', + 'ShippingOption', + 'ShippingQuery', + 'Sticker', + 'StickerSet', + 'SuccessfulPayment', + 'TelegramObject', + 'Update', + 'User', + 'UserProfilePhotos', + 'Venue', + 'Video', + 'VideoNote', + 'Voice', + 'VoiceChatStarted', + 'VoiceChatEnded', + 'VoiceChatScheduled', + 'VoiceChatParticipantsInvited', + 'warnings', + 'WebhookInfo', +) + + from ._telegramobject import TelegramObject from ._botcommand import BotCommand from ._user import User @@ -160,144 +307,3 @@ from ._botcommandscope import ( ) from ._bot import Bot from ._version import __version__, bot_api_version # noqa: F401 - -__author__ = 'devs@python-telegram-bot.org' - -__all__ = ( # Keep this alphabetically ordered - 'Animation', - 'Audio', - 'Bot', - 'bot_api_version', - 'BotCommand', - 'BotCommandScope', - 'BotCommandScopeAllChatAdministrators', - 'BotCommandScopeAllGroupChats', - 'BotCommandScopeAllPrivateChats', - 'BotCommandScopeChat', - 'BotCommandScopeChatAdministrators', - 'BotCommandScopeChatMember', - 'BotCommandScopeDefault', - 'CallbackGame', - 'CallbackQuery', - 'Chat', - 'ChatInviteLink', - 'ChatJoinRequest', - 'ChatLocation', - 'ChatMember', - 'ChatMemberOwner', - 'ChatMemberAdministrator', - 'ChatMemberMember', - 'ChatMemberRestricted', - 'ChatMemberLeft', - 'ChatMemberBanned', - 'ChatMemberUpdated', - 'ChatPermissions', - 'ChatPhoto', - 'ChosenInlineResult', - 'Contact', - 'Credentials', - 'DataCredentials', - 'Dice', - 'Document', - 'EncryptedCredentials', - 'EncryptedPassportElement', - 'File', - 'FileCredentials', - 'ForceReply', - 'Game', - 'GameHighScore', - 'IdDocumentData', - 'InlineKeyboardButton', - 'InlineKeyboardMarkup', - 'InlineQuery', - 'InlineQueryResult', - 'InlineQueryResultArticle', - 'InlineQueryResultAudio', - 'InlineQueryResultCachedAudio', - 'InlineQueryResultCachedDocument', - 'InlineQueryResultCachedGif', - 'InlineQueryResultCachedMpeg4Gif', - 'InlineQueryResultCachedPhoto', - 'InlineQueryResultCachedSticker', - 'InlineQueryResultCachedVideo', - 'InlineQueryResultCachedVoice', - 'InlineQueryResultContact', - 'InlineQueryResultDocument', - 'InlineQueryResultGame', - 'InlineQueryResultGif', - 'InlineQueryResultLocation', - 'InlineQueryResultMpeg4Gif', - 'InlineQueryResultPhoto', - 'InlineQueryResultVenue', - 'InlineQueryResultVideo', - 'InlineQueryResultVoice', - 'InputContactMessageContent', - 'InputFile', - 'InputInvoiceMessageContent', - 'InputLocationMessageContent', - 'InputMedia', - 'InputMediaAnimation', - 'InputMediaAudio', - 'InputMediaDocument', - 'InputMediaPhoto', - 'InputMediaVideo', - 'InputMessageContent', - 'InputTextMessageContent', - 'InputVenueMessageContent', - 'Invoice', - 'KeyboardButton', - 'KeyboardButtonPollType', - 'LabeledPrice', - 'Location', - 'LoginUrl', - 'MaskPosition', - 'Message', - 'MessageAutoDeleteTimerChanged', - 'MessageEntity', - 'MessageId', - 'OrderInfo', - 'PassportData', - 'PassportElementError', - 'PassportElementErrorDataField', - 'PassportElementErrorFile', - 'PassportElementErrorFiles', - 'PassportElementErrorFrontSide', - 'PassportElementErrorReverseSide', - 'PassportElementErrorSelfie', - 'PassportElementErrorTranslationFile', - 'PassportElementErrorTranslationFiles', - 'PassportElementErrorUnspecified', - 'PassportFile', - 'PersonalDetails', - 'PhotoSize', - 'Poll', - 'PollAnswer', - 'PollOption', - 'PreCheckoutQuery', - 'ProximityAlertTriggered', - 'ReplyKeyboardMarkup', - 'ReplyKeyboardRemove', - 'ReplyMarkup', - 'ResidentialAddress', - 'SecureData', - 'SecureValue', - 'ShippingAddress', - 'ShippingOption', - 'ShippingQuery', - 'Sticker', - 'StickerSet', - 'SuccessfulPayment', - 'TelegramObject', - 'Update', - 'User', - 'UserProfilePhotos', - 'Venue', - 'Video', - 'VideoNote', - 'Voice', - 'VoiceChatStarted', - 'VoiceChatEnded', - 'VoiceChatScheduled', - 'VoiceChatParticipantsInvited', - 'WebhookInfo', -) diff --git a/telegram/_version.py b/telegram/_version.py index db4fa89e9..0650990ed 100644 --- a/telegram/_version.py +++ b/telegram/_version.py @@ -18,7 +18,8 @@ # along with this program. If not, see [http://www.gnu.org/licenses/]. # pylint: disable=missing-module-docstring +__version__ = '13.11' + from telegram import constants -__version__ = '13.11' bot_api_version = constants.BOT_API_VERSION # pylint: disable=invalid-name diff --git a/telegram/constants.py b/telegram/constants.py index 6a9a3ab44..c4344f664 100644 --- a/telegram/constants.py +++ b/telegram/constants.py @@ -33,9 +33,6 @@ Attributes: The following constants are related to specific classes or topics and are grouped into enums. If they are related to a specific class, then they are also available as attributes of those classes. """ -from enum import Enum, IntEnum -from typing import List - __all__ = [ 'BOT_API_VERSION', @@ -66,6 +63,9 @@ __all__ = [ 'UpdateType', ] +from enum import Enum, IntEnum +from typing import List + class _StringEnum(str, Enum): """Helper class for string enums where the value is not important to be displayed on diff --git a/telegram/error.py b/telegram/error.py index 7e49506c7..364ba5a59 100644 --- a/telegram/error.py +++ b/telegram/error.py @@ -17,6 +17,19 @@ # You should have received a copy of the GNU Lesser Public License # along with this program. If not, see [http://www.gnu.org/licenses/]. """This module contains an classes that represent Telegram errors.""" + +__all__ = ( + 'BadRequest', + 'ChatMigrated', + 'Conflict', + 'InvalidToken', + 'NetworkError', + 'PassportDecryptionError', + 'RetryAfter', + 'TelegramError', + 'TimedOut', +) + from typing import Tuple, Union diff --git a/telegram/ext/__init__.py b/telegram/ext/__init__.py index fb8891d35..3e44c9982 100644 --- a/telegram/ext/__init__.py +++ b/telegram/ext/__init__.py @@ -18,36 +18,6 @@ # along with this program. If not, see [http://www.gnu.org/licenses/]. """Extensions over the Telegram Bot API to facilitate bot making""" -from ._extbot import ExtBot -from ._basepersistence import BasePersistence, PersistenceInput -from ._picklepersistence import PicklePersistence -from ._dictpersistence import DictPersistence -from ._handler import Handler -from ._callbackcontext import CallbackContext -from ._contexttypes import ContextTypes -from ._dispatcher import Dispatcher, DispatcherHandlerStop -from ._jobqueue import JobQueue, Job -from ._updater import Updater -from ._callbackqueryhandler import CallbackQueryHandler -from ._choseninlineresulthandler import ChosenInlineResultHandler -from ._inlinequeryhandler import InlineQueryHandler -from . import filters -from ._messagehandler import MessageHandler -from ._commandhandler import CommandHandler, PrefixHandler -from ._stringcommandhandler import StringCommandHandler -from ._stringregexhandler import StringRegexHandler -from ._typehandler import TypeHandler -from ._conversationhandler import ConversationHandler -from ._precheckoutqueryhandler import PreCheckoutQueryHandler -from ._shippingqueryhandler import ShippingQueryHandler -from ._pollanswerhandler import PollAnswerHandler -from ._pollhandler import PollHandler -from ._chatmemberhandler import ChatMemberHandler -from ._chatjoinrequesthandler import ChatJoinRequestHandler -from ._defaults import Defaults -from ._callbackdatacache import CallbackDataCache, InvalidCallbackData -from ._builders import DispatcherBuilder, UpdaterBuilder - __all__ = ( 'BasePersistence', 'CallbackContext', @@ -85,3 +55,33 @@ __all__ = ( 'Updater', 'UpdaterBuilder', ) + +from ._extbot import ExtBot +from ._basepersistence import BasePersistence, PersistenceInput +from ._picklepersistence import PicklePersistence +from ._dictpersistence import DictPersistence +from ._handler import Handler +from ._callbackcontext import CallbackContext +from ._contexttypes import ContextTypes +from ._dispatcher import Dispatcher, DispatcherHandlerStop +from ._jobqueue import JobQueue, Job +from ._updater import Updater +from ._callbackqueryhandler import CallbackQueryHandler +from ._choseninlineresulthandler import ChosenInlineResultHandler +from ._inlinequeryhandler import InlineQueryHandler +from . import filters +from ._messagehandler import MessageHandler +from ._commandhandler import CommandHandler, PrefixHandler +from ._stringcommandhandler import StringCommandHandler +from ._stringregexhandler import StringRegexHandler +from ._typehandler import TypeHandler +from ._conversationhandler import ConversationHandler +from ._precheckoutqueryhandler import PreCheckoutQueryHandler +from ._shippingqueryhandler import ShippingQueryHandler +from ._pollanswerhandler import PollAnswerHandler +from ._pollhandler import PollHandler +from ._chatmemberhandler import ChatMemberHandler +from ._chatjoinrequesthandler import ChatJoinRequestHandler +from ._defaults import Defaults +from ._callbackdatacache import CallbackDataCache, InvalidCallbackData +from ._builders import DispatcherBuilder, UpdaterBuilder diff --git a/telegram/ext/filters.py b/telegram/ext/filters.py index 51a078087..c7fe23150 100644 --- a/telegram/ext/filters.py +++ b/telegram/ext/filters.py @@ -36,6 +36,58 @@ This module contains filters for use with :class:`telegram.ext.MessageHandler`, """ +__all__ = ( + 'ALL', + 'ANIMATION', + 'ATTACHMENT', + 'AUDIO', + 'BaseFilter', + 'CAPTION', + 'CHAT', + 'COMMAND', + 'CONTACT', + 'Caption', + 'CaptionEntity', + 'CaptionRegex', + 'Chat', + 'ChatType', + 'Command', + 'DOCUMENT', + 'Dice', + 'Document', + 'Entity', + 'FORWARDED', + 'ForwardedFrom', + 'GAME', + 'HAS_PROTECTED_CONTENT', + 'INVOICE', + 'IS_AUTOMATIC_FORWARD', + 'LOCATION', + 'Language', + 'MessageFilter', + 'PASSPORT_DATA', + 'PHOTO', + 'POLL', + 'REPLY', + 'Regex', + 'STICKER', + 'SUCCESSFUL_PAYMENT', + 'SenderChat', + 'StatusUpdate', + 'TEXT', + 'Text', + 'USER', + 'UpdateFilter', + 'UpdateType', + 'User', + 'VENUE', + 'VIA_BOT', + 'VIDEO', + 'VIDEO_NOTE', + 'VOICE', + 'ViaBot', +) + import mimetypes import re diff --git a/telegram/helpers.py b/telegram/helpers.py index df91708ce..90962de9f 100644 --- a/telegram/helpers.py +++ b/telegram/helpers.py @@ -23,6 +23,14 @@ module ``telegram.utils.helpers``. """ +__all__ = ( + 'create_deep_linked_url', + 'effective_message_type', + 'escape_markdown', + 'mention_html', + 'mention_markdown', +) + import re from html import escape diff --git a/telegram/request.py b/telegram/request.py index 99f7fedbb..20cc485d5 100644 --- a/telegram/request.py +++ b/telegram/request.py @@ -19,6 +19,9 @@ """This module contains the Request class which handles the communication with the Telegram servers. """ + +__all__ = ('Request',) + import logging import os import socket diff --git a/telegram/warnings.py b/telegram/warnings.py index 4676765d8..34db803aa 100644 --- a/telegram/warnings.py +++ b/telegram/warnings.py @@ -21,6 +21,8 @@ .. versionadded:: 14.0 """ +__all__ = ['PTBDeprecationWarning', 'PTBRuntimeWarning', 'PTBUserWarning'] + class PTBUserWarning(UserWarning): """ @@ -42,7 +44,7 @@ class PTBRuntimeWarning(PTBUserWarning, RuntimeWarning): __slots__ = () -# https://www.python.org/dev/peps/pep-0565/ recommends to use a custom warning class derived from +# https://www.python.org/dev/peps/pep-0565/ recommends using a custom warning class derived from # DeprecationWarning. We also subclass from TGUserWarning so users can easily 'switch off' warnings class PTBDeprecationWarning(PTBUserWarning, DeprecationWarning): """ diff --git a/tests/test_filters.py b/tests/test_filters.py index ba26ae09f..853460730 100644 --- a/tests/test_filters.py +++ b/tests/test_filters.py @@ -112,18 +112,9 @@ class TestFilters: assert len(mro_slots(inst)) == len(set(mro_slots(inst))), f"same slot in {name}" - assert len(mro_slots(inst)) == len(set(mro_slots(inst))), f"same slot in {name}" - for attr in cls.__slots__: assert getattr(inst, attr, 'err') != 'err', f"got extra slot '{attr}' for {name}" - class CustomFilter(filters.MessageFilter): - def filter(self, message: Message): - pass - - with pytest.warns(None): - CustomFilter().custom = 'allowed' # Test setting custom attr to custom filters - def test_filters_all(self, update): assert filters.ALL.check_update(update) diff --git a/tests/test_modules.py b/tests/test_modules.py new file mode 100644 index 000000000..52939dc23 --- /dev/null +++ b/tests/test_modules.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015-2021 +# Leandro Toledo de Souza +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser Public License for more details. +# +# You should have received a copy of the GNU Lesser Public License +# along with this program. If not, see [http://www.gnu.org/licenses/]. +"""This tests whether our submodules have __all__ or not. +Additionally also tests if all public submodules are included in __all__ for __init__'s. +""" +import importlib +import os +from pathlib import Path + + +def test_public_submodules_dunder_all(): + modules_to_search = list(Path('telegram').rglob('*.py')) + + for mod_path in modules_to_search: + path = str(mod_path) + folder = mod_path.parent + + if 'vendor' in path: # skip anything vendor related + continue + + if mod_path.name == '__init__.py' and '_' not in path[:-11]: # init of public submodules + mod = load_module(mod_path) + assert hasattr(mod, '__all__'), f"{folder}'s __init__ does not have an __all__!" + + pub_mods = get_public_submodules_in_folder(folder) + cond = all(pub_mod in mod.__all__ for pub_mod in pub_mods) + + assert cond, f"{path}'s __all__ should contain all public submodules ({pub_mods})!" + continue + + if '_' in path: # skip private modules + continue + + mod = load_module(mod_path) + assert hasattr(mod, '__all__'), f"{mod_path.name} does not have an __all__!" + + +def load_module(path: Path): + if path.name == "__init__.py": + mod_name = str(path.parent).replace(os.sep, '.') # telegram(.ext) format + else: + mod_name = f"{path.parent}.{path.stem}".replace(os.sep, '.') # telegram(.ext).(...) format + return importlib.import_module(mod_name) + + +def get_public_submodules_in_folder(path: Path): + return [i.stem for i in path.glob('[!_]*.py')] diff --git a/tests/test_slots.py b/tests/test_slots.py index a702a7f40..f1168c34c 100644 --- a/tests/test_slots.py +++ b/tests/test_slots.py @@ -17,9 +17,8 @@ # You should have received a copy of the GNU Lesser Public License # along with this program. If not, see [http://www.gnu.org/licenses/]. import importlib -import importlib.util import os -from glob import iglob +from pathlib import Path import inspect @@ -31,18 +30,13 @@ included = { # These modules/classes intentionally have __dict__. def test_class_has_slots_and_no_dict(): - tg_paths = [p for p in iglob("telegram/**/*.py", recursive=True) if 'vendor' not in p] + tg_paths = [p for p in Path('telegram').rglob("*.py") if 'vendor' not in str(p)] for path in tg_paths: - # windows uses backslashes: - if os.name == 'nt': - split_path = path.split('\\') - else: - split_path = path.split('/') - mod_name = f"telegram{'.ext.' if split_path[1] == 'ext' else '.'}{split_path[-1][:-3]}" - spec = importlib.util.spec_from_file_location(mod_name, path) - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) # Exec module to get classes in it. + if '__' in str(path): # Exclude __init__, __main__, etc + continue + mod_name = str(path)[:-3].replace(os.sep, '.') + module = importlib.import_module(mod_name) # import module to get classes in it. for name, cls in inspect.getmembers(module, inspect.isclass): if cls.__module__ != module.__name__ or any( # exclude 'imported' modules