Sql
This commit is contained in:
@@ -0,0 +1,402 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2023
|
||||
# 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 an abstract class to make POST and GET requests."""
|
||||
import abc
|
||||
import asyncio
|
||||
import json
|
||||
from http import HTTPStatus
|
||||
from types import TracebackType
|
||||
from typing import AsyncContextManager, ClassVar, List, Optional, Tuple, Type, TypeVar, Union
|
||||
|
||||
from telegram._utils.defaultvalue import DEFAULT_NONE as _DEFAULT_NONE
|
||||
from telegram._utils.defaultvalue import DefaultValue
|
||||
from telegram._utils.logging import get_logger
|
||||
from telegram._utils.types import JSONDict, ODVInput
|
||||
from telegram._version import __version__ as ptb_ver
|
||||
from telegram.error import (
|
||||
BadRequest,
|
||||
ChatMigrated,
|
||||
Conflict,
|
||||
Forbidden,
|
||||
InvalidToken,
|
||||
NetworkError,
|
||||
RetryAfter,
|
||||
TelegramError,
|
||||
)
|
||||
from telegram.request._requestdata import RequestData
|
||||
|
||||
RT = TypeVar("RT", bound="BaseRequest")
|
||||
|
||||
_LOGGER = get_logger(__name__, class_name="BaseRequest")
|
||||
|
||||
|
||||
class BaseRequest(
|
||||
AsyncContextManager["BaseRequest"],
|
||||
abc.ABC,
|
||||
):
|
||||
"""Abstract interface class that allows python-telegram-bot to make requests to the Bot API.
|
||||
Can be implemented via different asyncio HTTP libraries. An implementation of this class
|
||||
must implement all abstract methods and properties.
|
||||
|
||||
Instances of this class can be used as asyncio context managers, where
|
||||
|
||||
.. code:: python
|
||||
|
||||
async with request_object:
|
||||
# code
|
||||
|
||||
is roughly equivalent to
|
||||
|
||||
.. code:: python
|
||||
|
||||
try:
|
||||
await request_object.initialize()
|
||||
# code
|
||||
finally:
|
||||
await request_object.shutdown()
|
||||
|
||||
Tip:
|
||||
JSON encoding and decoding is done with the standard library's :mod:`json` by default.
|
||||
To use a custom library for this, you can override :meth:`parse_json_payload` and implement
|
||||
custom logic to encode the keys of :attr:`telegram.request.RequestData.parameters`.
|
||||
|
||||
.. seealso:: :wiki:`Architecture Overview <Architecture>`,
|
||||
:wiki:`Builder Pattern <Builder-Pattern>`
|
||||
|
||||
.. versionadded:: 20.0
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
USER_AGENT: ClassVar[str] = f"python-telegram-bot v{ptb_ver} (https://python-telegram-bot.org)"
|
||||
""":obj:`str`: A description that can be used as user agent for requests made to the Bot API.
|
||||
"""
|
||||
DEFAULT_NONE: ClassVar[DefaultValue[None]] = _DEFAULT_NONE
|
||||
""":class:`object`: A special object that indicates that an argument of a function was not
|
||||
explicitly passed. Used for the timeout parameters of :meth:`post` and :meth:`do_request`.
|
||||
|
||||
Example:
|
||||
When calling ``request.post(url)``, ``request`` should use the default timeouts set on
|
||||
initialization. When calling ``request.post(url, connect_timeout=5, read_timeout=None)``,
|
||||
``request`` should use ``5`` for the connect timeout and :obj:`None` for the read timeout.
|
||||
|
||||
Use ``if parameter is (not) BaseRequest.DEFAULT_NONE:`` to check if the parameter was set.
|
||||
"""
|
||||
|
||||
async def __aenter__(self: RT) -> RT:
|
||||
try:
|
||||
await self.initialize()
|
||||
return self
|
||||
except Exception as exc:
|
||||
await self.shutdown()
|
||||
raise exc
|
||||
|
||||
async def __aexit__(
|
||||
self,
|
||||
exc_type: Optional[Type[BaseException]],
|
||||
exc_val: Optional[BaseException],
|
||||
exc_tb: Optional[TracebackType],
|
||||
) -> None:
|
||||
# Make sure not to return `True` so that exceptions are not suppressed
|
||||
# https://docs.python.org/3/reference/datamodel.html?#object.__aexit__
|
||||
await self.shutdown()
|
||||
|
||||
@abc.abstractmethod
|
||||
async def initialize(self) -> None:
|
||||
"""Initialize resources used by this class. Must be implemented by a subclass."""
|
||||
|
||||
@abc.abstractmethod
|
||||
async def shutdown(self) -> None:
|
||||
"""Stop & clear resources used by this class. Must be implemented by a subclass."""
|
||||
|
||||
async def post(
|
||||
self,
|
||||
url: str,
|
||||
request_data: RequestData = None,
|
||||
read_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
write_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
connect_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
pool_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
) -> Union[JSONDict, List[JSONDict], bool]:
|
||||
"""Makes a request to the Bot API handles the return code and parses the answer.
|
||||
|
||||
Warning:
|
||||
This method will be called by the methods of :class:`telegram.Bot` and should *not* be
|
||||
called manually.
|
||||
|
||||
Args:
|
||||
url (:obj:`str`): The URL to request.
|
||||
request_data (:class:`telegram.request.RequestData`, optional): An object containing
|
||||
information about parameters and files to upload for the request.
|
||||
read_timeout (:obj:`float` | :obj:`None`, optional): If passed, specifies the maximum
|
||||
amount of time (in seconds) to wait for a response from Telegram's server instead
|
||||
of the time specified during creating of this object. Defaults to
|
||||
:attr:`DEFAULT_NONE`.
|
||||
write_timeout (:obj:`float` | :obj:`None`, optional): If passed, specifies the maximum
|
||||
amount of time (in seconds) to wait for a write operation to complete (in terms of
|
||||
a network socket; i.e. POSTing a request or uploading a file) instead of the time
|
||||
specified during creating of this object. Defaults to :attr:`DEFAULT_NONE`.
|
||||
connect_timeout (:obj:`float` | :obj:`None`, optional): If passed, specifies the
|
||||
maximum amount of time (in seconds) to wait for a connection attempt to a server
|
||||
to succeed instead of the time specified during creating of this object. Defaults
|
||||
to :attr:`DEFAULT_NONE`.
|
||||
pool_timeout (:obj:`float` | :obj:`None`, optional): If passed, specifies the maximum
|
||||
amount of time (in seconds) to wait for a connection to become available instead
|
||||
of the time specified during creating of this object. Defaults to
|
||||
:attr:`DEFAULT_NONE`.
|
||||
|
||||
Returns:
|
||||
The JSON response of the Bot API.
|
||||
|
||||
"""
|
||||
result = await self._request_wrapper(
|
||||
url=url,
|
||||
method="POST",
|
||||
request_data=request_data,
|
||||
read_timeout=read_timeout,
|
||||
write_timeout=write_timeout,
|
||||
connect_timeout=connect_timeout,
|
||||
pool_timeout=pool_timeout,
|
||||
)
|
||||
json_data = self.parse_json_payload(result)
|
||||
# For successful requests, the results are in the 'result' entry
|
||||
# see https://core.telegram.org/bots/api#making-requests
|
||||
return json_data["result"]
|
||||
|
||||
async def retrieve(
|
||||
self,
|
||||
url: str,
|
||||
read_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
write_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
connect_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
pool_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
) -> bytes:
|
||||
"""Retrieve the contents of a file by its URL.
|
||||
|
||||
Warning:
|
||||
This method will be called by the methods of :class:`telegram.Bot` and should *not* be
|
||||
called manually.
|
||||
|
||||
Args:
|
||||
url (:obj:`str`): The web location we want to retrieve.
|
||||
read_timeout (:obj:`float` | :obj:`None`, optional): If passed, specifies the maximum
|
||||
amount of time (in seconds) to wait for a response from Telegram's server instead
|
||||
of the time specified during creating of this object. Defaults to
|
||||
:attr:`DEFAULT_NONE`.
|
||||
write_timeout (:obj:`float` | :obj:`None`, optional): If passed, specifies the maximum
|
||||
amount of time (in seconds) to wait for a write operation to complete (in terms of
|
||||
a network socket; i.e. POSTing a request or uploading a file) instead of the time
|
||||
specified during creating of this object. Defaults to :attr:`DEFAULT_NONE`.
|
||||
connect_timeout (:obj:`float` | :obj:`None`, optional): If passed, specifies the
|
||||
maximum amount of time (in seconds) to wait for a connection attempt to a server
|
||||
to succeed instead of the time specified during creating of this object. Defaults
|
||||
to :attr:`DEFAULT_NONE`.
|
||||
pool_timeout (:obj:`float` | :obj:`None`, optional): If passed, specifies the maximum
|
||||
amount of time (in seconds) to wait for a connection to become available instead
|
||||
of the time specified during creating of this object. Defaults to
|
||||
:attr:`DEFAULT_NONE`.
|
||||
|
||||
Returns:
|
||||
:obj:`bytes`: The files contents.
|
||||
|
||||
"""
|
||||
return await self._request_wrapper(
|
||||
url=url,
|
||||
method="GET",
|
||||
read_timeout=read_timeout,
|
||||
write_timeout=write_timeout,
|
||||
connect_timeout=connect_timeout,
|
||||
pool_timeout=pool_timeout,
|
||||
)
|
||||
|
||||
async def _request_wrapper(
|
||||
self,
|
||||
url: str,
|
||||
method: str,
|
||||
request_data: RequestData = None,
|
||||
read_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
write_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
connect_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
pool_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
) -> bytes:
|
||||
"""Wraps the real implementation request method.
|
||||
|
||||
Performs the following tasks:
|
||||
* Handle the various HTTP response codes.
|
||||
* Parse the Telegram server response.
|
||||
|
||||
Args:
|
||||
url (:obj:`str`): The URL to request.
|
||||
method (:obj:`str`): HTTP method (i.e. 'POST', 'GET', etc.).
|
||||
request_data (:class:`telegram.request.RequestData`, optional): An object containing
|
||||
information about parameters and files to upload for the request.
|
||||
read_timeout (:obj:`float` | :obj:`None`, optional): If passed, specifies the maximum
|
||||
amount of time (in seconds) to wait for a response from Telegram's server instead
|
||||
of the time specified during creating of this object. Defaults to
|
||||
:attr:`DEFAULT_NONE`.
|
||||
write_timeout (:obj:`float` | :obj:`None`, optional): If passed, specifies the maximum
|
||||
amount of time (in seconds) to wait for a write operation to complete (in terms of
|
||||
a network socket; i.e. POSTing a request or uploading a file) instead of the time
|
||||
specified during creating of this object. Defaults to :attr:`DEFAULT_NONE`.
|
||||
connect_timeout (:obj:`float` | :obj:`None`, optional): If passed, specifies the
|
||||
maximum amount of time (in seconds) to wait for a connection attempt to a server
|
||||
to succeed instead of the time specified during creating of this object. Defaults
|
||||
to :attr:`DEFAULT_NONE`.
|
||||
pool_timeout (:obj:`float` | :obj:`None`, optional): If passed, specifies the maximum
|
||||
amount of time (in seconds) to wait for a connection to become available instead
|
||||
of the time specified during creating of this object. Defaults to
|
||||
:attr:`DEFAULT_NONE`.
|
||||
|
||||
Returns:
|
||||
bytes: The payload part of the HTTP server response.
|
||||
|
||||
Raises:
|
||||
TelegramError
|
||||
|
||||
"""
|
||||
# TGs response also has the fields 'ok' and 'error_code'.
|
||||
# However, we rather rely on the HTTP status code for now.
|
||||
|
||||
try:
|
||||
code, payload = await self.do_request(
|
||||
url=url,
|
||||
method=method,
|
||||
request_data=request_data,
|
||||
read_timeout=read_timeout,
|
||||
write_timeout=write_timeout,
|
||||
connect_timeout=connect_timeout,
|
||||
pool_timeout=pool_timeout,
|
||||
)
|
||||
except asyncio.CancelledError as exc:
|
||||
# TODO: in py3.8+, CancelledError is a subclass of BaseException, so we can drop this
|
||||
# clause when we drop py3.7
|
||||
raise exc
|
||||
except TelegramError as exc:
|
||||
raise exc
|
||||
except Exception as exc:
|
||||
raise NetworkError(f"Unknown error in HTTP implementation: {repr(exc)}") from exc
|
||||
|
||||
if HTTPStatus.OK <= code <= 299:
|
||||
# 200-299 range are HTTP success statuses
|
||||
return payload
|
||||
|
||||
response_data = self.parse_json_payload(payload)
|
||||
|
||||
description = response_data.get("description")
|
||||
message = description if description else "Unknown HTTPError"
|
||||
|
||||
# In some special cases, we can raise more informative exceptions:
|
||||
# see https://core.telegram.org/bots/api#responseparameters and
|
||||
# https://core.telegram.org/bots/api#making-requests
|
||||
parameters = response_data.get("parameters")
|
||||
if parameters:
|
||||
migrate_to_chat_id = parameters.get("migrate_to_chat_id")
|
||||
if migrate_to_chat_id:
|
||||
raise ChatMigrated(migrate_to_chat_id)
|
||||
retry_after = parameters.get("retry_after")
|
||||
if retry_after:
|
||||
raise RetryAfter(retry_after)
|
||||
|
||||
message += f"\nThe server response contained unknown parameters: {parameters}"
|
||||
|
||||
if code == HTTPStatus.FORBIDDEN: # 403
|
||||
raise Forbidden(message)
|
||||
if code in (HTTPStatus.NOT_FOUND, HTTPStatus.UNAUTHORIZED): # 404 and 401
|
||||
# TG returns 404 Not found for
|
||||
# 1) malformed tokens
|
||||
# 2) correct tokens but non-existing method, e.g. api.tg.org/botTOKEN/unkonwnMethod
|
||||
# We can basically rule out 2) since we don't let users make requests manually
|
||||
# TG returns 401 Unauthorized for correctly formatted tokens that are not valid
|
||||
raise InvalidToken(message)
|
||||
if code == HTTPStatus.BAD_REQUEST: # 400
|
||||
raise BadRequest(message)
|
||||
if code == HTTPStatus.CONFLICT: # 409
|
||||
raise Conflict(message)
|
||||
if code == HTTPStatus.BAD_GATEWAY: # 502
|
||||
raise NetworkError(description or "Bad Gateway")
|
||||
raise NetworkError(f"{message} ({code})")
|
||||
|
||||
@staticmethod
|
||||
def parse_json_payload(payload: bytes) -> JSONDict:
|
||||
"""Parse the JSON returned from Telegram.
|
||||
|
||||
Tip:
|
||||
By default, this method uses the standard library's :func:`json.loads` and
|
||||
``errors="replace"`` in :meth:`bytes.decode`.
|
||||
You can override it to customize either of these behaviors.
|
||||
|
||||
Args:
|
||||
payload (:obj:`bytes`): The UTF-8 encoded JSON payload as returned by Telegram.
|
||||
|
||||
Returns:
|
||||
dict: A JSON parsed as Python dict with results.
|
||||
|
||||
Raises:
|
||||
TelegramError: If loading the JSON data failed
|
||||
"""
|
||||
decoded_s = payload.decode("utf-8", "replace")
|
||||
try:
|
||||
return json.loads(decoded_s)
|
||||
except ValueError as exc:
|
||||
_LOGGER.error('Can not load invalid JSON data: "%s"', decoded_s)
|
||||
raise TelegramError("Invalid server response") from exc
|
||||
|
||||
@abc.abstractmethod
|
||||
async def do_request(
|
||||
self,
|
||||
url: str,
|
||||
method: str,
|
||||
request_data: RequestData = None,
|
||||
read_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
write_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
connect_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
pool_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
) -> Tuple[int, bytes]:
|
||||
"""Makes a request to the Bot API. Must be implemented by a subclass.
|
||||
|
||||
Warning:
|
||||
This method will be called by :meth:`post` and :meth:`retrieve`. It should *not* be
|
||||
called manually.
|
||||
|
||||
Args:
|
||||
url (:obj:`str`): The URL to request.
|
||||
method (:obj:`str`): HTTP method (i.e. ``'POST'``, ``'GET'``, etc.).
|
||||
request_data (:class:`telegram.request.RequestData`, optional): An object containing
|
||||
information about parameters and files to upload for the request.
|
||||
read_timeout (:obj:`float` | :obj:`None`, optional): If passed, specifies the maximum
|
||||
amount of time (in seconds) to wait for a response from Telegram's server instead
|
||||
of the time specified during creating of this object. Defaults to
|
||||
:attr:`DEFAULT_NONE`.
|
||||
write_timeout (:obj:`float` | :obj:`None`, optional): If passed, specifies the maximum
|
||||
amount of time (in seconds) to wait for a write operation to complete (in terms of
|
||||
a network socket; i.e. POSTing a request or uploading a file) instead of the time
|
||||
specified during creating of this object. Defaults to :attr:`DEFAULT_NONE`.
|
||||
connect_timeout (:obj:`float` | :obj:`None`, optional): If passed, specifies the
|
||||
maximum amount of time (in seconds) to wait for a connection attempt to a server
|
||||
to succeed instead of the time specified during creating of this object. Defaults
|
||||
to :attr:`DEFAULT_NONE`.
|
||||
pool_timeout (:obj:`float` | :obj:`None`, optional): If passed, specifies the maximum
|
||||
amount of time (in seconds) to wait for a connection to become available instead
|
||||
of the time specified during creating of this object. Defaults to
|
||||
:attr:`DEFAULT_NONE`.
|
||||
|
||||
Returns:
|
||||
Tuple[:obj:`int`, :obj:`bytes`]: The HTTP return code & the payload part of the server
|
||||
response.
|
||||
"""
|
||||
Reference in New Issue
Block a user