Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions changes/unreleased/5334.Yat2ZJVdKq3PJCX7kWFLfD.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
breaking = "Dataclasses"
internal = "Dataclasses"
[[pull_requests]]
uid = "5334"
author_uids = ["aelkheir"]
closes_threads = ["5279"]
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ classifiers = [
]
dependencies = [
"httpx >=0.27,<0.29",
"httpcore >=1.0.9; python_version >= '3.14'" # httpx doesn't pin this as of 0.28.1
"httpcore >=1.0.9; python_version >= '3.14'", # httpx doesn't pin this as of 0.28.1
"typing-extensions~=4.16",
]

[project.urls]
Expand Down
32 changes: 7 additions & 25 deletions src/telegram/_birthdate.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@
import datetime as dtm

from telegram._telegramobject import TelegramObject
from telegram._utils.types import JSONDict
from telegram._utils.dataclass import tg_dataclass, tg_field


@tg_dataclass()
class Birthdate(TelegramObject):
"""
This object describes the birthdate of a user.
Expand All @@ -45,30 +46,11 @@ class Birthdate(TelegramObject):

"""

__slots__ = ("day", "month", "year")

def __init__(
self,
day: int,
month: int,
year: int | None = None,
*,
api_kwargs: JSONDict | None = None,
):
super().__init__(api_kwargs=api_kwargs)

# Required
self.day: int = day
self.month: int = month
# Optional
self.year: int | None = year

self._id_attrs = (
self.day,
self.month,
)

self._freeze()
# Required
day: int = tg_field(compare=True)
month: int = tg_field(compare=True)
# Optional
year: int | None = tg_field(default=None)

def to_date(self, year: int | None = None) -> dtm.date:
"""Return the birthdate as a date object.
Expand Down
22 changes: 4 additions & 18 deletions src/telegram/_botaccesssettings.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,13 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains an object that represents a Telegram Bot Access Settings."""

from collections.abc import Sequence

from telegram._telegramobject import TelegramObject
from telegram._user import User
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.types import JSONDict
from telegram._utils.dataclass import tg_dataclass, tg_field


@tg_dataclass()
class BotAccessSettings(TelegramObject):
"""
This object describes the access settings of a bot.
Expand All @@ -48,18 +47,5 @@ class BotAccessSettings(TelegramObject):
have access to the bot if the access is restricted.
"""

__slots__ = ("added_users", "is_access_restricted")

def __init__(
self,
is_access_restricted: bool,
added_users: Sequence[User] | None = None,
*,
api_kwargs: JSONDict | None = None,
):
super().__init__(api_kwargs=api_kwargs)
self.is_access_restricted: bool = is_access_restricted
self.added_users: tuple[User, ...] = parse_sequence_arg(added_users)

self._id_attrs = (self.is_access_restricted, self.added_users)
self._freeze()
is_access_restricted: bool = tg_field(compare=True)
added_users: tuple[User, ...] = tg_field(compare=True, converter=parse_sequence_arg)
26 changes: 10 additions & 16 deletions src/telegram/_botcommand.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,14 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains an object that represents a Telegram Bot Command."""

from typing import Final
from typing import ClassVar

from telegram import constants
from telegram._telegramobject import TelegramObject
from telegram._utils.types import JSONDict
from telegram._utils.dataclass import tg_dataclass, tg_field


@tg_dataclass()
class BotCommand(TelegramObject):
"""
This object represents a bot command.
Expand All @@ -50,33 +51,26 @@ class BotCommand(TelegramObject):

"""

__slots__ = ("command", "description")
command: str = tg_field(compare=True)
description: str = tg_field(compare=True)

def __init__(self, command: str, description: str, *, api_kwargs: JSONDict | None = None):
super().__init__(api_kwargs=api_kwargs)
self.command: str = command
self.description: str = description

self._id_attrs = (self.command, self.description)

self._freeze()

MIN_COMMAND: Final[int] = constants.BotCommandLimit.MIN_COMMAND
# TODO: https://docs.python.org/3.13/library/typing.html#typing.ClassVar
MIN_COMMAND: ClassVar[int] = constants.BotCommandLimit.MIN_COMMAND
""":const:`telegram.constants.BotCommandLimit.MIN_COMMAND`

