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:
Harshil 2024-06-18 16:25:02 -04:00 committed by GitHub
parent 5b1e7399a4
commit 9ce0f49882
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 123 additions and 66 deletions

View file

@ -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

View file

@ -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:

View file

@ -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",

View file

@ -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"]:

View file

@ -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,

View file

@ -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
)

View file

@ -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

View file

@ -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__(

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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)

View file

@ -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:

View file

@ -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):