From 92b64d484408d8c4171148238f24ec6c235306a3 Mon Sep 17 00:00:00 2001 From: Alexander Maximenyuk Date: Fri, 21 Jun 2024 12:51:41 +0200 Subject: [PATCH] feat: change the specification of synchronous requests --- README.md | 8 +- poetry.lock | 81 +++++---- pybotx/__init__.py | 12 +- pybotx/bot/bot.py | 18 +- pybotx/bot/handler.py | 6 +- pybotx/bot/handler_collector.py | 6 +- pybotx/client/smartapps_api/smartapp_event.py | 11 +- .../smartapps_api/sync_smartapp_event.py | 7 - pybotx/models/bot_account.py | 3 +- pybotx/models/sync_smartapp_event.py | 155 ++++++++++++++++ pybotx/models/system_events/smartapp_event.py | 6 +- pyproject.toml | 2 +- tests/conftest.py | 78 +++----- tests/test_base_command.py | 2 +- tests/test_end_to_end.py | 171 +++++++++--------- 15 files changed, 338 insertions(+), 228 deletions(-) delete mode 100644 pybotx/client/smartapps_api/sync_smartapp_event.py create mode 100644 pybotx/models/sync_smartapp_event.py diff --git a/README.md b/README.md index db365270..e8c06f25 100644 --- a/README.md +++ b/README.md @@ -234,16 +234,12 @@ collector = HandlerCollector() @collector.sync_smartapp_event async def handle_sync_smartapp_event( event: SmartAppEvent, bot: Bot, -) -> SyncSmartAppEventResponsePayload: +) -> BotAPISyncSmartAppEventResultResponse: print(f"Got sync smartapp event: {event}") - return SyncSmartAppEventResponsePayload.from_domain( + return BotAPISyncSmartAppEventResultResponse.from_domain( ref=event.ref, - smartapp_id=event.bot.id, - chat_id=event.chat.id, data={}, - opts={}, files=[], - encrypted=True, ) ``` diff --git a/poetry.lock b/poetry.lock index ca52781f..47d5ec47 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1047,48 +1047,55 @@ files = [ [[package]] name = "pydantic" -version = "1.10.15" +version = "1.10.17" description = "Data validation and settings management using python type hints" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "pydantic-1.10.15-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:22ed12ee588b1df028a2aa5d66f07bf8f8b4c8579c2e96d5a9c1f96b77f3bb55"}, - {file = "pydantic-1.10.15-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:75279d3cac98186b6ebc2597b06bcbc7244744f6b0b44a23e4ef01e5683cc0d2"}, - {file = "pydantic-1.10.15-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50f1666a9940d3d68683c9d96e39640f709d7a72ff8702987dab1761036206bb"}, - {file = "pydantic-1.10.15-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82790d4753ee5d00739d6cb5cf56bceb186d9d6ce134aca3ba7befb1eedbc2c8"}, - {file = "pydantic-1.10.15-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:d207d5b87f6cbefbdb1198154292faee8017d7495a54ae58db06762004500d00"}, - {file = "pydantic-1.10.15-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e49db944fad339b2ccb80128ffd3f8af076f9f287197a480bf1e4ca053a866f0"}, - {file = "pydantic-1.10.15-cp310-cp310-win_amd64.whl", hash = "sha256:d3b5c4cbd0c9cb61bbbb19ce335e1f8ab87a811f6d589ed52b0254cf585d709c"}, - {file = "pydantic-1.10.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c3d5731a120752248844676bf92f25a12f6e45425e63ce22e0849297a093b5b0"}, - {file = "pydantic-1.10.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c365ad9c394f9eeffcb30a82f4246c0006417f03a7c0f8315d6211f25f7cb654"}, - {file = "pydantic-1.10.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3287e1614393119c67bd4404f46e33ae3be3ed4cd10360b48d0a4459f420c6a3"}, - {file = "pydantic-1.10.15-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be51dd2c8596b25fe43c0a4a59c2bee4f18d88efb8031188f9e7ddc6b469cf44"}, - {file = "pydantic-1.10.15-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6a51a1dd4aa7b3f1317f65493a182d3cff708385327c1c82c81e4a9d6d65b2e4"}, - {file = "pydantic-1.10.15-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4e316e54b5775d1eb59187f9290aeb38acf620e10f7fd2f776d97bb788199e53"}, - {file = "pydantic-1.10.15-cp311-cp311-win_amd64.whl", hash = "sha256:0d142fa1b8f2f0ae11ddd5e3e317dcac060b951d605fda26ca9b234b92214986"}, - {file = "pydantic-1.10.15-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7ea210336b891f5ea334f8fc9f8f862b87acd5d4a0cbc9e3e208e7aa1775dabf"}, - {file = "pydantic-1.10.15-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3453685ccd7140715e05f2193d64030101eaad26076fad4e246c1cc97e1bb30d"}, - {file = "pydantic-1.10.15-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bea1f03b8d4e8e86702c918ccfd5d947ac268f0f0cc6ed71782e4b09353b26f"}, - {file = "pydantic-1.10.15-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:005655cabc29081de8243126e036f2065bd7ea5b9dff95fde6d2c642d39755de"}, - {file = "pydantic-1.10.15-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:af9850d98fc21e5bc24ea9e35dd80a29faf6462c608728a110c0a30b595e58b7"}, - {file = "pydantic-1.10.15-cp37-cp37m-win_amd64.whl", hash = "sha256:d31ee5b14a82c9afe2bd26aaa405293d4237d0591527d9129ce36e58f19f95c1"}, - {file = "pydantic-1.10.15-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5e09c19df304b8123938dc3c53d3d3be6ec74b9d7d0d80f4f4b5432ae16c2022"}, - {file = "pydantic-1.10.15-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7ac9237cd62947db00a0d16acf2f3e00d1ae9d3bd602b9c415f93e7a9fc10528"}, - {file = "pydantic-1.10.15-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:584f2d4c98ffec420e02305cf675857bae03c9d617fcfdc34946b1160213a948"}, - {file = "pydantic-1.10.15-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bbc6989fad0c030bd70a0b6f626f98a862224bc2b1e36bfc531ea2facc0a340c"}, - {file = "pydantic-1.10.15-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d573082c6ef99336f2cb5b667b781d2f776d4af311574fb53d908517ba523c22"}, - {file = "pydantic-1.10.15-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6bd7030c9abc80134087d8b6e7aa957e43d35714daa116aced57269a445b8f7b"}, - {file = "pydantic-1.10.15-cp38-cp38-win_amd64.whl", hash = "sha256:3350f527bb04138f8aff932dc828f154847fbdc7a1a44c240fbfff1b57f49a12"}, - {file = "pydantic-1.10.15-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:51d405b42f1b86703555797270e4970a9f9bd7953f3990142e69d1037f9d9e51"}, - {file = "pydantic-1.10.15-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a980a77c52723b0dc56640ced396b73a024d4b74f02bcb2d21dbbac1debbe9d0"}, - {file = "pydantic-1.10.15-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67f1a1fb467d3f49e1708a3f632b11c69fccb4e748a325d5a491ddc7b5d22383"}, - {file = "pydantic-1.10.15-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:676ed48f2c5bbad835f1a8ed8a6d44c1cd5a21121116d2ac40bd1cd3619746ed"}, - {file = "pydantic-1.10.15-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:92229f73400b80c13afcd050687f4d7e88de9234d74b27e6728aa689abcf58cc"}, - {file = "pydantic-1.10.15-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2746189100c646682eff0bce95efa7d2e203420d8e1c613dc0c6b4c1d9c1fde4"}, - {file = "pydantic-1.10.15-cp39-cp39-win_amd64.whl", hash = "sha256:394f08750bd8eaad714718812e7fab615f873b3cdd0b9d84e76e51ef3b50b6b7"}, - {file = "pydantic-1.10.15-py3-none-any.whl", hash = "sha256:28e552a060ba2740d0d2aabe35162652c1459a0b9069fe0db7f4ee0e18e74d58"}, - {file = "pydantic-1.10.15.tar.gz", hash = "sha256:ca832e124eda231a60a041da4f013e3ff24949d94a01154b137fc2f2a43c3ffb"}, + {file = "pydantic-1.10.17-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0fa51175313cc30097660b10eec8ca55ed08bfa07acbfe02f7a42f6c242e9a4b"}, + {file = "pydantic-1.10.17-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c7e8988bb16988890c985bd2093df9dd731bfb9d5e0860db054c23034fab8f7a"}, + {file = "pydantic-1.10.17-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:371dcf1831f87c9e217e2b6a0c66842879a14873114ebb9d0861ab22e3b5bb1e"}, + {file = "pydantic-1.10.17-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4866a1579c0c3ca2c40575398a24d805d4db6cb353ee74df75ddeee3c657f9a7"}, + {file = "pydantic-1.10.17-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:543da3c6914795b37785703ffc74ba4d660418620cc273490d42c53949eeeca6"}, + {file = "pydantic-1.10.17-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7623b59876f49e61c2e283551cc3647616d2fbdc0b4d36d3d638aae8547ea681"}, + {file = "pydantic-1.10.17-cp310-cp310-win_amd64.whl", hash = "sha256:409b2b36d7d7d19cd8310b97a4ce6b1755ef8bd45b9a2ec5ec2b124db0a0d8f3"}, + {file = "pydantic-1.10.17-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fa43f362b46741df8f201bf3e7dff3569fa92069bcc7b4a740dea3602e27ab7a"}, + {file = "pydantic-1.10.17-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2a72d2a5ff86a3075ed81ca031eac86923d44bc5d42e719d585a8eb547bf0c9b"}, + {file = "pydantic-1.10.17-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4ad32aed3bf5eea5ca5decc3d1bbc3d0ec5d4fbcd72a03cdad849458decbc63"}, + {file = "pydantic-1.10.17-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aeb4e741782e236ee7dc1fb11ad94dc56aabaf02d21df0e79e0c21fe07c95741"}, + {file = "pydantic-1.10.17-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d2f89a719411cb234105735a520b7c077158a81e0fe1cb05a79c01fc5eb59d3c"}, + {file = "pydantic-1.10.17-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:db3b48d9283d80a314f7a682f7acae8422386de659fffaba454b77a083c3937d"}, + {file = "pydantic-1.10.17-cp311-cp311-win_amd64.whl", hash = "sha256:9c803a5113cfab7bbb912f75faa4fc1e4acff43e452c82560349fff64f852e1b"}, + {file = "pydantic-1.10.17-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:820ae12a390c9cbb26bb44913c87fa2ff431a029a785642c1ff11fed0a095fcb"}, + {file = "pydantic-1.10.17-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c1e51d1af306641b7d1574d6d3307eaa10a4991542ca324f0feb134fee259815"}, + {file = "pydantic-1.10.17-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e53fb834aae96e7b0dadd6e92c66e7dd9cdf08965340ed04c16813102a47fab"}, + {file = "pydantic-1.10.17-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e2495309b1266e81d259a570dd199916ff34f7f51f1b549a0d37a6d9b17b4dc"}, + {file = "pydantic-1.10.17-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:098ad8de840c92ea586bf8efd9e2e90c6339d33ab5c1cfbb85be66e4ecf8213f"}, + {file = "pydantic-1.10.17-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:525bbef620dac93c430d5d6bdbc91bdb5521698d434adf4434a7ef6ffd5c4b7f"}, + {file = "pydantic-1.10.17-cp312-cp312-win_amd64.whl", hash = "sha256:6654028d1144df451e1da69a670083c27117d493f16cf83da81e1e50edce72ad"}, + {file = "pydantic-1.10.17-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c87cedb4680d1614f1d59d13fea353faf3afd41ba5c906a266f3f2e8c245d655"}, + {file = "pydantic-1.10.17-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11289fa895bcbc8f18704efa1d8020bb9a86314da435348f59745473eb042e6b"}, + {file = "pydantic-1.10.17-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94833612d6fd18b57c359a127cbfd932d9150c1b72fea7c86ab58c2a77edd7c7"}, + {file = "pydantic-1.10.17-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:d4ecb515fa7cb0e46e163ecd9d52f9147ba57bc3633dca0e586cdb7a232db9e3"}, + {file = "pydantic-1.10.17-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7017971ffa7fd7808146880aa41b266e06c1e6e12261768a28b8b41ba55c8076"}, + {file = "pydantic-1.10.17-cp37-cp37m-win_amd64.whl", hash = "sha256:e840e6b2026920fc3f250ea8ebfdedf6ea7a25b77bf04c6576178e681942ae0f"}, + {file = "pydantic-1.10.17-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bfbb18b616abc4df70591b8c1ff1b3eabd234ddcddb86b7cac82657ab9017e33"}, + {file = "pydantic-1.10.17-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ebb249096d873593e014535ab07145498957091aa6ae92759a32d40cb9998e2e"}, + {file = "pydantic-1.10.17-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8c209af63ccd7b22fba94b9024e8b7fd07feffee0001efae50dd99316b27768"}, + {file = "pydantic-1.10.17-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d4b40c9e13a0b61583e5599e7950490c700297b4a375b55b2b592774332798b7"}, + {file = "pydantic-1.10.17-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c31d281c7485223caf6474fc2b7cf21456289dbaa31401844069b77160cab9c7"}, + {file = "pydantic-1.10.17-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ae5184e99a060a5c80010a2d53c99aee76a3b0ad683d493e5f0620b5d86eeb75"}, + {file = "pydantic-1.10.17-cp38-cp38-win_amd64.whl", hash = "sha256:ad1e33dc6b9787a6f0f3fd132859aa75626528b49cc1f9e429cdacb2608ad5f0"}, + {file = "pydantic-1.10.17-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7e17c0ee7192e54a10943f245dc79e36d9fe282418ea05b886e1c666063a7b54"}, + {file = "pydantic-1.10.17-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cafb9c938f61d1b182dfc7d44a7021326547b7b9cf695db5b68ec7b590214773"}, + {file = "pydantic-1.10.17-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95ef534e3c22e5abbdbdd6f66b6ea9dac3ca3e34c5c632894f8625d13d084cbe"}, + {file = "pydantic-1.10.17-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62d96b8799ae3d782df7ec9615cb59fc32c32e1ed6afa1b231b0595f6516e8ab"}, + {file = "pydantic-1.10.17-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ab2f976336808fd5d539fdc26eb51f9aafc1f4b638e212ef6b6f05e753c8011d"}, + {file = "pydantic-1.10.17-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b8ad363330557beac73159acfbeed220d5f1bfcd6b930302a987a375e02f74fd"}, + {file = "pydantic-1.10.17-cp39-cp39-win_amd64.whl", hash = "sha256:48db882e48575ce4b39659558b2f9f37c25b8d348e37a2b4e32971dd5a7d6227"}, + {file = "pydantic-1.10.17-py3-none-any.whl", hash = "sha256:e41b5b973e5c64f674b3b4720286ded184dcc26a691dd55f34391c62c6934688"}, + {file = "pydantic-1.10.17.tar.gz", hash = "sha256:f434160fb14b353caf634149baaf847206406471ba70e64657c1e8330277a991"}, ] [package.dependencies] diff --git a/pybotx/__init__.py b/pybotx/__init__.py index 1c20c086..d8c02efa 100644 --- a/pybotx/__init__.py +++ b/pybotx/__init__.py @@ -64,9 +64,6 @@ SmartappManifest, SmartappManifestWebParams, ) -from pybotx.client.smartapps_api.sync_smartapp_event import ( - SyncSmartAppEventResponsePayload, -) from pybotx.client.stickers_api.exceptions import ( InvalidEmojiError, InvalidImageError, @@ -126,6 +123,11 @@ from pybotx.models.smartapps import SmartApp from pybotx.models.status import BotMenu, StatusRecipient from pybotx.models.stickers import Sticker, StickerPack +from pybotx.models.sync_smartapp_event import ( + BotAPISyncSmartAppEventErrorResponse, + BotAPISyncSmartAppEventResponse, + BotAPISyncSmartAppEventResultResponse, +) from pybotx.models.system_events.added_to_chat import AddedToChatEvent from pybotx.models.system_events.chat_created import ChatCreatedEvent, ChatCreatedMember from pybotx.models.system_events.cts_login import CTSLoginEvent @@ -151,6 +153,9 @@ "BotAPIBotDisabledErrorData", "BotAPIBotDisabledResponse", "BotAPIMethodFailedCallback", + "BotAPISyncSmartAppEventErrorResponse", + "BotAPISyncSmartAppEventResponse", + "BotAPISyncSmartAppEventResultResponse", "BotAPIUnverifiedRequestErrorData", "BotAPIUnverifiedRequestResponse", "BotAccount", @@ -226,7 +231,6 @@ "RequestHeadersNotProvidedError", "SmartApp", "SmartAppEvent", - "SyncSmartAppEventResponsePayload", "SmartappManifest", "SmartappManifestWebLayoutChoices", "SmartappManifestWebParams", diff --git a/pybotx/bot/bot.py b/pybotx/bot/bot.py index d3ee2fea..b311cf32 100644 --- a/pybotx/bot/bot.py +++ b/pybotx/bot/bot.py @@ -148,9 +148,6 @@ BotXAPISmartAppsListRequestPayload, SmartAppsListMethod, ) -from pybotx.client.smartapps_api.sync_smartapp_event import ( - SyncSmartAppEventResponsePayload, -) from pybotx.client.smartapps_api.upload_file import ( UploadFileMethod as SmartappsUploadFileMethod, ) @@ -245,10 +242,11 @@ build_bot_status_response, ) from pybotx.models.stickers import Sticker, StickerPack, StickerPackFromList -from pybotx.models.system_events.smartapp_event import ( - BotAPISmartAppEvent, - SmartAppEvent, +from pybotx.models.sync_smartapp_event import ( + BotAPISyncSmartAppEvent, + BotAPISyncSmartAppEventResponse, ) +from pybotx.models.system_events.smartapp_event import SmartAppEvent from pybotx.models.users import UserFromCSV, UserFromSearch MissingOptionalAttachment = MissingOptional[ @@ -331,7 +329,7 @@ async def sync_execute_raw_smartapp_event( verify_request: bool = True, request_headers: Optional[Mapping[str, str]] = None, logging_command: bool = True, - ) -> SyncSmartAppEventResponsePayload: + ) -> BotAPISyncSmartAppEventResponse: if logging_command: log_incoming_request( raw_smartapp_event, @@ -342,8 +340,8 @@ async def sync_execute_raw_smartapp_event( self._verify_request(request_headers) try: - bot_api_smartapp_event: BotAPISmartAppEvent = parse_obj_as( - BotAPISmartAppEvent, + bot_api_smartapp_event: BotAPISyncSmartAppEvent = parse_obj_as( + BotAPISyncSmartAppEvent, raw_smartapp_event, ) except ValidationError as validation_exc: @@ -357,7 +355,7 @@ async def sync_execute_raw_smartapp_event( async def sync_execute_smartapp_event( self, smartapp_event: SmartAppEvent, - ) -> SyncSmartAppEventResponsePayload: + ) -> BotAPISyncSmartAppEventResponse: self._bot_accounts_storage.ensure_bot_id_exists(smartapp_event.bot.id) return await self._handler_collector.handle_sync_smartapp_event( self, diff --git a/pybotx/bot/handler.py b/pybotx/bot/handler.py index 206b8523..c654ab38 100644 --- a/pybotx/bot/handler.py +++ b/pybotx/bot/handler.py @@ -2,12 +2,10 @@ from functools import partial from typing import TYPE_CHECKING, Awaitable, Callable, List, Literal, TypeVar, Union -from pybotx.client.smartapps_api.sync_smartapp_event import ( - SyncSmartAppEventResponsePayload, -) from pybotx.models.commands import BotCommand from pybotx.models.message.incoming_message import IncomingMessage from pybotx.models.status import StatusRecipient +from pybotx.models.sync_smartapp_event import BotAPISyncSmartAppEventResponse from pybotx.models.system_events.added_to_chat import AddedToChatEvent from pybotx.models.system_events.chat_created import ChatCreatedEvent from pybotx.models.system_events.cts_login import CTSLoginEvent @@ -28,7 +26,7 @@ SyncSmartAppEventHandlerFunc = Callable[ [SmartAppEvent, "Bot"], - Awaitable[SyncSmartAppEventResponsePayload], + Awaitable[BotAPISyncSmartAppEventResponse], ] IncomingMessageHandlerFunc = HandlerFunc[IncomingMessage] diff --git a/pybotx/bot/handler_collector.py b/pybotx/bot/handler_collector.py index 411a7b6f..cd068653 100644 --- a/pybotx/bot/handler_collector.py +++ b/pybotx/bot/handler_collector.py @@ -33,14 +33,12 @@ ExceptionMiddleware, ) from pybotx.client.smartapps_api.exceptions import SyncSmartAppEventHandlerNotFoundError -from pybotx.client.smartapps_api.sync_smartapp_event import ( - SyncSmartAppEventResponsePayload, -) from pybotx.converters import optional_sequence_to_list from pybotx.logger import logger from pybotx.models.commands import BotCommand, SystemEvent from pybotx.models.message.incoming_message import IncomingMessage from pybotx.models.status import BotMenu, StatusRecipient +from pybotx.models.sync_smartapp_event import BotAPISyncSmartAppEventResponse from pybotx.models.system_events.added_to_chat import AddedToChatEvent from pybotx.models.system_events.chat_created import ChatCreatedEvent from pybotx.models.system_events.cts_login import CTSLoginEvent @@ -126,7 +124,7 @@ async def handle_sync_smartapp_event( self, bot: "Bot", smartapp_event: SmartAppEvent, - ) -> SyncSmartAppEventResponsePayload: + ) -> BotAPISyncSmartAppEventResponse: if not isinstance(smartapp_event, SmartAppEvent): raise NotImplementedError( f"Unsupported event type for sync smartapp event: `{smartapp_event}`", diff --git a/pybotx/client/smartapps_api/smartapp_event.py b/pybotx/client/smartapps_api/smartapp_event.py index abf7c61f..e8649ac5 100644 --- a/pybotx/client/smartapps_api/smartapp_event.py +++ b/pybotx/client/smartapps_api/smartapp_event.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, List, Literal, Type, TypeVar +from typing import Any, Dict, List, Literal from uuid import UUID from pybotx.client.authorized_botx_method import AuthorizedBotXMethod @@ -7,11 +7,6 @@ from pybotx.models.api_base import UnverifiedPayloadBaseModel, VerifiedPayloadBaseModel from pybotx.models.async_files import APIAsyncFile, File, convert_async_file_from_domain -TBotXAPISmartAppEventRequestPayload = TypeVar( - "TBotXAPISmartAppEventRequestPayload", - bound="BotXAPISmartAppEventRequestPayload", -) - class BotXAPISmartAppEventRequestPayload(UnverifiedPayloadBaseModel): ref: MissingOptional[UUID] @@ -25,7 +20,7 @@ class BotXAPISmartAppEventRequestPayload(UnverifiedPayloadBaseModel): @classmethod def from_domain( - cls: Type[TBotXAPISmartAppEventRequestPayload], + cls, ref: MissingOptional[UUID], smartapp_id: UUID, chat_id: UUID, @@ -33,7 +28,7 @@ def from_domain( opts: Missing[Dict[str, Any]], files: Missing[List[File]], encrypted: bool, - ) -> TBotXAPISmartAppEventRequestPayload: + ) -> "BotXAPISmartAppEventRequestPayload": api_async_files: Missing[List[APIAsyncFile]] = Undefined if files: api_async_files = [convert_async_file_from_domain(file) for file in files] diff --git a/pybotx/client/smartapps_api/sync_smartapp_event.py b/pybotx/client/smartapps_api/sync_smartapp_event.py deleted file mode 100644 index 99cd788d..00000000 --- a/pybotx/client/smartapps_api/sync_smartapp_event.py +++ /dev/null @@ -1,7 +0,0 @@ -from pybotx.client.smartapps_api.smartapp_event import ( - BotXAPISmartAppEventRequestPayload, -) - - -class SyncSmartAppEventResponsePayload(BotXAPISmartAppEventRequestPayload): - """The response payload to a synchronous smartapp event at /smartapps/request.""" diff --git a/pybotx/models/bot_account.py b/pybotx/models/bot_account.py index 48200525..7fe3a468 100644 --- a/pybotx/models/bot_account.py +++ b/pybotx/models/bot_account.py @@ -1,5 +1,6 @@ from dataclasses import dataclass from functools import cached_property +from typing import Optional from urllib.parse import urlparse from uuid import UUID @@ -9,7 +10,7 @@ @dataclass class BotAccount: id: UUID - host: str + host: Optional[str] class BotAccountWithSecret(BaseModel): diff --git a/pybotx/models/sync_smartapp_event.py b/pybotx/models/sync_smartapp_event.py new file mode 100644 index 00000000..0ab122d0 --- /dev/null +++ b/pybotx/models/sync_smartapp_event.py @@ -0,0 +1,155 @@ +from typing import Any, Dict, List, Optional, Union +from uuid import UUID + +from pybotx.missing import Missing, Undefined +from pybotx.models.api_base import UnverifiedPayloadBaseModel, VerifiedPayloadBaseModel +from pybotx.models.async_files import ( + APIAsyncFile, + File, + convert_async_file_from_domain, + convert_async_file_to_domain, +) +from pybotx.models.bot_account import BotAccount +from pybotx.models.chats import Chat +from pybotx.models.enums import ( + BotAPIClientPlatforms, + ChatTypes, + convert_client_platform_to_domain, +) +from pybotx.models.message.incoming_message import UserDevice, UserSender +from pybotx.models.system_events.smartapp_event import SmartAppEvent + + +class BotAPISyncSmartAppSender(VerifiedPayloadBaseModel): + user_huid: UUID + udid: Optional[UUID] + platform: Optional[BotAPIClientPlatforms] + + +class BotAPISyncSmartAppPayload(VerifiedPayloadBaseModel): + ref: UUID + data: Dict[str, Any] + files: List[APIAsyncFile] + + +class BotAPISyncSmartAppEvent(VerifiedPayloadBaseModel): + bot_id: UUID + group_chat_id: UUID + sender_info: BotAPISyncSmartAppSender + method: str + payload: BotAPISyncSmartAppPayload + + def to_domain(self, raw_smartapp_event: Dict[str, Any]) -> SmartAppEvent: + platform = ( + convert_client_platform_to_domain(self.sender_info.platform) + if self.sender_info.platform + else None + ) + + device = UserDevice( + platform=platform, + manufacturer=None, + device_name=None, + os=None, + pushes=None, + timezone=None, + permissions=None, + platform_package_id=None, + app_version=None, + locale=None, + ) + + sender = UserSender( + huid=self.sender_info.user_huid, + udid=self.sender_info.udid, + device=device, + ad_login=None, + ad_domain=None, + username=None, + is_chat_admin=None, + is_chat_creator=None, + ) + + return SmartAppEvent( + bot=BotAccount(id=self.bot_id, host=None), + chat=Chat( + id=self.group_chat_id, + type=ChatTypes.PERSONAL_CHAT, + ), + sender=sender, + data={ + "method": self.method, + "type": "smartapp_rpc", + "params": self.payload.data, + }, + ref=self.payload.ref, + smartapp_id=self.bot_id, + opts=None, + files=[convert_async_file_to_domain(file) for file in self.payload.files], + smartapp_api_version=None, + raw_command=raw_smartapp_event, + ) + + +class BotAPISyncSmartAppEventResultResponse(UnverifiedPayloadBaseModel): + ref: UUID + data: Any + files: List[APIAsyncFile] + + @classmethod + def from_domain( + cls, + ref: UUID, + data: Any, + files: Missing[List[File]] = Undefined, + ) -> "BotAPISyncSmartAppEventResultResponse": + api_async_files: List[APIAsyncFile] = [] + if files: + api_async_files = [convert_async_file_from_domain(file) for file in files] + + return cls( + ref=ref, + data=data, + files=api_async_files, + ) + + def jsonable_dict(self) -> Dict[str, Any]: + return { + "status": "ok", + "result": { + "ref": str(self.ref), + "data": self.data, + "files": [file.jsonable_dict() for file in self.files], + }, + } + + +class BotAPISyncSmartAppEventErrorResponse(UnverifiedPayloadBaseModel): + reason: str + errors: List[Any] + error_data: Dict[str, Any] + + @classmethod + def from_domain( + cls, + reason: Missing[str] = Undefined, + errors: Missing[List[Any]] = Undefined, + error_data: Missing[Dict[str, Any]] = Undefined, + ) -> "BotAPISyncSmartAppEventErrorResponse": + return cls( + reason="smartapp_error" if reason is Undefined else reason, + errors=[] if errors is Undefined else errors, + error_data={} if error_data is Undefined else error_data, + ) + + def jsonable_dict(self) -> Dict[str, Any]: + return { + "status": "error", + **self.dict(), + } + + +BotAPISyncSmartAppEventResponse = Union[ + BotAPISyncSmartAppEventResultResponse, + BotAPISyncSmartAppEventErrorResponse, +] diff --git a/pybotx/models/system_events/smartapp_event.py b/pybotx/models/system_events/smartapp_event.py index 4bdcbbf2..f372d5e3 100644 --- a/pybotx/models/system_events/smartapp_event.py +++ b/pybotx/models/system_events/smartapp_event.py @@ -1,5 +1,5 @@ from dataclasses import dataclass -from typing import Any, Dict, List, Literal +from typing import Any, Dict, List, Literal, Optional from uuid import UUID from pydantic import Field @@ -40,8 +40,8 @@ class SmartAppEvent(BotCommandBase): ref: UUID smartapp_id: UUID data: Dict[str, Any] # noqa: WPS110 - opts: Dict[str, Any] - smartapp_api_version: int + opts: Optional[Dict[str, Any]] + smartapp_api_version: Optional[int] files: List[File] chat: Chat sender: UserSender diff --git a/pyproject.toml b/pyproject.toml index 731023e2..882dfd4b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "pybotx" -version = "0.68.1" +version = "0.69.0" description = "A python library for interacting with eXpress BotX API" authors = [ "Sidnev Nikolay ", diff --git a/tests/conftest.py b/tests/conftest.py index 9e2d1eeb..6c61c77b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -21,12 +21,12 @@ HandlerCollector, IncomingMessage, SmartAppEvent, - SyncSmartAppEventResponsePayload, UserDevice, UserSender, ) from pybotx.bot.bot_accounts_storage import BotAccountsStorage from pybotx.logger import logger +from pybotx.models.sync_smartapp_event import BotAPISyncSmartAppEventResultResponse @pytest.fixture(autouse=True) @@ -235,62 +235,32 @@ def decorator( bot_id: Optional[UUID] = None, group_chat_id: Optional[UUID] = None, user_huid: Optional[UUID] = None, - host: Optional[str] = None, - attachment: Optional[Dict[str, Any]] = None, async_file: Optional[Dict[str, Any]] = None, method: Optional[str] = None, params: Optional[Dict[str, Any]] = None, ) -> Dict[str, Any]: return { - "sync_id": "a465f0f3-1354-491c-8f11-f400164295cb", - "command": { - "body": "system:smartapp_event", - "data": { - "ref": "6fafda2c-6505-57a5-a088-25ea5d1d0364", - "smartapp_id": str(bot_id) - if bot_id - else "8dada2c8-67a6-4434-9dec-570d244e78ee", - "data": { - "type": "smartapp_rpc", - "method": method or "folders.get", - "params": params or {}, - }, - "opts": {"option": "test_option"}, - "smartapp_api_version": 1, - }, - "command_type": "system", - "metadata": {}, - }, - "async_files": [async_file] if async_file else [], - "attachments": [attachment] if attachment else [], - "entities": [], - "from": { - "user_huid": str(user_huid) - if user_huid - else "b9197d3a-d855-5d34-ba8a-eff3a975ab20", - "user_udid": None, - "group_chat_id": str(group_chat_id) + "bot_id": str(bot_id) if bot_id else "8dada2c8-67a6-4434-9dec-570d244e78ee", + "group_chat_id": ( + str(group_chat_id) if group_chat_id - else "dea55ee4-7a9f-5da0-8c73-079f400ee517", - "host": host or "cts.example.com", - "ad_login": None, - "ad_domain": None, - "username": None, - "chat_type": "group_chat", - "manufacturer": None, - "device": None, - "device_software": None, - "device_meta": {}, - "platform": None, - "platform_package_id": None, - "is_admin": False, - "is_creator": False, - "app_version": None, - "locale": "en", + else "30dc1980-643a-00ad-37fc-7cc10d74e935" + ), + "sender_info": { + "user_huid": ( + str(user_huid) + if user_huid + else "f16cdc5f-6366-5552-9ecd-c36290ab3d11" + ), + "platform": "web", + "udid": "49eac56a-c0d8-51d7-863e-925028f05110", + }, + "method": method or "list.get", + "payload": { + "ref": "6fafda2c-6505-57a5-a088-25ea5d1d0364", + "data": params or {}, + "files": [async_file] if async_file else [], }, - "bot_id": str(bot_id) if bot_id else "8dada2c8-67a6-4434-9dec-570d244e78ee", - "proto_version": 4, - "source_sync_id": None, } return decorator @@ -370,15 +340,11 @@ def collector_with_sync_smartapp_event_handler() -> HandlerCollector: async def handle_sync_smartapp_event( event: SmartAppEvent, _: Bot, - ) -> SyncSmartAppEventResponsePayload: - return SyncSmartAppEventResponsePayload.from_domain( + ) -> BotAPISyncSmartAppEventResultResponse: + return BotAPISyncSmartAppEventResultResponse.from_domain( ref=event.ref, - smartapp_id=event.bot.id, - chat_id=event.chat.id, data=event.data, - opts={}, files=event.files, - encrypted=True, ) return collector diff --git a/tests/test_base_command.py b/tests/test_base_command.py index 5de76535..a361a76d 100644 --- a/tests/test_base_command.py +++ b/tests/test_base_command.py @@ -190,7 +190,7 @@ async def test__sync_execute_raw_smartapp_event__not_logging_incoming_request( # - Act - async with lifespan_wrapper(built_bot) as bot: with loguru_caplog.at_level(logging.DEBUG): - bot.async_execute_raw_bot_command( + await bot.sync_execute_raw_smartapp_event( payload, verify_request=False, logging_command=False, diff --git a/tests/test_end_to_end.py b/tests/test_end_to_end.py index 442e64a8..96f30679 100644 --- a/tests/test_end_to_end.py +++ b/tests/test_end_to_end.py @@ -1,5 +1,5 @@ from http import HTTPStatus -from typing import List +from typing import Any, Callable, Dict, List, Optional from uuid import UUID import httpx @@ -16,7 +16,6 @@ HandlerCollector, IncomingMessage, SmartAppEvent, - SyncSmartAppEventResponsePayload, UnknownBotAccountError, UnverifiedRequestError, build_bot_disabled_response, @@ -25,6 +24,10 @@ from pybotx.bot.api.responses.unverified_request import ( build_unverified_request_response, ) +from pybotx.models.sync_smartapp_event import ( + BotAPISyncSmartAppEventErrorResponse, + BotAPISyncSmartAppEventResultResponse, +) # - Bot setup - collector = HandlerCollector() @@ -39,22 +42,19 @@ async def debug_handler(message: IncomingMessage, bot: Bot) -> None: async def handle_sync_smartapp_event( event: SmartAppEvent, _: Bot, -) -> SyncSmartAppEventResponsePayload: - return SyncSmartAppEventResponsePayload.from_domain( +) -> BotAPISyncSmartAppEventResultResponse: + return BotAPISyncSmartAppEventResultResponse.from_domain( ref=event.ref, - smartapp_id=event.bot.id, - chat_id=event.chat.id, - data=event.data, - opts={}, + data=event.data["params"], files=event.files, - encrypted=True, ) def bot_factory( bot_accounts: List[BotAccountWithSecret], + bot_collector: Optional[HandlerCollector] = None, ) -> Bot: - return Bot(collectors=[collector], bot_accounts=bot_accounts) + return Bot(collectors=[bot_collector or collector], bot_accounts=bot_accounts) # - FastAPI integration - @@ -417,71 +417,84 @@ def test__web_app__unverified_request_response( } -def test__web_app__sync_smartapp_event(bot: Bot) -> None: +def test__web_app__sync_smartapp_event__success(bot: Bot, bot_id: UUID) -> None: # - Arrange - request_payload = { - "sync_id": "a465f0f3-1354-491c-8f11-f400164295cb", - "command": { - "body": "system:smartapp_event", - "data": { - "ref": "6fafda2c-6505-57a5-a088-25ea5d1d0364", - "smartapp_id": "8dada2c8-67a6-4434-9dec-570d244e78ee", - "data": { - "type": "smartapp_rpc", - "method": "folders.get", - "params": { - "q": 1, - }, + "bot_id": str(bot_id), + "group_chat_id": "8dada2c8-67a6-4434-9dec-570d244e78ee", + "sender_info": { + "user_huid": "ab103983-6001-44e9-889e-d55feb295494", + "platform": "web", + "udid": "49eac56a-c0d8-51d7-863e-925028f05110", + }, + "method": "list.get", + "payload": { + "ref": "6fafda2c-6505-57a5-a088-25ea5d1d0364", + "data": {"category_id": 1}, + "files": [ + { + "file": "/uploads/files/b0232da0bf3d406eb5653e37b2bb6517.bin", + "file_name": "cts1-test.ast-innovation.ru.har", + "file_size": 349372, + "file_hash": "qVSzEUJITWP+TgCvcF3UCzQrBaY3RHqB92CHObz4E70=", + "file_mime_type": "application/octet-stream", + "chunk_size": 2097152, + "file_encryption_algo": "stream", + "file_id": "a0ec914f-8235-5021-9b8d-05c3cd303536", + "type": "document", }, - "opts": {"option": "test_option"}, - "smartapp_api_version": 1, - }, - "command_type": "system", - "metadata": {}, + ], }, - "async_files": [ - { - "type": "image", - "file": "https://link.to/file", - "file_mime_type": "image/png", - "file_name": "pass.png", - "file_preview": "https://link.to/preview", - "file_preview_height": 300, - "file_preview_width": 300, - "file_size": 1502345, - "file_hash": "Jd9r+OKpw5y+FSCg1xNTSUkwEo4nCW1Sn1AkotkOpH0=", - "file_encryption_algo": "stream", - "chunk_size": 2097152, - "file_id": "8dada2c8-67a6-4434-9dec-570d244e78ee", - }, - ], - "attachments": [], - "entities": [], - "from": { - "user_huid": "b9197d3a-d855-5d34-ba8a-eff3a975ab20", - "user_udid": None, - "group_chat_id": "dea55ee4-7a9f-5da0-8c73-079f400ee517", - "host": "cts.example.com", - "ad_login": None, - "ad_domain": None, - "username": None, - "chat_type": "group_chat", - "manufacturer": None, - "device": None, - "device_software": None, - "device_meta": {}, - "platform": None, - "platform_package_id": None, - "is_admin": False, - "is_creator": False, - "app_version": None, - "locale": "en", + } + + # - Act - + with TestClient(fastapi_factory(bot)) as test_client: + response = test_client.post( + "/smartapps/request", + json=request_payload, + ) + + # - Assert - + assert response.status_code == HTTPStatus.OK + assert response.json() == { + "status": "ok", + "result": { + "ref": "6fafda2c-6505-57a5-a088-25ea5d1d0364", + "data": {"category_id": 1}, + "files": [ + { + "file": "/uploads/files/b0232da0bf3d406eb5653e37b2bb6517.bin", + "file_name": "cts1-test.ast-innovation.ru.har", + "file_size": 349372, + "file_hash": "qVSzEUJITWP+TgCvcF3UCzQrBaY3RHqB92CHObz4E70=", + "file_mime_type": "application/octet-stream", + "file_id": "a0ec914f-8235-5021-9b8d-05c3cd303536", + "type": "document", + }, + ], }, - "bot_id": "24348246-6791-4ac0-9d86-b948cd6a0e46", - "proto_version": 4, - "source_sync_id": None, } + +def test__web_app__sync_smartapp_event__error( + bot_id: UUID, + bot_account: BotAccountWithSecret, + api_sync_smartapp_event_factory: Callable[..., Dict[str, Any]], +) -> None: + # - Arrange - + request_payload = api_sync_smartapp_event_factory(bot_id=bot_id) + local_collector = HandlerCollector() + + @local_collector.sync_smartapp_event + async def handle_sync_smartapp_event_with_error( + *_: Any, + ) -> BotAPISyncSmartAppEventErrorResponse: + return BotAPISyncSmartAppEventErrorResponse.from_domain( + errors=[{"id": "Error", "reason": "some error"}], + ) + + bot = bot_factory(bot_accounts=[bot_account], bot_collector=local_collector) + # - Act - with TestClient(fastapi_factory(bot)) as test_client: response = test_client.post( @@ -492,22 +505,8 @@ def test__web_app__sync_smartapp_event(bot: Bot) -> None: # - Assert - assert response.status_code == HTTPStatus.OK assert response.json() == { - "ref": "6fafda2c-6505-57a5-a088-25ea5d1d0364", - "smartapp_id": "24348246-6791-4ac0-9d86-b948cd6a0e46", - "group_chat_id": "dea55ee4-7a9f-5da0-8c73-079f400ee517", - "data": {"type": "smartapp_rpc", "method": "folders.get", "params": {"q": 1}}, - "opts": {}, - "smartapp_api_version": 1, - "async_files": [ - { - "type": "image", - "file": "https://link.to/file", - "file_mime_type": "image/png", - "file_id": "8dada2c8-67a6-4434-9dec-570d244e78ee", - "file_name": "pass.png", - "file_size": 1502345, - "file_hash": "Jd9r+OKpw5y+FSCg1xNTSUkwEo4nCW1Sn1AkotkOpH0=", - }, - ], - "encrypted": True, + "status": "error", + "reason": "smartapp_error", + "errors": [{"id": "Error", "reason": "some error"}], + "error_data": {}, }