Skip to content

Commit

Permalink
Feature/internal bot notification (#159)
Browse files Browse the repository at this point in the history
* feat: internal bot notification

* feat: internal bot notification

* feat: internal bot notification

* feat: internal bot notification

* chore: update packages and fix some comments and docstrings

* test: internal bot notification sending

* test: internal bot notification handling

* chore: version bump, changelog

* chore: fix mkdocs

* chore: version to 0.22.0

* fix: wrong data shape in message builder

* fix: lint
  • Loading branch information
vonabarak committed Aug 23, 2021
1 parent 832c257 commit 371b631
Show file tree
Hide file tree
Showing 29 changed files with 763 additions and 349 deletions.
5 changes: 4 additions & 1 deletion botx/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from botx.bots.bots import Bot
from botx.clients.clients.async_client import AsyncClient
from botx.clients.clients.sync_client import Client
from botx.clients.types.message_payload import InternalBotNotificationPayload
from botx.collecting.collectors.collector import Collector
from botx.dependencies.injection_params import Depends
from botx.exceptions import BotXAPIError, DependencyFailure, TokenError, UnknownBotError
Expand Down Expand Up @@ -40,7 +41,7 @@
UserKinds,
)
from botx.models.errors import BotDisabledErrorData, BotDisabledResponse
from botx.models.events import ChatCreatedEvent
from botx.models.events import ChatCreatedEvent, InternalBotNotificationEvent
from botx.models.files import File
from botx.models.menu import Status
from botx.models.messages.incoming_message import IncomingMessage
Expand Down Expand Up @@ -92,6 +93,7 @@
"BotDisabledResponse",
# events
"ChatCreatedEvent",
"InternalBotNotificationEvent",
# files
"File",
# attachments
Expand Down Expand Up @@ -128,6 +130,7 @@
"NotificationOptions",
"MessagePayload",
"UpdatePayload",
"InternalBotNotificationPayload",
# testing
"TestClient",
"MessageBuilder",
Expand Down
47 changes: 47 additions & 0 deletions botx/bots/mixins/requests/internal_bot_notification.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
"""Mixin for shortcut for internal bot notification resource requests."""

from typing import Any, Dict, List, Optional
from uuid import UUID

from botx.bots.mixins.requests.call_protocol import BotXMethodCallProtocol
from botx.clients.methods.v4.notifications.internal_bot_notification import (
InternalBotNotification,
)
from botx.clients.types.message_payload import InternalBotNotificationPayload
from botx.models.messages.sending.credentials import SendingCredentials


class InternalBotNotificationRequestsMixin:
"""Mixin for shortcut for internal bot notification resource requests."""

async def internal_bot_notification( # noqa: WPS211
self: BotXMethodCallProtocol,
credentials: SendingCredentials,
group_chat_id: UUID,
text: str,
sender: Optional[str] = None,
recipients: Optional[List[UUID]] = None,
opts: Optional[Dict[str, Any]] = None,
) -> UUID:
"""Send internal bot notifications into chat.
Arguments:
credentials: credentials for making request.
group_chat_id: ID of chats into which message should be sent.
text: notification text.
sender: information about notification sender.
recipients: List of recipients' UUIDs (send to all if None)
opts: additional user-defined data to send
Returns:
Sync ID of sent notification.
"""
return await self.call_method(
InternalBotNotification(
group_chat_id=group_chat_id,
recipients=recipients,
data=InternalBotNotificationPayload(message=text, sender=sender),
opts=opts or {},
),
credentials=credentials,
)
11 changes: 10 additions & 1 deletion botx/bots/mixins/requests/mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,15 @@

from loguru import logger

from botx.bots.mixins.requests import bots, chats, command, events, notification, users
from botx.bots.mixins.requests import (
bots,
chats,
command,
events,
internal_bot_notification,
notification,
users,
)
from botx.clients.clients.async_client import AsyncClient
from botx.clients.methods.base import BotXMethod
from botx.models.credentials import BotXCredentials
Expand Down Expand Up @@ -36,6 +44,7 @@ class BotXRequestsMixin( # noqa: WPS215
events.EventsRequestsMixin,
notification.NotificationRequestsMixin,
users.UsersRequestsMixin,
internal_bot_notification.InternalBotNotificationRequestsMixin,
):
"""Mixin that defines methods for communicating with BotX API."""