.. versionadded:: 20.0
"""
MAX_COMMAND: Final[int] = constants.BotCommandLimit.MAX_COMMAND
MAX_COMMAND: ClassVar[int] = constants.BotCommandLimit.MAX_COMMAND
""":const:`telegram.constants.BotCommandLimit.MAX_COMMAND`

.. versionadded:: 20.0
"""
MIN_DESCRIPTION: Final[int] = constants.BotCommandLimit.MIN_DESCRIPTION
MIN_DESCRIPTION: ClassVar[int] = constants.BotCommandLimit.MIN_DESCRIPTION
""":const:`telegram.constants.BotCommandLimit.MIN_DESCRIPTION`

.. versionadded:: 20.0
"""
MAX_DESCRIPTION: Final[int] = constants.BotCommandLimit.MAX_DESCRIPTION
MAX_DESCRIPTION: ClassVar[int] = constants.BotCommandLimit.MAX_DESCRIPTION
""":const:`telegram.constants.BotCommandLimit.MAX_DESCRIPTION`

.. versionadded:: 20.0
Expand Down
105 changes: 43 additions & 62 deletions src/telegram/_botcommandscope.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,17 @@
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
# pylint: disable=redefined-builtin
"""This module contains objects representing Telegram bot command scopes."""

from typing import ClassVar, Final
from typing import ClassVar

from telegram import constants
from telegram._telegramobject import TelegramObject
from telegram._utils import enum
from telegram._utils.types import JSONDict
from telegram._utils.dataclass import tg_dataclass, tg_field


@tg_dataclass()
class BotCommandScope(TelegramObject):
"""Base class for objects that represent the scope to which bot commands are applied.
Currently, the following 7 scopes are supported:
Expand Down Expand Up @@ -57,8 +57,6 @@ class BotCommandScope(TelegramObject):
type (:obj:`str`): Scope type.
"""

__slots__ = ("type",)

__DE_JSON_DISPATCH__: ClassVar[tuple[str, dict[str, str]] | None] = (
"type",
{
Expand All @@ -72,29 +70,34 @@ class BotCommandScope(TelegramObject):
},
)

DEFAULT: Final[str] = constants.BotCommandScopeType.DEFAULT
# TODO: https://docs.python.org/3.13/library/typing.html#typing.ClassVar
DEFAULT: ClassVar[str] = constants.BotCommandScopeType.DEFAULT
""":const:`telegram.constants.BotCommandScopeType.DEFAULT`"""
ALL_PRIVATE_CHATS: Final[str] = constants.BotCommandScopeType.ALL_PRIVATE_CHATS
ALL_PRIVATE_CHATS: ClassVar[str] = constants.BotCommandScopeType.ALL_PRIVATE_CHATS
""":const:`telegram.constants.BotCommandScopeType.ALL_PRIVATE_CHATS`"""
ALL_GROUP_CHATS: Final[str] = constants.BotCommandScopeType.ALL_GROUP_CHATS
ALL_GROUP_CHATS: ClassVar[str] = constants.BotCommandScopeType.ALL_GROUP_CHATS
""":const:`telegram.constants.BotCommandScopeType.ALL_GROUP_CHATS`"""
ALL_CHAT_ADMINISTRATORS: Final[str] = constants.BotCommandScopeType.ALL_CHAT_ADMINISTRATORS
ALL_CHAT_ADMINISTRATORS: ClassVar[str] = constants.BotCommandScopeType.ALL_CHAT_ADMINISTRATORS
""":const:`telegram.constants.BotCommandScopeType.ALL_CHAT_ADMINISTRATORS`"""
CHAT: Final[str] = constants.BotCommandScopeType.CHAT
CHAT: ClassVar[str] = constants.BotCommandScopeType.CHAT
""":const:`telegram.constants.BotCommandScopeType.CHAT`"""
CHAT_ADMINISTRATORS: Final[str] = constants.BotCommandScopeType.CHAT_ADMINISTRATORS
CHAT_ADMINISTRATORS: ClassVar[str] = constants.BotCommandScopeType.CHAT_ADMINISTRATORS
""":const:`telegram.constants.BotCommandScopeType.CHAT_ADMINISTRATORS`"""
CHAT_MEMBER: Final[str] = constants.BotCommandScopeType.CHAT_MEMBER
CHAT_MEMBER: ClassVar[str] = constants.BotCommandScopeType.CHAT_MEMBER
""":const:`telegram.constants.BotCommandScopeType.CHAT_MEMBER`"""

