Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

User like properties#4713

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Open
david-shiko wants to merge4 commits intopython-telegram-bot:master
base:master
Choose a base branch
Loading
fromdavid-shiko:UserLikeProperties
Open
Show file tree
Hide file tree
Changes fromall 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
5 changes: 2 additions & 3 deletionstelegram/_chat.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -39,6 +39,7 @@
ReplyMarkup,
TimePeriod,
)
from telegram._utils.usernames import get_link
from telegram.helpers import escape_markdown
from telegram.helpers import mention_html as helpers_mention_html
from telegram.helpers import mention_markdown as helpers_mention_markdown
Expand DownExpand Up@@ -162,9 +163,7 @@ def link(self) -> Optional[str]:
""":obj:`str`: Convenience property. If the chat has a :attr:`~Chat.username`, returns a
t.me link of the chat.
"""
if self.username:
return f"https://t.me/{self.username}"
return None
return get_link(user=self)

def mention_markdown(self, name: Optional[str] = None) -> str:
"""
Expand Down
24 changes: 23 additions & 1 deletiontelegram/_shared.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -18,11 +18,12 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains two objects used for request chats/users service messages."""
from collections.abc import Sequence
from typing import TYPE_CHECKING, Optional
from typing import TYPE_CHECKING, Optional, Union

from telegram._files.photosize import PhotoSize
from telegram._telegramobject import TelegramObject
from telegram._utils.argumentparsing import de_list_optional, parse_sequence_arg
from telegram._utils.usernames import get_name, get_full_name, get_link
from telegram._utils.types import JSONDict