Expand Down
1 change: 1 addition & 0 deletions botx/clients/methods/v4/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Definition for V4 API methods for BotX API."""
1 change: 1 addition & 0 deletions botx/clients/methods/v4/notifications/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Definition for methods for notifications resource."""
29 changes: 29 additions & 0 deletions botx/clients/methods/v4/notifications/internal_bot_notification.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"""Method for sending internal bot notification."""
from typing import Any, Dict, List, Optional
from uuid import UUID

from botx.clients.methods.base import AuthorizedBotXMethod
from botx.clients.methods.extractors import extract_generated_sync_id
from botx.clients.types.message_payload import InternalBotNotificationPayload
from botx.clients.types.response_results import InternalBotNotificationResult


class InternalBotNotification(AuthorizedBotXMethod[UUID]):
"""Method for sending internal bot notification."""

__url__ = "/api/v4/botx/notifications/internal"
__method__ = "POST"
__returning__ = InternalBotNotificationResult
__result_extractor__ = extract_generated_sync_id

#: IDs of chats for new notification.
group_chat_id: UUID

#: HUIDs of bots that should receive notifications (None for all bots in chat).
recipients: Optional[List[UUID]] = None

# notification payload
data: InternalBotNotificationPayload # noqa: WPS110

#: extra options for message.
opts: Dict[str, Any] = {}
10 changes: 10 additions & 0 deletions botx/clients/types/message_payload.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,13 @@ class UpdatePayload(BaseModel):

#: new mentions that BotX API will append before new message text.
mentions: Optional[List[Mention]] = None


class InternalBotNotificationPayload(BaseModel):
"""Data that is sent in internal bot notification."""

#: message data
message: str

#: extra information about notification sender
sender: Optional[str]
7 changes: 7 additions & 0 deletions botx/clients/types/response_results.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,10 @@ class ChatCreatedResult(BaseModel):

#: id of created chat.
chat_id: UUID


class InternalBotNotificationResult(BaseModel):
"""Entity that contains result from internal bot notification."""

#: event id of pushed message.
sync_id: UUID
26 changes: 26 additions & 0 deletions botx/collecting/collectors/mixins/system_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,3 +183,29 @@ def left_from_chat(
dependencies=dependencies,
dependency_overrides_provider=dependency_overrides_provider,
)

def internal_bot_notification(
self,
handler: Optional[Callable] = None,
*,
dependencies: Optional[Sequence[Depends]] = None,
dependency_overrides_provider: Any = None,
) -> Callable:
"""Register handler for `internal_bot_notification` event.
Arguments:
handler: callable that will be used for executing handler.
dependencies: sequence of dependencies that should be executed before
handler.
dependency_overrides_provider: mock of callable for handler.
Returns:
Passed in `handler` callable.
"""
return self.system_event(
handler=handler,
event=SystemEvents.internal_bot_notification,
name=SystemEvents.internal_bot_notification.value,
dependencies=dependencies,
dependency_overrides_provider=dependency_overrides_provider,
)
3 changes: 3 additions & 0 deletions botx/models/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ class SystemEvents(Enum):
#: `system:left_from_chat` event.
left_from_chat = "system:left_from_chat"

#: `system:internal_bot_notification` event
internal_bot_notification = "system:internal_bot_notification"

#: `file_transfer` message.
file_transfer = "file_transfer"

Expand Down
14 changes: 13 additions & 1 deletion botx/models/events.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
"""Definition of different schemas for system events."""

from types import MappingProxyType
from typing import List, Mapping, Type
from typing import Any, Dict, List, Mapping, Type
from uuid import UUID

from botx.clients.types.message_payload import InternalBotNotificationPayload
from botx.models.base import BotXBaseModel
from botx.models.enums import ChatTypes, SystemEvents
from botx.models.users import UserInChatCreated
Expand Down Expand Up @@ -49,12 +50,23 @@ class LeftFromChatEvent(BotXBaseModel):
left_members: List[UUID]


class InternalBotNotificationEvent(BotXBaseModel):
"""Shape for `system:internal_bot_notification` event data."""

#: notification data
data: InternalBotNotificationPayload # noqa: WPS110

#: user-defined extra options
opts: Dict[str, Any]


# dict for validating shape for different events
EVENTS_SHAPE_MAP: Mapping[SystemEvents, Type[BotXBaseModel]] = MappingProxyType(
{
SystemEvents.chat_created: ChatCreatedEvent,
SystemEvents.added_to_chat: AddedToChatEvent,
SystemEvents.deleted_from_chat: DeletedFromChatEvent,
SystemEvents.left_from_chat: LeftFromChatEvent,
SystemEvents.internal_bot_notification: InternalBotNotificationEvent,
},
)
1 change: 1 addition & 0 deletions botx/models/messages/incoming_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
events.AddedToChatEvent,
events.DeletedFromChatEvent,
events.LeftFromChatEvent,
events.InternalBotNotificationEvent,
Dict[str, Any],
]

