Skip to content
This repository has been archived by the owner on Jun 2, 2019. It is now read-only.

GAE compatibility #202

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
285 changes: 145 additions & 140 deletions telepot/api.py
Original file line number Diff line number Diff line change
@@ -1,140 +1,145 @@
import urllib3
import json
import re
import os
from . import exception, _isstring

# Suppress InsecurePlatformWarning
urllib3.disable_warnings()


_pools = {
'default': urllib3.PoolManager(num_pools=3, maxsize=10, retries=3, timeout=30),
}

_onetime_pool_spec = (urllib3.PoolManager, dict(num_pools=1, maxsize=1, retries=3, timeout=30))


def _create_onetime_pool():
cls, kw = _onetime_pool_spec
return cls(**kw)

def _methodurl(req, **user_kw):
token, method, params, files = req
return 'https://api.telegram.org/bot%s/%s' % (token, method)

def _which_pool(req, **user_kw):
token, method, params, files = req
return None if files else 'default'

def _guess_filename(obj):
name = getattr(obj, 'name', None)
if name and _isstring(name) and name[0] != '<' and name[-1] != '>':
return os.path.basename(name)

def _filetuple(key, f):
if not isinstance(f, tuple):
return (_guess_filename(f) or key, f.read())
elif len(f) == 1:
return (_guess_filename(f[0]) or key, f[0].read())
elif len(f) == 2:
return (f[0], f[1].read())
elif len(f) == 3:
return (f[0], f[1].read(), f[2])
else:
raise ValueError()

import sys
PY_3 = sys.version_info.major >= 3
def _fix_type(v):
if isinstance(v, float if PY_3 else (long, float)):
return str(v)
else:
return v

def _compose_fields(req, **user_kw):
token, method, params, files = req

fields = {k:_fix_type(v) for k,v in params.items()} if params is not None else {}
if files:
fields.update({k:_filetuple(k,v) for k,v in files.items()})

return fields

def _default_timeout(req, **user_kw):
name = _which_pool(req, **user_kw)
if name is None:
return _onetime_pool_spec[1]['timeout']
else:
return _pools[name].connection_pool_kw['timeout']

def _compose_kwargs(req, **user_kw):
token, method, params, files = req
kw = {}

if not params and not files:
kw['encode_multipart'] = False

if method == 'getUpdates' and params and 'timeout' in params:
# Ensure HTTP timeout is longer than getUpdates timeout
kw['timeout'] = params['timeout'] + _default_timeout(req, **user_kw)
elif files:
# Disable timeout if uploading files. For some reason, the larger the file,
# the longer it takes for the server to respond (after upload is finished).
# It is unclear how long timeout should be.
kw['timeout'] = None

# Let user-supplied arguments override
kw.update(user_kw)
return kw

def _transform(req, **user_kw):
kwargs = _compose_kwargs(req, **user_kw)

fields = _compose_fields(req, **user_kw)

url = _methodurl(req, **user_kw)

name = _which_pool(req, **user_kw)

if name is None:
pool = _create_onetime_pool()
else:
pool = _pools[name]

return pool.request_encode_body, ('POST', url, fields), kwargs

def _parse(response):
try:
text = response.data.decode('utf-8')
data = json.loads(text)
except ValueError: # No JSON object could be decoded
raise exception.BadHTTPResponse(response.status, text, response)

if data['ok']:
return data['result']
else:
description, error_code = data['description'], data['error_code']

# Look for specific error ...
for e in exception.TelegramError.__subclasses__():
n = len(e.DESCRIPTION_PATTERNS)
if any(map(re.search, e.DESCRIPTION_PATTERNS, n*[description], n*[re.IGNORECASE])):
raise e(description, error_code, data)

# ... or raise generic error
raise exception.TelegramError(description, error_code, data)

def request(req, **user_kw):
fn, args, kwargs = _transform(req, **user_kw)
r = fn(*args, **kwargs) # `fn` must be thread-safe
return _parse(r)

def _fileurl(req):
token, path = req
return 'https://api.telegram.org/file/bot%s/%s' % (token, path)