def __init__(self, type: str, *, api_kwargs: JSONDict | None = None):
super().__init__(api_kwargs=api_kwargs)
self.type: str = enum.get_member(constants.BotCommandScopeType, type, type)
self._id_attrs = (self.type,)
@staticmethod
def _type_converter(value: str) -> str:
return enum.get_member(constants.BotCommandScopeType, value, value)

@staticmethod
def _chat_id_converter(value: str | int) -> str | int:
return value if isinstance(value, str) and value.startswith("@") else int(value)

self._freeze()
type: str = tg_field(compare=True, converter=_type_converter)


@tg_dataclass()
class BotCommandScopeDefault(BotCommandScope):
"""Represents the default scope of bot commands. Default commands are used if no commands with
a `narrower scope`_ are specified for the user.
Expand All @@ -106,13 +109,11 @@ class BotCommandScopeDefault(BotCommandScope):
type (:obj:`str`): Scope type :tg-const:`telegram.BotCommandScope.DEFAULT`.
"""

__slots__ = ()

def __init__(self, *, api_kwargs: JSONDict | None = None):
super().__init__(type=BotCommandScope.DEFAULT, api_kwargs=api_kwargs)
self._freeze()
# Attribute only (init=False)
type: str = tg_field(init=False, default=BotCommandScope.DEFAULT)


@tg_dataclass()
class BotCommandScopeAllPrivateChats(BotCommandScope):
"""Represents the scope of bot commands, covering all private chats.

Expand All @@ -122,13 +123,11 @@ class BotCommandScopeAllPrivateChats(BotCommandScope):
type (:obj:`str`): Scope type :tg-const:`telegram.BotCommandScope.ALL_PRIVATE_CHATS`.
"""

__slots__ = ()

def __init__(self, *, api_kwargs: JSONDict | None = None):
super().__init__(type=BotCommandScope.ALL_PRIVATE_CHATS, api_kwargs=api_kwargs)
self._freeze()
# Attribute only (init=False)
type: str = tg_field(init=False, default=BotCommandScope.ALL_PRIVATE_CHATS)


@tg_dataclass()
class BotCommandScopeAllGroupChats(BotCommandScope):
"""Represents the scope of bot commands, covering all group and supergroup chats.

Expand All @@ -137,13 +136,10 @@ class BotCommandScopeAllGroupChats(BotCommandScope):
type (:obj:`str`): Scope type :tg-const:`telegram.BotCommandScope.ALL_GROUP_CHATS`.
"""

__slots__ = ()

def __init__(self, *, api_kwargs: JSONDict | None = None):
super().__init__(type=BotCommandScope.ALL_GROUP_CHATS, api_kwargs=api_kwargs)
self._freeze()
type: str = tg_field(init=False, default=BotCommandScope.ALL_GROUP_CHATS)


