Skip to content

Commit

Permalink
feat: add send_smartapp_manifest method (#461)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexhook committed Apr 11, 2024
1 parent 6b27924 commit 4089eec
Show file tree
Hide file tree
Showing 5 changed files with 246 additions and 0 deletions.
8 changes: 8 additions & 0 deletions pybotx/__init__.py
Expand Up @@ -55,6 +55,10 @@
StealthModeDisabledError,
)
from pybotx.client.exceptions.users import UserNotFoundError
from pybotx.client.smartapps_api.smartapp_manifest import (
SmartappManifest,
SmartappManifestWebParams,
)
from pybotx.client.stickers_api.exceptions import (
InvalidEmojiError,
InvalidImageError,
Expand All @@ -78,6 +82,7 @@
ChatTypes,
ClientPlatforms,
MentionTypes,
SmartappManifestWebLayoutChoices,
SyncSourceTypes,
UserKinds,
)
Expand Down Expand Up @@ -214,6 +219,9 @@
"SmartApp",
"SmartAppEvent",
"SmartAppEvent",
"SmartappManifest",
"SmartappManifestWebLayoutChoices",
"SmartappManifestWebParams",
"StatusRecipient",
"StealthModeDisabledError",
"Sticker",
Expand Down
38 changes: 38 additions & 0 deletions pybotx/bot/bot.py
Expand Up @@ -135,6 +135,11 @@
BotXAPISmartAppEventRequestPayload,
SmartAppEventMethod,
)
from pybotx.client.smartapps_api.smartapp_manifest import (
BotXAPISmartAppManifestRequestPayload,
SmartappManifest,
SmartAppManifestMethod,
)
from pybotx.client.smartapps_api.smartapp_notification import (
BotXAPISmartAppNotificationRequestPayload,
SmartAppNotificationMethod,
Expand Down Expand Up @@ -222,6 +227,7 @@
from pybotx.models.chats import ChatInfo, ChatListItem
from pybotx.models.commands import BotAPICommand, BotCommand
from pybotx.models.enums import ChatTypes
from pybotx.models.enums import SmartappManifestWebLayoutChoices as WebLayoutChoices
from pybotx.models.message.edit_message import EditMessage
from pybotx.models.message.markup import BubbleMarkup, KeyboardMarkup
from pybotx.models.message.message_status import MessageStatus
Expand Down Expand Up @@ -1500,6 +1506,38 @@ async def get_smartapps_list(

return botx_api_smartapps_list.to_domain()

async def send_smartapp_manifest(
self,
*,
bot_id: UUID,
web_default_layout: WebLayoutChoices = WebLayoutChoices.minimal,
web_expanded_layout: WebLayoutChoices = WebLayoutChoices.half,
web_always_pinned: bool = False,
) -> SmartappManifest:
"""Send smartapp manifest with given parameters.
:param bot_id: Bot which should perform the request.
:param web_default_layout: default smartapp layout for web clients.
:param web_expanded_layout: expanded smartapp layout for web clients.
:param web_always_pinned: True if smartapp icon should be always pinned
in the web clients sidebar.
:return: Smartapp manifest with the set parameters received from BotX.
"""

method = SmartAppManifestMethod(
bot_id,
self._httpx_client,
self._bot_accounts_storage,
)
payload = BotXAPISmartAppManifestRequestPayload.from_domain(
web_default_layout=web_default_layout,
web_expanded_layout=web_expanded_layout,
web_always_pinned=web_always_pinned,
)
smartapp_manifest_response = await method.execute(payload)
return smartapp_manifest_response.to_domain()

async def upload_static_file(
self,
*,
Expand Down
63 changes: 63 additions & 0 deletions pybotx/client/smartapps_api/smartapp_manifest.py
@@ -0,0 +1,63 @@
from typing import Literal

from pybotx.client.authorized_botx_method import AuthorizedBotXMethod
from pybotx.models.api_base import VerifiedPayloadBaseModel
from pybotx.models.enums import SmartappManifestWebLayoutChoices as WebLayoutChoices


class SmartappManifestWebParams(VerifiedPayloadBaseModel):
default_layout: WebLayoutChoices = WebLayoutChoices.minimal
expanded_layout: WebLayoutChoices = WebLayoutChoices.half
always_pinned: bool = False


class SmartappManifest(VerifiedPayloadBaseModel):
web: SmartappManifestWebParams


class BotXAPISmartAppManifestRequestPayload(VerifiedPayloadBaseModel):
manifest: SmartappManifest

@classmethod
def from_domain(
cls,
web_default_layout: WebLayoutChoices = WebLayoutChoices.minimal,
web_expanded_layout: WebLayoutChoices = WebLayoutChoices.half,
web_always_pinned: bool = False,
) -> "BotXAPISmartAppManifestRequestPayload":
return cls(
manifest=SmartappManifest(
web=SmartappManifestWebParams(
default_layout=web_default_layout,
expanded_layout=web_expanded_layout,
always_pinned=web_always_pinned,
),
),
)


class BotXAPISmartAppManifestResponsePayload(VerifiedPayloadBaseModel):
status: Literal["ok"]
result: SmartappManifest

def to_domain(self) -> SmartappManifest:
return self.result


class SmartAppManifestMethod(AuthorizedBotXMethod):
async def execute(
self,
payload: BotXAPISmartAppManifestRequestPayload,
) -> BotXAPISmartAppManifestResponsePayload:
path = "/api/v1/botx/smartapps/manifest"

response = await self._botx_method_call(
"POST",
self._build_url(path),
json=payload.jsonable_dict(),
)

return self._verify_and_extract_api_model(
BotXAPISmartAppManifestResponsePayload,
response,
)
6 changes: 6 additions & 0 deletions pybotx/models/enums.py
Expand Up @@ -151,6 +151,12 @@ class APISyncSourceTypes(Enum):
BOTX = "botx"


class SmartappManifestWebLayoutChoices(StrEnum):
minimal = "minimal"
half = "half"
full = "full"


def convert_client_platform_to_domain(
client_platform: BotAPIClientPlatforms,
) -> ClientPlatforms:
Expand Down
131 changes: 131 additions & 0 deletions tests/client/smartapps_api/test_smartapp_manifest.py
@@ -0,0 +1,131 @@
from http import HTTPStatus
from uuid import UUID

import httpx
import pytest
from respx.router import MockRouter

from pybotx import (
Bot,
BotAccountWithSecret,
HandlerCollector,
SmartappManifest,
SmartappManifestWebLayoutChoices,
SmartappManifestWebParams,
lifespan_wrapper,
)

pytestmark = [
pytest.mark.asyncio,
pytest.mark.mock_authorization,
pytest.mark.usefixtures("respx_mock"),
]


async def test__send_smartapp_manifest__all_params_provided__succeed(
respx_mock: MockRouter,
host: str,
bot_id: UUID,
bot_account: BotAccountWithSecret,
) -> None:
# - Arrange -
endpoint = respx_mock.post(
f"https://{host}/api/v1/botx/smartapps/manifest",
headers={"Authorization": "Bearer token", "Content-Type": "application/json"},
json={
"manifest": {
"web": {
"always_pinned": True,
"default_layout": "full",
"expanded_layout": "full",
},
},
},
).mock(
return_value=httpx.Response(
HTTPStatus.ACCEPTED,
json={
"result": {
"web": {
"always_pinned": True,
"default_layout": "full",
"expanded_layout": "full",
},
},
"status": "ok",
},
),
)

built_bot = Bot(collectors=[HandlerCollector()], bot_accounts=[bot_account])

# - Act -
async with lifespan_wrapper(built_bot) as bot:
smartapp_manifest = await bot.send_smartapp_manifest(
bot_id=bot_id,
web_default_layout=SmartappManifestWebLayoutChoices.full,
web_expanded_layout=SmartappManifestWebLayoutChoices.full,
web_always_pinned=True,
)

# - Assert -
assert endpoint.called
assert smartapp_manifest == SmartappManifest(
web=SmartappManifestWebParams(
default_layout=SmartappManifestWebLayoutChoices.full,
expanded_layout=SmartappManifestWebLayoutChoices.full,
always_pinned=True,
),
)


async def test__send_smartapp_manifest__only_default_params_provided__succeed(
respx_mock: MockRouter,
host: str,
bot_id: UUID,
bot_account: BotAccountWithSecret,
) -> None:
# - Arrange -
endpoint = respx_mock.post(
f"https://{host}/api/v1/botx/smartapps/manifest",
headers={"Authorization": "Bearer token", "Content-Type": "application/json"},
json={
"manifest": {
"web": {
"always_pinned": False,
"default_layout": "minimal",
"expanded_layout": "half",
},
},
},
).mock(
return_value=httpx.Response(
HTTPStatus.ACCEPTED,
json={
"result": {
"web": {
"always_pinned": False,
"default_layout": "minimal",
"expanded_layout": "half",
},
},
"status": "ok",
},
),
)

built_bot = Bot(collectors=[HandlerCollector()], bot_accounts=[bot_account])

# - Act -
async with lifespan_wrapper(built_bot) as bot:
smartapp_manifest = await bot.send_smartapp_manifest(bot_id=bot_id)

# - Assert -
assert endpoint.called
assert smartapp_manifest == SmartappManifest(
web=SmartappManifestWebParams(
default_layout=SmartappManifestWebLayoutChoices.minimal,
expanded_layout=SmartappManifestWebLayoutChoices.half,
always_pinned=False,
),
)

0 comments on commit 4089eec

Please sign in to comment.