diff --git a/Makefile b/Makefile index 2f2c72679..878d2b0df 100644 --- a/Makefile +++ b/Makefile @@ -20,7 +20,6 @@ install: poetry run python -m pip install -U pip poetry install - clean: find . -name '*.pyc' -exec rm -f {} + find . -name '*.pyo' -exec rm -f {} + @@ -37,7 +36,7 @@ formatter: lint: poetry run ruff check rasa_sdk tests --ignore D - poetry run black --check rasa_sdk tests + poetry run black --exclude="rasa_sdk/grpc_py" --check rasa_sdk tests make lint-docstrings # Compare against `main` if no branch was provided @@ -62,3 +61,11 @@ cleanup-generated-changelog: release: poetry run python scripts/release.py +generate-grpc: + python -m grpc_tools.protoc \ + -Irasa_sdk/grpc_py=./proto \ + --python_out=. \ + --grpc_python_out=. \ + --pyi_out=. \ + proto/action_webhook.proto \ + proto/health.proto diff --git a/changelog/1109.feature.md b/changelog/1109.feature.md new file mode 100644 index 000000000..64c709bca --- /dev/null +++ b/changelog/1109.feature.md @@ -0,0 +1,6 @@ +Rasa SDK now supports gRPC protocol. +This allows users to use gRPC to invoke custom actions. +Users can use secure (TLS) and insecure connections to communicate over gRPC. +To start action server with gRPC use `--grpc` flag. +For SSL support, users can provide `--ssl-keyfile`, `--ssl-certificate` and `--ssl-ca-file`. +Support for `--ssl-password` is not available yet due to a limitation in the gRPC Python library. diff --git a/poetry.lock b/poetry.lock index 2f688c463..f155c4f6b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -11,6 +11,20 @@ files = [ {file = "aiofiles-22.1.0.tar.gz", hash = "sha256:9107f1ca0b2a5553987a94a3c9959fe5b491fdf731389aa5b7b1bd0733e32de6"}, ] +[[package]] +name = "annotated-types" +version = "0.7.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.8" +files = [ + {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, + {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.9\""} + [[package]] name = "anyio" version = "3.6.2" @@ -367,77 +381,145 @@ test = ["pytest (>=6)"] [[package]] name = "googleapis-common-protos" -version = "1.56.2" +version = "1.56.1" description = "Common protobufs used in Google APIs" optional = false python-versions = ">=3.6" files = [ - {file = "googleapis-common-protos-1.56.2.tar.gz", hash = "sha256:b09b56f5463070c2153753ef123f07d2e49235e89148e9b2459ec8ed2f68d7d3"}, - {file = "googleapis_common_protos-1.56.2-py2.py3-none-any.whl", hash = "sha256:023eaea9d8c1cceccd9587c6af6c20f33eeeb05d4148670f2b0322dc1511700c"}, + {file = "googleapis-common-protos-1.56.1.tar.gz", hash = "sha256:6b5ee59dc646eb61a8eb65ee1db186d3df6687c8804830024f32573298bca19b"}, + {file = "googleapis_common_protos-1.56.1-py2.py3-none-any.whl", hash = "sha256:ddcd955b5bb6589368f659fa475373faa1ed7d09cde5ba25e88513d87007e174"}, ] [package.dependencies] -protobuf = ">=3.15.0,<4.0.0dev" +protobuf = ">=3.15.0" [package.extras] -grpc = ["grpcio (>=1.0.0,<2.0.0dev)"] +grpc = ["grpcio (>=1.0.0)"] [[package]] name = "grpcio" -version = "1.56.2" +version = "1.59.3" description = "HTTP/2-based RPC framework" optional = false python-versions = ">=3.7" files = [ - {file = "grpcio-1.56.2-cp310-cp310-linux_armv7l.whl", hash = "sha256:bf0b9959e673505ee5869950642428046edb91f99942607c2ecf635f8a4b31c9"}, - {file = "grpcio-1.56.2-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:5144feb20fe76e73e60c7d73ec3bf54f320247d1ebe737d10672480371878b48"}, - {file = "grpcio-1.56.2-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:a72797549935c9e0b9bc1def1768c8b5a709538fa6ab0678e671aec47ebfd55e"}, - {file = "grpcio-1.56.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c3f3237a57e42f79f1e560726576aedb3a7ef931f4e3accb84ebf6acc485d316"}, - {file = "grpcio-1.56.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:900bc0096c2ca2d53f2e5cebf98293a7c32f532c4aeb926345e9747452233950"}, - {file = "grpcio-1.56.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:97e0efaebbfd222bcaac2f1735c010c1d3b167112d9d237daebbeedaaccf3d1d"}, - {file = "grpcio-1.56.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c0c85c5cbe8b30a32fa6d802588d55ffabf720e985abe9590c7c886919d875d4"}, - {file = "grpcio-1.56.2-cp310-cp310-win32.whl", hash = "sha256:06e84ad9ae7668a109e970c7411e7992751a116494cba7c4fb877656527f9a57"}, - {file = "grpcio-1.56.2-cp310-cp310-win_amd64.whl", hash = "sha256:10954662f77dc36c9a1fb5cc4a537f746580d6b5734803be1e587252682cda8d"}, - {file = "grpcio-1.56.2-cp311-cp311-linux_armv7l.whl", hash = "sha256:c435f5ce1705de48e08fcbcfaf8aee660d199c90536e3e06f2016af7d6a938dd"}, - {file = "grpcio-1.56.2-cp311-cp311-macosx_10_10_universal2.whl", hash = "sha256:6108e5933eb8c22cd3646e72d5b54772c29f57482fd4c41a0640aab99eb5071d"}, - {file = "grpcio-1.56.2-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:8391cea5ce72f4a12368afd17799474015d5d3dc00c936a907eb7c7eaaea98a5"}, - {file = "grpcio-1.56.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:750de923b456ca8c0f1354d6befca45d1f3b3a789e76efc16741bd4132752d95"}, - {file = "grpcio-1.56.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fda2783c12f553cdca11c08e5af6eecbd717280dc8fbe28a110897af1c15a88c"}, - {file = "grpcio-1.56.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:9e04d4e4cfafa7c5264e535b5d28e786f0571bea609c3f0aaab13e891e933e9c"}, - {file = "grpcio-1.56.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:89a49cc5ad08a38b6141af17e00d1dd482dc927c7605bc77af457b5a0fca807c"}, - {file = "grpcio-1.56.2-cp311-cp311-win32.whl", hash = "sha256:6a007a541dff984264981fbafeb052bfe361db63578948d857907df9488d8774"}, - {file = "grpcio-1.56.2-cp311-cp311-win_amd64.whl", hash = "sha256:af4063ef2b11b96d949dccbc5a987272f38d55c23c4c01841ea65a517906397f"}, - {file = "grpcio-1.56.2-cp37-cp37m-linux_armv7l.whl", hash = "sha256:a6ff459dac39541e6a2763a4439c4ca6bc9ecb4acc05a99b79246751f9894756"}, - {file = "grpcio-1.56.2-cp37-cp37m-macosx_10_10_universal2.whl", hash = "sha256:f20fd21f7538f8107451156dd1fe203300b79a9ddceba1ee0ac8132521a008ed"}, - {file = "grpcio-1.56.2-cp37-cp37m-manylinux_2_17_aarch64.whl", hash = "sha256:d1fbad1f9077372b6587ec589c1fc120b417b6c8ad72d3e3cc86bbbd0a3cee93"}, - {file = "grpcio-1.56.2-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ee26e9dfb3996aff7c870f09dc7ad44a5f6732b8bdb5a5f9905737ac6fd4ef1"}, - {file = "grpcio-1.56.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4c60abd950d6de3e4f1ddbc318075654d275c29c846ab6a043d6ed2c52e4c8c"}, - {file = "grpcio-1.56.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1c31e52a04e62c8577a7bf772b3e7bed4df9c9e0dd90f92b6ffa07c16cab63c9"}, - {file = "grpcio-1.56.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:345356b307cce5d14355e8e055b4ca5f99bc857c33a3dc1ddbc544fca9cd0475"}, - {file = "grpcio-1.56.2-cp37-cp37m-win_amd64.whl", hash = "sha256:42e63904ee37ae46aa23de50dac8b145b3596f43598fa33fe1098ab2cbda6ff5"}, - {file = "grpcio-1.56.2-cp38-cp38-linux_armv7l.whl", hash = "sha256:7c5ede2e2558f088c49a1ddda19080e4c23fb5d171de80a726b61b567e3766ed"}, - {file = "grpcio-1.56.2-cp38-cp38-macosx_10_10_universal2.whl", hash = "sha256:33971197c47965cc1d97d78d842163c283e998223b151bab0499b951fd2c0b12"}, - {file = "grpcio-1.56.2-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:d39f5d4af48c138cb146763eda14eb7d8b3ccbbec9fe86fb724cd16e0e914c64"}, - {file = "grpcio-1.56.2-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ded637176addc1d3eef35331c39acc598bac550d213f0a1bedabfceaa2244c87"}, - {file = "grpcio-1.56.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c90da4b124647547a68cf2f197174ada30c7bb9523cb976665dfd26a9963d328"}, - {file = "grpcio-1.56.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3ccb621749a81dc7755243665a70ce45536ec413ef5818e013fe8dfbf5aa497b"}, - {file = "grpcio-1.56.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4eb37dd8dd1aa40d601212afa27ca5be255ba792e2e0b24d67b8af5e012cdb7d"}, - {file = "grpcio-1.56.2-cp38-cp38-win32.whl", hash = "sha256:ddb4a6061933bd9332b74eac0da25f17f32afa7145a33a0f9711ad74f924b1b8"}, - {file = "grpcio-1.56.2-cp38-cp38-win_amd64.whl", hash = "sha256:8940d6de7068af018dfa9a959a3510e9b7b543f4c405e88463a1cbaa3b2b379a"}, - {file = "grpcio-1.56.2-cp39-cp39-linux_armv7l.whl", hash = "sha256:51173e8fa6d9a2d85c14426bdee5f5c4a0654fd5fddcc21fe9d09ab0f6eb8b35"}, - {file = "grpcio-1.56.2-cp39-cp39-macosx_10_10_universal2.whl", hash = "sha256:373b48f210f43327a41e397391715cd11cfce9ded2fe76a5068f9bacf91cc226"}, - {file = "grpcio-1.56.2-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:42a3bbb2bc07aef72a7d97e71aabecaf3e4eb616d39e5211e2cfe3689de860ca"}, - {file = "grpcio-1.56.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5344be476ac37eb9c9ad09c22f4ea193c1316bf074f1daf85bddb1b31fda5116"}, - {file = "grpcio-1.56.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3fa3ab0fb200a2c66493828ed06ccd1a94b12eddbfb985e7fd3e5723ff156c6"}, - {file = "grpcio-1.56.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:b975b85d1d5efc36cf8b237c5f3849b64d1ba33d6282f5e991f28751317504a1"}, - {file = "grpcio-1.56.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:cbdf2c498e077282cd427cfd88bdce4668019791deef0be8155385ab2ba7837f"}, - {file = "grpcio-1.56.2-cp39-cp39-win32.whl", hash = "sha256:139f66656a762572ae718fa0d1f2dce47c05e9fbf7a16acd704c354405b97df9"}, - {file = "grpcio-1.56.2-cp39-cp39-win_amd64.whl", hash = "sha256:830215173ad45d670140ff99aac3b461f9be9a6b11bee1a17265aaaa746a641a"}, - {file = "grpcio-1.56.2.tar.gz", hash = "sha256:0ff789ae7d8ddd76d2ac02e7d13bfef6fc4928ac01e1dcaa182be51b6bcc0aaa"}, + {file = "grpcio-1.59.3-cp310-cp310-linux_armv7l.whl", hash = "sha256:aca028a6c7806e5b61e5f9f4232432c52856f7fcb98e330b20b6bc95d657bdcc"}, + {file = "grpcio-1.59.3-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:19ad26a7967f7999c8960d2b9fe382dae74c55b0c508c613a6c2ba21cddf2354"}, + {file = "grpcio-1.59.3-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:72b71dad2a3d1650e69ad42a5c4edbc59ee017f08c32c95694172bc501def23c"}, + {file = "grpcio-1.59.3-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c0f0a11d82d0253656cc42e04b6a149521e02e755fe2e4edd21123de610fd1d4"}, + {file = "grpcio-1.59.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60cddafb70f9a2c81ba251b53b4007e07cca7389e704f86266e22c4bffd8bf1d"}, + {file = "grpcio-1.59.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6c75a1fa0e677c1d2b6d4196ad395a5c381dfb8385f07ed034ef667cdcdbcc25"}, + {file = "grpcio-1.59.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e1d8e01438d5964a11167eec1edb5f85ed8e475648f36c834ed5db4ffba24ac8"}, + {file = "grpcio-1.59.3-cp310-cp310-win32.whl", hash = "sha256:c4b0076f0bf29ee62335b055a9599f52000b7941f577daa001c7ef961a1fbeab"}, + {file = "grpcio-1.59.3-cp310-cp310-win_amd64.whl", hash = "sha256:b1f00a3e6e0c3dccccffb5579fc76ebfe4eb40405ba308505b41ef92f747746a"}, + {file = "grpcio-1.59.3-cp311-cp311-linux_armv7l.whl", hash = "sha256:3996aaa21231451161dc29df6a43fcaa8b332042b6150482c119a678d007dd86"}, + {file = "grpcio-1.59.3-cp311-cp311-macosx_10_10_universal2.whl", hash = "sha256:cb4e9cbd9b7388fcb06412da9f188c7803742d06d6f626304eb838d1707ec7e3"}, + {file = "grpcio-1.59.3-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:8022ca303d6c694a0d7acfb2b472add920217618d3a99eb4b14edc7c6a7e8fcf"}, + {file = "grpcio-1.59.3-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b36683fad5664283755a7f4e2e804e243633634e93cd798a46247b8e54e3cb0d"}, + {file = "grpcio-1.59.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8239b853226e4824e769517e1b5232e7c4dda3815b200534500338960fcc6118"}, + {file = "grpcio-1.59.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:0511af8653fbda489ff11d542a08505d56023e63cafbda60e6e00d4e0bae86ea"}, + {file = "grpcio-1.59.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e78dc982bda74cef2ddfce1c91d29b96864c4c680c634e279ed204d51e227473"}, + {file = "grpcio-1.59.3-cp311-cp311-win32.whl", hash = "sha256:6a5c3a96405966c023e139c3bcccb2c7c776a6f256ac6d70f8558c9041bdccc3"}, + {file = "grpcio-1.59.3-cp311-cp311-win_amd64.whl", hash = "sha256:ed26826ee423b11477297b187371cdf4fa1eca874eb1156422ef3c9a60590dd9"}, + {file = "grpcio-1.59.3-cp312-cp312-linux_armv7l.whl", hash = "sha256:45dddc5cb5227d30fa43652d8872dc87f086d81ab4b500be99413bad0ae198d7"}, + {file = "grpcio-1.59.3-cp312-cp312-macosx_10_10_universal2.whl", hash = "sha256:1736496d74682e53dd0907fd515f2694d8e6a96c9a359b4080b2504bf2b2d91b"}, + {file = "grpcio-1.59.3-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:ddbd1a16138e52e66229047624de364f88a948a4d92ba20e4e25ad7d22eef025"}, + {file = "grpcio-1.59.3-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fcfa56f8d031ffda902c258c84c4b88707f3a4be4827b4e3ab8ec7c24676320d"}, + {file = "grpcio-1.59.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2eb8f0c7c0c62f7a547ad7a91ba627a5aa32a5ae8d930783f7ee61680d7eb8d"}, + {file = "grpcio-1.59.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8d993399cc65e3a34f8fd48dd9ad7a376734564b822e0160dd18b3d00c1a33f9"}, + {file = "grpcio-1.59.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c0bd141f4f41907eb90bda74d969c3cb21c1c62779419782a5b3f5e4b5835718"}, + {file = "grpcio-1.59.3-cp312-cp312-win32.whl", hash = "sha256:33b8fd65d4e97efa62baec6171ce51f9cf68f3a8ba9f866f4abc9d62b5c97b79"}, + {file = "grpcio-1.59.3-cp312-cp312-win_amd64.whl", hash = "sha256:0e735ed002f50d4f3cb9ecfe8ac82403f5d842d274c92d99db64cfc998515e07"}, + {file = "grpcio-1.59.3-cp37-cp37m-linux_armv7l.whl", hash = "sha256:ea40ce4404e7cca0724c91a7404da410f0144148fdd58402a5942971e3469b94"}, + {file = "grpcio-1.59.3-cp37-cp37m-macosx_10_10_universal2.whl", hash = "sha256:83113bcc393477b6f7342b9f48e8a054330c895205517edc66789ceea0796b53"}, + {file = "grpcio-1.59.3-cp37-cp37m-manylinux_2_17_aarch64.whl", hash = "sha256:73afbac602b8f1212a50088193601f869b5073efa9855b3e51aaaec97848fc8a"}, + {file = "grpcio-1.59.3-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:575d61de1950b0b0699917b686b1ca108690702fcc2df127b8c9c9320f93e069"}, + {file = "grpcio-1.59.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cd76057b5c9a4d68814610ef9226925f94c1231bbe533fdf96f6181f7d2ff9e"}, + {file = "grpcio-1.59.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:95d6fd804c81efe4879e38bfd84d2b26e339a0a9b797e7615e884ef4686eb47b"}, + {file = "grpcio-1.59.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0d42048b8a3286ea4134faddf1f9a59cf98192b94aaa10d910a25613c5eb5bfb"}, + {file = "grpcio-1.59.3-cp37-cp37m-win_amd64.whl", hash = "sha256:4619fea15c64bcdd9d447cdbdde40e3d5f1da3a2e8ae84103d94a9c1df210d7e"}, + {file = "grpcio-1.59.3-cp38-cp38-linux_armv7l.whl", hash = "sha256:95b5506e70284ac03b2005dd9ffcb6708c9ae660669376f0192a710687a22556"}, + {file = "grpcio-1.59.3-cp38-cp38-macosx_10_10_universal2.whl", hash = "sha256:9e17660947660ccfce56c7869032910c179a5328a77b73b37305cd1ee9301c2e"}, + {file = "grpcio-1.59.3-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:00912ce19914d038851be5cd380d94a03f9d195643c28e3ad03d355cc02ce7e8"}, + {file = "grpcio-1.59.3-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e58b3cadaa3c90f1efca26ba33e0d408b35b497307027d3d707e4bcd8de862a6"}, + {file = "grpcio-1.59.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d787ecadea865bdf78f6679f6f5bf4b984f18f659257ba612979df97a298b3c3"}, + {file = "grpcio-1.59.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:0814942ba1bba269db4e760a34388640c601dece525c6a01f3b4ff030cc0db69"}, + {file = "grpcio-1.59.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fb111aa99d3180c361a35b5ae1e2c63750220c584a1344229abc139d5c891881"}, + {file = "grpcio-1.59.3-cp38-cp38-win32.whl", hash = "sha256:eb8ba504c726befe40a356ecbe63c6c3c64c9a439b3164f5a718ec53c9874da0"}, + {file = "grpcio-1.59.3-cp38-cp38-win_amd64.whl", hash = "sha256:cdbc6b32fadab9bebc6f49d3e7ec4c70983c71e965497adab7f87de218e84391"}, + {file = "grpcio-1.59.3-cp39-cp39-linux_armv7l.whl", hash = "sha256:c82ca1e4be24a98a253d6dbaa216542e4163f33f38163fc77964b0f0d255b552"}, + {file = "grpcio-1.59.3-cp39-cp39-macosx_10_10_universal2.whl", hash = "sha256:36636babfda14f9e9687f28d5b66d349cf88c1301154dc71c6513de2b6c88c59"}, + {file = "grpcio-1.59.3-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:5f9b2e591da751ac7fdd316cc25afafb7a626dededa9b414f90faad7f3ccebdb"}, + {file = "grpcio-1.59.3-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a93a82876a4926bf451db82ceb725bd87f42292bacc94586045261f501a86994"}, + {file = "grpcio-1.59.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce31fa0bfdd1f2bb15b657c16105c8652186eab304eb512e6ae3b99b2fdd7d13"}, + {file = "grpcio-1.59.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:16da0e40573962dab6cba16bec31f25a4f468e6d05b658e589090fe103b03e3d"}, + {file = "grpcio-1.59.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d1d1a17372fd425addd5812049fa7374008ffe689585f27f802d0935522cf4b7"}, + {file = "grpcio-1.59.3-cp39-cp39-win32.whl", hash = "sha256:52cc38a7241b5f7b4a91aaf9000fdd38e26bb00d5e8a71665ce40cfcee716281"}, + {file = "grpcio-1.59.3-cp39-cp39-win_amd64.whl", hash = "sha256:b491e5bbcad3020a96842040421e508780cade35baba30f402df9d321d1c423e"}, + {file = "grpcio-1.59.3.tar.gz", hash = "sha256:7800f99568a74a06ebdccd419dd1b6e639b477dcaf6da77ea702f8fb14ce5f80"}, ] [package.extras] -protobuf = ["grpcio-tools (>=1.56.2)"] +protobuf = ["grpcio-tools (>=1.59.3)"] + +[[package]] +name = "grpcio-tools" +version = "1.56.2" +description = "Protobuf code generator for gRPC" +optional = false +python-versions = ">=3.7" +files = [ + {file = "grpcio-tools-1.56.2.tar.gz", hash = "sha256:82af2f4040084141a732f0ef1ecf3f14fdf629923d74d850415e4d09a077e77a"}, + {file = "grpcio_tools-1.56.2-cp310-cp310-linux_armv7l.whl", hash = "sha256:42272376e9a5a1c631863cda056c143c98d21e5b670db5c8c5b7ed0ba3a1a6eb"}, + {file = "grpcio_tools-1.56.2-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:a8735d7aa34be99dddfbd476eff6005e684bb2c893c0f62a5811528b84c5b371"}, + {file = "grpcio_tools-1.56.2-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:68ef3aa7509e5e7a6e7c0ecc183e28118e73da4bef0fc77079648601ce35e58f"}, + {file = "grpcio_tools-1.56.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:380985b8d95ea2469e103945bd83a815d1213e370f580631fdd5a3dbaa17e446"}, + {file = "grpcio_tools-1.56.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bfb375eb4f1946d68b8bc7b963c756defa31aa573a35c152a7233d06c0ad6ad"}, + {file = "grpcio_tools-1.56.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:13388a22fcba9a1a87f217130a1a01365716af74bd5d0a8a54fc383b8e048ef2"}, + {file = "grpcio_tools-1.56.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7a26160bc0ea5b464715789d4d2a66f01816271677673d65da39bac65b9ea838"}, + {file = "grpcio_tools-1.56.2-cp310-cp310-win32.whl", hash = "sha256:ff16dd0b086e75f574dbc122e018a44dbd1c6dae3f3621ea99e8e5a6b2706e12"}, + {file = "grpcio_tools-1.56.2-cp310-cp310-win_amd64.whl", hash = "sha256:2037109c1ce253a8e013c9e3ad3722e887d28a1807acdeb1a51b295c8200137b"}, + {file = "grpcio_tools-1.56.2-cp311-cp311-linux_armv7l.whl", hash = "sha256:6dc43300189a69807857c52a3d782e9d3bbfb1cb72dcb27b4043c25161919601"}, + {file = "grpcio_tools-1.56.2-cp311-cp311-macosx_10_10_universal2.whl", hash = "sha256:e7009623635ebcd3dd7fe974883fc2d9a3ff0fcef419bfc0a2da8071b372d9f5"}, + {file = "grpcio_tools-1.56.2-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:c7ca2272022f90b73efe900244aaebe9dd7cf3b379e99e08a88984e2fdd229c2"}, + {file = "grpcio_tools-1.56.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:493775d17ea09cea6047ba81e4d3f0eb82e34d2fbd3b96e43f72b44ce74726ee"}, + {file = "grpcio_tools-1.56.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41af279cf5359b123138236c0980440f4cb4d3d18f03b5c1c314cc1512048351"}, + {file = "grpcio_tools-1.56.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:857d72e991d449ec4d2f8337e5e24ddf77b4539965f5cabc84d4b63585832982"}, + {file = "grpcio_tools-1.56.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c0640728d63c9fa56e9a1679943ae4e33ad43a10802dd7a93255870731f44d07"}, + {file = "grpcio_tools-1.56.2-cp311-cp311-win32.whl", hash = "sha256:355204d1b33c7a19e7d69afda411e6595d39ba1e9cbf561770ac1d5403296554"}, + {file = "grpcio_tools-1.56.2-cp311-cp311-win_amd64.whl", hash = "sha256:ea5d108d28b4cd2e28539241c6aee96bda83086d8888c36785d9f84ea690d896"}, + {file = "grpcio_tools-1.56.2-cp37-cp37m-linux_armv7l.whl", hash = "sha256:28444615b7a76b3d9267f81d1487fcad21a581d00564164d9e25ccc28635a811"}, + {file = "grpcio_tools-1.56.2-cp37-cp37m-macosx_10_10_universal2.whl", hash = "sha256:45d8b5ad6716848d5b68d9cee29a1a9c5c4baa1824ec5b92d9e35acedddba076"}, + {file = "grpcio_tools-1.56.2-cp37-cp37m-manylinux_2_17_aarch64.whl", hash = "sha256:31d1183d28ffc8da242333cb9f683f5093941da80dd5281db0fa93077aecb518"}, + {file = "grpcio_tools-1.56.2-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0059dfc9bea8f7bca69c15ca62c88904c4f907fde1137e0743b5eee054661873"}, + {file = "grpcio_tools-1.56.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24fc857252181c9950ed2d8cee3df5bd0f42861c4ad0db2a57400186827f96e5"}, + {file = "grpcio_tools-1.56.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:3a74a5e4fc8121a51401665f96f9a70aee50a2f1221e4a199e67b3b8f55881e8"}, + {file = "grpcio_tools-1.56.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:bec47db5d8b5c3b2a44afdbc3f3bf306e34279289a206d20222824381ca3cb13"}, + {file = "grpcio_tools-1.56.2-cp37-cp37m-win_amd64.whl", hash = "sha256:c0dbaac63a25c088f864295f394230eeb7be48dac2264433fda2603f86c36b25"}, + {file = "grpcio_tools-1.56.2-cp38-cp38-linux_armv7l.whl", hash = "sha256:0a4f9cce5a16613b6d3123c89f9d50e0d13b466799af17bc723dc7d2901a54e4"}, + {file = "grpcio_tools-1.56.2-cp38-cp38-macosx_10_10_universal2.whl", hash = "sha256:ea5fc1b49514b44a3e5a45156c025002f172ade4c509e58c51967865c7c6fa45"}, + {file = "grpcio_tools-1.56.2-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:54da410124547bacb97a54546c1a95f1af0125e48edc8b5679412ef8b2844f81"}, + {file = "grpcio_tools-1.56.2-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5223668649172d879ee780253b8e4a79144c56a3cc1bb021847f583508c2b0be"}, + {file = "grpcio_tools-1.56.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:483256d5f5be6a77b24d8a5f06ca152d1571c62bf5c738834da61107c7563afe"}, + {file = "grpcio_tools-1.56.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1f334718eb796799bfadbac5567456fb745cee8c7b438c93b74d1ce676c6ad07"}, + {file = "grpcio_tools-1.56.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:26751f69cbbc8ea19cf0657b7d109a6db7df81f80caf16380ebcd20eea27652c"}, + {file = "grpcio_tools-1.56.2-cp38-cp38-win32.whl", hash = "sha256:4056ff13e30813d42a30ce1cdfeaeb6bbee915515c161c1df896dac3143ae643"}, + {file = "grpcio_tools-1.56.2-cp38-cp38-win_amd64.whl", hash = "sha256:878b9269ceb0dd934b61697a9dd9a5c3e9552521e8f46ab32cf4d72a223f7b6c"}, + {file = "grpcio_tools-1.56.2-cp39-cp39-linux_armv7l.whl", hash = "sha256:216e86d3a6ccc31b27fa4c12491981a0a39d4787d2358b6df05baffa40084494"}, + {file = "grpcio_tools-1.56.2-cp39-cp39-macosx_10_10_universal2.whl", hash = "sha256:778224fcbc1cc7eaf222ce94676afbac8d72b4f84cf4239e30b01d2450a46126"}, + {file = "grpcio_tools-1.56.2-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:14120fb2c6f7894fac5b689934368c692ec50f50a320e8073277ab7778fd612f"}, + {file = "grpcio_tools-1.56.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:014da3ed176beb2b1c8430ccc34c8fe962cdd5480e56fb4ab9de60e60c315f3f"}, + {file = "grpcio_tools-1.56.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8febb4f90b8fab3179f5bdaa159f1d2a20523ea17ec0d66bdec7732f9532de91"}, + {file = "grpcio_tools-1.56.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ffae7df3318266614f7aa440acb2098c064b6b5ae061fc22125092386349e526"}, + {file = "grpcio_tools-1.56.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7d86e24eb6e3973c55e9c74412ff755d1b9d15518c4eaf95676acff49d0162a2"}, + {file = "grpcio_tools-1.56.2-cp39-cp39-win32.whl", hash = "sha256:506d00a86950adf4017395551a4547c0b7fcefa90e4c220135fc3e34e31be81b"}, + {file = "grpcio_tools-1.56.2-cp39-cp39-win_amd64.whl", hash = "sha256:8da04f033b8f4c597e8fc990e2f626bad2b269227bdd554592ea618f624f1aa9"}, +] + +[package.dependencies] +grpcio = ">=1.56.2" +protobuf = ">=4.21.6,<5.0dev" +setuptools = "*" [[package]] name = "h11" @@ -1079,35 +1161,134 @@ wcwidth = "*" [[package]] name = "protobuf" -version = "3.20.3" -description = "Protocol Buffers" +version = "4.25.3" +description = "" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "protobuf-3.20.3-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:f4bd856d702e5b0d96a00ec6b307b0f51c1982c2bf9c0052cf9019e9a544ba99"}, - {file = "protobuf-3.20.3-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9aae4406ea63d825636cc11ffb34ad3379335803216ee3a856787bcf5ccc751e"}, - {file = "protobuf-3.20.3-cp310-cp310-win32.whl", hash = "sha256:28545383d61f55b57cf4df63eebd9827754fd2dc25f80c5253f9184235db242c"}, - {file = "protobuf-3.20.3-cp310-cp310-win_amd64.whl", hash = "sha256:67a3598f0a2dcbc58d02dd1928544e7d88f764b47d4a286202913f0b2801c2e7"}, - {file = "protobuf-3.20.3-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:899dc660cd599d7352d6f10d83c95df430a38b410c1b66b407a6b29265d66469"}, - {file = "protobuf-3.20.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e64857f395505ebf3d2569935506ae0dfc4a15cb80dc25261176c784662cdcc4"}, - {file = "protobuf-3.20.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:d9e4432ff660d67d775c66ac42a67cf2453c27cb4d738fc22cb53b5d84c135d4"}, - {file = "protobuf-3.20.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:74480f79a023f90dc6e18febbf7b8bac7508420f2006fabd512013c0c238f454"}, - {file = "protobuf-3.20.3-cp37-cp37m-win32.whl", hash = "sha256:b6cc7ba72a8850621bfec987cb72623e703b7fe2b9127a161ce61e61558ad905"}, - {file = "protobuf-3.20.3-cp37-cp37m-win_amd64.whl", hash = "sha256:8c0c984a1b8fef4086329ff8dd19ac77576b384079247c770f29cc8ce3afa06c"}, - {file = "protobuf-3.20.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:de78575669dddf6099a8a0f46a27e82a1783c557ccc38ee620ed8cc96d3be7d7"}, - {file = "protobuf-3.20.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:f4c42102bc82a51108e449cbb32b19b180022941c727bac0cfd50170341f16ee"}, - {file = "protobuf-3.20.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:44246bab5dd4b7fbd3c0c80b6f16686808fab0e4aca819ade6e8d294a29c7050"}, - {file = "protobuf-3.20.3-cp38-cp38-win32.whl", hash = "sha256:c02ce36ec760252242a33967d51c289fd0e1c0e6e5cc9397e2279177716add86"}, - {file = "protobuf-3.20.3-cp38-cp38-win_amd64.whl", hash = "sha256:447d43819997825d4e71bf5769d869b968ce96848b6479397e29fc24c4a5dfe9"}, - {file = "protobuf-3.20.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:398a9e0c3eaceb34ec1aee71894ca3299605fa8e761544934378bbc6c97de23b"}, - {file = "protobuf-3.20.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:bf01b5720be110540be4286e791db73f84a2b721072a3711efff6c324cdf074b"}, - {file = "protobuf-3.20.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:daa564862dd0d39c00f8086f88700fdbe8bc717e993a21e90711acfed02f2402"}, - {file = "protobuf-3.20.3-cp39-cp39-win32.whl", hash = "sha256:819559cafa1a373b7096a482b504ae8a857c89593cf3a25af743ac9ecbd23480"}, - {file = "protobuf-3.20.3-cp39-cp39-win_amd64.whl", hash = "sha256:03038ac1cfbc41aa21f6afcbcd357281d7521b4157926f30ebecc8d4ea59dcb7"}, - {file = "protobuf-3.20.3-py2.py3-none-any.whl", hash = "sha256:a7ca6d488aa8ff7f329d4c545b2dbad8ac31464f1d8b1c87ad1346717731e4db"}, - {file = "protobuf-3.20.3.tar.gz", hash = "sha256:2e3427429c9cffebf259491be0af70189607f365c2f41c7c3764af6f337105f2"}, + {file = "protobuf-4.25.3-cp310-abi3-win32.whl", hash = "sha256:d4198877797a83cbfe9bffa3803602bbe1625dc30d8a097365dbc762e5790faa"}, + {file = "protobuf-4.25.3-cp310-abi3-win_amd64.whl", hash = "sha256:209ba4cc916bab46f64e56b85b090607a676f66b473e6b762e6f1d9d591eb2e8"}, + {file = "protobuf-4.25.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:f1279ab38ecbfae7e456a108c5c0681e4956d5b1090027c1de0f934dfdb4b35c"}, + {file = "protobuf-4.25.3-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:e7cb0ae90dd83727f0c0718634ed56837bfeeee29a5f82a7514c03ee1364c019"}, + {file = "protobuf-4.25.3-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:7c8daa26095f82482307bc717364e7c13f4f1c99659be82890dcfc215194554d"}, + {file = "protobuf-4.25.3-cp38-cp38-win32.whl", hash = "sha256:f4f118245c4a087776e0a8408be33cf09f6c547442c00395fbfb116fac2f8ac2"}, + {file = "protobuf-4.25.3-cp38-cp38-win_amd64.whl", hash = "sha256:c053062984e61144385022e53678fbded7aea14ebb3e0305ae3592fb219ccfa4"}, + {file = "protobuf-4.25.3-cp39-cp39-win32.whl", hash = "sha256:19b270aeaa0099f16d3ca02628546b8baefe2955bbe23224aaf856134eccf1e4"}, + {file = "protobuf-4.25.3-cp39-cp39-win_amd64.whl", hash = "sha256:e3c97a1555fd6388f857770ff8b9703083de6bf1f9274a002a332d65fbb56c8c"}, + {file = "protobuf-4.25.3-py3-none-any.whl", hash = "sha256:f0700d54bcf45424477e46a9f0944155b46fb0639d69728739c0e47bab83f2b9"}, + {file = "protobuf-4.25.3.tar.gz", hash = "sha256:25b5d0b42fd000320bd7830b349e3b696435f3b329810427a6bcce6a5492cc5c"}, ] +[[package]] +name = "pydantic" +version = "2.6.4" +description = "Data validation using Python type hints" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic-2.6.4-py3-none-any.whl", hash = "sha256:cc46fce86607580867bdc3361ad462bab9c222ef042d3da86f2fb333e1d916c5"}, + {file = "pydantic-2.6.4.tar.gz", hash = "sha256:b1704e0847db01817624a6b86766967f552dd9dbf3afba4004409f908dcc84e6"}, +] + +[package.dependencies] +annotated-types = ">=0.4.0" +pydantic-core = "2.16.3" +typing-extensions = ">=4.6.1" + +[package.extras] +email = ["email-validator (>=2.0.0)"] + +[[package]] +name = "pydantic-core" +version = "2.16.3" +description = "" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic_core-2.16.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:75b81e678d1c1ede0785c7f46690621e4c6e63ccd9192af1f0bd9d504bbb6bf4"}, + {file = "pydantic_core-2.16.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9c865a7ee6f93783bd5d781af5a4c43dadc37053a5b42f7d18dc019f8c9d2bd1"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:162e498303d2b1c036b957a1278fa0899d02b2842f1ff901b6395104c5554a45"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2f583bd01bbfbff4eaee0868e6fc607efdfcc2b03c1c766b06a707abbc856187"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b926dd38db1519ed3043a4de50214e0d600d404099c3392f098a7f9d75029ff8"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:716b542728d4c742353448765aa7cdaa519a7b82f9564130e2b3f6766018c9ec"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc4ad7f7ee1a13d9cb49d8198cd7d7e3aa93e425f371a68235f784e99741561f"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bd87f48924f360e5d1c5f770d6155ce0e7d83f7b4e10c2f9ec001c73cf475c99"}, + {file = "pydantic_core-2.16.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0df446663464884297c793874573549229f9eca73b59360878f382a0fc085979"}, + {file = "pydantic_core-2.16.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4df8a199d9f6afc5ae9a65f8f95ee52cae389a8c6b20163762bde0426275b7db"}, + {file = "pydantic_core-2.16.3-cp310-none-win32.whl", hash = "sha256:456855f57b413f077dff513a5a28ed838dbbb15082ba00f80750377eed23d132"}, + {file = "pydantic_core-2.16.3-cp310-none-win_amd64.whl", hash = "sha256:732da3243e1b8d3eab8c6ae23ae6a58548849d2e4a4e03a1924c8ddf71a387cb"}, + {file = "pydantic_core-2.16.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:519ae0312616026bf4cedc0fe459e982734f3ca82ee8c7246c19b650b60a5ee4"}, + {file = "pydantic_core-2.16.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b3992a322a5617ded0a9f23fd06dbc1e4bd7cf39bc4ccf344b10f80af58beacd"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d62da299c6ecb04df729e4b5c52dc0d53f4f8430b4492b93aa8de1f541c4aac"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2acca2be4bb2f2147ada8cac612f8a98fc09f41c89f87add7256ad27332c2fda"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b662180108c55dfbf1280d865b2d116633d436cfc0bba82323554873967b340"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e7c6ed0dc9d8e65f24f5824291550139fe6f37fac03788d4580da0d33bc00c97"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6b1bb0827f56654b4437955555dc3aeeebeddc47c2d7ed575477f082622c49e"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e56f8186d6210ac7ece503193ec84104da7ceb98f68ce18c07282fcc2452e76f"}, + {file = "pydantic_core-2.16.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:936e5db01dd49476fa8f4383c259b8b1303d5dd5fb34c97de194560698cc2c5e"}, + {file = "pydantic_core-2.16.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:33809aebac276089b78db106ee692bdc9044710e26f24a9a2eaa35a0f9fa70ba"}, + {file = "pydantic_core-2.16.3-cp311-none-win32.whl", hash = "sha256:ded1c35f15c9dea16ead9bffcde9bb5c7c031bff076355dc58dcb1cb436c4721"}, + {file = "pydantic_core-2.16.3-cp311-none-win_amd64.whl", hash = "sha256:d89ca19cdd0dd5f31606a9329e309d4fcbb3df860960acec32630297d61820df"}, + {file = "pydantic_core-2.16.3-cp311-none-win_arm64.whl", hash = "sha256:6162f8d2dc27ba21027f261e4fa26f8bcb3cf9784b7f9499466a311ac284b5b9"}, + {file = "pydantic_core-2.16.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:0f56ae86b60ea987ae8bcd6654a887238fd53d1384f9b222ac457070b7ac4cff"}, + {file = "pydantic_core-2.16.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9bd22a2a639e26171068f8ebb5400ce2c1bc7d17959f60a3b753ae13c632975"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4204e773b4b408062960e65468d5346bdfe139247ee5f1ca2a378983e11388a2"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f651dd19363c632f4abe3480a7c87a9773be27cfe1341aef06e8759599454120"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aaf09e615a0bf98d406657e0008e4a8701b11481840be7d31755dc9f97c44053"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8e47755d8152c1ab5b55928ab422a76e2e7b22b5ed8e90a7d584268dd49e9c6b"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:500960cb3a0543a724a81ba859da816e8cf01b0e6aaeedf2c3775d12ee49cade"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cf6204fe865da605285c34cf1172879d0314ff267b1c35ff59de7154f35fdc2e"}, + {file = "pydantic_core-2.16.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d33dd21f572545649f90c38c227cc8631268ba25c460b5569abebdd0ec5974ca"}, + {file = "pydantic_core-2.16.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:49d5d58abd4b83fb8ce763be7794d09b2f50f10aa65c0f0c1696c677edeb7cbf"}, + {file = "pydantic_core-2.16.3-cp312-none-win32.whl", hash = "sha256:f53aace168a2a10582e570b7736cc5bef12cae9cf21775e3eafac597e8551fbe"}, + {file = "pydantic_core-2.16.3-cp312-none-win_amd64.whl", hash = "sha256:0d32576b1de5a30d9a97f300cc6a3f4694c428d956adbc7e6e2f9cad279e45ed"}, + {file = "pydantic_core-2.16.3-cp312-none-win_arm64.whl", hash = "sha256:ec08be75bb268473677edb83ba71e7e74b43c008e4a7b1907c6d57e940bf34b6"}, + {file = "pydantic_core-2.16.3-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:b1f6f5938d63c6139860f044e2538baeee6f0b251a1816e7adb6cbce106a1f01"}, + {file = "pydantic_core-2.16.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2a1ef6a36fdbf71538142ed604ad19b82f67b05749512e47f247a6ddd06afdc7"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:704d35ecc7e9c31d48926150afada60401c55efa3b46cd1ded5a01bdffaf1d48"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d937653a696465677ed583124b94a4b2d79f5e30b2c46115a68e482c6a591c8a"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9803edf8e29bd825f43481f19c37f50d2b01899448273b3a7758441b512acf8"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:72282ad4892a9fb2da25defeac8c2e84352c108705c972db82ab121d15f14e6d"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f752826b5b8361193df55afcdf8ca6a57d0232653494ba473630a83ba50d8c9"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4384a8f68ddb31a0b0c3deae88765f5868a1b9148939c3f4121233314ad5532c"}, + {file = "pydantic_core-2.16.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a4b2bf78342c40b3dc830880106f54328928ff03e357935ad26c7128bbd66ce8"}, + {file = "pydantic_core-2.16.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:13dcc4802961b5f843a9385fc821a0b0135e8c07fc3d9949fd49627c1a5e6ae5"}, + {file = "pydantic_core-2.16.3-cp38-none-win32.whl", hash = "sha256:e3e70c94a0c3841e6aa831edab1619ad5c511199be94d0c11ba75fe06efe107a"}, + {file = "pydantic_core-2.16.3-cp38-none-win_amd64.whl", hash = "sha256:ecdf6bf5f578615f2e985a5e1f6572e23aa632c4bd1dc67f8f406d445ac115ed"}, + {file = "pydantic_core-2.16.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:bda1ee3e08252b8d41fa5537413ffdddd58fa73107171a126d3b9ff001b9b820"}, + {file = "pydantic_core-2.16.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:21b888c973e4f26b7a96491c0965a8a312e13be108022ee510248fe379a5fa23"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be0ec334369316fa73448cc8c982c01e5d2a81c95969d58b8f6e272884df0074"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b5b6079cc452a7c53dd378c6f881ac528246b3ac9aae0f8eef98498a75657805"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ee8d5f878dccb6d499ba4d30d757111847b6849ae07acdd1205fffa1fc1253c"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7233d65d9d651242a68801159763d09e9ec96e8a158dbf118dc090cd77a104c9"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c6119dc90483a5cb50a1306adb8d52c66e447da88ea44f323e0ae1a5fcb14256"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:578114bc803a4c1ff9946d977c221e4376620a46cf78da267d946397dc9514a8"}, + {file = "pydantic_core-2.16.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d8f99b147ff3fcf6b3cc60cb0c39ea443884d5559a30b1481e92495f2310ff2b"}, + {file = "pydantic_core-2.16.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4ac6b4ce1e7283d715c4b729d8f9dab9627586dafce81d9eaa009dd7f25dd972"}, + {file = "pydantic_core-2.16.3-cp39-none-win32.whl", hash = "sha256:e7774b570e61cb998490c5235740d475413a1f6de823169b4cf94e2fe9e9f6b2"}, + {file = "pydantic_core-2.16.3-cp39-none-win_amd64.whl", hash = "sha256:9091632a25b8b87b9a605ec0e61f241c456e9248bfdcf7abdf344fdb169c81cf"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:36fa178aacbc277bc6b62a2c3da95226520da4f4e9e206fdf076484363895d2c"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:dcca5d2bf65c6fb591fff92da03f94cd4f315972f97c21975398bd4bd046854a"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a72fb9963cba4cd5793854fd12f4cfee731e86df140f59ff52a49b3552db241"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b60cc1a081f80a2105a59385b92d82278b15d80ebb3adb200542ae165cd7d183"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cbcc558401de90a746d02ef330c528f2e668c83350f045833543cd57ecead1ad"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:fee427241c2d9fb7192b658190f9f5fd6dfe41e02f3c1489d2ec1e6a5ab1e04a"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f4cb85f693044e0f71f394ff76c98ddc1bc0953e48c061725e540396d5c8a2e1"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b29eeb887aa931c2fcef5aa515d9d176d25006794610c264ddc114c053bf96fe"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a425479ee40ff021f8216c9d07a6a3b54b31c8267c6e17aa88b70d7ebd0e5e5b"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:5c5cbc703168d1b7a838668998308018a2718c2130595e8e190220238addc96f"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99b6add4c0b39a513d323d3b93bc173dac663c27b99860dd5bf491b240d26137"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75f76ee558751746d6a38f89d60b6228fa174e5172d143886af0f85aa306fd89"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:00ee1c97b5364b84cb0bd82e9bbf645d5e2871fb8c58059d158412fee2d33d8a"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:287073c66748f624be4cef893ef9174e3eb88fe0b8a78dc22e88eca4bc357ca6"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ed25e1835c00a332cb10c683cd39da96a719ab1dfc08427d476bce41b92531fc"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:86b3d0033580bd6bbe07590152007275bd7af95f98eaa5bd36f3da219dcd93da"}, + {file = "pydantic_core-2.16.3.tar.gz", hash = "sha256:1cac689f80a3abab2d3c0048b29eea5751114054f032a941a32de4c852c59cad"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" + [[package]] name = "pyparsing" version = "3.0.9" @@ -1515,6 +1696,17 @@ tomli = {version = "*", markers = "python_version < \"3.11\""} [package.extras] dev = ["furo", "packaging", "sphinx (>=5)", "twisted"] +[[package]] +name = "types-protobuf" +version = "4.25.0.20240417" +description = "Typing stubs for protobuf" +optional = false +python-versions = ">=3.8" +files = [ + {file = "types-protobuf-4.25.0.20240417.tar.gz", hash = "sha256:c34eff17b9b3a0adb6830622f0f302484e4c089f533a46e3f147568313544352"}, + {file = "types_protobuf-4.25.0.20240417-py3-none-any.whl", hash = "sha256:e9b613227c2127e3d4881d75d93c93b4d6fd97b5f6a099a0b654a05351c8685d"}, +] + [[package]] name = "typing-extensions" version = "4.9.0" @@ -1851,4 +2043,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = ">=3.8,<3.11" -content-hash = "0c854c620eb789aa7b5697a6430b0af5b2c5d9f29a2f045adfe71be21b35ed48" +content-hash = "2c8e39ac94a55fe5bc581cb40e0a6bd16bd3ba1982904d82ce331373faf0f58b" diff --git a/proto/action_webhook.proto b/proto/action_webhook.proto new file mode 100644 index 000000000..5d4e9da91 --- /dev/null +++ b/proto/action_webhook.proto @@ -0,0 +1,68 @@ +syntax = "proto3"; + +package action_server_webhook; +import "google/protobuf/struct.proto"; + +service ActionService { + rpc Webhook (WebhookRequest) returns (WebhookResponse); + rpc Actions (ActionsRequest) returns (ActionsResponse); +} + +message ActionsRequest {} + +message ActionsResponse { + map actions = 1; +} + +message Tracker { + string sender_id = 1; + google.protobuf.Struct slots = 2; + google.protobuf.Struct latest_message = 3; + repeated google.protobuf.Struct events = 4; + bool paused = 5; + optional string followup_action = 6; + map active_loop = 7; + optional string latest_action_name = 8; + repeated google.protobuf.Struct stack = 9; +} + +message Intent { + string string_value = 1; + google.protobuf.Struct dict_value = 2; +} + +message Entity { + string string_value = 1; + google.protobuf.Struct dict_value = 2; +} + +message Action { + string string_value = 1; + google.protobuf.Struct dict_value = 2; +} + +message Domain { + google.protobuf.Struct config = 1; + google.protobuf.Struct session_config = 2; + repeated Intent intents = 3; + repeated Entity entities = 4; + google.protobuf.Struct slots = 5; + google.protobuf.Struct responses = 6; + repeated Action actions = 7; + google.protobuf.Struct forms = 8; + repeated google.protobuf.Struct e2e_actions = 9; +} + +message WebhookRequest { + string next_action = 1; + string sender_id = 2; + Tracker tracker = 3; + Domain domain = 4; + string version = 5; + optional string domain_digest = 6; +} + +message WebhookResponse { + repeated google.protobuf.Struct events = 1; + repeated google.protobuf.Struct responses = 2; +} \ No newline at end of file diff --git a/proto/health.proto b/proto/health.proto new file mode 100644 index 000000000..d6d4f8a14 --- /dev/null +++ b/proto/health.proto @@ -0,0 +1,11 @@ +syntax = "proto3"; + +package grpc.health.v1; + +message HealthCheckRequest {} + +message HealthCheckResponse {} + +service HealthService { + rpc Check(HealthCheckRequest) returns (HealthCheckResponse); +} diff --git a/pyproject.toml b/pyproject.toml index c4c03b318..139296a36 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "poetry.core.masonry.api" [tool.black] line-length = 88 target-version = [ "py37", "py38", "py39", "py310",] -exclude = "((.eggs | .git | .mypy_cache | .pytest_cache | build | dist))" +exclude = "((.eggs | .git | .mypy_cache | .pytest_cache | build | dist ))" [tool.poetry] name = "rasa-sdk" @@ -67,11 +67,13 @@ ignore_missing_imports = true show_error_codes = true warn_redundant_casts = true warn_unused_ignores = true +exclude = "rasa_sdk/grpc_py" [tool.ruff] ignore = [ "D100", "D104", "D105", "RUF005",] line-length = 88 select = [ "D", "E", "F", "W", "RUF",] +exclude = [ "rasa_sdk/grpc_py" ] [tool.poetry.dependencies] python = ">=3.8,<3.11" @@ -87,6 +89,10 @@ opentelemetry-api = "~1.15.0" opentelemetry-sdk = "~1.15.0" opentelemetry-exporter-jaeger = "~1.15.0" opentelemetry-exporter-otlp = "~1.15.0" +grpcio = "1.59.3" +protobuf = "4.25.3" +grpcio-tools = "1.56.2" +pydantic = "2.6.4" [tool.poetry.dev-dependencies] pytest-cov = "^4.1.0" @@ -111,3 +117,4 @@ asyncio_mode = "auto" [tool.poetry.group.dev.dependencies] ruff = ">=0.0.256,<0.0.286" pytest-asyncio = "^0.21.0" +types-protobuf = "4.25.0.20240417" diff --git a/rasa_sdk/__main__.py b/rasa_sdk/__main__.py index 67b9fb497..7cbfd6a2b 100644 --- a/rasa_sdk/__main__.py +++ b/rasa_sdk/__main__.py @@ -1,8 +1,12 @@ import logging +import asyncio from rasa_sdk import utils from rasa_sdk.endpoint import create_argument_parser, run from rasa_sdk.constants import APPLICATION_ROOT_LOGGER_NAME +from rasa_sdk.grpc_server import run_grpc + +logger = logging.getLogger(__name__) def main_from_args(args): @@ -18,20 +22,33 @@ def main_from_args(args): ) utils.update_sanic_log_level() - run( - args.actions, - args.port, - args.cors, - args.ssl_certificate, - args.ssl_keyfile, - args.ssl_password, - args.auto_reload, - args.endpoints, - ) + if args.grpc: + asyncio.run( + run_grpc( + args.actions, + args.port, + args.ssl_certificate, + args.ssl_keyfile, + args.ssl_ca_file, + args.auto_reload, + args.endpoints, + ) + ) + else: + run( + args.actions, + args.port, + args.cors, + args.ssl_certificate, + args.ssl_keyfile, + args.ssl_password, + args.auto_reload, + args.endpoints, + ) def main(): - # Running as standalone python application + """Runs the action server as standalone application.""" arg_parser = create_argument_parser() cmdline_args = arg_parser.parse_args() diff --git a/rasa_sdk/cli/arguments.py b/rasa_sdk/cli/arguments.py index c520d72af..90e282e2c 100644 --- a/rasa_sdk/cli/arguments.py +++ b/rasa_sdk/cli/arguments.py @@ -3,17 +3,31 @@ from rasa_sdk.constants import DEFAULT_SERVER_PORT, DEFAULT_ENDPOINTS_PATH -def action_arg(action): - if "/" in action: +def action_arg(actions_module_path: str) -> str: + """Validate the action module path. + + Valid action module path is python module, so it should not contain a slash. + + Args: + actions_module_path: Path to the actions python module. + + Returns: + actions_module_path: If provided module path is valid. + + Raises: + argparse.ArgumentTypeError: If the module path is invalid. + """ + if "/" in actions_module_path: raise argparse.ArgumentTypeError( "Invalid actions format. Actions file should be a python module " "and passed with module notation (e.g. directory.actions)." ) else: - return action + return actions_module_path -def add_endpoint_arguments(parser): +def add_endpoint_arguments(parser: argparse.ArgumentParser) -> None: + """Add all the arguments to the argument parser.""" parser.add_argument( "-p", "--port", @@ -47,7 +61,15 @@ def add_endpoint_arguments(parser): "--ssl-password", default=None, help="If your ssl-keyfile is protected by a password, you can specify it " - "using this paramer.", + "using this parameter. " + "Not supported in grpc mode.", + ) + parser.add_argument( + "--ssl-ca-file", + default=None, + help="If you want to authenticate the client using a certificate, you can " + "specify the CA certificate of the client using this parameter. " + "Supported only in grpc mode.", ) parser.add_argument( "--auto-reload", @@ -59,3 +81,6 @@ def add_endpoint_arguments(parser): default=DEFAULT_ENDPOINTS_PATH, help="Configuration file for the assistant as a yml file.", ) + parser.add_argument( + "--grpc", help="Starts grpc server instead of http", action="store_true" + ) diff --git a/rasa_sdk/constants.py b/rasa_sdk/constants.py index 16637dfb1..4af04e6e9 100644 --- a/rasa_sdk/constants.py +++ b/rasa_sdk/constants.py @@ -11,3 +11,4 @@ "https://docs.python.org/3/library/logging.config.html#dictionary-schema-details" ) DEFAULT_ENDPOINTS_PATH = "endpoints.yml" +NO_GRACE_PERIOD = 0 diff --git a/rasa_sdk/endpoint.py b/rasa_sdk/endpoint.py index 5818026b1..0a0326042 100644 --- a/rasa_sdk/endpoint.py +++ b/rasa_sdk/endpoint.py @@ -172,7 +172,10 @@ async def actions(_) -> HTTPResponse: if auto_reload: executor.reload() - body = [{"name": k} for k in executor.actions.keys()] + body = [ + action_name_item.model_dump() + for action_name_item in executor.list_actions() + ] return response.json(body, status=200) @app.exception(Exception) diff --git a/rasa_sdk/executor.py b/rasa_sdk/executor.py index 802d84a7a..24d4705ec 100644 --- a/rasa_sdk/executor.py +++ b/rasa_sdk/executor.py @@ -1,3 +1,4 @@ +from __future__ import annotations import importlib import inspect import logging @@ -9,6 +10,8 @@ import sys import os +from pydantic import BaseModel, Field + from rasa_sdk.interfaces import ( Tracker, ActionNotFoundException, @@ -22,9 +25,10 @@ class CollectingDispatcher: - """Send messages back to user""" + """Send messages back to user.""" def __init__(self) -> None: + """Create a `CollectingDispatcher` object.""" self.messages: List[Dict[Text, Any]] = [] def utter_message( @@ -63,6 +67,15 @@ def utter_message( # deprecated def utter_custom_message(self, *elements: Dict[Text, Any], **kwargs: Any) -> None: + """Sends a message with custom elements to the output channel. + + Deprecated: + Use `utter_message(elements=)` instead. + + Args: + elements: List of elements to be sent to the output channel. + kwargs: Additional parameters to be sent to the output channel. + """ warnings.warn( "Use of `utter_custom_message` is deprecated. " "Use `utter_message(elements=)` instead.", @@ -158,7 +171,10 @@ def utter_image_url(self, image: Text, **kwargs: Any) -> None: class ActionExecutor: + """Executes actions.""" + def __init__(self) -> None: + """Initializes the `ActionExecutor`.""" self.actions: Dict[Text, Callable] = {} self._modules: Dict[Text, TimestampModule] = {} self._loaded: Set[Type[Action]] = set() @@ -166,6 +182,12 @@ def __init__(self) -> None: self.domain_digest: Optional[Text] = None def register_action(self, action: Union[Type[Action], Action]) -> None: + """Register an action with the executor. + + Args: + action: Action to be registered. It can either be an instance of + `Action` subclass class or an actual `Action` subclass. + """ if inspect.isclass(action): action = cast(Type[Action], action) if action.__module__.startswith("rasa."): @@ -189,7 +211,13 @@ def register_action(self, action: Union[Type[Action], Action]) -> None: "a function, use `register_function` instead." ) - def register_function(self, name: Text, f: Callable) -> None: + def register_function(self, action_name: Text, f: Callable) -> None: + """Register an executor function for an action. + + Args: + action_name: Name of the action. + f: Function to be registered. + """ valid_keys = utils.arguments_of(f) if len(valid_keys) < 3: raise Exception( @@ -200,12 +228,12 @@ def register_function(self, name: Text, f: Callable) -> None: "parameters." ) - if name in self.actions: - logger.info(f"Re-registered function for '{name}'.") + if action_name in self.actions: + logger.info(f"Re-registered function for '{action_name}'.") else: - logger.info(f"Registered function for '{name}'.") + logger.info(f"Registered function for '{action_name}'.") - self.actions[name] = f + self.actions[action_name] = f def _import_submodules( self, package: Union[Text, types.ModuleType], recursive: bool = True @@ -252,8 +280,7 @@ def _import_module(self, name: Text) -> types.ModuleType: return module def register_package(self, package: Union[Text, types.ModuleType]) -> None: - """Register all the `Action` subclasses contained in a Python module or - package. + """Register all the `Action` subclasses contained in a Python module or package. If an `ImportError` is raised when loading the module or package, the action server is stopped with exit code 1. @@ -286,14 +313,14 @@ def _register_all_actions(self) -> None: self.register_action(action) def _find_modules_to_reload(self) -> Dict[Text, TimestampModule]: - """Finds all Python modules that should be reloaded by checking their - files' timestamps. + """Finds all Python modules that should be reloaded. + + Reloads modules by checking their files' timestamps. Returns: Dictionary containing file paths, new timestamps and Python modules that should be reloaded. """ - to_reload = {} for path, (timestamp, module) in self._modules.items(): @@ -349,7 +376,20 @@ def _create_api_response( return {"events": events, "responses": messages} @staticmethod - def validate_events(events: List[Dict[Text, Any]], action_name: Text): + def validate_events( + events: List[Dict[Text, Any]], + action_name: Text, + ) -> List[Dict[Text, Any]]: + """Validate the events returned by the action. + + Args: + events: List of events returned by the action. + + action_name: Name of the action that should be executed. + + Returns: + List of validated events. + """ validated = [] for event in events: if isinstance(event, dict): @@ -383,10 +423,13 @@ def validate_events(events: List[Dict[Text, Any]], action_name: Text): return validated def is_domain_digest_valid(self, domain_digest: Optional[Text]) -> bool: - """Check if the domain_digest is valid + """Check if the domain_digest is valid. + If the domain_digest is empty or different from the one provided, it is invalid. + Args: domain_digest: latest value provided to compare the current value with. + Returns: True if the domain_digest is valid, False otherwise. """ @@ -396,15 +439,19 @@ def update_and_return_domain( self, payload: Dict[Text, Any], action_name: Text ) -> Optional[Dict[Text, Any]]: """Validate the digest, store the domain if available, and return the domain. + This method validates the domain digest from the payload. If the digest is invalid and no domain is provided, an exception is raised. If domain data is available, it stores the domain and digest. Finally, it returns the domain. + Args: payload: Request payload containing the domain data. action_name: Name of the action that should be executed. + Returns: The domain dictionary. + Raises: ActionMissingDomainException: Invalid digest and no domain data available. """ @@ -425,6 +472,15 @@ def update_and_return_domain( return self.domain async def run(self, action_call: Dict[Text, Any]) -> Optional[Dict[Text, Any]]: + """Run the action and return the response. + + Args: + action_call: Request payload containing the action data. + + Returns: + Response containing the events and messages or None if + the action does not exist. + """ from rasa_sdk.interfaces import Tracker action_name = action_call.get("next_action") @@ -453,3 +509,13 @@ async def run(self, action_call: Dict[Text, Any]) -> Optional[Dict[Text, Any]]: logger.warning("Received an action call without an action.") return None + + def list_actions(self) -> List[ActionName]: + """List all registered action names.""" + return [ActionName(name=action_name) for action_name in self.actions.keys()] + + +class ActionName(BaseModel): + """Model for action name.""" + + name: str = Field(alias="name") diff --git a/rasa_sdk/grpc_errors.py b/rasa_sdk/grpc_errors.py new file mode 100644 index 000000000..a9d0f0121 --- /dev/null +++ b/rasa_sdk/grpc_errors.py @@ -0,0 +1,37 @@ +from __future__ import annotations + +from pydantic import BaseModel, Field + +from enum import Enum + + +class ActionExecutionFailed(BaseModel): + """Error which indicates that an action execution failed. + + Attributes: + action_name: Name of the action that failed. + message: Message which describes the error. + """ + + action_name: str = Field(alias="action_name") + message: str = Field(alias="message") + + +class ResourceNotFoundType(str, Enum): + """Type of resource that was not found.""" + + ACTION = "ACTION" + DOMAIN = "DOMAIN" + + +class ResourceNotFound(BaseModel): + """Error which indicates that a resource was not found. + + Attributes: + action_name: Name of the action that was not found. + message: Message which describes the error. + """ + + action_name: str = Field(alias="action_name") + message: str = Field(alias="message") + resource_type: ResourceNotFoundType = Field(alias="resource_type") diff --git a/rasa_sdk/grpc_py/__init__.py b/rasa_sdk/grpc_py/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/rasa_sdk/grpc_py/action_webhook_pb2.py b/rasa_sdk/grpc_py/action_webhook_pb2.py new file mode 100644 index 000000000..26f550a65 --- /dev/null +++ b/rasa_sdk/grpc_py/action_webhook_pb2.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: rasa_sdk/grpc_py/action_webhook.proto +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from google.protobuf import struct_pb2 as google_dot_protobuf_dot_struct__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n%rasa_sdk/grpc_py/action_webhook.proto\x12\x15\x61\x63tion_server_webhook\x1a\x1cgoogle/protobuf/struct.proto\"\x10\n\x0e\x41\x63tionsRequest\"\x87\x01\n\x0f\x41\x63tionsResponse\x12\x44\n\x07\x61\x63tions\x18\x01 \x03(\x0b\x32\x33.action_server_webhook.ActionsResponse.ActionsEntry\x1a.\n\x0c\x41\x63tionsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xb8\x03\n\x07Tracker\x12\x11\n\tsender_id\x18\x01 \x01(\t\x12&\n\x05slots\x18\x02 \x01(\x0b\x32\x17.google.protobuf.Struct\x12/\n\x0elatest_message\x18\x03 \x01(\x0b\x32\x17.google.protobuf.Struct\x12\'\n\x06\x65vents\x18\x04 \x03(\x0b\x32\x17.google.protobuf.Struct\x12\x0e\n\x06paused\x18\x05 \x01(\x08\x12\x1c\n\x0f\x66ollowup_action\x18\x06 \x01(\tH\x00\x88\x01\x01\x12\x43\n\x0b\x61\x63tive_loop\x18\x07 \x03(\x0b\x32..action_server_webhook.Tracker.ActiveLoopEntry\x12\x1f\n\x12latest_action_name\x18\x08 \x01(\tH\x01\x88\x01\x01\x12&\n\x05stack\x18\t \x03(\x0b\x32\x17.google.protobuf.Struct\x1a\x31\n\x0f\x41\x63tiveLoopEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x42\x12\n\x10_followup_actionB\x15\n\x13_latest_action_name\"K\n\x06Intent\x12\x14\n\x0cstring_value\x18\x01 \x01(\t\x12+\n\ndict_value\x18\x02 \x01(\x0b\x32\x17.google.protobuf.Struct\"K\n\x06\x45ntity\x12\x14\n\x0cstring_value\x18\x01 \x01(\t\x12+\n\ndict_value\x18\x02 \x01(\x0b\x32\x17.google.protobuf.Struct\"K\n\x06\x41\x63tion\x12\x14\n\x0cstring_value\x18\x01 \x01(\t\x12+\n\ndict_value\x18\x02 \x01(\x0b\x32\x17.google.protobuf.Struct\"\x9d\x03\n\x06\x44omain\x12\'\n\x06\x63onfig\x18\x01 \x01(\x0b\x32\x17.google.protobuf.Struct\x12/\n\x0esession_config\x18\x02 \x01(\x0b\x32\x17.google.protobuf.Struct\x12.\n\x07intents\x18\x03 \x03(\x0b\x32\x1d.action_server_webhook.Intent\x12/\n\x08\x65ntities\x18\x04 \x03(\x0b\x32\x1d.action_server_webhook.Entity\x12&\n\x05slots\x18\x05 \x01(\x0b\x32\x17.google.protobuf.Struct\x12*\n\tresponses\x18\x06 \x01(\x0b\x32\x17.google.protobuf.Struct\x12.\n\x07\x61\x63tions\x18\x07 \x03(\x0b\x32\x1d.action_server_webhook.Action\x12&\n\x05\x66orms\x18\x08 \x01(\x0b\x32\x17.google.protobuf.Struct\x12,\n\x0b\x65\x32\x65_actions\x18\t \x03(\x0b\x32\x17.google.protobuf.Struct\"\xd7\x01\n\x0eWebhookRequest\x12\x13\n\x0bnext_action\x18\x01 \x01(\t\x12\x11\n\tsender_id\x18\x02 \x01(\t\x12/\n\x07tracker\x18\x03 \x01(\x0b\x32\x1e.action_server_webhook.Tracker\x12-\n\x06\x64omain\x18\x04 \x01(\x0b\x32\x1d.action_server_webhook.Domain\x12\x0f\n\x07version\x18\x05 \x01(\t\x12\x1a\n\rdomain_digest\x18\x06 \x01(\tH\x00\x88\x01\x01\x42\x10\n\x0e_domain_digest\"f\n\x0fWebhookResponse\x12\'\n\x06\x65vents\x18\x01 \x03(\x0b\x32\x17.google.protobuf.Struct\x12*\n\tresponses\x18\x02 \x03(\x0b\x32\x17.google.protobuf.Struct2\xc3\x01\n\rActionService\x12X\n\x07Webhook\x12%.action_server_webhook.WebhookRequest\x1a&.action_server_webhook.WebhookResponse\x12X\n\x07\x41\x63tions\x12%.action_server_webhook.ActionsRequest\x1a&.action_server_webhook.ActionsResponseb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'rasa_sdk.grpc_py.action_webhook_pb2', _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + + DESCRIPTOR._options = None + _ACTIONSRESPONSE_ACTIONSENTRY._options = None + _ACTIONSRESPONSE_ACTIONSENTRY._serialized_options = b'8\001' + _TRACKER_ACTIVELOOPENTRY._options = None + _TRACKER_ACTIVELOOPENTRY._serialized_options = b'8\001' + _globals['_ACTIONSREQUEST']._serialized_start=94 + _globals['_ACTIONSREQUEST']._serialized_end=110 + _globals['_ACTIONSRESPONSE']._serialized_start=113 + _globals['_ACTIONSRESPONSE']._serialized_end=248 + _globals['_ACTIONSRESPONSE_ACTIONSENTRY']._serialized_start=202 + _globals['_ACTIONSRESPONSE_ACTIONSENTRY']._serialized_end=248 + _globals['_TRACKER']._serialized_start=251 + _globals['_TRACKER']._serialized_end=691 + _globals['_TRACKER_ACTIVELOOPENTRY']._serialized_start=599 + _globals['_TRACKER_ACTIVELOOPENTRY']._serialized_end=648 + _globals['_INTENT']._serialized_start=693 + _globals['_INTENT']._serialized_end=768 + _globals['_ENTITY']._serialized_start=770 + _globals['_ENTITY']._serialized_end=845 + _globals['_ACTION']._serialized_start=847 + _globals['_ACTION']._serialized_end=922 + _globals['_DOMAIN']._serialized_start=925 + _globals['_DOMAIN']._serialized_end=1338 + _globals['_WEBHOOKREQUEST']._serialized_start=1341 + _globals['_WEBHOOKREQUEST']._serialized_end=1556 + _globals['_WEBHOOKRESPONSE']._serialized_start=1558 + _globals['_WEBHOOKRESPONSE']._serialized_end=1660 + _globals['_ACTIONSERVICE']._serialized_start=1663 + _globals['_ACTIONSERVICE']._serialized_end=1858 +# @@protoc_insertion_point(module_scope) diff --git a/rasa_sdk/grpc_py/action_webhook_pb2.pyi b/rasa_sdk/grpc_py/action_webhook_pb2.pyi new file mode 100644 index 000000000..c8c43431f --- /dev/null +++ b/rasa_sdk/grpc_py/action_webhook_pb2.pyi @@ -0,0 +1,123 @@ +from google.protobuf import struct_pb2 as _struct_pb2 +from google.protobuf.internal import containers as _containers +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from typing import ClassVar as _ClassVar, Iterable as _Iterable, Mapping as _Mapping, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class ActionsRequest(_message.Message): + __slots__ = [] + def __init__(self) -> None: ... + +class ActionsResponse(_message.Message): + __slots__ = ["actions"] + class ActionsEntry(_message.Message): + __slots__ = ["key", "value"] + KEY_FIELD_NUMBER: _ClassVar[int] + VALUE_FIELD_NUMBER: _ClassVar[int] + key: str + value: str + def __init__(self, key: _Optional[str] = ..., value: _Optional[str] = ...) -> None: ... + ACTIONS_FIELD_NUMBER: _ClassVar[int] + actions: _containers.ScalarMap[str, str] + def __init__(self, actions: _Optional[_Mapping[str, str]] = ...) -> None: ... + +class Tracker(_message.Message): + __slots__ = ["sender_id", "slots", "latest_message", "events", "paused", "followup_action", "active_loop", "latest_action_name", "stack"] + class ActiveLoopEntry(_message.Message): + __slots__ = ["key", "value"] + KEY_FIELD_NUMBER: _ClassVar[int] + VALUE_FIELD_NUMBER: _ClassVar[int] + key: str + value: str + def __init__(self, key: _Optional[str] = ..., value: _Optional[str] = ...) -> None: ... + SENDER_ID_FIELD_NUMBER: _ClassVar[int] + SLOTS_FIELD_NUMBER: _ClassVar[int] + LATEST_MESSAGE_FIELD_NUMBER: _ClassVar[int] + EVENTS_FIELD_NUMBER: _ClassVar[int] + PAUSED_FIELD_NUMBER: _ClassVar[int] + FOLLOWUP_ACTION_FIELD_NUMBER: _ClassVar[int] + ACTIVE_LOOP_FIELD_NUMBER: _ClassVar[int] + LATEST_ACTION_NAME_FIELD_NUMBER: _ClassVar[int] + STACK_FIELD_NUMBER: _ClassVar[int] + sender_id: str + slots: _struct_pb2.Struct + latest_message: _struct_pb2.Struct + events: _containers.RepeatedCompositeFieldContainer[_struct_pb2.Struct] + paused: bool + followup_action: str + active_loop: _containers.ScalarMap[str, str] + latest_action_name: str + stack: _containers.RepeatedCompositeFieldContainer[_struct_pb2.Struct] + def __init__(self, sender_id: _Optional[str] = ..., slots: _Optional[_Union[_struct_pb2.Struct, _Mapping]] = ..., latest_message: _Optional[_Union[_struct_pb2.Struct, _Mapping]] = ..., events: _Optional[_Iterable[_Union[_struct_pb2.Struct, _Mapping]]] = ..., paused: bool = ..., followup_action: _Optional[str] = ..., active_loop: _Optional[_Mapping[str, str]] = ..., latest_action_name: _Optional[str] = ..., stack: _Optional[_Iterable[_Union[_struct_pb2.Struct, _Mapping]]] = ...) -> None: ... + +class Intent(_message.Message): + __slots__ = ["string_value", "dict_value"] + STRING_VALUE_FIELD_NUMBER: _ClassVar[int] + DICT_VALUE_FIELD_NUMBER: _ClassVar[int] + string_value: str + dict_value: _struct_pb2.Struct + def __init__(self, string_value: _Optional[str] = ..., dict_value: _Optional[_Union[_struct_pb2.Struct, _Mapping]] = ...) -> None: ... + +class Entity(_message.Message): + __slots__ = ["string_value", "dict_value"] + STRING_VALUE_FIELD_NUMBER: _ClassVar[int] + DICT_VALUE_FIELD_NUMBER: _ClassVar[int] + string_value: str + dict_value: _struct_pb2.Struct + def __init__(self, string_value: _Optional[str] = ..., dict_value: _Optional[_Union[_struct_pb2.Struct, _Mapping]] = ...) -> None: ... + +class Action(_message.Message): + __slots__ = ["string_value", "dict_value"] + STRING_VALUE_FIELD_NUMBER: _ClassVar[int] + DICT_VALUE_FIELD_NUMBER: _ClassVar[int] + string_value: str + dict_value: _struct_pb2.Struct + def __init__(self, string_value: _Optional[str] = ..., dict_value: _Optional[_Union[_struct_pb2.Struct, _Mapping]] = ...) -> None: ... + +class Domain(_message.Message): + __slots__ = ["config", "session_config", "intents", "entities", "slots", "responses", "actions", "forms", "e2e_actions"] + CONFIG_FIELD_NUMBER: _ClassVar[int] + SESSION_CONFIG_FIELD_NUMBER: _ClassVar[int] + INTENTS_FIELD_NUMBER: _ClassVar[int] + ENTITIES_FIELD_NUMBER: _ClassVar[int] + SLOTS_FIELD_NUMBER: _ClassVar[int] + RESPONSES_FIELD_NUMBER: _ClassVar[int] + ACTIONS_FIELD_NUMBER: _ClassVar[int] + FORMS_FIELD_NUMBER: _ClassVar[int] + E2E_ACTIONS_FIELD_NUMBER: _ClassVar[int] + config: _struct_pb2.Struct + session_config: _struct_pb2.Struct + intents: _containers.RepeatedCompositeFieldContainer[Intent] + entities: _containers.RepeatedCompositeFieldContainer[Entity] + slots: _struct_pb2.Struct + responses: _struct_pb2.Struct + actions: _containers.RepeatedCompositeFieldContainer[Action] + forms: _struct_pb2.Struct + e2e_actions: _containers.RepeatedCompositeFieldContainer[_struct_pb2.Struct] + def __init__(self, config: _Optional[_Union[_struct_pb2.Struct, _Mapping]] = ..., session_config: _Optional[_Union[_struct_pb2.Struct, _Mapping]] = ..., intents: _Optional[_Iterable[_Union[Intent, _Mapping]]] = ..., entities: _Optional[_Iterable[_Union[Entity, _Mapping]]] = ..., slots: _Optional[_Union[_struct_pb2.Struct, _Mapping]] = ..., responses: _Optional[_Union[_struct_pb2.Struct, _Mapping]] = ..., actions: _Optional[_Iterable[_Union[Action, _Mapping]]] = ..., forms: _Optional[_Union[_struct_pb2.Struct, _Mapping]] = ..., e2e_actions: _Optional[_Iterable[_Union[_struct_pb2.Struct, _Mapping]]] = ...) -> None: ... + +class WebhookRequest(_message.Message): + __slots__ = ["next_action", "sender_id", "tracker", "domain", "version", "domain_digest"] + NEXT_ACTION_FIELD_NUMBER: _ClassVar[int] + SENDER_ID_FIELD_NUMBER: _ClassVar[int] + TRACKER_FIELD_NUMBER: _ClassVar[int] + DOMAIN_FIELD_NUMBER: _ClassVar[int] + VERSION_FIELD_NUMBER: _ClassVar[int] + DOMAIN_DIGEST_FIELD_NUMBER: _ClassVar[int] + next_action: str + sender_id: str + tracker: Tracker + domain: Domain + version: str + domain_digest: str + def __init__(self, next_action: _Optional[str] = ..., sender_id: _Optional[str] = ..., tracker: _Optional[_Union[Tracker, _Mapping]] = ..., domain: _Optional[_Union[Domain, _Mapping]] = ..., version: _Optional[str] = ..., domain_digest: _Optional[str] = ...) -> None: ... + +class WebhookResponse(_message.Message): + __slots__ = ["events", "responses"] + EVENTS_FIELD_NUMBER: _ClassVar[int] + RESPONSES_FIELD_NUMBER: _ClassVar[int] + events: _containers.RepeatedCompositeFieldContainer[_struct_pb2.Struct] + responses: _containers.RepeatedCompositeFieldContainer[_struct_pb2.Struct] + def __init__(self, events: _Optional[_Iterable[_Union[_struct_pb2.Struct, _Mapping]]] = ..., responses: _Optional[_Iterable[_Union[_struct_pb2.Struct, _Mapping]]] = ...) -> None: ... diff --git a/rasa_sdk/grpc_py/action_webhook_pb2_grpc.py b/rasa_sdk/grpc_py/action_webhook_pb2_grpc.py new file mode 100644 index 000000000..47a33414e --- /dev/null +++ b/rasa_sdk/grpc_py/action_webhook_pb2_grpc.py @@ -0,0 +1,99 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc + +from rasa_sdk.grpc_py import action_webhook_pb2 as rasa__sdk_dot_grpc__py_dot_action__webhook__pb2 + + +class ActionServiceStub(object): + """Missing associated documentation comment in .proto file.""" + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.Webhook = channel.unary_unary( + '/action_server_webhook.ActionService/Webhook', + request_serializer=rasa__sdk_dot_grpc__py_dot_action__webhook__pb2.WebhookRequest.SerializeToString, + response_deserializer=rasa__sdk_dot_grpc__py_dot_action__webhook__pb2.WebhookResponse.FromString, + ) + self.Actions = channel.unary_unary( + '/action_server_webhook.ActionService/Actions', + request_serializer=rasa__sdk_dot_grpc__py_dot_action__webhook__pb2.ActionsRequest.SerializeToString, + response_deserializer=rasa__sdk_dot_grpc__py_dot_action__webhook__pb2.ActionsResponse.FromString, + ) + + +class ActionServiceServicer(object): + """Missing associated documentation comment in .proto file.""" + + def Webhook(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Actions(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_ActionServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'Webhook': grpc.unary_unary_rpc_method_handler( + servicer.Webhook, + request_deserializer=rasa__sdk_dot_grpc__py_dot_action__webhook__pb2.WebhookRequest.FromString, + response_serializer=rasa__sdk_dot_grpc__py_dot_action__webhook__pb2.WebhookResponse.SerializeToString, + ), + 'Actions': grpc.unary_unary_rpc_method_handler( + servicer.Actions, + request_deserializer=rasa__sdk_dot_grpc__py_dot_action__webhook__pb2.ActionsRequest.FromString, + response_serializer=rasa__sdk_dot_grpc__py_dot_action__webhook__pb2.ActionsResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'action_server_webhook.ActionService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + + + # This class is part of an EXPERIMENTAL API. +class ActionService(object): + """Missing associated documentation comment in .proto file.""" + + @staticmethod + def Webhook(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/action_server_webhook.ActionService/Webhook', + rasa__sdk_dot_grpc__py_dot_action__webhook__pb2.WebhookRequest.SerializeToString, + rasa__sdk_dot_grpc__py_dot_action__webhook__pb2.WebhookResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def Actions(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/action_server_webhook.ActionService/Actions', + rasa__sdk_dot_grpc__py_dot_action__webhook__pb2.ActionsRequest.SerializeToString, + rasa__sdk_dot_grpc__py_dot_action__webhook__pb2.ActionsResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) diff --git a/rasa_sdk/grpc_py/health_pb2.py b/rasa_sdk/grpc_py/health_pb2.py new file mode 100644 index 000000000..8f58d31d7 --- /dev/null +++ b/rasa_sdk/grpc_py/health_pb2.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: rasa_sdk/grpc_py/health.proto +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1drasa_sdk/grpc_py/health.proto\x12\x0egrpc.health.v1\"\x14\n\x12HealthCheckRequest\"\x15\n\x13HealthCheckResponse2a\n\rHealthService\x12P\n\x05\x43heck\x12\".grpc.health.v1.HealthCheckRequest\x1a#.grpc.health.v1.HealthCheckResponseb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'rasa_sdk.grpc_py.health_pb2', _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + + DESCRIPTOR._options = None + _globals['_HEALTHCHECKREQUEST']._serialized_start=49 + _globals['_HEALTHCHECKREQUEST']._serialized_end=69 + _globals['_HEALTHCHECKRESPONSE']._serialized_start=71 + _globals['_HEALTHCHECKRESPONSE']._serialized_end=92 + _globals['_HEALTHSERVICE']._serialized_start=94 + _globals['_HEALTHSERVICE']._serialized_end=191 +# @@protoc_insertion_point(module_scope) diff --git a/rasa_sdk/grpc_py/health_pb2.pyi b/rasa_sdk/grpc_py/health_pb2.pyi new file mode 100644 index 000000000..a58962527 --- /dev/null +++ b/rasa_sdk/grpc_py/health_pb2.pyi @@ -0,0 +1,13 @@ +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from typing import ClassVar as _ClassVar + +DESCRIPTOR: _descriptor.FileDescriptor + +class HealthCheckRequest(_message.Message): + __slots__ = [] + def __init__(self) -> None: ... + +class HealthCheckResponse(_message.Message): + __slots__ = [] + def __init__(self) -> None: ... diff --git a/rasa_sdk/grpc_py/health_pb2_grpc.py b/rasa_sdk/grpc_py/health_pb2_grpc.py new file mode 100644 index 000000000..904f921bb --- /dev/null +++ b/rasa_sdk/grpc_py/health_pb2_grpc.py @@ -0,0 +1,66 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc + +from rasa_sdk.grpc_py import health_pb2 as rasa__sdk_dot_grpc__py_dot_health__pb2 + + +class HealthServiceStub(object): + """Missing associated documentation comment in .proto file.""" + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.Check = channel.unary_unary( + '/grpc.health.v1.HealthService/Check', + request_serializer=rasa__sdk_dot_grpc__py_dot_health__pb2.HealthCheckRequest.SerializeToString, + response_deserializer=rasa__sdk_dot_grpc__py_dot_health__pb2.HealthCheckResponse.FromString, + ) + + +class HealthServiceServicer(object): + """Missing associated documentation comment in .proto file.""" + + def Check(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_HealthServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'Check': grpc.unary_unary_rpc_method_handler( + servicer.Check, + request_deserializer=rasa__sdk_dot_grpc__py_dot_health__pb2.HealthCheckRequest.FromString, + response_serializer=rasa__sdk_dot_grpc__py_dot_health__pb2.HealthCheckResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'grpc.health.v1.HealthService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + + + # This class is part of an EXPERIMENTAL API. +class HealthService(object): + """Missing associated documentation comment in .proto file.""" + + @staticmethod + def Check(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/grpc.health.v1.HealthService/Check', + rasa__sdk_dot_grpc__py_dot_health__pb2.HealthCheckRequest.SerializeToString, + rasa__sdk_dot_grpc__py_dot_health__pb2.HealthCheckResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) diff --git a/rasa_sdk/grpc_server.py b/rasa_sdk/grpc_server.py new file mode 100644 index 000000000..f3f875d8a --- /dev/null +++ b/rasa_sdk/grpc_server.py @@ -0,0 +1,265 @@ +from __future__ import annotations + +import signal + +import asyncio + +import grpc +import logging +import types +from typing import Union, Optional +from concurrent import futures +from grpc import aio +from google.protobuf.json_format import MessageToDict, ParseDict +from opentelemetry import trace + +from rasa_sdk.constants import ( + DEFAULT_SERVER_PORT, + DEFAULT_ENDPOINTS_PATH, + NO_GRACE_PERIOD, +) +from rasa_sdk.executor import ActionExecutor +from rasa_sdk.grpc_errors import ( + ResourceNotFound, + ResourceNotFoundType, + ActionExecutionFailed, +) +from rasa_sdk.grpc_py import ( + action_webhook_pb2, + action_webhook_pb2_grpc, + health_pb2_grpc, +) +from rasa_sdk.grpc_py.action_webhook_pb2 import ( + ActionsResponse, + ActionsRequest, + WebhookRequest, +) +from rasa_sdk.grpc_py.health_pb2 import HealthCheckRequest, HealthCheckResponse +from rasa_sdk.interfaces import ( + ActionExecutionRejection, + ActionNotFoundException, + ActionMissingDomainException, +) +from rasa_sdk.tracing.utils import ( + get_tracer_provider, + TracerProvider, +) +from rasa_sdk.utils import ( + check_version_compatibility, + number_of_sanic_workers, + file_as_bytes, +) + +logger = logging.getLogger(__name__) + + +class GRPCActionServerHealthCheck(health_pb2_grpc.HealthServiceServicer): + """Runs health check RPC which is served through gRPC server.""" + + def __init__(self) -> None: + """Initializes the HealthServicer.""" + pass + + def Check(self, request: HealthCheckRequest, context) -> HealthCheckResponse: + """Handle RPC request for the health check. + + Args: + request: The health check request. + context: The context of the request. + + Returns: + gRPC response. + """ + response = HealthCheckResponse() + return response + + +class GRPCActionServerWebhook(action_webhook_pb2_grpc.ActionServiceServicer): + """Runs webhook RPC which is served through gRPC server.""" + + def __init__( + self, + executor: ActionExecutor, + auto_reload: bool = False, + tracer_provider: Optional[TracerProvider] = None, + ) -> None: + """Initializes the ActionServerWebhook. + + Args: + tracer_provider: The tracer provider. + auto_reload: Enable auto-reloading of modules containing Action subclasses. + executor: The action executor. + """ + self.tracer_provider = tracer_provider + self.auto_reload = auto_reload + self.executor = executor + + async def Actions(self, request: ActionsRequest, context) -> ActionsResponse: + """Handle RPC request for the actions. + + Args: + request: The actions request. + context: The context of the request. + + Returns: + gRPC response. + """ + if self.auto_reload: + self.executor.reload() + + actions = self.executor.list_actions() + response = ActionsResponse() + return ParseDict(actions, response) + + async def Webhook( + self, + request: WebhookRequest, + context: grpc.aio.ServicerContext, + ) -> action_webhook_pb2.WebhookResponse: + """Handle RPC request for the webhook. + + Args: + request: The webhook request. + context: The context of the request. + + Returns: + gRPC response. + """ + span_name = "GRPCActionServerWebhook.Webhook" + tracer = ( + self.tracer_provider.get_tracer(span_name) + if self.tracer_provider + else trace.get_tracer(span_name) + ) + + with tracer.start_as_current_span(span_name): + check_version_compatibility(request.version) + if self.auto_reload: + self.executor.reload() + try: + action_call = MessageToDict(request, preserving_proto_field_name=True) + result = await self.executor.run(action_call) + except ActionExecutionRejection as e: + logger.debug(e) + + body = ActionExecutionFailed( + action_name=e.action_name, message=e.message + ).model_dump_json() + context.set_code(grpc.StatusCode.INTERNAL) + context.set_details(body) + return action_webhook_pb2.WebhookResponse() + except ActionNotFoundException as e: + logger.error(e) + body = ResourceNotFound( + action_name=e.action_name, + message=e.message, + resource_type=ResourceNotFoundType.ACTION, + ).model_dump_json() + context.set_code(grpc.StatusCode.NOT_FOUND) + context.set_details(body) + return action_webhook_pb2.WebhookResponse() + except ActionMissingDomainException as e: + logger.error(e) + body = ResourceNotFound( + action_name=e.action_name, + message=e.message, + resource_type=ResourceNotFoundType.DOMAIN, + ).model_dump_json() + context.set_code(grpc.StatusCode.NOT_FOUND) + context.set_details(body) + return action_webhook_pb2.WebhookResponse() + if not result: + return action_webhook_pb2.WebhookResponse() + # set_span_attributes(span, request) + response = action_webhook_pb2.WebhookResponse() + + return ParseDict(result, response) + + +def get_signal_name(signal_number: int) -> str: + """Return the signal name for the given signal number.""" + return signal.Signals(signal_number).name + + +def initialise_interrupts(server: grpc.aio.Server) -> None: + """Initialise handlers for kernel signal interrupts.""" + + async def handle_sigint(signal_received: int): + """Handle the received signal.""" + logger.info( + f"Received {get_signal_name(signal_received)} signal." + "Stopping gRPC server..." + ) + await server.stop(NO_GRACE_PERIOD) + logger.info("gRPC server stopped.") + + loop = asyncio.get_event_loop() + loop.add_signal_handler( + signal.SIGINT, lambda: asyncio.create_task(handle_sigint(signal.SIGINT)) + ) + loop.add_signal_handler( + signal.SIGTERM, lambda: asyncio.create_task(handle_sigint(signal.SIGTERM)) + ) + + +async def run_grpc( + action_package_name: Union[str, types.ModuleType], + port: int = DEFAULT_SERVER_PORT, + ssl_certificate: Optional[str] = None, + ssl_keyfile: Optional[str] = None, + ssl_ca_file: Optional[str] = None, + auto_reload: bool = False, + endpoints: str = DEFAULT_ENDPOINTS_PATH, +): + """Start a gRPC server to handle incoming action requests. + + Args: + action_package_name: Name of the package which contains the custom actions. + port: Port to start the server on. + ssl_certificate: File path to the SSL certificate. + ssl_keyfile: File path to the SSL key file. + ssl_ca_file: File path to the SSL CA certificate file. + auto_reload: Enable auto-reloading of modules containing Action subclasses. + endpoints: Path to the endpoints file. + """ + workers = number_of_sanic_workers() + server = aio.server( + futures.ThreadPoolExecutor(max_workers=workers), + compression=grpc.Compression.Gzip, + ) + initialise_interrupts(server) + executor = ActionExecutor() + executor.register_package(action_package_name) + tracer_provider = get_tracer_provider(endpoints) + action_webhook_pb2_grpc.add_ActionServiceServicer_to_server( + GRPCActionServerWebhook(executor, auto_reload, tracer_provider), server + ) + + health_pb2_grpc.add_HealthServiceServicer_to_server( + GRPCActionServerHealthCheck(), server + ) + + ca_cert = file_as_bytes(ssl_ca_file) if ssl_ca_file else None + + if ssl_certificate and ssl_keyfile: + # Use SSL/TLS if certificate and key are provided + grpc.ssl_channel_credentials() + private_key = file_as_bytes(ssl_keyfile) + certificate_chain = file_as_bytes(ssl_certificate) + logger.info(f"Starting gRPC server with SSL support on port {port}") + server.add_secure_port( + f"[::]:{port}", + server_credentials=grpc.ssl_server_credentials( + private_key_certificate_chain_pairs=[(private_key, certificate_chain)], + root_certificates=ca_cert, + require_client_auth=True if ca_cert else False, + ), + ) + else: + logger.info(f"Starting gRPC server without SSL on port {port}") + # Use insecure connection if no SSL/TLS information is provided + server.add_insecure_port(f"[::]:{port}") + + await server.start() + logger.info(f"gRPC Server started on port {port}") + await server.wait_for_termination() diff --git a/rasa_sdk/interfaces.py b/rasa_sdk/interfaces.py index 454ad24b7..21791f416 100644 --- a/rasa_sdk/interfaces.py +++ b/rasa_sdk/interfaces.py @@ -23,7 +23,6 @@ class Tracker: @classmethod def from_dict(cls, state: "TrackerState") -> "Tracker": """Create a tracker from dump.""" - return Tracker( state["sender_id"], state.get("slots", {}), @@ -46,10 +45,9 @@ def __init__( followup_action: Optional[Text], active_loop: Dict[Text, Any], latest_action_name: Optional[Text], - stack: List[Dict[Text, Any]] = [], + stack: Optional[List[Dict[Text, Any]]] = None, ) -> None: """Initialize the tracker.""" - # list of previously seen events self.events = events # id of the source of the messages @@ -68,10 +66,11 @@ def __init__( self.latest_message = latest_message if latest_message else {} self.active_loop = active_loop self.latest_action_name = latest_action_name - self.stack = stack + self.stack = stack if stack else [] @property def active_form(self) -> Dict[Text, Any]: + """Get the currently active form.""" warnings.warn( "Use of `active_form` is deprecated. Please use `active_loop insteaad.", DeprecationWarning, @@ -80,7 +79,6 @@ def active_form(self) -> Dict[Text, Any]: def current_state(self) -> Dict[Text, Any]: """Return the current tracker state as an object.""" - if len(self.events) > 0: latest_event_time = self.events[-1].get("timestamp") else: @@ -100,12 +98,11 @@ def current_state(self) -> Dict[Text, Any]: } def current_slot_values(self) -> Dict[Text, Any]: - """Return the currently set values of the slots""" + """Return the currently set values of the slots.""" return self.slots def get_slot(self, key) -> Optional[Any]: """Retrieves the value of a slot.""" - if key in self.slots: return self.slots[key] else: @@ -133,7 +130,6 @@ def get_latest_entity_values( Returns: List of entity values. """ - entities = self.latest_message.get("entities", []) return ( x.get("value") @@ -144,8 +140,7 @@ def get_latest_entity_values( ) def get_latest_input_channel(self) -> Optional[Text]: - """Get the name of the input_channel of the latest UserUttered event""" - + """Get the name of the input_channel of the latest UserUttered event.""" for e in reversed(self.events): if e.get("event") == "user": return e.get("input_channel") @@ -229,7 +224,8 @@ def applied_events(self) -> List[Dict[Text, Any]]: def undo_till_previous(event_type: Text, done_events: List[Dict[Text, Any]]): """Removes events from `done_events` until the first - occurrence `event_type` is found which is also removed.""" + occurrence `event_type` is found which is also removed. + """ # list gets modified - hence we need to copy events! for e in reversed(done_events[:]): del done_events[-1] @@ -262,7 +258,6 @@ def slots_to_validate(self) -> Dict[Text, Any]: Returns: A mapping of extracted slot candidates and their values. """ - slots: Dict[Text, Any] = {} count: int = 0 @@ -331,7 +326,6 @@ class Action: def name(self) -> Text: """Unique identifier of this simple action.""" - raise NotImplementedError("An action must implement a name") async def run( @@ -356,7 +350,6 @@ async def run( A dictionary of `rasa_sdk.events.Event` instances that is returned through the endpoint """ - raise NotImplementedError("An action must implement its run method") def __str__(self) -> Text: @@ -365,7 +358,9 @@ def __str__(self) -> Text: class ActionExecutionRejection(Exception): """Raising this exception will allow other policies - to predict another action""" + to predict another action + . + """ def __init__(self, action_name: Text, message: Optional[Text] = None) -> None: self.action_name = action_name diff --git a/rasa_sdk/tracing/utils.py b/rasa_sdk/tracing/utils.py index 32b759b24..18c848439 100644 --- a/rasa_sdk/tracing/utils.py +++ b/rasa_sdk/tracing/utils.py @@ -5,7 +5,7 @@ from opentelemetry.sdk.trace import TracerProvider from sanic.request import Request -from typing import Optional, Tuple, Any, Text +from typing import Optional, Tuple, Any, Text, Union def get_tracer_provider(endpoints_file: str) -> Optional[TracerProvider]: @@ -17,10 +17,11 @@ def get_tracer_provider(endpoints_file: str) -> Optional[TracerProvider]: def get_tracer_and_context( - tracer_provider: Optional[TracerProvider], request: Request + tracer_provider: Optional[TracerProvider], request: Union[Request] ) -> Tuple[Any, Any, Text]: """Gets tracer and context.""" span_name = "create_app.webhook" + if tracer_provider is None: tracer = trace.get_tracer(span_name) context = None diff --git a/rasa_sdk/utils.py b/rasa_sdk/utils.py index a9fb35cc9..b1404b48e 100644 --- a/rasa_sdk/utils.py +++ b/rasa_sdk/utils.py @@ -32,9 +32,12 @@ class Element(dict): + """Represents an element in a list of elements in a rich message.""" + __acceptable_keys = ["title", "item_url", "image_url", "subtitle", "buttons"] def __init__(self, *args, **kwargs): + """Initializes an element in a list of elements in a rich message.""" kwargs = { key: value for key, value in kwargs.items() if key in self.__acceptable_keys } @@ -43,6 +46,8 @@ def __init__(self, *args, **kwargs): class Button(dict): + """Represents a button in a rich message.""" + pass @@ -257,12 +262,12 @@ def check_version_compatibility(rasa_version: Optional[Text]) -> None: rasa and rasa_sdk. Args: - rasa_version - A string containing the version of rasa that - is making the call to the action server. + rasa_version: A string containing the version of rasa that + is making the call to the action server. Raises: - Warning - The version of rasa version unknown or not compatible with - this version of rasa_sdk. + Warning: The version of rasa version unknown or not compatible with + this version of rasa_sdk. """ # Check for versions of Rasa that are too old to report their version number if rasa_version is None: @@ -386,3 +391,24 @@ def read_yaml_file(filename: Union[Text, Path]) -> Dict[Text, Any]: return read_yaml(read_file(filename, DEFAULT_ENCODING)) except (YAMLError, DuplicateKeyError) as e: raise YamlSyntaxException(filename, e) + + +def file_as_bytes(file_path: Text) -> bytes: + """Read in a file as a byte array. + + Args: + file_path: Path to the file to read. + + Returns: + The file content as a byte array. + + Raises: + FileNotFoundException: If the file does not exist. + """ + try: + with open(file_path, "rb") as f: + return f.read() + except FileNotFoundError: + raise FileNotFoundException( + f"Failed to read file, " f"'{os.path.abspath(file_path)}' does not exist." + )