python-telegram-bot/telegram/utils/request.py

183 lines
4.9 KiB
Python
Raw Normal View History

#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# 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 module contains methods to make POST and GET requests"""
import json
2016-05-31 13:45:43 +02:00
import socket
2016-05-29 23:49:29 +02:00
import certifi
2016-05-29 23:26:18 +02:00
import urllib3
2016-05-31 13:45:43 +02:00
from urllib3.connection import HTTPConnection
from telegram import (InputFile, TelegramError)
from telegram.error import Unauthorized, NetworkError, TimedOut, BadRequest
2016-05-29 23:26:18 +02:00
_CON_POOL = None
2016-06-17 22:59:32 +02:00
""":type: urllib3.HTTPSConnectionPool"""
CON_POOL_SIZE = 1
2016-05-29 23:26:18 +02:00
def _get_con_pool():
2016-06-01 21:38:08 +02:00
global _CON_POOL
2016-05-29 23:26:18 +02:00
if _CON_POOL is not None:
return _CON_POOL
2016-05-31 13:45:43 +02:00
_CON_POOL = urllib3.HTTPSConnectionPool(
host='api.telegram.org',
maxsize=CON_POOL_SIZE,
cert_reqs='CERT_REQUIRED',
ca_certs=certifi.where(),
socket_options=HTTPConnection.default_socket_options + [
(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1),
])
2016-05-29 23:26:18 +02:00
return _CON_POOL
def is_con_pool_initialized():
return _CON_POOL is not None
2016-05-29 23:26:18 +02:00
def _parse(json_data):
"""Try and parse the JSON returned from Telegram.
Returns:
2016-05-29 23:26:18 +02:00
dict: A JSON parsed as Python dict with results - on error this dict will be empty.
"""
decoded_s = json_data.decode('utf-8')
try:
data = json.loads(decoded_s)
except ValueError:
raise TelegramError('Invalid server response')
2015-09-07 20:54:12 +02:00
if not data.get('ok') and data.get('description'):
return data['description']
return data['result']
2015-09-16 05:21:45 +02:00
2016-05-29 23:26:18 +02:00
def _request_wrapper(*args, **kwargs):
"""Wraps urllib3 request for handling known exceptions.
2016-05-29 23:26:18 +02:00
Args:
args: unnamed arguments, passed to urllib3 request.
kwargs: keyword arguments, passed tp urllib3 request.
2016-05-29 23:26:18 +02:00
Returns:
str: A non-parsed JSON text.
2016-05-29 23:26:18 +02:00
Raises:
TelegramError
2016-05-29 23:26:18 +02:00
"""
2016-05-29 23:26:18 +02:00
try:
resp = _get_con_pool().request(*args, **kwargs)
except urllib3.exceptions.TimeoutError as error:
raise TimedOut()
except urllib3.exceptions.HTTPError as error:
# HTTPError must come last as its the base urllib3 exception class
# TODO: do something smart here; for now just raise NetowrkError
raise NetworkError('urllib3 HTTPError {0}'.format(error))
if 200 <= resp.status <= 299:
# 200-299 range are HTTP success statuses
return resp.data
2016-05-29 23:26:18 +02:00
try:
message = _parse(resp.data)
except ValueError:
raise NetworkError('Unknown HTTPError {0}'.format(resp.status))
if resp.status in (401, 403):
raise Unauthorized()
elif resp.status == 400:
raise BadRequest(repr(message))
elif resp.status == 502:
raise NetworkError('Bad Gateway')
else:
raise NetworkError('{0} ({1})'.format(message, resp.status))
2015-09-16 05:21:45 +02:00
def get(url):
"""Request an URL.
Args:
url:
The web location we want to retrieve.
2015-09-07 20:54:12 +02:00
Returns:
A JSON object.
2016-05-29 23:26:18 +02:00
"""
2016-05-29 23:26:18 +02:00
result = _request_wrapper('GET', url)
2015-09-07 20:54:12 +02:00
return _parse(result)
2016-05-15 03:46:40 +02:00
def post(url, data, timeout=None):
"""Request an URL.
Args:
url:
The web location we want to retrieve.
data:
A dict of (str, unicode) key/value pairs.
timeout:
float. If this value is specified, use it as the definitive timeout (in
seconds) for urlopen() operations. [Optional]
Notes:
If neither `timeout` nor `data['timeout']` is specified. The underlying
defaults are used.
2015-09-07 20:54:12 +02:00
Returns:
A JSON object.
"""
urlopen_kwargs = {}
2015-11-10 05:16:16 +01:00
if timeout is not None:
urlopen_kwargs['timeout'] = timeout
if InputFile.is_inputfile(data):
data = InputFile(data)
2016-05-29 23:26:18 +02:00
result = _request_wrapper('POST', url, body=data.to_form(), headers=data.headers)
else:
data = json.dumps(data)
2016-05-29 23:26:18 +02:00
result = _request_wrapper('POST',
url,
body=data.encode(),
headers={'Content-Type': 'application/json'})
2015-09-07 20:54:12 +02:00
return _parse(result)
2015-09-20 17:28:10 +02:00
2016-05-15 03:46:40 +02:00
def download(url, filename):
2015-09-20 17:28:10 +02:00
"""Download a file by its URL.
Args:
url:
The web location we want to retrieve.
filename:
The filename within the path to download the file.
2015-09-20 17:28:10 +02:00
2016-05-29 23:26:18 +02:00
"""
buf = _request_wrapper('GET', url)
with open(filename, 'wb') as fobj:
fobj.write(buf)