def download(req, **user_kw):
pool = _create_onetime_pool()
r = pool.request('GET', _fileurl(req), **user_kw)
return r
import urllib3
from urllib3.contrib.appengine import AppEngineManager, is_appengine_sandbox
import json
import re
import os
from . import exception, _isstring

# Suppress InsecurePlatformWarning
urllib3.disable_warnings()

if is_appengine_sandbox():
_pools = {
'default': AppEngineManager(),
}
_onetime_pool_spec = (AppEngineManager, {})
else:
_pools = {
'default': urllib3.PoolManager(num_pools=3, maxsize=10, retries=3, timeout=30),
}
_onetime_pool_spec = (urllib3.PoolManager, dict(num_pools=1, maxsize=1, retries=3, timeout=30))


def _create_onetime_pool():
cls, kw = _onetime_pool_spec
return cls(**kw)

def _methodurl(req, **user_kw):
token, method, params, files = req
return 'https://api.telegram.org/bot%s/%s' % (token, method)

def _which_pool(req, **user_kw):
token, method, params, files = req
return None if files else 'default'

def _guess_filename(obj):
name = getattr(obj, 'name', None)
if name and _isstring(name) and name[0] != '<' and name[-1] != '>':
return os.path.basename(name)

def _filetuple(key, f):
if not isinstance(f, tuple):
return (_guess_filename(f) or key, f.read())
elif len(f) == 1:
return (_guess_filename(f[0]) or key, f[0].read())
elif len(f) == 2:
return (f[0], f[1].read())
elif len(f) == 3:
return (f[0], f[1].read(), f[2])
else:
raise ValueError()

import sys
PY_3 = sys.version_info.major >= 3
def _fix_type(v):
if isinstance(v, float if PY_3 else (long, float)):
return str(v)
else:
return v

def _compose_fields(req, **user_kw):
token, method, params, files = req

fields = {k:_fix_type(v) for k,v in params.items()} if params is not None else {}
if files:
fields.update({k:_filetuple(k,v) for k,v in files.items()})

return fields

def _default_timeout(req, **user_kw):
name = _which_pool(req, **user_kw)
if name is None:
return _onetime_pool_spec[1]['timeout']
else:
return _pools[name].connection_pool_kw['timeout']

def _compose_kwargs(req, **user_kw):
token, method, params, files = req
kw = {}

if not params and not files:
kw['encode_multipart'] = False

if method == 'getUpdates' and params and 'timeout' in params:
# Ensure HTTP timeout is longer than getUpdates timeout
kw['timeout'] = params['timeout'] + _default_timeout(req, **user_kw)
elif files:
# Disable timeout if uploading files. For some reason, the larger the file,
# the longer it takes for the server to respond (after upload is finished).
# It is unclear how long timeout should be.
kw['timeout'] = None

# Let user-supplied arguments override
kw.update(user_kw)
return kw

def _transform(req, **user_kw):
kwargs = _compose_kwargs(req, **user_kw)

fields = _compose_fields(req, **user_kw)

url = _methodurl(req, **user_kw)

name = _which_pool(req, **user_kw)

if name is None:
pool = _create_onetime_pool()
else:
pool = _pools[name]

return pool.request_encode_body, ('POST', url, fields), kwargs

def _parse(response):
try:
text = response.data.decode('utf-8')
data = json.loads(text)
except ValueError: # No JSON object could be decoded
raise exception.BadHTTPResponse(response.status, text, response)

if data['ok']:
return data['result']
else:
description, error_code = data['description'], data['error_code']

# Look for specific error ...
for e in exception.TelegramError.__subclasses__():
n = len(e.DESCRIPTION_PATTERNS)
if any(map(re.search, e.DESCRIPTION_PATTERNS, n*[description], n*[re.IGNORECASE])):
raise e(description, error_code, data)

# ... or raise generic error
raise exception.TelegramError(description, error_code, data)

def request(req, **user_kw):
fn, args, kwargs = _transform(req, **user_kw)
r = fn(*args, **kwargs) # `fn` must be thread-safe
return _parse(r)

def _fileurl(req):
token, path = req
return 'https://api.telegram.org/file/bot%s/%s' % (token, path)

def download(req, **user_kw):
pool = _create_onetime_pool()
r = pool.request('GET', _fileurl(req), **user_kw)
return r