@tg_dataclass()
class BotCommandScopeAllChatAdministrators(BotCommandScope):
"""Represents the scope of bot commands, covering all group and supergroup chat administrators.

Expand All @@ -152,13 +148,11 @@ class BotCommandScopeAllChatAdministrators(BotCommandScope):
type (:obj:`str`): Scope type :tg-const:`telegram.BotCommandScope.ALL_CHAT_ADMINISTRATORS`.
"""

__slots__ = ()

def __init__(self, *, api_kwargs: JSONDict | None = None):
super().__init__(type=BotCommandScope.ALL_CHAT_ADMINISTRATORS, api_kwargs=api_kwargs)
self._freeze()
# Attribute only (init=False)
type: str = tg_field(init=False, default=BotCommandScope.ALL_CHAT_ADMINISTRATORS)


@tg_dataclass()
class BotCommandScopeChat(BotCommandScope):
"""Represents the scope of bot commands, covering a specific chat.

Expand All @@ -175,17 +169,13 @@ class BotCommandScopeChat(BotCommandScope):
chat_id (:obj:`str` | :obj:`int`): |chat_id_group|
"""

__slots__ = ("chat_id",)
# Attribute only (init=False)
type: str = tg_field(compare=True, init=False, default=BotCommandScope.CHAT)

def __init__(self, chat_id: str | int, *, api_kwargs: JSONDict | None = None):
super().__init__(type=BotCommandScope.CHAT, api_kwargs=api_kwargs)
with self._unfrozen():
self.chat_id: str | int = (
chat_id if isinstance(chat_id, str) and chat_id.startswith("@") else int(chat_id)
)
self._id_attrs = (self.type, self.chat_id)
chat_id: str | int = tg_field(compare=True, converter=BotCommandScope._chat_id_converter)


@tg_dataclass()
class BotCommandScopeChatAdministrators(BotCommandScope):
"""Represents the scope of bot commands, covering all administrators of a specific group or
supergroup chat.
Expand All @@ -202,17 +192,13 @@ class BotCommandScopeChatAdministrators(BotCommandScope):
chat_id (:obj:`str` | :obj:`int`): |chat_id_group|
"""

__slots__ = ("chat_id",)
# Attribute only (init=False)
type: str = tg_field(compare=True, init=False, default=BotCommandScope.CHAT_ADMINISTRATORS)

def __init__(self, chat_id: str | int, *, api_kwargs: JSONDict | None = None):
super().__init__(type=BotCommandScope.CHAT_ADMINISTRATORS, api_kwargs=api_kwargs)
with self._unfrozen():
self.chat_id: str | int = (
chat_id if isinstance(chat_id, str) and chat_id.startswith("@") else int(chat_id)
)
self._id_attrs = (self.type, self.chat_id)
chat_id: str | int = tg_field(compare=True, converter=BotCommandScope._chat_id_converter)


@tg_dataclass()
class BotCommandScopeChatMember(BotCommandScope):
"""Represents the scope of bot commands, covering a specific member of a group or supergroup
chat.
Expand All @@ -232,13 +218,8 @@ class BotCommandScopeChatMember(BotCommandScope):
user_id (:obj:`int`): Unique identifier of the target user.
"""

__slots__ = ("chat_id", "user_id")
# Attribute only (init=False)
type: str = tg_field(compare=True, init=False, default=BotCommandScope.CHAT_MEMBER)

def __init__(self, chat_id: str | int, user_id: int, *, api_kwargs: JSONDict | None = None):
super().__init__(type=BotCommandScope.CHAT_MEMBER, api_kwargs=api_kwargs)
with self._unfrozen():
self.chat_id: str | int = (
chat_id if isinstance(chat_id, str) and chat_id.startswith("@") else int(chat_id)
)
self.user_id: int = user_id
self._id_attrs = (self.type, self.chat_id, self.user_id)
chat_id: str | int = tg_field(compare=True, converter=BotCommandScope._chat_id_converter)
user_id: int = tg_field(compare=True)
Loading
Loading