Expand Down
3 changes: 3 additions & 0 deletions botx/testing/botx_mock/asgi/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
command,
events,
notification,
notifications,
users,
)
from botx.testing.typing import APIMessage, APIRequest
Expand Down Expand Up @@ -44,6 +45,8 @@
users.get_by_huid,
users.get_by_email,
users.get_by_login,
# notifications
notifications.post_internal_bot_notification,
)


Expand Down
27 changes: 26 additions & 1 deletion botx/testing/botx_mock/asgi/responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,13 @@
from botx.clients.methods.base import APIResponse
from botx.clients.methods.v3.command.command_result import CommandResult
from botx.clients.methods.v3.notification.direct_notification import NotificationDirect
from botx.clients.types.response_results import PushResult
from botx.clients.methods.v4.notifications.internal_bot_notification import (
InternalBotNotification,
)
from botx.clients.types.response_results import (
InternalBotNotificationResult,
PushResult,
)


class PydanticResponse(Response):
Expand Down Expand Up @@ -53,3 +59,22 @@ def generate_push_response(
return PydanticResponse(
APIResponse[PushResult](result=PushResult(sync_id=sync_id)),
)


def generate_internal_bot_notification_response(
payload: InternalBotNotification,
) -> Response:
"""Generate response as like internal bot notification was sent.
Arguments:
payload: sent notification.
Returns:
Response with sync_id for new message.
"""
sync_id = uuid.uuid4()
return PydanticResponse(
APIResponse[InternalBotNotificationResult](
result=InternalBotNotificationResult(sync_id=sync_id),
),
)
28 changes: 28 additions & 0 deletions botx/testing/botx_mock/asgi/routes/notifications.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"""Endpoints for notification resource."""

from starlette.requests import Request
from starlette.responses import Response

from botx.clients.methods.v4.notifications.internal_bot_notification import (
InternalBotNotification,
)
from botx.testing.botx_mock.asgi.messages import add_message_to_collection
from botx.testing.botx_mock.asgi.responses import (
generate_internal_bot_notification_response,
)
from botx.testing.botx_mock.binders import bind_implementation_to_method


@bind_implementation_to_method(InternalBotNotification)
async def post_internal_bot_notification(request: Request) -> Response:
"""Handle pushed notification request.
Arguments:
request: HTTP request from Starlette.
Returns:
Response with sync_id of pushed message.
"""
payload = InternalBotNotification.parse_obj(await request.json())
add_message_to_collection(request, payload)
return generate_internal_bot_notification_response(payload)
3 changes: 3 additions & 0 deletions botx/testing/botx_mock/wsgi/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
command,
events,
notification,
notifications,
users,
)
from botx.testing.typing import APIMessage, APIRequest
Expand Down Expand Up @@ -41,6 +42,8 @@
users.get_by_huid,
users.get_by_email,
users.get_by_login,
# notifications
notifications.post_internal_bot_notification,
)


Expand Down
27 changes: 26 additions & 1 deletion botx/testing/botx_mock/wsgi/responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,13 @@
from botx.clients.methods.base import APIResponse
from botx.clients.methods.v3.command.command_result import CommandResult
from botx.clients.methods.v3.notification.direct_notification import NotificationDirect
from botx.clients.types.response_results import PushResult
from botx.clients.methods.v4.notifications.internal_bot_notification import (
InternalBotNotification,
)
from botx.clients.types.response_results import (
InternalBotNotificationResult,
PushResult,
)


class PydanticResponse(Response):
Expand Down Expand Up @@ -52,3 +58,22 @@ def generate_push_response(
return PydanticResponse(
APIResponse[PushResult](result=PushResult(sync_id=sync_id)),
)


def generate_internal_bot_notification_response(
payload: InternalBotNotification,
) -> Response:
"""Generate response as like internal bot notification was sent.
Arguments:
payload: sent notification.
Returns:
Response with sync_id for new message.
"""
sync_id = uuid.uuid4()
return PydanticResponse(
APIResponse[InternalBotNotificationResult](
result=InternalBotNotificationResult(sync_id=sync_id),
),
)
Loading

1 comment on commit 371b631

@github-actions
Copy link

Choose a reason for hiding this comment

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

🎉 Published on https://pybotx.netlify.app as production
🚀 Deployed on https://61234e991980fc617efe0de5--pybotx.netlify.app

Please sign in to comment.