From 0962db5bc030dbfab707b28c925d9515a823d643 Mon Sep 17 00:00:00 2001 From: "Davletyarov, Ildar" Date: Tue, 11 Aug 2026 13:28:59 +0300 Subject: [PATCH] feat: add trusted search to user lookup methods Expose trusts_search and partial_response for HUID, AD login, and other ID lookups while preserving the existing request shape by default. --- README.md | 5 ++- pybotx/bot/bot.py | 22 +++++++++- .../client/users_api/search_user_by_huid.py | 16 ++++++- .../client/users_api/search_user_by_login.py | 12 +++++- .../users_api/search_user_by_other_id.py | 16 ++++++- pyproject.toml | 2 +- .../users_api/test_search_user_by_huid.py | 40 ++++++++++++++++++ .../users_api/test_search_user_by_login.py | 42 +++++++++++++++++++ .../users_api/test_search_user_by_other_id.py | 40 ++++++++++++++++++ 9 files changed, 186 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index e5e1ea28..3aaab0e3 100644 --- a/README.md +++ b/README.md @@ -643,9 +643,10 @@ async def search_user_handler(message: IncomingMessage, bot: Bot) -> None: user_info = await bot.search_user_by_huid( bot_id=message.bot.id, huid=message.sender.huid, + trusts_search=True, ) - except UserNotFoundError: # Если пользователь и бот находятся на разных CTS - await bot.answer_message("User not found. Maybe you are on a different cts.") + except UserNotFoundError: + await bot.answer_message("User not found.") return await bot.answer_message(f"Your info:\n{dataclasses.asdict(user_info)}\n") diff --git a/pybotx/bot/bot.py b/pybotx/bot/bot.py index 0e1746fc..eb8400ce 100644 --- a/pybotx/bot/bot.py +++ b/pybotx/bot/bot.py @@ -1578,11 +1578,15 @@ async def search_user_by_huid( *, bot_id: UUID, huid: UUID, + trusts_search: bool = False, + partial_response: bool = False, ) -> UserFromSearch: """Search user by huid for search. :param bot_id: Bot which should perform the request. :param huid: User huid. + :param trusts_search: Search users on trusted servers. + :param partial_response: Return local results if trusted server lookup fails. :return: User information. """ @@ -1592,7 +1596,11 @@ async def search_user_by_huid( self._httpx_client, self._bot_accounts_storage, ) - payload = BotXAPISearchUserByHUIDRequestPayload.from_domain(huid=huid) + payload = BotXAPISearchUserByHUIDRequestPayload.from_domain( + huid=huid, + trusts_search=trusts_search, + partial_response=partial_response, + ) botx_api_user_from_search = await method.execute(payload) @@ -1604,12 +1612,16 @@ async def search_user_by_ad( bot_id: UUID, ad_login: str, ad_domain: str, + trusts_search: bool = False, + partial_response: bool = False, ) -> UserFromSearch: """Search user by AD login and AD domain for search. :param bot_id: Bot which should perform the request. :param ad_login: User AD login. :param ad_domain: User AD domain. + :param trusts_search: Search users on trusted servers. + :param partial_response: Return local results if trusted server lookup fails. :return: User information. """ @@ -1622,6 +1634,8 @@ async def search_user_by_ad( payload = BotXAPISearchUserByLoginRequestPayload.from_domain( ad_login=ad_login, ad_domain=ad_domain, + trusts_search=trusts_search, + partial_response=partial_response, ) botx_api_user_from_search = await method.execute(payload) @@ -1633,11 +1647,15 @@ async def search_user_by_other_id( *, bot_id: UUID, other_id: str, + trusts_search: bool = False, + partial_response: bool = False, ) -> UserFromSearch: """Search user by other identificator for search. :param bot_id: Bot which should perform the request. :param other_id: User other identificator. + :param trusts_search: Search users on trusted servers. + :param partial_response: Return local results if trusted server lookup fails. :return: User information. """ @@ -1649,6 +1667,8 @@ async def search_user_by_other_id( ) payload = BotXAPISearchUserByOtherIdRequestPayload.from_domain( other_id=other_id, + trusts_search=trusts_search, + partial_response=partial_response, ) botx_api_user_from_search = await method.execute(payload) diff --git a/pybotx/client/users_api/search_user_by_huid.py b/pybotx/client/users_api/search_user_by_huid.py index 9fbf8b34..d9a24246 100644 --- a/pybotx/client/users_api/search_user_by_huid.py +++ b/pybotx/client/users_api/search_user_by_huid.py @@ -4,15 +4,27 @@ from pybotx.client.botx_method import response_exception_thrower from pybotx.client.exceptions.users import UserNotFoundError from pybotx.client.users_api.user_from_search import BotXAPISearchUserResponsePayload +from pybotx.missing import Missing, Undefined from pybotx.models.api_base import UnverifiedPayloadBaseModel class BotXAPISearchUserByHUIDRequestPayload(UnverifiedPayloadBaseModel): user_huid: UUID + trusts_search: Missing[bool] = Undefined + partial_response: Missing[bool] = Undefined @classmethod - def from_domain(cls, huid: UUID) -> "BotXAPISearchUserByHUIDRequestPayload": - return cls(user_huid=huid) + def from_domain( + cls, + huid: UUID, + trusts_search: bool = False, + partial_response: bool = False, + ) -> "BotXAPISearchUserByHUIDRequestPayload": + return cls( + user_huid=huid, + trusts_search=trusts_search or Undefined, + partial_response=partial_response or Undefined, + ) class SearchUserByHUIDMethod(AuthorizedBotXMethod): diff --git a/pybotx/client/users_api/search_user_by_login.py b/pybotx/client/users_api/search_user_by_login.py index 0b74ba1e..a5eb3669 100644 --- a/pybotx/client/users_api/search_user_by_login.py +++ b/pybotx/client/users_api/search_user_by_login.py @@ -2,20 +2,30 @@ from pybotx.client.botx_method import response_exception_thrower from pybotx.client.exceptions.users import UserNotFoundError from pybotx.client.users_api.user_from_search import BotXAPISearchUserResponsePayload +from pybotx.missing import Missing, Undefined from pybotx.models.api_base import UnverifiedPayloadBaseModel class BotXAPISearchUserByLoginRequestPayload(UnverifiedPayloadBaseModel): ad_login: str ad_domain: str + trusts_search: Missing[bool] = Undefined + partial_response: Missing[bool] = Undefined @classmethod def from_domain( cls, ad_login: str, ad_domain: str, + trusts_search: bool = False, + partial_response: bool = False, ) -> "BotXAPISearchUserByLoginRequestPayload": - return cls(ad_login=ad_login, ad_domain=ad_domain) + return cls( + ad_login=ad_login, + ad_domain=ad_domain, + trusts_search=trusts_search or Undefined, + partial_response=partial_response or Undefined, + ) class SearchUserByLoginMethod(AuthorizedBotXMethod): diff --git a/pybotx/client/users_api/search_user_by_other_id.py b/pybotx/client/users_api/search_user_by_other_id.py index 0e511c53..74af996d 100644 --- a/pybotx/client/users_api/search_user_by_other_id.py +++ b/pybotx/client/users_api/search_user_by_other_id.py @@ -2,15 +2,27 @@ from pybotx.client.botx_method import response_exception_thrower from pybotx.client.exceptions.users import UserNotFoundError from pybotx.client.users_api.user_from_search import BotXAPISearchUserResponsePayload +from pybotx.missing import Missing, Undefined from pybotx.models.api_base import UnverifiedPayloadBaseModel class BotXAPISearchUserByOtherIdRequestPayload(UnverifiedPayloadBaseModel): other_id: str + trusts_search: Missing[bool] = Undefined + partial_response: Missing[bool] = Undefined @classmethod - def from_domain(cls, other_id: str) -> "BotXAPISearchUserByOtherIdRequestPayload": - return cls(other_id=other_id) + def from_domain( + cls, + other_id: str, + trusts_search: bool = False, + partial_response: bool = False, + ) -> "BotXAPISearchUserByOtherIdRequestPayload": + return cls( + other_id=other_id, + trusts_search=trusts_search or Undefined, + partial_response=partial_response or Undefined, + ) class SearchUserByOtherIdMethod(AuthorizedBotXMethod): diff --git a/pyproject.toml b/pyproject.toml index b635e579..a4e789fd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "pybotx" -version = "0.76.5" +version = "0.76.6" description = "A python library for interacting with eXpress BotX API" authors = [ "Sidnev Nikolay ", diff --git a/tests/client/users_api/test_search_user_by_huid.py b/tests/client/users_api/test_search_user_by_huid.py index 7ada442b..a0917033 100644 --- a/tests/client/users_api/test_search_user_by_huid.py +++ b/tests/client/users_api/test_search_user_by_huid.py @@ -151,3 +151,43 @@ async def test__search_user_by_huid_without_data__succeed( # - Assert - assert_deep_equal(user, user_from_search_without_data) assert endpoint.called + + +async def test__search_user_by_huid_with_trusts_search__succeed( + respx_mock: MockRouter, + host: str, + bot_id: UUID, + user_from_search_with_data: UserFromSearch, + user_from_search_with_data_json: dict[str, Any], + bot_factory: Any, +) -> None: + # - Arrange - + request = BotXRequest( + method="GET", + path="/api/v3/botx/users/by_huid", + params={ + "user_huid": "f837dff4-d3ad-4b8d-a0a3-5c6ca9c747d1", + "trusts_search": True, + "partial_response": True, + }, + ) + endpoint = mock_botx( + respx_mock, + host, + request, + ok_payload(user_from_search_with_data_json), + HTTPStatus.OK, + ) + + # - Act - + async with bot_factory() as bot: + user = await bot.search_user_by_huid( + bot_id=bot_id, + huid=UUID("f837dff4-d3ad-4b8d-a0a3-5c6ca9c747d1"), + trusts_search=True, + partial_response=True, + ) + + # - Assert - + assert_deep_equal(user, user_from_search_with_data) + assert endpoint.called diff --git a/tests/client/users_api/test_search_user_by_login.py b/tests/client/users_api/test_search_user_by_login.py index 75ea05cb..90f485fb 100644 --- a/tests/client/users_api/test_search_user_by_login.py +++ b/tests/client/users_api/test_search_user_by_login.py @@ -117,3 +117,45 @@ async def test__search_user_by_ad_without_data__succeed( # - Assert - assert_deep_equal(user, user_from_search_without_data) assert endpoint.called + + +async def test__search_user_by_ad_with_trusts_search__succeed( + respx_mock: MockRouter, + host: str, + bot_id: UUID, + user_from_search_with_data: UserFromSearch, + user_from_search_with_data_json: dict[str, Any], + bot_factory: Any, +) -> None: + # - Arrange - + request = BotXRequest( + method="GET", + path="/api/v3/botx/users/by_login", + params={ + "ad_login": "ad_user_login", + "ad_domain": "cts.com", + "trusts_search": True, + "partial_response": True, + }, + ) + endpoint = mock_botx( + respx_mock, + host, + request, + ok_payload(user_from_search_with_data_json), + HTTPStatus.OK, + ) + + # - Act - + async with bot_factory() as bot: + user = await bot.search_user_by_ad( + bot_id=bot_id, + ad_login="ad_user_login", + ad_domain="cts.com", + trusts_search=True, + partial_response=True, + ) + + # - Assert - + assert_deep_equal(user, user_from_search_with_data) + assert endpoint.called diff --git a/tests/client/users_api/test_search_user_by_other_id.py b/tests/client/users_api/test_search_user_by_other_id.py index df8d3465..b55b85c4 100644 --- a/tests/client/users_api/test_search_user_by_other_id.py +++ b/tests/client/users_api/test_search_user_by_other_id.py @@ -114,3 +114,43 @@ async def test__search_user_by_other_id_without_data__succeed( # - Assert - assert_deep_equal(user, user_from_search_without_data) assert endpoint.called + + +async def test__search_user_by_other_id_with_trusts_search__succeed( + respx_mock: MockRouter, + host: str, + bot_id: UUID, + user_from_search_with_data: UserFromSearch, + user_from_search_with_data_json: dict[str, Any], + bot_factory: Any, +) -> None: + # - Arrange - + request = BotXRequest( + method="GET", + path="/api/v3/botx/users/by_other_id", + params={ + "other_id": "some_id", + "trusts_search": True, + "partial_response": True, + }, + ) + endpoint = mock_botx( + respx_mock, + host, + request, + ok_payload(user_from_search_with_data_json), + HTTPStatus.OK, + ) + + # - Act - + async with bot_factory() as bot: + user = await bot.search_user_by_other_id( + bot_id=bot_id, + other_id="some_id", + trusts_search=True, + partial_response=True, + ) + + # - Assert - + assert_deep_equal(user, user_from_search_with_data) + assert endpoint.called