if TYPE_CHECKING:
Expand DownExpand Up@@ -244,6 +245,27 @@ def __init__(

self._freeze()

@property
def name(self) -> Union[str, None]:
""":obj:`str`: Convenience property. If available, returns the user's :attr:`username`
prefixed with "@". If :attr:`username` is not available, returns :attr:`full_name`.
"""
return get_name(user=self, )

@property
def full_name(self) -> Union[str, None]:
""":obj:`str`: Convenience property. The user's :attr:`first_name`, followed by (if
available) :attr:`last_name`, otherwise None.
"""
return get_full_name(user=self, )

@property
def link(self) -> Union[str, None]:
""":obj:`str`: Convenience property. The user's :attr:`first_name`, followed by (if
available) :attr:`last_name`, otherwise None.
"""
return get_link(user=self, )

@classmethod
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "SharedUser":
"""See :meth:`telegram.TelegramObject.de_json`."""
Expand Down
13 changes: 4 additions & 9 deletionstelegram/_user.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,6 +26,7 @@
from telegram._menubutton import MenuButton
from telegram._telegramobject import TelegramObject
from telegram._utils.defaultvalue import DEFAULT_NONE
from telegram._utils.usernames import get_name, get_full_name, get_link
from telegram._utils.types import (
CorrectOptionID,
FileInput,
Expand DownExpand Up@@ -207,27 +208,21 @@ def name(self) -> str:
""":obj:`str`: Convenience property. If available, returns the user's :attr:`username`
prefixed with "@". If :attr:`username` is not available, returns :attr:`full_name`.
"""
if self.username:
return f"@{self.username}"
return self.full_name
return get_name(self, )

@property
def full_name(self) -> str:
""":obj:`str`: Convenience property. The user's :attr:`first_name`, followed by (if
available) :attr:`last_name`.
"""
if self.last_name:
return f"{self.first_name} {self.last_name}"
return self.first_name
return get_full_name(self, )

@property
def link(self) -> Optional[str]:
""":obj:`str`: Convenience property. If :attr:`username` is available, returns a t.me link
of the user.
"""
if self.username:
return f"https://t.me/{self.username}"
return None
return get_link(self, )

async def get_profile_photos(
self,
Expand Down
103 changes: 103 additions & 0 deletionstelegram/_utils/usernames.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""Shared properties to extract username, first_name, last_name values if filled."""
from __future__ import annotations
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

please avoid using this. If this is for using|, please useUnion andOptional instead

from typing import Protocol, overload


class UserLikeOptional(Protocol):
"""
Note:
`User`, `Contact` (and maybe some other) objects always have first_name,
unlike the `Chat` and `Shared`, were they are optional.
The `last_name` is always optional.
"""
last_name: str | None
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Suggested change
last_name:str|None
first_name:Optional[str]
last_name:str|None

that way you should be able to removeMiniUserLike

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

  1. One of the hooks, probably black or isort, converts automatically during commit.
  2. Sometimes, the field always exists (for example, for theUser type) and sometimes it might not (like in theSharedUser type). This affects the return type: it can beOptional[str] or guaranteed to be anstr . That's why I introduced it.
  3. I know the nameMiniUserLike is pretty silly, but I couldn't think of anything better. I hope you can help me with that :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

  • running the pre-commit hooks on python 3.9 (lowest supported version) should make sure that this doesn't happen. Though I admit I'm not completely sure why it does in the first place
  • you already differentiate betweenUserLikeOptional andUserLike. That should already do the trick IMO

username: str | None


class UserLike(UserLikeOptional):
"""
Note:
`User`, `Contact` (and maybe some other) objects always have first_name,
unlike the `Chat` and `Shared`, were they are optional.
The `last_name` is always optional.
"""
first_name: str


class MiniUserLike(UserLikeOptional):
"""
Note:
`User`, `Contact` (and maybe some other) objects always have first_name,
unlike the `Chat` and `Shared`, were they are optional.
The `last_name` is always optional.
"""
first_name: str | None


@overload
def get_name(user: UserLike) -> str:
...


@overload
def get_name(user: MiniUserLike) -> str | None:
...


def get_name(user: UserLike | MiniUserLike) -> str | None:
""":obj:`str`: Convenience property. If available, returns the user's :attr:`username`
prefixed with "@". If :attr:`username` is not available, returns :attr:`full_name`.
For the UserLike object str will always be returned as `first_name`always exists.
"""
if user.username:
return f"@{user.username}"
return get_full_name(user=user, )


@overload
def get_full_name(user: UserLike) -> str:
...


@overload
def get_full_name(user: MiniUserLike) -> str | None:
...


def get_full_name(user: UserLike | MiniUserLike) -> str | None:
""":obj:`str`: Convenience property. The user's :attr:`first_name`, followed by (if
available) :attr:`last_name`, otherwise None.
For the UserLike object str will always be returned as `first_name`always exists.
"""
if user.first_name and user.last_name:
return f"{user.first_name} {user.last_name}"
if user.first_name or user.last_name:
return f"{user.first_name or user.last_name}"
return None


def get_link(user: UserLike | MiniUserLike) -> str | None:
""":obj:`str`: Convenience property. If :attr:`username` is available, returns a t.me link
of the user.
"""
if user.username:
return f"https://t.me/{user.username}"
return None
72 changes: 72 additions & 0 deletionstests/_utils/test_usernames.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].

from __future__ import annotations
import pytest
from typing import TYPE_CHECKING
from telegram import SharedUser
from tests.test_user import user # noqa: F401 noqa: F811
from telegram._utils.usernames import get_name, get_full_name, get_link

if TYPE_CHECKING:
from telegram._utils.usernames import UserLike, MiniUserLike


@pytest.fixture(scope="class")
def shared_user():
result = SharedUser(
user_id=1,
first_name="first\u2022name",
last_name="last\u2022name",
username="username",
)
result._unfreeze()
return result


def test_get_name(user: UserLike, shared_user: MiniUserLike, ): # noqa: F811
assert get_name(user=user) == get_name(user=shared_user) == "@username"
shared_user.username = user.username = None
assert get_name(user=user) == get_name(user=shared_user) == "first\u2022name last\u2022name"


def test_full_name_both_exists(user: UserLike, shared_user: MiniUserLike, ): # noqa: F811
expected = "first\u2022name last\u2022name"
assert get_full_name(user=user) == get_full_name(user=shared_user) == expected


def test_full_name_last_name_missed(user: UserLike, shared_user: MiniUserLike, ): # noqa: F811
user.last_name = shared_user.last_name = None
assert get_full_name(user=user) == get_full_name(user=shared_user) == "first\u2022name"


def test_full_name_first_name_missed(user: UserLike, shared_user: MiniUserLike, ): # noqa: F811
user.first_name = shared_user.first_name = None
assert get_full_name(user=user) == get_full_name(user=shared_user) == "last\u2022name"


def test_full_name_both_missed(user: UserLike, shared_user: MiniUserLike, ): # noqa: F811
user.first_name = user.last_name = shared_user.first_name = shared_user.last_name = None
assert get_full_name(user=user) is get_full_name(user=shared_user) is None


def test_link(user: UserLike, shared_user: MiniUserLike, ): # noqa: F811
assert get_link(user=user, ) == get_link(user=shared_user, ) == f"https://t.me/{user.username}"
user.username = shared_user.username = None
assert get_link(user=user, ) is get_link(user=shared_user, ) is None
Loading

[8]ページ先頭

©2009-2025 Movatter.jp