Skip to content

Commit 4f0026e

Browse files
committed
Accept File Paths for Updater/DispatcherBuilder.private_key (python-telegram-bot#2724)
1 parent 007fd7f commit 4f0026e

File tree

10 files changed

+111
-24
lines changed

10 files changed

+111
-24
lines changed

telegram/_files/file.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from telegram import TelegramObject
2727
from telegram._passport.credentials import decrypt
2828
from telegram._utils.files import is_local_file
29+
from telegram._utils.types import FilePathInput
2930

3031
if TYPE_CHECKING:
3132
from telegram import Bot, FileCredentials
@@ -96,7 +97,7 @@ def __init__(
9697
self._id_attrs = (self.file_unique_id,)
9798

9899
def download(
99-
self, custom_path: Union[Path, str] = None, out: IO = None, timeout: int = None
100+
self, custom_path: FilePathInput = None, out: IO = None, timeout: int = None
100101
) -> Union[Path, IO]:
101102
"""
102103
Download this file. By default, the file is saved in the current working directory with its

telegram/_utils/files.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,13 @@
3131
from pathlib import Path
3232
from typing import Optional, Union, Type, Any, cast, IO, TYPE_CHECKING
3333

34-
from telegram._utils.types import FileInput
34+
from telegram._utils.types import FileInput, FilePathInput
3535

3636
if TYPE_CHECKING:
3737
from telegram import TelegramObject, InputFile
3838

3939

40-
def is_local_file(obj: Optional[Union[str, Path]]) -> bool:
40+
def is_local_file(obj: Optional[FilePathInput]) -> bool:
4141
"""
4242
Checks if a given string is a file on local system.
4343

telegram/_utils/types.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,10 @@
4343
FileLike = Union[IO, 'InputFile']
4444
"""Either an open file handler or a :class:`telegram.InputFile`."""
4545

46-
FileInput = Union[str, bytes, FileLike, Path]
46+
FilePathInput = Union[str, Path]
47+
"""A filepath either as string or as :obj:`pathlib.Path` object."""
48+
49+
FileInput = Union[FilePathInput, bytes, FileLike]
4750
"""Valid input for passing files to Telegram. Either a file id as string, a file like object,
4851
a local file path as string, :class:`pathlib.Path` or the file contents as :obj:`bytes`."""
4952

telegram/ext/_builders.py

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
# flake8: noqa: E501
2222
# pylint: disable=line-too-long
2323
"""This module contains the Builder classes for the telegram.ext module."""
24+
from pathlib import Path
2425
from queue import Queue
2526
from threading import Event
2627
from typing import (
@@ -38,7 +39,7 @@
3839

3940
from telegram import Bot
4041
from telegram.request import Request
41-
from telegram._utils.types import ODVInput, DVInput
42+
from telegram._utils.types import ODVInput, DVInput, FilePathInput
4243
from telegram._utils.warnings import warn
4344
from telegram._utils.defaultvalue import DEFAULT_NONE, DefaultValue, DEFAULT_FALSE
4445
from telegram.ext import Dispatcher, JobQueue, Updater, ExtBot, ContextTypes, CallbackContext
@@ -349,14 +350,23 @@ def _set_request(self: BuilderType, request: Request) -> BuilderType:
349350
return self
350351

351352
def _set_private_key(
352-
self: BuilderType, private_key: bytes, password: bytes = None
353+
self: BuilderType,
354+
private_key: Union[bytes, FilePathInput],
355+
password: Union[bytes, FilePathInput] = None,
353356
) -> BuilderType:
354357
if self._bot is not DEFAULT_NONE:
355358
raise RuntimeError(_TWO_ARGS_REQ.format('private_key', 'bot instance'))
356359
if self._dispatcher_check:
357360
raise RuntimeError(_TWO_ARGS_REQ.format('private_key', 'Dispatcher instance'))
358-
self._private_key = private_key
359-
self._private_key_password = password
361+
362+
self._private_key = (
363+
private_key if isinstance(private_key, bytes) else Path(private_key).read_bytes()
364+
)
365+
if password is None or isinstance(password, bytes):
366+
self._private_key_password = password
367+
else:
368+
self._private_key_password = Path(password).read_bytes()
369+
360370
return self
361371

362372
def _set_defaults(self: BuilderType, defaults: 'Defaults') -> BuilderType:
@@ -608,16 +618,24 @@ def request(self: BuilderType, request: Request) -> BuilderType:
608618
"""
609619
return self._set_request(request)
610620

611-
def private_key(self: BuilderType, private_key: bytes, password: bytes = None) -> BuilderType:
621+
def private_key(
622+
self: BuilderType,
623+
private_key: Union[bytes, FilePathInput],
624+
password: Union[bytes, FilePathInput] = None,
625+
) -> BuilderType:
612626
"""Sets the private key and corresponding password for decryption of telegram passport data
613627
to be used for :attr:`telegram.ext.Dispatcher.bot`.
614628
615629
.. seealso:: `passportbot.py <https://github.com/python-telegram-bot/python-telegram-bot\
616630
/tree/master/examples#passportbotpy>`_, `Telegram Passports <https://git.io/fAvYd>`_
617631
618632
Args:
619-
private_key (:obj:`bytes`): The private key.
620-
password (:obj:`bytes`): Optional. The corresponding password.
633+
private_key (:obj:`bytes` | :obj:`str` | :obj:`pathlib.Path`): The private key or the
634+
file path of a file that contains the key. In the latter case, the file's content
635+
will be read automatically.
636+
password (:obj:`bytes` | :obj:`str` | :obj:`pathlib.Path`, optional): The corresponding
637+
password or the file path of a file that contains the password. In the latter case,
638+
the file's content will be read automatically.
621639
622640
Returns:
623641
:class:`DispatcherBuilder`: The same builder with the updated argument.
@@ -958,16 +976,24 @@ def request(self: BuilderType, request: Request) -> BuilderType:
958976
"""
959977
return self._set_request(request)
960978

961-
def private_key(self: BuilderType, private_key: bytes, password: bytes = None) -> BuilderType:
979+
def private_key(
980+
self: BuilderType,
981+
private_key: Union[bytes, FilePathInput],
982+
password: Union[bytes, FilePathInput] = None,
983+
) -> BuilderType:
962984
"""Sets the private key and corresponding password for decryption of telegram passport data
963985
to be used for :attr:`telegram.ext.Updater.bot`.
964986
965987
.. seealso:: `passportbot.py <https://github.com/python-telegram-bot/python-telegram-bot\
966988
/tree/master/examples#passportbotpy>`_, `Telegram Passports <https://git.io/fAvYd>`_
967989
968990
Args:
969-
private_key (:obj:`bytes`): The private key.
970-
password (:obj:`bytes`): Optional. The corresponding password.
991+
private_key (:obj:`bytes` | :obj:`str` | :obj:`pathlib.Path`): The private key or the
992+
file path of a file that contains the key. In the latter case, the file's content
993+
will be read automatically.
994+
password (:obj:`bytes` | :obj:`str` | :obj:`pathlib.Path`, optional): The corresponding
995+
password or the file path of a file that contains the password. In the latter case,
996+
the file's content will be read automatically.
971997
972998
Returns:
973999
:class:`UpdaterBuilder`: The same builder with the updated argument.

telegram/ext/_picklepersistence.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@
2828
overload,
2929
cast,
3030
DefaultDict,
31-
Union,
3231
)
3332

33+
from telegram._utils.types import FilePathInput
3434
from telegram.ext import BasePersistence, PersistenceInput
3535
from telegram.ext._contexttypes import ContextTypes
3636
from telegram.ext._utils.types import UD, CD, BD, ConversationDict, CDCData
@@ -107,7 +107,7 @@ class PicklePersistence(BasePersistence[UD, CD, BD]):
107107
@overload
108108
def __init__(
109109
self: 'PicklePersistence[Dict, Dict, Dict]',
110-
filepath: Union[Path, str],
110+
filepath: FilePathInput,
111111
store_data: PersistenceInput = None,
112112
single_file: bool = True,
113113
on_flush: bool = False,
@@ -117,7 +117,7 @@ def __init__(
117117
@overload
118118
def __init__(
119119
self: 'PicklePersistence[UD, CD, BD]',
120-
filepath: Union[Path, str],
120+
filepath: FilePathInput,
121121
store_data: PersistenceInput = None,
122122
single_file: bool = True,
123123
on_flush: bool = False,
@@ -127,7 +127,7 @@ def __init__(
127127

128128
def __init__(
129129
self,
130-
filepath: Union[Path, str],
130+
filepath: FilePathInput,
131131
store_data: PersistenceInput = None,
132132
single_file: bool = True,
133133
on_flush: bool = False,

telegram/ext/_updater.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,11 @@
3939
)
4040

4141
from telegram.error import InvalidToken, RetryAfter, TimedOut, Unauthorized, TelegramError
42+
from telegram._utils.warnings import warn
4243
from telegram.ext import Dispatcher
4344
from telegram.ext._utils.webhookhandler import WebhookAppClass, WebhookServer
4445
from telegram.ext._utils.stack import was_called_by
4546
from telegram.ext._utils.types import BT
46-
from telegram._utils.warnings import warn
4747

4848
if TYPE_CHECKING:
4949
from .builders import InitUpdaterBuilder

telegram/request.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@
7373
TimedOut,
7474
Unauthorized,
7575
)
76-
from telegram._utils.types import JSONDict
76+
from telegram._utils.types import JSONDict, FilePathInput
7777

7878

7979
# pylint: disable=unused-argument
@@ -385,7 +385,7 @@ def retrieve(self, url: str, timeout: float = None) -> bytes:
385385

386386
return self._request_wrapper('GET', url, **urlopen_kwargs)
387387

388-
def download(self, url: str, filepath: Union[Path, str], timeout: float = None) -> None:
388+
def download(self, url: str, filepath: FilePathInput, timeout: float = None) -> None:
389389
"""Download a file by its URL.
390390
391391
Args:

tests/data/private.key

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
-----BEGIN RSA PRIVATE KEY-----
2+
Proc-Type: 4,ENCRYPTED
3+
DEK-Info: AES-128-CBC,C4A419CEBF7D18FB5E1D98D6DDAEAD5F
4+
5+
LHkVkhpWH0KU4UrdUH4DMNGqAZkRzSwO8CqEkowQrrkdRyFwJQCgsgIywkDQsqyh
6+
bvIkRpRb2gwQ1D9utrRQ1IFsJpreulErSPxx47b1xwXhMiX0vOzWprhZ8mYYrAZH
7+
T9o7YXgUuF7Dk8Am51rZH50mWHUEljjkIlH2RQg1QFQr4recrZxlA3Ypn/SvOf0P
8+
gaYrBvcX0am1JSqar0BA9sQO6u1STBjUm/e4csAubutxg/k/N69zlMcr098lqGWO
9+
ppQmFa0grg3S2lUSuh42MYGtzluemrtWiktjrHKtm33zQX4vIgnMjuDZO4maqLD/
10+
qHvbixY2TX28gHsoIednr2C9p/rBl8uItDlVyqWengykcDYczii0Pa8PKRmseOJh
11+
sHGum3u5WTRRv41jK7i7PBeKsKHxMxLqTroXpCfx59XzGB5kKiPhG9Zm6NY7BZ3j
12+
JA02+RKwlmm4v64XLbTVtV+2M4pk1cOaRx8CTB1Coe0uN+o+kJwMffqKioeaB9lE
13+
zs9At5rdSpamG1G+Eop6hqGjYip8cLDaa9yuStIo0eOt/Q6YtU9qHOyMlOywptof
14+
hJUMPoFjO06nsME69QvzRu9CPMGIcj4GAVYn1He6LoRVj59skPAUcn1DpytL9Ghi
15+
9r7rLCRCExX32MuIxBq+fWBd//iOTkvnSlISc2MjXSYWu0QhKUvVZgy23pA3RH6X
16+
px/dPdw1jF4WTlJL7IEaF3eOLgKqfYebHa+i2E64ncECvsl8WFb/T+ru1qa4n3RB
17+
HPIaBRzPSqF1nc5BIQD12GPf/A7lq1pJpcQQN7gTkpUwJ8ydPB45sadHrc3Fz1C5
18+
XPvL3eLfCEau2Wrz4IVgMTJ61lQnzSZG9Z+R0JYpd1+SvNpbm9YdocDYam8wIFS3
19+
9RsJOKCansvOXfuXp26gggzsAP3mXq/DV1e86ramRbMyczSd3v+EsKmsttW0oWC6
20+
Hhuozy11w6Q+jgsiSBrOFJ0JwgHAaCGb4oFluYzTOgdrmPgQomrz16TJLjjmn56B
21+
9msoVGH5Kk/ifVr9waFuQFhcUfoWUUPZB3GrSGpr3Rz5XCh/BuXQDW8mDu29odzD
22+
6hDoNITsPv+y9F/BvqWOK+JeL+wP/F+AnciGMzIDnP4a4P4yj8Gf2rr1Eriok6wz
23+
aQr6NwnKsT4UAqjlmQ+gdPE4Joxk/ixlD41TZ97rq0LUSx2bcanM8GXZUjL74EuB
24+
TVABCeIX2ADBwHZ6v2HEkZvK7Miy23FP75JmLdNXw4GTcYmqD1bPIfsxgUkSwG63
25+
t0ChOqi9VdT62eAs5wShwhcrjc4xztjn6kypFu55a0neNr2qKYrwFo3QgZAbKWc1
26+
5jfS4kAq0gxyoQTCZnGhbbL095q3Sy7GV3EaW4yk78EuRwPFOqVUQ0D5tvrKsPT4
27+
B5AlxlarcDcMQayWKLj2pWmQm3YVlx5NfoRkSbd14h6ZryzDhG8ZfooLQ5dFh1ba
28+
f8+YbBtvFshzUDYdnr0fS0RYc/WtYmfJdb4+Fkc268BkJzg43rMSrdzaleS6jypU
29+
vzPs8WO0xU1xCIgB92vqZ+/4OlFwjbHHoQlnFHdNPbrfc8INbtLZgLCrELw4UEga
30+
-----END RSA PRIVATE KEY-----

tests/data/private_key.password

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
python-telegram-bot

tests/test_builders.py

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"""
2121
We mainly test on UpdaterBuilder because it has all methods that DispatcherBuilder already has
2222
"""
23+
from pathlib import Path
2324
from random import randint
2425
from threading import Event
2526

@@ -63,7 +64,9 @@ def test_mutually_exclusive_for_bot(self, builder, method, description):
6364
pytest.skip(f'{builder.__class__} has no method called {method}')
6465

6566
# First that e.g. `bot` can't be set if `request` was already set
66-
getattr(builder, method)(1)
67+
# We pass the private key since `private_key` is the only method that doesn't just save
68+
# the passed value
69+
getattr(builder, method)(Path('tests/data/private.key'))
6770
with pytest.raises(RuntimeError, match=f'`bot` may only be set, if no {description}'):
6871
builder.bot(None)
6972

@@ -84,7 +87,9 @@ def test_mutually_exclusive_for_dispatcher(self, builder, method, description):
8487
pytest.skip(f'{builder.__class__} has no method called {method}')
8588

8689
# First that e.g. `dispatcher` can't be set if `bot` was already set
87-
getattr(builder, method)(None)
90+
# We pass the private key since `private_key` is the only method that doesn't just save
91+
# the passed value
92+
getattr(builder, method)(Path('tests/data/private.key'))
8893
with pytest.raises(
8994
RuntimeError, match=f'`dispatcher` may only be set, if no {description}'
9095
):
@@ -102,7 +107,9 @@ def test_mutually_exclusive_for_dispatcher(self, builder, method, description):
102107
builder = builder.__class__()
103108
builder.dispatcher(None)
104109
if method != 'dispatcher_class':
105-
getattr(builder, method)(None)
110+
# We pass the private key since `private_key` is the only method that doesn't just save
111+
# the passed value
112+
getattr(builder, method)(Path('tests/data/private.key'))
106113
else:
107114
with pytest.raises(
108115
RuntimeError, match=f'`{method}` may only be set, if no Dispatcher instance'
@@ -251,3 +258,22 @@ def __init__(self, arg, **kwargs):
251258
else:
252259
assert isinstance(obj, CustomDispatcher)
253260
assert obj.arg == 2
261+
262+
@pytest.mark.parametrize('input_type', ('bytes', 'str', 'Path'))
263+
def test_all_private_key_input_types(self, builder, bot, input_type):
264+
private_key = Path('tests/data/private.key')
265+
password = Path('tests/data/private_key.password')
266+
267+
if input_type == 'bytes':
268+
private_key = private_key.read_bytes()
269+
password = password.read_bytes()
270+
if input_type == 'str':
271+
private_key = str(private_key)
272+
password = str(password)
273+
274+
builder.token(bot.token).private_key(
275+
private_key=private_key,
276+
password=password,
277+
)
278+
bot = builder.build().bot
279+
assert bot.private_key

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy