2017-06-14 13:42:57 +02:00
|
|
|
import sys
|
|
|
|
import inspect
|
|
|
|
import warnings
|
|
|
|
from collections import namedtuple
|
2017-06-22 20:17:47 +02:00
|
|
|
import platform
|
2017-06-14 13:42:57 +02:00
|
|
|
|
|
|
|
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():
|
2017-06-22 20:17:47 +02:00
|
|
|
if not sys.version_info >= (3, 5) or platform.python_implementation() != 'CPython':
|
|
|
|
warnings.warn('Not running "official" tests, since follow_wrapped is not supported'
|
|
|
|
'on this platform (cpython version >= 3.5 required)')
|
2017-06-14 13:42:57 +02:00
|
|
|
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)
|
2017-06-20 21:49:00 +02:00
|
|
|
fn.description = '{}({}) ({})'.format(test.__name__, h4.text, __name__)
|
2017-06-14 13:42:57 +02:00
|
|
|
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()
|