mirror of
https://github.com/python-telegram-bot/python-telegram-bot.git
synced 2024-11-24 08:06:26 +01:00
Add Support for Python 3.13 Beta (#4253)
Co-authored-by: Bibo-Joshi <22366557+Bibo-Joshi@users.noreply.github.com>
This commit is contained in:
parent
5b1e7399a4
commit
9ce0f49882
14 changed files with 123 additions and 66 deletions
4
.github/workflows/type_completeness.yml
vendored
4
.github/workflows/type_completeness.yml
vendored
|
@ -18,12 +18,12 @@ jobs:
|
|||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.9
|
||||
python-version: 3.12
|
||||
cache: 'pip'
|
||||
cache-dependency-path: '**/requirements*.txt'
|
||||
- name: Install Pyright
|
||||
run: |
|
||||
python -W ignore -m pip install pyright~=1.1.316
|
||||
python -W ignore -m pip install pyright~=1.1.367
|
||||
- name: Get PR Completeness
|
||||
# Must run before base completeness, as base completeness will checkout the base branch
|
||||
# And we can't go back to the PR branch after that in case the PR is coming from a fork
|
||||
|
|
2
.github/workflows/unit_tests.yml
vendored
2
.github/workflows/unit_tests.yml
vendored
|
@ -19,7 +19,7 @@ jobs:
|
|||
runs-on: ${{matrix.os}}
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
|
||||
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13.0-beta.2']
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
fail-fast: False
|
||||
steps:
|
||||
|
|
|
@ -36,6 +36,7 @@ classifiers = [
|
|||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Programming Language :: Python :: 3.13",
|
||||
]
|
||||
dependencies = [
|
||||
"httpx ~= 0.27",
|
||||
|
@ -82,6 +83,8 @@ job-queue = [
|
|||
]
|
||||
passport = [
|
||||
"cryptography!=3.4,!=3.4.1,!=3.4.2,!=3.4.3,>=39.0.1",
|
||||
# cffi is a dependency of cryptography and added support for python 3.13 in 1.17.0rc1
|
||||
"cffi >= 1.17.0rc1; python_version > '3.12'"
|
||||
]
|
||||
rate-limiter = [
|
||||
"aiolimiter~=1.1.0",
|
||||
|
|
|
@ -490,10 +490,10 @@ class ChatFullInfo(_ChatBase):
|
|||
self.unrestrict_boost_count: Optional[int] = unrestrict_boost_count
|
||||
self.custom_emoji_sticker_set_name: Optional[str] = custom_emoji_sticker_set_name
|
||||
self.birthdate: Optional[Birthdate] = birthdate
|
||||
self.personal_chat: Optional["Chat"] = personal_chat
|
||||
self.business_intro: Optional["BusinessIntro"] = business_intro
|
||||
self.business_location: Optional["BusinessLocation"] = business_location
|
||||
self.business_opening_hours: Optional["BusinessOpeningHours"] = business_opening_hours
|
||||
self.personal_chat: Optional[Chat] = personal_chat
|
||||
self.business_intro: Optional[BusinessIntro] = business_intro
|
||||
self.business_location: Optional[BusinessLocation] = business_location
|
||||
self.business_opening_hours: Optional[BusinessOpeningHours] = business_opening_hours
|
||||
|
||||
@classmethod
|
||||
def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["ChatFullInfo"]:
|
||||
|
|
|
@ -313,7 +313,7 @@ class GiveawayCompleted(TelegramObject):
|
|||
|
||||
self.winner_count: int = winner_count
|
||||
self.unclaimed_prize_count: Optional[int] = unclaimed_prize_count
|
||||
self.giveaway_message: Optional["Message"] = giveaway_message
|
||||
self.giveaway_message: Optional[Message] = giveaway_message
|
||||
|
||||
self._id_attrs = (
|
||||
self.winner_count,
|
||||
|
|
|
@ -108,7 +108,7 @@ class InputTextMessageContent(InputMessageContent):
|
|||
# Optionals
|
||||
self.parse_mode: ODVInput[str] = parse_mode
|
||||
self.entities: Tuple[MessageEntity, ...] = parse_sequence_arg(entities)
|
||||
self.link_preview_options: ODVInput["LinkPreviewOptions"] = parse_lpo_and_dwpp(
|
||||
self.link_preview_options: ODVInput[LinkPreviewOptions] = parse_lpo_and_dwpp(
|
||||
disable_web_page_preview, link_preview_options
|
||||
)
|
||||
|
||||
|
|
|
@ -446,7 +446,7 @@ class Update(TelegramObject):
|
|||
)
|
||||
|
||||
self._effective_user: Optional[User] = None
|
||||
self._effective_sender: Optional[Union["User", "Chat"]] = None
|
||||
self._effective_sender: Optional[Union[User, Chat]] = None
|
||||
self._effective_chat: Optional[Chat] = None
|
||||
self._effective_message: Optional[Message] = None
|
||||
|
||||
|
@ -568,7 +568,7 @@ class Update(TelegramObject):
|
|||
if self._effective_sender:
|
||||
return self._effective_sender
|
||||
|
||||
sender: Optional[Union["User", "Chat"]] = None
|
||||
sender: Optional[Union[User, Chat]] = None
|
||||
|
||||
if message := (
|
||||
self.message
|
||||
|
|
|
@ -251,39 +251,44 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
|
|||
"""
|
||||
|
||||
__slots__ = (
|
||||
"__create_task_tasks",
|
||||
"__update_fetcher_task",
|
||||
"__update_persistence_event",
|
||||
"__update_persistence_lock",
|
||||
"__update_persistence_task",
|
||||
( # noqa: RUF005
|
||||
"__create_task_tasks",
|
||||
"__update_fetcher_task",
|
||||
"__update_persistence_event",
|
||||
"__update_persistence_lock",
|
||||
"__update_persistence_task",
|
||||
"__stop_running_marker",
|
||||
"_chat_data",
|
||||
"_chat_ids_to_be_deleted_in_persistence",
|
||||
"_chat_ids_to_be_updated_in_persistence",
|
||||
"_conversation_handler_conversations",
|
||||
"_initialized",
|
||||
"_job_queue",
|
||||
"_running",
|
||||
"_update_processor",
|
||||
"_user_data",
|
||||
"_user_ids_to_be_deleted_in_persistence",
|
||||
"_user_ids_to_be_updated_in_persistence",
|
||||
"bot",
|
||||
"bot_data",
|
||||
"chat_data",
|
||||
"context_types",
|
||||
"error_handlers",
|
||||
"handlers",
|
||||
"persistence",
|
||||
"post_init",
|
||||
"post_shutdown",
|
||||
"post_stop",
|
||||
"update_queue",
|
||||
"updater",
|
||||
"user_data",
|
||||
)
|
||||
# Allowing '__weakref__' creation here since we need it for the JobQueue
|
||||
# Uncomment if necessary - currently the __weakref__ slot is already created
|
||||
# in the AsyncContextManager base class
|
||||
# "__weakref__",
|
||||
"_chat_data",
|
||||
"_chat_ids_to_be_deleted_in_persistence",
|
||||
"_chat_ids_to_be_updated_in_persistence",
|
||||
"_conversation_handler_conversations",
|
||||
"_initialized",
|
||||
"_job_queue",
|
||||
"_running",
|
||||
"_update_processor",
|
||||
"_user_data",
|
||||
"_user_ids_to_be_deleted_in_persistence",
|
||||
"_user_ids_to_be_updated_in_persistence",
|
||||
"bot",
|
||||
"bot_data",
|
||||
"chat_data",
|
||||
"context_types",
|
||||
"error_handlers",
|
||||
"handlers",
|
||||
"persistence",
|
||||
"post_init",
|
||||
"post_shutdown",
|
||||
"post_stop",
|
||||
"update_queue",
|
||||
"updater",
|
||||
"user_data",
|
||||
# Currently the __weakref__ slot is already created
|
||||
# in the AsyncContextManager base class for pythons < 3.13
|
||||
+ ("__weakref__",)
|
||||
if sys.version_info >= (3, 13)
|
||||
else ()
|
||||
)
|
||||
|
||||
def __init__(
|
||||
|
|
|
@ -21,7 +21,7 @@ modify behavior of the respective parent classes in order to make them easier to
|
|||
pytest framework. A common change is to allow monkeypatching of the class members by not
|
||||
enforcing slots in the subclasses."""
|
||||
from telegram import Bot, Message, User
|
||||
from telegram.ext import Application, ExtBot
|
||||
from telegram.ext import Application, ExtBot, Updater
|
||||
from tests.auxil.ci_bots import BOT_INFO_PROVIDER
|
||||
from tests.auxil.constants import PRIVATE_KEY
|
||||
from tests.auxil.envvars import TEST_WITH_OPT_DEPS
|
||||
|
@ -89,6 +89,10 @@ class PytestMessage(Message):
|
|||
pass
|
||||
|
||||
|
||||
class PytestUpdater(Updater):
|
||||
pass
|
||||
|
||||
|
||||
def make_bot(bot_info=None, **kwargs):
|
||||
"""
|
||||
Tests are executed on tg.ext.ExtBot, as that class only extends the functionality of tg.bot
|
||||
|
|
|
@ -20,6 +20,7 @@ import asyncio
|
|||
import datetime
|
||||
import logging
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Dict, List
|
||||
from uuid import uuid4
|
||||
|
||||
|
@ -291,6 +292,5 @@ def timezone(tzinfo):
|
|||
|
||||
|
||||
@pytest.fixture()
|
||||
def tmp_file(tmp_path):
|
||||
with tmp_path / uuid4().hex as file:
|
||||
yield file
|
||||
def tmp_file(tmp_path) -> Path:
|
||||
return tmp_path / uuid4().hex
|
||||
|
|
|
@ -61,7 +61,7 @@ from tests.auxil.asyncio_helpers import call_after
|
|||
from tests.auxil.build_messages import make_message_update
|
||||
from tests.auxil.files import PROJECT_ROOT_PATH
|
||||
from tests.auxil.networking import send_webhook_message
|
||||
from tests.auxil.pytest_classes import make_bot
|
||||
from tests.auxil.pytest_classes import PytestApplication, PytestUpdater, make_bot
|
||||
from tests.auxil.slots import mro_slots
|
||||
|
||||
|
||||
|
@ -1581,7 +1581,13 @@ class TestApplication:
|
|||
async def post_init(app: Application) -> None:
|
||||
events.append("post_init")
|
||||
|
||||
app = Application.builder().bot(one_time_bot).post_init(post_init).build()
|
||||
app = (
|
||||
Application.builder()
|
||||
.application_class(PytestApplication)
|
||||
.updater(PytestUpdater(one_time_bot, asyncio.Queue()))
|
||||
.post_init(post_init)
|
||||
.build()
|
||||
)
|
||||
app.bot._unfreeze()
|
||||
monkeypatch.setattr(app.bot, "get_updates", get_updates)
|
||||
monkeypatch.setattr(
|
||||
|
@ -1624,7 +1630,13 @@ class TestApplication:
|
|||
async def post_shutdown(app: Application) -> None:
|
||||
events.append("post_shutdown")
|
||||
|
||||
app = Application.builder().bot(one_time_bot).post_shutdown(post_shutdown).build()
|
||||
app = (
|
||||
Application.builder()
|
||||
.application_class(PytestApplication)
|
||||
.updater(PytestUpdater(one_time_bot, asyncio.Queue()))
|
||||
.post_shutdown(post_shutdown)
|
||||
.build()
|
||||
)
|
||||
app.bot._unfreeze()
|
||||
monkeypatch.setattr(app.bot, "get_updates", get_updates)
|
||||
monkeypatch.setattr(
|
||||
|
@ -1650,7 +1662,7 @@ class TestApplication:
|
|||
platform.system() == "Windows",
|
||||
reason="Can't send signals without stopping whole process on windows",
|
||||
)
|
||||
def test_run_polling_post_stop(self, bot, monkeypatch):
|
||||
def test_run_polling_post_stop(self, one_time_bot, monkeypatch):
|
||||
events = []
|
||||
|
||||
async def get_updates(*args, **kwargs):
|
||||
|
@ -1671,7 +1683,13 @@ class TestApplication:
|
|||
async def post_stop(app: Application) -> None:
|
||||
events.append("post_stop")
|
||||
|
||||
app = Application.builder().token(bot.token).post_stop(post_stop).build()
|
||||
app = (
|
||||
Application.builder()
|
||||
.application_class(PytestApplication)
|
||||
.updater(PytestUpdater(one_time_bot, asyncio.Queue()))
|
||||
.post_stop(post_stop)
|
||||
.build()
|
||||
)
|
||||
app.bot._unfreeze()
|
||||
monkeypatch.setattr(app.bot, "get_updates", get_updates)
|
||||
monkeypatch.setattr(app, "stop", call_after(app.stop, lambda _: events.append("stop")))
|
||||
|
@ -1863,7 +1881,13 @@ class TestApplication:
|
|||
async def post_init(app: Application) -> None:
|
||||
events.append("post_init")
|
||||
|
||||
app = Application.builder().bot(one_time_bot).post_init(post_init).build()
|
||||
app = (
|
||||
Application.builder()
|
||||
.post_init(post_init)
|
||||
.application_class(PytestApplication)
|
||||
.updater(PytestUpdater(one_time_bot, asyncio.Queue()))
|
||||
.build()
|
||||
)
|
||||
app.bot._unfreeze()
|
||||
monkeypatch.setattr(app.bot, "set_webhook", set_webhook)
|
||||
monkeypatch.setattr(app.bot, "delete_webhook", delete_webhook)
|
||||
|
@ -1923,7 +1947,13 @@ class TestApplication:
|
|||
async def post_shutdown(app: Application) -> None:
|
||||
events.append("post_shutdown")
|
||||
|
||||
app = Application.builder().bot(one_time_bot).post_shutdown(post_shutdown).build()
|
||||
app = (
|
||||
Application.builder()
|
||||
.application_class(PytestApplication)
|
||||
.updater(PytestUpdater(one_time_bot, asyncio.Queue()))
|
||||
.post_shutdown(post_shutdown)
|
||||
.build()
|
||||
)
|
||||
app.bot._unfreeze()
|
||||
monkeypatch.setattr(app.bot, "set_webhook", set_webhook)
|
||||
monkeypatch.setattr(app.bot, "delete_webhook", delete_webhook)
|
||||
|
@ -1960,7 +1990,7 @@ class TestApplication:
|
|||
platform.system() == "Windows",
|
||||
reason="Can't send signals without stopping whole process on windows",
|
||||
)
|
||||
def test_run_webhook_post_stop(self, bot, monkeypatch):
|
||||
def test_run_webhook_post_stop(self, one_time_bot, monkeypatch):
|
||||
events = []
|
||||
|
||||
async def delete_webhook(*args, **kwargs):
|
||||
|
@ -1987,7 +2017,13 @@ class TestApplication:
|
|||
async def post_stop(app: Application) -> None:
|
||||
events.append("post_stop")
|
||||
|
||||
app = Application.builder().token(bot.token).post_stop(post_stop).build()
|
||||
app = (
|
||||
Application.builder()
|
||||
.application_class(PytestApplication)
|
||||
.updater(PytestUpdater(one_time_bot, asyncio.Queue()))
|
||||
.post_stop(post_stop)
|
||||
.build()
|
||||
)
|
||||
app.bot._unfreeze()
|
||||
monkeypatch.setattr(app.bot, "set_webhook", set_webhook)
|
||||
monkeypatch.setattr(app.bot, "delete_webhook", delete_webhook)
|
||||
|
@ -2480,7 +2516,13 @@ class TestApplication:
|
|||
|
||||
app.create_task(task(app))
|
||||
|
||||
app = ApplicationBuilder().bot(one_time_bot).post_init(post_init).build()
|
||||
app = (
|
||||
ApplicationBuilder()
|
||||
.application_class(PytestApplication)
|
||||
.updater(PytestUpdater(one_time_bot, asyncio.Queue()))
|
||||
.post_init(post_init)
|
||||
.build()
|
||||
)
|
||||
monkeypatch.setattr(app.bot, "get_updates", get_updates)
|
||||
monkeypatch.setattr(app.bot, "set_webhook", set_webhook)
|
||||
monkeypatch.setattr(app.bot, "delete_webhook", delete_webhook)
|
||||
|
|
|
@ -47,6 +47,7 @@ from telegram.request._httpxrequest import HTTPXRequest
|
|||
from telegram.request._requestparameter import RequestParameter
|
||||
from telegram.warnings import PTBDeprecationWarning
|
||||
from tests.auxil.envvars import TEST_WITH_OPT_DEPS
|
||||
from tests.auxil.networking import NonchalantHttpxRequest
|
||||
from tests.auxil.slots import mro_slots
|
||||
|
||||
# We only need mixed_rqs fixture, but it uses the others, so pytest needs us to import them as well
|
||||
|
@ -72,7 +73,7 @@ def mocker_factory(
|
|||
|
||||
@pytest.fixture()
|
||||
async def httpx_request():
|
||||
async with HTTPXRequest() as rq:
|
||||
async with NonchalantHttpxRequest() as rq:
|
||||
yield rq
|
||||
|
||||
|
||||
|
@ -137,7 +138,7 @@ class TestRequestWithoutRequest:
|
|||
async def shutdown():
|
||||
self.test_flag.append("stop")
|
||||
|
||||
httpx_request = HTTPXRequest()
|
||||
httpx_request = NonchalantHttpxRequest()
|
||||
|
||||
monkeypatch.setattr(httpx_request, "initialize", initialize)
|
||||
monkeypatch.setattr(httpx_request, "shutdown", shutdown)
|
||||
|
@ -154,7 +155,7 @@ class TestRequestWithoutRequest:
|
|||
async def shutdown():
|
||||
self.test_flag = "stop"
|
||||
|
||||
httpx_request = HTTPXRequest()
|
||||
httpx_request = NonchalantHttpxRequest()
|
||||
|
||||
monkeypatch.setattr(httpx_request, "initialize", initialize)
|
||||
monkeypatch.setattr(httpx_request, "shutdown", shutdown)
|
||||
|
@ -545,7 +546,7 @@ class TestHTTPXRequestWithoutRequest:
|
|||
async def aclose(*args):
|
||||
self.test_flag.append("stop")
|
||||
|
||||
httpx_request = HTTPXRequest()
|
||||
httpx_request = NonchalantHttpxRequest()
|
||||
|
||||
monkeypatch.setattr(httpx_request, "initialize", initialize)
|
||||
monkeypatch.setattr(httpx.AsyncClient, "aclose", aclose)
|
||||
|
@ -562,7 +563,7 @@ class TestHTTPXRequestWithoutRequest:
|
|||
async def aclose(*args):
|
||||
self.test_flag = "stop"
|
||||
|
||||
httpx_request = HTTPXRequest()
|
||||
httpx_request = NonchalantHttpxRequest()
|
||||
|
||||
monkeypatch.setattr(httpx_request, "initialize", initialize)
|
||||
monkeypatch.setattr(httpx.AsyncClient, "aclose", aclose)
|
||||
|
|
|
@ -97,7 +97,7 @@ from tests.auxil.bot_method_checks import check_defaults_handling
|
|||
from tests.auxil.ci_bots import FALLBACKS
|
||||
from tests.auxil.envvars import GITHUB_ACTION, TEST_WITH_OPT_DEPS
|
||||
from tests.auxil.files import data_file
|
||||
from tests.auxil.networking import expect_bad_request
|
||||
from tests.auxil.networking import NonchalantHttpxRequest, expect_bad_request
|
||||
from tests.auxil.pytest_classes import PytestBot, PytestExtBot, make_bot
|
||||
from tests.auxil.slots import mro_slots
|
||||
|
||||
|
@ -253,7 +253,7 @@ class TestBotWithoutRequest:
|
|||
async def stop(*args, **kwargs):
|
||||
self.test_flag.append("stop")
|
||||
|
||||
temp_bot = PytestBot(token=bot.token)
|
||||
temp_bot = PytestBot(token=bot.token, request=NonchalantHttpxRequest())
|
||||
orig_stop = temp_bot.request.shutdown
|
||||
|
||||
try:
|
||||
|
|
|
@ -2624,7 +2624,9 @@ class TestMessageWithoutRequest(TestMessageBase):
|
|||
async def test_default_do_quote(
|
||||
self, bot, message, default_quote, chat_type, expected, monkeypatch
|
||||
):
|
||||
message.set_bot(PytestExtBot(token=bot.token, defaults=Defaults(do_quote=default_quote)))
|
||||
original_bot = message.get_bot()
|
||||
temp_bot = PytestExtBot(token=bot.token, defaults=Defaults(do_quote=default_quote))
|
||||
message.set_bot(temp_bot)
|
||||
|
||||
async def make_assertion(*_, **kwargs):
|
||||
reply_parameters = kwargs.get("reply_parameters") or ReplyParameters(message_id=False)
|
||||
|
@ -2637,7 +2639,7 @@ class TestMessageWithoutRequest(TestMessageBase):
|
|||
message.chat.type = chat_type
|
||||
assert await message.reply_text("test")
|
||||
finally:
|
||||
message.get_bot()._defaults = None
|
||||
message.set_bot(original_bot)
|
||||
|
||||
async def test_edit_forum_topic(self, monkeypatch, message):
|
||||
async def make_assertion(*_, **kwargs):
|
||||
|
|
Loading…
Reference in a new issue