mirror of
https://github.com/python-telegram-bot/python-telegram-bot.git
synced 2025-01-16 14:33:12 +01:00
Add official test
Cross references method and class signatures with official docs
This commit is contained in:
parent
3863b4f371
commit
3ca6f6aa0e
1 changed files with 153 additions and 0 deletions
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)
|
||||
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