Rename proxy_url to proxy and Allow httpx.{Proxy, URL} as Input (#3939)

This commit is contained in:
Bibo-Joshi 2023-10-23 21:09:28 +02:00 committed by GitHub
parent c82a0808d1
commit 075f517458
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 186 additions and 57 deletions

View file

@ -32,9 +32,12 @@ from typing import (
Union,
)
import httpx
from telegram._bot import Bot
from telegram._utils.defaultvalue import DEFAULT_FALSE, DEFAULT_NONE, DefaultValue
from telegram._utils.types import DVInput, DVType, FilePathInput, HTTPVersion, ODVInput
from telegram._utils.warnings import warn
from telegram.ext._application import Application
from telegram.ext._baseupdateprocessor import BaseUpdateProcessor, SimpleUpdateProcessor
from telegram.ext._contexttypes import ContextTypes
@ -44,6 +47,7 @@ from telegram.ext._updater import Updater
from telegram.ext._utils.types import BD, BT, CCT, CD, JQ, UD
from telegram.request import BaseRequest
from telegram.request._httpxrequest import HTTPXRequest
from telegram.warnings import PTBDeprecationWarning
if TYPE_CHECKING:
from telegram.ext import BasePersistence, BaseRateLimiter, CallbackContext, Defaults
@ -66,14 +70,14 @@ _BOT_CHECKS = [
("request", "request instance"),
("get_updates_request", "get_updates_request instance"),
("connection_pool_size", "connection_pool_size"),
("proxy_url", "proxy_url"),
("proxy", "proxy"),
("pool_timeout", "pool_timeout"),
("connect_timeout", "connect_timeout"),
("read_timeout", "read_timeout"),
("write_timeout", "write_timeout"),
("http_version", "http_version"),
("get_updates_connection_pool_size", "get_updates_connection_pool_size"),
("get_updates_proxy_url", "get_updates_proxy_url"),
("get_updates_proxy", "get_updates_proxy"),
("get_updates_pool_timeout", "get_updates_pool_timeout"),
("get_updates_connect_timeout", "get_updates_connect_timeout"),
("get_updates_read_timeout", "get_updates_read_timeout"),
@ -136,7 +140,7 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]):
"_get_updates_connect_timeout",
"_get_updates_connection_pool_size",
"_get_updates_pool_timeout",
"_get_updates_proxy_url",
"_get_updates_proxy",
"_get_updates_read_timeout",
"_get_updates_request",
"_get_updates_write_timeout",
@ -149,7 +153,7 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]):
"_post_stop",
"_private_key",
"_private_key_password",
"_proxy_url",
"_proxy",
"_rate_limiter",
"_read_timeout",
"_request",
@ -166,14 +170,14 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]):
self._base_url: DVType[str] = DefaultValue("https://api.telegram.org/bot")
self._base_file_url: DVType[str] = DefaultValue("https://api.telegram.org/file/bot")
self._connection_pool_size: DVInput[int] = DEFAULT_NONE
self._proxy_url: DVInput[str] = DEFAULT_NONE
self._proxy: DVInput[Union[str, httpx.Proxy, httpx.URL]] = DEFAULT_NONE
self._connect_timeout: ODVInput[float] = DEFAULT_NONE
self._read_timeout: ODVInput[float] = DEFAULT_NONE
self._write_timeout: ODVInput[float] = DEFAULT_NONE
self._pool_timeout: ODVInput[float] = DEFAULT_NONE
self._request: DVInput[BaseRequest] = DEFAULT_NONE
self._get_updates_connection_pool_size: DVInput[int] = DEFAULT_NONE
self._get_updates_proxy_url: DVInput[str] = DEFAULT_NONE
self._get_updates_proxy: DVInput[Union[str, httpx.Proxy, httpx.URL]] = DEFAULT_NONE
self._get_updates_connect_timeout: ODVInput[float] = DEFAULT_NONE
self._get_updates_read_timeout: ODVInput[float] = DEFAULT_NONE
self._get_updates_write_timeout: ODVInput[float] = DEFAULT_NONE
@ -214,7 +218,7 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]):
if not isinstance(getattr(self, f"{prefix}request"), DefaultValue):
return getattr(self, f"{prefix}request")
proxy_url = DefaultValue.get_value(getattr(self, f"{prefix}proxy_url"))
proxy = DefaultValue.get_value(getattr(self, f"{prefix}proxy"))
if get_updates:
connection_pool_size = (
DefaultValue.get_value(getattr(self, f"{prefix}connection_pool_size")) or 1
@ -239,7 +243,7 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]):
return HTTPXRequest(
connection_pool_size=connection_pool_size,
proxy_url=proxy_url,
proxy=proxy,
http_version=http_version, # type: ignore[arg-type]
**effective_timeouts,
)
@ -419,8 +423,8 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]):
if not isinstance(getattr(self, f"_{prefix}connection_pool_size"), DefaultValue):
raise RuntimeError(_TWO_ARGS_REQ.format(name, "connection_pool_size"))
if not isinstance(getattr(self, f"_{prefix}proxy_url"), DefaultValue):
raise RuntimeError(_TWO_ARGS_REQ.format(name, "proxy_url"))
if not isinstance(getattr(self, f"_{prefix}proxy"), DefaultValue):
raise RuntimeError(_TWO_ARGS_REQ.format(name, "proxy"))
if not isinstance(getattr(self, f"_{prefix}http_version"), DefaultValue):
raise RuntimeError(_TWO_ARGS_REQ.format(name, "http_version"))
@ -486,21 +490,45 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]):
return self
def proxy_url(self: BuilderType, proxy_url: str) -> BuilderType:
"""Sets the proxy for the :paramref:`~telegram.request.HTTPXRequest.proxy_url`
parameter of :attr:`telegram.Bot.request`. Defaults to :obj:`None`.
"""Legacy name for :meth:`proxy`, kept for backward compatibility.
.. seealso:: :meth:`get_updates_proxy`
.. seealso:: :meth:`get_updates_proxy_url`
.. deprecated:: NEXT.VERSION
Args:
proxy_url (:obj:`str`): The URL to the proxy server. See
:paramref:`telegram.request.HTTPXRequest.proxy_url` for more information.
proxy_url (:obj:`str` | ``httpx.Proxy`` | ``httpx.URL``): See
:paramref:`telegram.ext.ApplicationBuilder.proxy.proxy`.
Returns:
:class:`ApplicationBuilder`: The same builder with the updated argument.
"""
self._request_param_check(name="proxy_url", get_updates=False)
self._proxy_url = proxy_url
warn(
"`ApplicationBuilder.proxy_url` is deprecated since version "
"NEXT.VERSION. Use `ApplicationBuilder.proxy` instead.",
PTBDeprecationWarning,
stacklevel=2,
)
return self.proxy(proxy_url)
def proxy(self: BuilderType, proxy: Union[str, httpx.Proxy, httpx.URL]) -> BuilderType:
"""Sets the proxy for the :paramref:`~telegram.request.HTTPXRequest.proxy`
parameter of :attr:`telegram.Bot.request`. Defaults to :obj:`None`.
.. seealso:: :meth:`get_updates_proxy`
.. versionadded:: NEXT.VERSION
Args:
proxy (:obj:`str` | ``httpx.Proxy`` | ``httpx.URL``): The URL to a proxy
server, a ``httpx.Proxy`` object or a ``httpx.URL`` object. See
:paramref:`telegram.request.HTTPXRequest.proxy` for more information.
Returns:
:class:`ApplicationBuilder`: The same builder with the updated argument.
"""
self._request_param_check(name="proxy", get_updates=False)
self._proxy = proxy
return self
def connect_timeout(self: BuilderType, connect_timeout: Optional[float]) -> BuilderType:
@ -655,21 +683,47 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]):
return self
def get_updates_proxy_url(self: BuilderType, get_updates_proxy_url: str) -> BuilderType:
"""Sets the proxy for the :paramref:`telegram.request.HTTPXRequest.proxy_url`
parameter which is used for :meth:`telegram.Bot.get_updates`. Defaults to :obj:`None`.
"""Legacy name for :meth:`get_updates_proxy`, kept for backward compatibility.
.. seealso:: :meth:`proxy`
.. seealso:: :meth:`proxy_url`
.. deprecated:: NEXT.VERSION
Args:
get_updates_proxy_url (:obj:`str`): The URL to the proxy server. See
:paramref:`telegram.request.HTTPXRequest.proxy_url` for more information.
get_updates_proxy_url (:obj:`str` | ``httpx.Proxy`` | ``httpx.URL``): See
:paramref:`telegram.ext.ApplicationBuilder.get_updates_proxy.get_updates_proxy`.
Returns:
:class:`ApplicationBuilder`: The same builder with the updated argument.
"""
self._request_param_check(name="proxy_url", get_updates=True)
self._get_updates_proxy_url = get_updates_proxy_url
warn(
"`ApplicationBuilder.get_updates_proxy_url` is deprecated since version "
"NEXT.VERSION. Use `ApplicationBuilder.get_updates_proxy` instead.",
PTBDeprecationWarning,
stacklevel=2,
)
return self.get_updates_proxy(get_updates_proxy_url)
def get_updates_proxy(
self: BuilderType, get_updates_proxy: Union[str, httpx.Proxy, httpx.URL]
) -> BuilderType:
"""Sets the proxy for the :paramref:`telegram.request.HTTPXRequest.proxy`
parameter which is used for :meth:`telegram.Bot.get_updates`. Defaults to :obj:`None`.
.. seealso:: :meth:`proxy`
.. versionadded:: NEXT.VERSION
Args:
proxy (:obj:`str` | ``httpx.Proxy`` | ``httpx.URL``): The URL to a proxy server,
a ``httpx.Proxy`` object or a ``httpx.URL`` object. See
:paramref:`telegram.request.HTTPXRequest.proxy` for more information.
Returns:
:class:`ApplicationBuilder`: The same builder with the updated argument.
"""
self._request_param_check(name="proxy", get_updates=True)
self._get_updates_proxy = get_updates_proxy
return self
def get_updates_connect_timeout(

View file

@ -24,9 +24,11 @@ import httpx
from telegram._utils.defaultvalue import DefaultValue
from telegram._utils.logging import get_logger
from telegram._utils.types import HTTPVersion, ODVInput
from telegram._utils.warnings import warn
from telegram.error import NetworkError, TimedOut
from telegram.request._baserequest import BaseRequest
from telegram.request._requestdata import RequestData
from telegram.warnings import PTBDeprecationWarning
# Note to future devs:
# Proxies are currently only tested manually. The httpx development docs have a nice guide on that:
@ -55,17 +57,10 @@ class HTTPXRequest(BaseRequest):
Note:
Independent of the value, one additional connection will be reserved for
:meth:`telegram.Bot.get_updates`.
proxy_url (:obj:`str`, optional): The URL to the proxy server. For example
``'http://127.0.0.1:3128'`` or ``'socks5://127.0.0.1:3128'``. Defaults to :obj:`None`.
proxy_url (:obj:`str`, optional): Legacy name for :paramref:`proxy`, kept for backward
compatibility. Defaults to :obj:`None`.
Note:
* The proxy URL can also be set via the environment variables ``HTTPS_PROXY`` or
``ALL_PROXY``. See `the docs of httpx`_ for more info.
* For Socks5 support, additional dependencies are required. Make sure to install
PTB via :command:`pip install "python-telegram-bot[socks]"` in this case.
* Socks5 proxies can not be set via environment variables.
.. _the docs of httpx: https://www.python-httpx.org/environment_variables/#proxies
.. deprecated:: NEXT.VERSION
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.
This value is used unless a different value is passed to :meth:`do_request`.
@ -107,6 +102,22 @@ class HTTPXRequest(BaseRequest):
these concepts.
.. versionadded:: NEXT.VERSION
proxy (:obj:`str` | ``httpx.Proxy`` | ``httpx.URL``, optional): The URL to a proxy server,
a ``httpx.Proxy`` object or a ``httpx.URL`` object. For example
``'http://127.0.0.1:3128'`` or ``'socks5://127.0.0.1:3128'``. Defaults to :obj:`None`.
Note:
* The proxy URL can also be set via the environment variables ``HTTPS_PROXY`` or
``ALL_PROXY``. See `the docs of httpx`_ for more info.
* HTTPS proxies can be configured by passing a ``httpx.Proxy`` object with
a corresponding ``ssl_context``.
* For Socks5 support, additional dependencies are required. Make sure to install
PTB via :command:`pip install "python-telegram-bot[socks]"` in this case.
* Socks5 proxies can not be set via environment variables.
.. _the docs of httpx: https://www.python-httpx.org/environment_variables/#proxies
.. versionadded:: NEXT.VERSION
"""
@ -115,14 +126,27 @@ class HTTPXRequest(BaseRequest):
def __init__(
self,
connection_pool_size: int = 1,
proxy_url: Optional[str] = None,
proxy_url: Optional[Union[str, httpx.Proxy, httpx.URL]] = None,
read_timeout: Optional[float] = 5.0,
write_timeout: Optional[float] = 5.0,
connect_timeout: Optional[float] = 5.0,
pool_timeout: Optional[float] = 1.0,
http_version: HTTPVersion = "1.1",
socket_options: Optional[Collection[_SocketOpt]] = None,
proxy: Optional[Union[str, httpx.Proxy, httpx.URL]] = None,
):
if proxy_url is not None and proxy is not None:
raise ValueError("The parameters `proxy_url` and `proxy` are mutually exclusive.")
if proxy_url is not None:
proxy = proxy_url
warn(
"The parameter `proxy_url` is deprecated since version NEXT.VERSION. Use `proxy` "
"instead.",
PTBDeprecationWarning,
stacklevel=2,
)
self._http_version = http_version
timeout = httpx.Timeout(
connect=connect_timeout,
@ -149,7 +173,7 @@ class HTTPXRequest(BaseRequest):
)
self._client_kwargs = {
"timeout": timeout,
"proxies": proxy_url,
"proxies": proxy,
"limits": limits,
"transport": transport,
**http_kwargs,

View file

@ -37,6 +37,7 @@ from telegram.ext import (
from telegram.ext._applicationbuilder import _BOT_CHECKS
from telegram.ext._baseupdateprocessor import SimpleUpdateProcessor
from telegram.request import HTTPXRequest
from telegram.warnings import PTBDeprecationWarning
from tests.auxil.constants import PRIVATE_KEY
from tests.auxil.envvars import TEST_WITH_OPT_DEPS
from tests.auxil.files import data_file
@ -169,6 +170,7 @@ class TestApplicationBuilder:
"pool_timeout",
"read_timeout",
"write_timeout",
"proxy",
"proxy_url",
"bot",
"updater",
@ -178,8 +180,9 @@ class TestApplicationBuilder:
def test_mutually_exclusive_for_request(self, builder, method):
builder.request(1)
method_name = method.replace("proxy_url", "proxy")
with pytest.raises(
RuntimeError, match=f"`{method}` may only be set, if no request instance"
RuntimeError, match=f"`{method_name}` may only be set, if no request instance"
):
getattr(builder, method)(data_file("private.key"))
@ -196,6 +199,7 @@ class TestApplicationBuilder:
"get_updates_pool_timeout",
"get_updates_read_timeout",
"get_updates_write_timeout",
"get_updates_proxy",
"get_updates_proxy_url",
"get_updates_http_version",
"bot",
@ -205,9 +209,10 @@ class TestApplicationBuilder:
def test_mutually_exclusive_for_get_updates_request(self, builder, method):
builder.get_updates_request(1)
method_name = method.replace("proxy_url", "proxy")
with pytest.raises(
RuntimeError,
match=f"`{method}` may only be set, if no get_updates_request instance",
match=f"`{method_name}` may only be set, if no get_updates_request instance",
):
getattr(builder, method)(data_file("private.key"))
@ -225,12 +230,14 @@ class TestApplicationBuilder:
"get_updates_read_timeout",
"get_updates_write_timeout",
"get_updates_proxy_url",
"get_updates_proxy",
"get_updates_http_version",
"connection_pool_size",
"connect_timeout",
"pool_timeout",
"read_timeout",
"write_timeout",
"proxy",
"proxy_url",
"http_version",
"bot",
@ -242,14 +249,17 @@ class TestApplicationBuilder:
def test_mutually_exclusive_for_updater(self, builder, method):
builder.updater(1)
method_name = method.replace("proxy_url", "proxy")
with pytest.raises(
RuntimeError,
match=f"`{method}` may only be set, if no updater",
match=f"`{method_name}` may only be set, if no updater",
):
getattr(builder, method)(data_file("private.key"))
builder = ApplicationBuilder()
getattr(builder, method)(data_file("private.key"))
method = method.replace("proxy_url", "proxy")
with pytest.raises(RuntimeError, match=f"`updater` may only be set, if no {method}"):
builder.updater(1)
@ -261,6 +271,7 @@ class TestApplicationBuilder:
"get_updates_pool_timeout",
"get_updates_read_timeout",
"get_updates_write_timeout",
"get_updates_proxy",
"get_updates_proxy_url",
"get_updates_http_version",
"connection_pool_size",
@ -268,6 +279,7 @@ class TestApplicationBuilder:
"pool_timeout",
"read_timeout",
"write_timeout",
"proxy",
"proxy_url",
"bot",
"http_version",
@ -285,7 +297,15 @@ class TestApplicationBuilder:
getattr(builder, method)(data_file("private.key"))
builder.updater(None)
def test_all_bot_args_custom(self, builder, bot, monkeypatch):
# We test with bot the new & legacy version to ensure that the legacy version still works
@pytest.mark.parametrize(
("proxy_method", "get_updates_proxy_method"),
[("proxy", "get_updates_proxy"), ("proxy_url", "get_updates_proxy_url")],
ids=["new", "legacy"],
)
def test_all_bot_args_custom(
self, builder, bot, monkeypatch, proxy_method, get_updates_proxy_method
):
defaults = Defaults()
request = HTTPXRequest()
get_updates_request = HTTPXRequest()
@ -330,13 +350,14 @@ class TestApplicationBuilder:
builder = ApplicationBuilder().token(bot.token)
builder.connection_pool_size(1).connect_timeout(2).pool_timeout(3).read_timeout(
4
).write_timeout(5).proxy_url("proxy_url").http_version("1.1")
).write_timeout(5).http_version("1.1")
getattr(builder, proxy_method)("proxy")
app = builder.build()
client = app.bot.request._client
assert client.timeout == httpx.Timeout(pool=3, connect=2, read=4, write=5)
assert client.limits == httpx.Limits(max_connections=1, max_keepalive_connections=1)
assert client.proxies == "proxy_url"
assert client.proxies == "proxy"
assert client.http1 is True
assert client.http2 is False
@ -345,17 +366,16 @@ class TestApplicationBuilder:
2
).get_updates_pool_timeout(3).get_updates_read_timeout(4).get_updates_write_timeout(
5
).get_updates_proxy_url(
"proxy_url"
).get_updates_http_version(
"1.1"
)
getattr(builder, get_updates_proxy_method)("get_updates_proxy")
app = builder.build()
client = app.bot._request[0]._client
assert client.timeout == httpx.Timeout(pool=3, connect=2, read=4, write=5)
assert client.limits == httpx.Limits(max_connections=1, max_keepalive_connections=1)
assert client.proxies == "proxy_url"
assert client.proxies == "get_updates_proxy"
assert client.http1 is True
assert client.http2 is False
@ -478,3 +498,19 @@ class TestApplicationBuilder:
assert app.job_queue is None
assert isinstance(app.update_queue, asyncio.Queue)
assert isinstance(app.updater, Updater)
def test_proxy_url_deprecation_warning(self, bot, builder, recwarn):
builder.token(bot.token).proxy_url("proxy_url")
assert len(recwarn) == 1
assert "`ApplicationBuilder.proxy_url` is deprecated" in str(recwarn[0].message)
assert recwarn[0].category is PTBDeprecationWarning
assert recwarn[0].filename == __file__, "wrong stacklevel"
def test_get_updates_proxy_url_deprecation_warning(self, bot, builder, recwarn):
builder.token(bot.token).get_updates_proxy_url("get_updates_proxy_url")
assert len(recwarn) == 1
assert "`ApplicationBuilder.get_updates_proxy_url` is deprecated" in str(
recwarn[0].message
)
assert recwarn[0].category is PTBDeprecationWarning
assert recwarn[0].filename == __file__, "wrong stacklevel"

View file

@ -43,6 +43,7 @@ from telegram.error import (
TimedOut,
)
from telegram.request._httpxrequest import HTTPXRequest
from telegram.warnings import PTBDeprecationWarning
from tests.auxil.envvars import TEST_WITH_OPT_DEPS
from tests.auxil.slots import mro_slots
@ -79,7 +80,7 @@ async def httpx_request():
class TestNoSocksHTTP2WithoutRequest:
async def test_init(self, bot):
with pytest.raises(RuntimeError, match=r"python-telegram-bot\[socks\]"):
HTTPXRequest(proxy_url="socks5://foo")
HTTPXRequest(proxy="socks5://foo")
with pytest.raises(RuntimeError, match=r"python-telegram-bot\[http2\]"):
HTTPXRequest(http_version="2")
@ -118,7 +119,7 @@ class TestRequestWithoutRequest:
# Make sure that other exceptions are forwarded
with pytest.raises(ImportError, match=r"Other Error Message"):
HTTPXRequest(proxy_url="socks5://foo")
HTTPXRequest(proxy="socks5://foo")
def test_slot_behaviour(self):
inst = HTTPXRequest()
@ -359,7 +360,9 @@ class TestHTTPXRequestWithoutRequest:
def _reset(self):
self.test_flag = None
def test_init(self, monkeypatch):
# We parametrize this to make sure that the legacy `proxy_url` argument is still supported
@pytest.mark.parametrize("proxy_argument", ["proxy", "proxy_url"])
def test_init(self, monkeypatch, proxy_argument):
@dataclass
class Client:
timeout: object
@ -380,20 +383,32 @@ class TestHTTPXRequestWithoutRequest:
assert request._client.http1 is True
assert not request._client.http2
request = HTTPXRequest(
connection_pool_size=42,
proxy_url="proxy_url",
connect_timeout=43,
read_timeout=44,
write_timeout=45,
pool_timeout=46,
)
assert request._client.proxies == "proxy_url"
kwargs = {
"connection_pool_size": 42,
proxy_argument: "proxy",
"connect_timeout": 43,
"read_timeout": 44,
"write_timeout": 45,
"pool_timeout": 46,
}
request = HTTPXRequest(**kwargs)
assert request._client.proxies == "proxy"
assert request._client.limits == httpx.Limits(
max_connections=42, max_keepalive_connections=42
)
assert request._client.timeout == httpx.Timeout(connect=43, read=44, write=45, pool=46)
def test_proxy_mutually_exclusive(self):
with pytest.raises(ValueError, match="mutually exclusive"):
HTTPXRequest(proxy="proxy", proxy_url="proxy_url")
def test_proxy_url_deprecation_warning(self, recwarn):
HTTPXRequest(proxy_url="http://127.0.0.1:3128")
assert len(recwarn) == 1
assert recwarn[0].category is PTBDeprecationWarning
assert "`proxy_url` is deprecated" in str(recwarn[0].message)
assert recwarn[0].filename == __file__, "incorrect stacklevel"
async def test_multiple_inits_and_shutdowns(self, monkeypatch):
self.test_flag = defaultdict(int)