Skip to content

Commit

Permalink
Add Support for Python 3.13 Beta (#4253)
Browse files Browse the repository at this point in the history
Co-authored-by: Bibo-Joshi <22366557+Bibo-Joshi@users.noreply.github.com>
  • Loading branch information
harshil21 and Bibo-Joshi committed Jun 18, 2024
1 parent 5b1e739 commit 9ce0f49
Show file tree
Hide file tree
Showing 14 changed files with 123 additions and 66 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/type_completeness.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/unit_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
8 changes: 4 additions & 4 deletions telegram/_chatfullinfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -490,10 +490,10 @@ def __init__(
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"]:
Expand Down
2 changes: 1 addition & 1 deletion telegram/_giveaway.py
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ def __init__(

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,
Expand Down
2 changes: 1 addition & 1 deletion telegram/_inline/inputtextmessagecontent.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ def __init__(
# 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
)

Expand Down
4 changes: 2 additions & 2 deletions telegram/_update.py
Original file line number Diff line number Diff line change
Expand Up @@ -446,7 +446,7 @@ def __init__(
)

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

Expand Down Expand Up @@ -568,7 +568,7 @@ def effective_sender(self) -> Optional[Union["User", "Chat"]]:
if self._effective_sender:
return self._effective_sender

sender: Optional[Union["User", "Chat"]] = None
sender: Optional[Union[User, Chat]] = None

if message := (
self.message
Expand Down
69 changes: 37 additions & 32 deletions telegram/ext/_application.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__(
Expand Down
6 changes: 5 additions & 1 deletion tests/auxil/pytest_classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
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
Expand Down Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import datetime
import logging
import sys
from pathlib import Path
from typing import Dict, List
from uuid import uuid4

Expand Down Expand Up @@ -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
62 changes: 52 additions & 10 deletions tests/ext/test_application.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
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


Expand Down Expand Up @@ -1581,7 +1581,13 @@ def thread_target():
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(
Expand Down Expand Up @@ -1624,7 +1630,13 @@ def thread_target():
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(
Expand All @@ -1650,7 +1662,7 @@ async def post_shutdown(app: Application) -> None:
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):
Expand All @@ -1671,7 +1683,13 @@ def thread_target():
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")))
Expand Down Expand Up @@ -1863,7 +1881,13 @@ def thread_target():
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)
Expand Down Expand Up @@ -1923,7 +1947,13 @@ def thread_target():
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)
Expand Down Expand Up @@ -1960,7 +1990,7 @@ async def post_shutdown(app: Application) -> None:
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):
Expand All @@ -1987,7 +2017,13 @@ def thread_target():
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)
Expand Down Expand Up @@ -2480,7 +2516,13 @@ async def task(app):

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)
Expand Down
11 changes: 6 additions & 5 deletions tests/request/test_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
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
Expand All @@ -72,7 +73,7 @@ async def make_assertion(*args, **kwargs):

@pytest.fixture()
async def httpx_request():
async with HTTPXRequest() as rq:
async with NonchalantHttpxRequest() as rq:
yield rq


Expand Down Expand Up @@ -137,7 +138,7 @@ async def initialize():
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)
Expand All @@ -154,7 +155,7 @@ async def initialize():
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)
Expand Down Expand Up @@ -545,7 +546,7 @@ async def initialize():
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)
Expand All @@ -562,7 +563,7 @@ async def initialize():
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)
Expand Down
Loading

0 comments on commit 9ce0f49

Please sign in to comment.