mirror of
https://github.com/python-telegram-bot/python-telegram-bot.git
synced 2024-11-21 22:56:38 +01:00
Accept File Paths for Updater/DispatcherBuilder.private_key (#2724)
This commit is contained in:
parent
5275c45199
commit
0cb8d50aea
10 changed files with 111 additions and 24 deletions
|
@ -26,6 +26,7 @@ from typing import IO, TYPE_CHECKING, Any, Optional, Union
|
|||
from telegram import TelegramObject
|
||||
from telegram._passport.credentials import decrypt
|
||||
from telegram._utils.files import is_local_file
|
||||
from telegram._utils.types import FilePathInput
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram import Bot, FileCredentials
|
||||
|
@ -96,7 +97,7 @@ class File(TelegramObject):
|
|||
self._id_attrs = (self.file_unique_id,)
|
||||
|
||||
def download(
|
||||
self, custom_path: Union[Path, str] = None, out: IO = None, timeout: int = None
|
||||
self, custom_path: FilePathInput = None, out: IO = None, timeout: int = None
|
||||
) -> Union[Path, IO]:
|
||||
"""
|
||||
Download this file. By default, the file is saved in the current working directory with its
|
||||
|
|
|
@ -31,13 +31,13 @@ Warning:
|
|||
from pathlib import Path
|
||||
from typing import Optional, Union, Type, Any, cast, IO, TYPE_CHECKING
|
||||
|
||||
from telegram._utils.types import FileInput
|
||||
from telegram._utils.types import FileInput, FilePathInput
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram import TelegramObject, InputFile
|
||||
|
||||
|
||||
def is_local_file(obj: Optional[Union[str, Path]]) -> bool:
|
||||
def is_local_file(obj: Optional[FilePathInput]) -> bool:
|
||||
"""
|
||||
Checks if a given string is a file on local system.
|
||||
|
||||
|
|
|
@ -43,7 +43,10 @@ if TYPE_CHECKING:
|
|||
FileLike = Union[IO, 'InputFile']
|
||||
"""Either an open file handler or a :class:`telegram.InputFile`."""
|
||||
|
||||
FileInput = Union[str, bytes, FileLike, Path]
|
||||
FilePathInput = Union[str, Path]
|
||||
"""A filepath either as string or as :obj:`pathlib.Path` object."""
|
||||
|
||||
FileInput = Union[FilePathInput, bytes, FileLike]
|
||||
"""Valid input for passing files to Telegram. Either a file id as string, a file like object,
|
||||
a local file path as string, :class:`pathlib.Path` or the file contents as :obj:`bytes`."""
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
# flake8: noqa: E501
|
||||
# pylint: disable=line-too-long
|
||||
"""This module contains the Builder classes for the telegram.ext module."""
|
||||
from pathlib import Path
|
||||
from queue import Queue
|
||||
from threading import Event
|
||||
from typing import (
|
||||
|
@ -38,7 +39,7 @@ from typing import (
|
|||
|
||||
from telegram import Bot
|
||||
from telegram.request import Request
|
||||
from telegram._utils.types import ODVInput, DVInput
|
||||
from telegram._utils.types import ODVInput, DVInput, FilePathInput
|
||||
from telegram._utils.warnings import warn
|
||||
from telegram._utils.defaultvalue import DEFAULT_NONE, DefaultValue, DEFAULT_FALSE
|
||||
from telegram.ext import Dispatcher, JobQueue, Updater, ExtBot, ContextTypes, CallbackContext
|
||||
|
@ -349,14 +350,23 @@ class _BaseBuilder(Generic[ODT, BT, CCT, UD, CD, BD, JQ, PT]):
|
|||
return self
|
||||
|
||||
def _set_private_key(
|
||||
self: BuilderType, private_key: bytes, password: bytes = None
|
||||
self: BuilderType,
|
||||
private_key: Union[bytes, FilePathInput],
|
||||
password: Union[bytes, FilePathInput] = None,
|
||||
) -> BuilderType:
|
||||
if self._bot is not DEFAULT_NONE:
|
||||
raise RuntimeError(_TWO_ARGS_REQ.format('private_key', 'bot instance'))
|
||||
if self._dispatcher_check:
|
||||
raise RuntimeError(_TWO_ARGS_REQ.format('private_key', 'Dispatcher instance'))
|
||||
self._private_key = private_key
|
||||
self._private_key_password = password
|
||||
|
||||
self._private_key = (
|
||||
private_key if isinstance(private_key, bytes) else Path(private_key).read_bytes()
|
||||
)
|
||||
if password is None or isinstance(password, bytes):
|
||||
self._private_key_password = password
|
||||
else:
|
||||
self._private_key_password = Path(password).read_bytes()
|
||||
|
||||
return self
|
||||
|
||||
def _set_defaults(self: BuilderType, defaults: 'Defaults') -> BuilderType:
|
||||
|
@ -608,7 +618,11 @@ class DispatcherBuilder(_BaseBuilder[ODT, BT, CCT, UD, CD, BD, JQ, PT]):
|
|||
"""
|
||||
return self._set_request(request)
|
||||
|
||||
def private_key(self: BuilderType, private_key: bytes, password: bytes = None) -> BuilderType:
|
||||
def private_key(
|
||||
self: BuilderType,
|
||||
private_key: Union[bytes, FilePathInput],
|
||||
password: Union[bytes, FilePathInput] = None,
|
||||
) -> BuilderType:
|
||||
"""Sets the private key and corresponding password for decryption of telegram passport data
|
||||
to be used for :attr:`telegram.ext.Dispatcher.bot`.
|
||||
|
||||
|
@ -616,8 +630,12 @@ class DispatcherBuilder(_BaseBuilder[ODT, BT, CCT, UD, CD, BD, JQ, PT]):
|
|||
/tree/master/examples#passportbotpy>`_, `Telegram Passports <https://git.io/fAvYd>`_
|
||||
|
||||
Args:
|
||||
private_key (:obj:`bytes`): The private key.
|
||||
password (:obj:`bytes`): Optional. The corresponding password.
|
||||
private_key (:obj:`bytes` | :obj:`str` | :obj:`pathlib.Path`): The private key or the
|
||||
file path of a file that contains the key. In the latter case, the file's content
|
||||
will be read automatically.
|
||||
password (:obj:`bytes` | :obj:`str` | :obj:`pathlib.Path`, optional): The corresponding
|
||||
password or the file path of a file that contains the password. In the latter case,
|
||||
the file's content will be read automatically.
|
||||
|
||||
Returns:
|
||||
:class:`DispatcherBuilder`: The same builder with the updated argument.
|
||||
|
@ -958,7 +976,11 @@ class UpdaterBuilder(_BaseBuilder[ODT, BT, CCT, UD, CD, BD, JQ, PT]):
|
|||
"""
|
||||
return self._set_request(request)
|
||||
|
||||
def private_key(self: BuilderType, private_key: bytes, password: bytes = None) -> BuilderType:
|
||||
def private_key(
|
||||
self: BuilderType,
|
||||
private_key: Union[bytes, FilePathInput],
|
||||
password: Union[bytes, FilePathInput] = None,
|
||||
) -> BuilderType:
|
||||
"""Sets the private key and corresponding password for decryption of telegram passport data
|
||||
to be used for :attr:`telegram.ext.Updater.bot`.
|
||||
|
||||
|
@ -966,8 +988,12 @@ class UpdaterBuilder(_BaseBuilder[ODT, BT, CCT, UD, CD, BD, JQ, PT]):
|
|||
/tree/master/examples#passportbotpy>`_, `Telegram Passports <https://git.io/fAvYd>`_
|
||||
|
||||
Args:
|
||||
private_key (:obj:`bytes`): The private key.
|
||||
password (:obj:`bytes`): Optional. The corresponding password.
|
||||
private_key (:obj:`bytes` | :obj:`str` | :obj:`pathlib.Path`): The private key or the
|
||||
file path of a file that contains the key. In the latter case, the file's content
|
||||
will be read automatically.
|
||||
password (:obj:`bytes` | :obj:`str` | :obj:`pathlib.Path`, optional): The corresponding
|
||||
password or the file path of a file that contains the password. In the latter case,
|
||||
the file's content will be read automatically.
|
||||
|
||||
Returns:
|
||||
:class:`UpdaterBuilder`: The same builder with the updated argument.
|
||||
|
|
|
@ -28,9 +28,9 @@ from typing import (
|
|||
overload,
|
||||
cast,
|
||||
DefaultDict,
|
||||
Union,
|
||||
)
|
||||
|
||||
from telegram._utils.types import FilePathInput
|
||||
from telegram.ext import BasePersistence, PersistenceInput
|
||||
from telegram.ext._contexttypes import ContextTypes
|
||||
from telegram.ext._utils.types import UD, CD, BD, ConversationDict, CDCData
|
||||
|
@ -107,7 +107,7 @@ class PicklePersistence(BasePersistence[UD, CD, BD]):
|
|||
@overload
|
||||
def __init__(
|
||||
self: 'PicklePersistence[Dict, Dict, Dict]',
|
||||
filepath: Union[Path, str],
|
||||
filepath: FilePathInput,
|
||||
store_data: PersistenceInput = None,
|
||||
single_file: bool = True,
|
||||
on_flush: bool = False,
|
||||
|
@ -117,7 +117,7 @@ class PicklePersistence(BasePersistence[UD, CD, BD]):
|
|||
@overload
|
||||
def __init__(
|
||||
self: 'PicklePersistence[UD, CD, BD]',
|
||||
filepath: Union[Path, str],
|
||||
filepath: FilePathInput,
|
||||
store_data: PersistenceInput = None,
|
||||
single_file: bool = True,
|
||||
on_flush: bool = False,
|
||||
|
@ -127,7 +127,7 @@ class PicklePersistence(BasePersistence[UD, CD, BD]):
|
|||
|
||||
def __init__(
|
||||
self,
|
||||
filepath: Union[Path, str],
|
||||
filepath: FilePathInput,
|
||||
store_data: PersistenceInput = None,
|
||||
single_file: bool = True,
|
||||
on_flush: bool = False,
|
||||
|
|
|
@ -39,11 +39,11 @@ from typing import (
|
|||
)
|
||||
|
||||
from telegram.error import InvalidToken, RetryAfter, TimedOut, Unauthorized, TelegramError
|
||||
from telegram._utils.warnings import warn
|
||||
from telegram.ext import Dispatcher
|
||||
from telegram.ext._utils.webhookhandler import WebhookAppClass, WebhookServer
|
||||
from telegram.ext._utils.stack import was_called_by
|
||||
from telegram.ext._utils.types import BT
|
||||
from telegram._utils.warnings import warn
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .builders import InitUpdaterBuilder
|
||||
|
|
|
@ -73,7 +73,7 @@ from telegram.error import (
|
|||
TimedOut,
|
||||
Unauthorized,
|
||||
)
|
||||
from telegram._utils.types import JSONDict
|
||||
from telegram._utils.types import JSONDict, FilePathInput
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
|
@ -385,7 +385,7 @@ class Request:
|
|||
|
||||
return self._request_wrapper('GET', url, **urlopen_kwargs)
|
||||
|
||||
def download(self, url: str, filepath: Union[Path, str], timeout: float = None) -> None:
|
||||
def download(self, url: str, filepath: FilePathInput, timeout: float = None) -> None:
|
||||
"""Download a file by its URL.
|
||||
|
||||
Args:
|
||||
|
|
30
tests/data/private.key
Normal file
30
tests/data/private.key
Normal file
|
@ -0,0 +1,30 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
Proc-Type: 4,ENCRYPTED
|
||||
DEK-Info: AES-128-CBC,C4A419CEBF7D18FB5E1D98D6DDAEAD5F
|
||||
|
||||
LHkVkhpWH0KU4UrdUH4DMNGqAZkRzSwO8CqEkowQrrkdRyFwJQCgsgIywkDQsqyh
|
||||
bvIkRpRb2gwQ1D9utrRQ1IFsJpreulErSPxx47b1xwXhMiX0vOzWprhZ8mYYrAZH
|
||||
T9o7YXgUuF7Dk8Am51rZH50mWHUEljjkIlH2RQg1QFQr4recrZxlA3Ypn/SvOf0P
|
||||
gaYrBvcX0am1JSqar0BA9sQO6u1STBjUm/e4csAubutxg/k/N69zlMcr098lqGWO
|
||||
ppQmFa0grg3S2lUSuh42MYGtzluemrtWiktjrHKtm33zQX4vIgnMjuDZO4maqLD/
|
||||
qHvbixY2TX28gHsoIednr2C9p/rBl8uItDlVyqWengykcDYczii0Pa8PKRmseOJh
|
||||
sHGum3u5WTRRv41jK7i7PBeKsKHxMxLqTroXpCfx59XzGB5kKiPhG9Zm6NY7BZ3j
|
||||
JA02+RKwlmm4v64XLbTVtV+2M4pk1cOaRx8CTB1Coe0uN+o+kJwMffqKioeaB9lE
|
||||
zs9At5rdSpamG1G+Eop6hqGjYip8cLDaa9yuStIo0eOt/Q6YtU9qHOyMlOywptof
|
||||
hJUMPoFjO06nsME69QvzRu9CPMGIcj4GAVYn1He6LoRVj59skPAUcn1DpytL9Ghi
|
||||
9r7rLCRCExX32MuIxBq+fWBd//iOTkvnSlISc2MjXSYWu0QhKUvVZgy23pA3RH6X
|
||||
px/dPdw1jF4WTlJL7IEaF3eOLgKqfYebHa+i2E64ncECvsl8WFb/T+ru1qa4n3RB
|
||||
HPIaBRzPSqF1nc5BIQD12GPf/A7lq1pJpcQQN7gTkpUwJ8ydPB45sadHrc3Fz1C5
|
||||
XPvL3eLfCEau2Wrz4IVgMTJ61lQnzSZG9Z+R0JYpd1+SvNpbm9YdocDYam8wIFS3
|
||||
9RsJOKCansvOXfuXp26gggzsAP3mXq/DV1e86ramRbMyczSd3v+EsKmsttW0oWC6
|
||||
Hhuozy11w6Q+jgsiSBrOFJ0JwgHAaCGb4oFluYzTOgdrmPgQomrz16TJLjjmn56B
|
||||
9msoVGH5Kk/ifVr9waFuQFhcUfoWUUPZB3GrSGpr3Rz5XCh/BuXQDW8mDu29odzD
|
||||
6hDoNITsPv+y9F/BvqWOK+JeL+wP/F+AnciGMzIDnP4a4P4yj8Gf2rr1Eriok6wz
|
||||
aQr6NwnKsT4UAqjlmQ+gdPE4Joxk/ixlD41TZ97rq0LUSx2bcanM8GXZUjL74EuB
|
||||
TVABCeIX2ADBwHZ6v2HEkZvK7Miy23FP75JmLdNXw4GTcYmqD1bPIfsxgUkSwG63
|
||||
t0ChOqi9VdT62eAs5wShwhcrjc4xztjn6kypFu55a0neNr2qKYrwFo3QgZAbKWc1
|
||||
5jfS4kAq0gxyoQTCZnGhbbL095q3Sy7GV3EaW4yk78EuRwPFOqVUQ0D5tvrKsPT4
|
||||
B5AlxlarcDcMQayWKLj2pWmQm3YVlx5NfoRkSbd14h6ZryzDhG8ZfooLQ5dFh1ba
|
||||
f8+YbBtvFshzUDYdnr0fS0RYc/WtYmfJdb4+Fkc268BkJzg43rMSrdzaleS6jypU
|
||||
vzPs8WO0xU1xCIgB92vqZ+/4OlFwjbHHoQlnFHdNPbrfc8INbtLZgLCrELw4UEga
|
||||
-----END RSA PRIVATE KEY-----
|
1
tests/data/private_key.password
Normal file
1
tests/data/private_key.password
Normal file
|
@ -0,0 +1 @@
|
|||
python-telegram-bot
|
|
@ -20,6 +20,7 @@
|
|||
"""
|
||||
We mainly test on UpdaterBuilder because it has all methods that DispatcherBuilder already has
|
||||
"""
|
||||
from pathlib import Path
|
||||
from random import randint
|
||||
from threading import Event
|
||||
|
||||
|
@ -63,7 +64,9 @@ class TestBuilder:
|
|||
pytest.skip(f'{builder.__class__} has no method called {method}')
|
||||
|
||||
# First that e.g. `bot` can't be set if `request` was already set
|
||||
getattr(builder, method)(1)
|
||||
# We pass the private key since `private_key` is the only method that doesn't just save
|
||||
# the passed value
|
||||
getattr(builder, method)(Path('tests/data/private.key'))
|
||||
with pytest.raises(RuntimeError, match=f'`bot` may only be set, if no {description}'):
|
||||
builder.bot(None)
|
||||
|
||||
|
@ -84,7 +87,9 @@ class TestBuilder:
|
|||
pytest.skip(f'{builder.__class__} has no method called {method}')
|
||||
|
||||
# First that e.g. `dispatcher` can't be set if `bot` was already set
|
||||
getattr(builder, method)(None)
|
||||
# We pass the private key since `private_key` is the only method that doesn't just save
|
||||
# the passed value
|
||||
getattr(builder, method)(Path('tests/data/private.key'))
|
||||
with pytest.raises(
|
||||
RuntimeError, match=f'`dispatcher` may only be set, if no {description}'
|
||||
):
|
||||
|
@ -102,7 +107,9 @@ class TestBuilder:
|
|||
builder = builder.__class__()
|
||||
builder.dispatcher(None)
|
||||
if method != 'dispatcher_class':
|
||||
getattr(builder, method)(None)
|
||||
# We pass the private key since `private_key` is the only method that doesn't just save
|
||||
# the passed value
|
||||
getattr(builder, method)(Path('tests/data/private.key'))
|
||||
else:
|
||||
with pytest.raises(
|
||||
RuntimeError, match=f'`{method}` may only be set, if no Dispatcher instance'
|
||||
|
@ -251,3 +258,22 @@ class TestBuilder:
|
|||
else:
|
||||
assert isinstance(obj, CustomDispatcher)
|
||||
assert obj.arg == 2
|
||||
|
||||
@pytest.mark.parametrize('input_type', ('bytes', 'str', 'Path'))
|
||||
def test_all_private_key_input_types(self, builder, bot, input_type):
|
||||
private_key = Path('tests/data/private.key')
|
||||
password = Path('tests/data/private_key.password')
|
||||
|
||||
if input_type == 'bytes':
|
||||
private_key = private_key.read_bytes()
|
||||
password = password.read_bytes()
|
||||
if input_type == 'str':
|
||||
private_key = str(private_key)
|
||||
password = str(password)
|
||||
|
||||
builder.token(bot.token).private_key(
|
||||
private_key=private_key,
|
||||
password=password,
|
||||
)
|
||||
bot = builder.build().bot
|
||||
assert bot.private_key
|
||||
|
|
Loading…
Reference in a new issue