mirror of
https://github.com/python-telegram-bot/python-telegram-bot.git
synced 2025-01-08 19:34:12 +01:00
Merge pull request #668 from python-telegram-bot/official-test
Add an "official test"
This commit is contained in:
commit
4b3b0cb4cd
4 changed files with 164 additions and 1 deletions
|
@ -6,3 +6,4 @@ flaky
|
|||
yapf
|
||||
pre-commit
|
||||
pre-commit-hooks
|
||||
beautifulsoup4
|
||||
|
|
|
@ -534,6 +534,8 @@ class Bot(TelegramObject):
|
|||
reply_to_message_id=None,
|
||||
reply_markup=None,
|
||||
timeout=20.,
|
||||
width=None,
|
||||
height=None,
|
||||
**kwargs):
|
||||
"""Use this method to send video files, Telegram clients support mp4
|
||||
videos (other formats may be sent as telegram.Document).
|
||||
|
@ -544,6 +546,8 @@ class Bot(TelegramObject):
|
|||
already on the Telegram servers, or upload a new video file using
|
||||
multipart/form-data.
|
||||
duration (Optional[int]): Duration of sent video in seconds.
|
||||
width (Optional[int)): Video width.
|
||||
height (Optional[int]): Video height.
|
||||
caption (Optional[str]): Video caption (may also be used when resending videos by
|
||||
file_id).
|
||||
disable_notification (Optional[bool]): Sends the message silently. iOS users will not
|
||||
|
@ -570,6 +574,10 @@ class Bot(TelegramObject):
|
|||
data['duration'] = duration
|
||||
if caption:
|
||||
data['caption'] = caption
|
||||
if width:
|
||||
data['width'] = width
|
||||
if height:
|
||||
data['height'] = height
|
||||
|
||||
return url, data
|
||||
|
||||
|
|
|
@ -147,6 +147,7 @@ class Message(TelegramObject):
|
|||
successful_payment=None,
|
||||
bot=None,
|
||||
video_note=None,
|
||||
game=None,
|
||||
**kwargs):
|
||||
# Required
|
||||
self.message_id = int(message_id)
|
||||
|
@ -162,7 +163,7 @@ class Message(TelegramObject):
|
|||
self.text = text
|
||||
self.entities = entities or list()
|
||||
self.audio = audio
|
||||
self.game = kwargs.get('game')
|
||||
self.game = game
|
||||
self.document = document
|
||||
self.photo = photo
|
||||
self.sticker = sticker
|
||||
|
|
153
tests/test_official.py
Normal file
153
tests/test_official.py
Normal file
|
@ -0,0 +1,153 @@
|
|||
import sys
|
||||
import inspect
|
||||
import warnings
|
||||
from collections import namedtuple
|
||||
|
||||
import certifi
|
||||
import logging
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
sys.path.append('.')
|
||||
|
||||
from telegram.vendor.ptb_urllib3 import urllib3
|
||||
import telegram
|
||||
|
||||
IGNORED_OBJECTS = ('ResponseParameters', 'CallbackGame')
|
||||
IGNORED_PARAMETERS = {'self', 'args', 'kwargs', 'read_latency', 'network_delay', 'timeout', 'bot'}
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def find_next_sibling_until(tag, name, until):
|
||||
for sibling in tag.next_siblings:
|
||||
if sibling is until:
|
||||
return
|
||||
if sibling.name == name:
|
||||
return sibling
|
||||
|
||||
|
||||
def parse_table(h4):
|
||||
table = find_next_sibling_until(h4, 'table', h4.find_next_sibling('h4'))
|
||||
if not table:
|
||||
return []
|
||||
head = [td.text for td in table.tr.find_all('td')]
|
||||
row = namedtuple('{}TableRow'.format(h4.text), ','.join(head))
|
||||
t = []
|
||||
for tr in table.find_all('tr')[1:]:
|
||||
t.append(row(*[td.text for td in tr.find_all('td')]))
|
||||
return t
|
||||
|
||||
|
||||
def check_method(h4):
|
||||
name = h4.text
|
||||
method = getattr(telegram.Bot, name)
|
||||
table = parse_table(h4)
|
||||
|
||||
# Check arguments based on source
|
||||
sig = inspect.signature(method, follow_wrapped=True)
|
||||
|
||||
checked = []
|
||||
for parameter in table:
|
||||
param = sig.parameters.get(parameter.Parameters)
|
||||
logger.debug(parameter)
|
||||
assert param is not None
|
||||
# TODO: Check type via docstring
|
||||
# TODO: Check if optional or required
|
||||
checked.append(parameter.Parameters)
|
||||
|
||||
ignored = IGNORED_PARAMETERS.copy()
|
||||
if name == 'getUpdates':
|
||||
ignored -= {'timeout'} # Has it's own timeout parameter that we do wanna check for
|
||||
elif name == 'sendDocument':
|
||||
ignored |= {'filename'} # Undocumented
|
||||
elif name == 'setGameScore':
|
||||
ignored |= {'edit_message'} # TODO: Now deprecated, so no longer in telegrams docs
|
||||
|
||||
logger.debug((sig.parameters.keys(), checked, ignored,
|
||||
sig.parameters.keys() - checked - ignored))
|
||||
assert (sig.parameters.keys() ^ checked) - ignored == set()
|
||||
|
||||
|
||||
def check_object(h4):
|
||||
name = h4.text
|
||||
obj = getattr(telegram, name)
|
||||
table = parse_table(h4)
|
||||
|
||||
# Check arguments based on source
|
||||
sig = inspect.signature(obj, follow_wrapped=True)
|
||||
|
||||
checked = []
|
||||
for parameter in table:
|
||||
field = parameter.Field
|
||||
if field == 'from':
|
||||
field = 'from_user'
|
||||
elif name.startswith('InlineQueryResult') and field == 'type':
|
||||
continue
|
||||
elif field == 'remove_keyboard':
|
||||
continue
|
||||
|
||||
param = sig.parameters.get(field)
|
||||
logger.debug(parameter)
|
||||
assert param is not None
|
||||
# TODO: Check type via docstring
|
||||
# TODO: Check if optional or required
|
||||
checked.append(field)
|
||||
|
||||
ignored = IGNORED_PARAMETERS.copy()
|
||||
if name == 'InputFile':
|
||||
ignored |= {'data'}
|
||||
elif name == 'InlineQueryResult':
|
||||
ignored |= {'id'}
|
||||
elif name == 'User':
|
||||
ignored |= {'type'} # TODO: Deprecation
|
||||
|
||||
if name.startswith('InlineQueryResult'):
|
||||
ignored |= {'type'}
|
||||
|
||||
logger.debug((sig.parameters.keys(), checked, ignored,
|
||||
sig.parameters.keys() - checked - ignored))
|
||||
assert (sig.parameters.keys() ^ checked) - ignored == set()
|
||||
|
||||
|
||||
def test_official():
|
||||
if not sys.version_info >= (3, 5):
|
||||
warnings.warn('Not running tests, since follow_wrapped is not supported on this platform'
|
||||
'(python version >= 3.5 required)')
|
||||
return
|
||||
|
||||
http = urllib3.PoolManager(
|
||||
cert_reqs='CERT_REQUIRED',
|
||||
ca_certs=certifi.where())
|
||||
request = http.request('GET', 'https://core.telegram.org/bots/api')
|
||||
soup = BeautifulSoup(request.data.decode('utf-8'), 'html.parser')
|
||||
|
||||
for thing in soup.select('h4 > a.anchor'):
|
||||
# Methods and types don't have spaces in them, luckily all other sections of the docs do
|
||||
# TODO: don't depend on that
|
||||
if '-' not in thing['name']:
|
||||
h4 = thing.parent
|
||||
name = h4.text
|
||||
|
||||
test = None
|
||||
# Is it a method
|
||||
if h4.text[0].lower() == h4.text[0]:
|
||||
test = check_method
|
||||
else: # Or a type/object
|
||||
if name not in IGNORED_OBJECTS:
|
||||
test = check_object
|
||||
|
||||
if test:
|
||||
def fn():
|
||||
return test(h4)
|
||||
fn.description = '{}({}) ({})'.format(test.__name__, h4.text, __name__)
|
||||
yield fn
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Since we don't have the nice unittest asserts which show the comparison
|
||||
# We turn on debugging
|
||||
logging.basicConfig(
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
level=logging.DEBUG)
|
||||
for f in test_official():
|
||||
f()
|
Loading…
Reference in a new issue