diff --git a/poetry.lock b/poetry.lock index 3321e721..53bad6b7 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.0.1 and should not be changed by hand. [[package]] name = "aiorun" @@ -6,6 +6,7 @@ version = "2023.7.2" description = "Boilerplate for asyncio applications" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "aiorun-2023.7.2-py3-none-any.whl", hash = "sha256:61f839265d86d3f3b37921eb4a99ef42fa2ee026241b67c54315423d7dac29c9"}, {file = "aiorun-2023.7.2.tar.gz", hash = "sha256:95e689dc1b263aaf4556ef8fa73ccae68f38ba0f1c1017fe196ef7da244031f9"}, @@ -20,6 +21,7 @@ version = "23.12.1" description = "The uncompromising code formatter." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "black-23.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0aaf6041986767a5e0ce663c7a2f0e9eaf21e6ff87a5f95cbf3675bfd4c41d2"}, {file = "black-23.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c88b3711d12905b74206227109272673edce0cb29f27e1385f33b0163c414bba"}, @@ -66,6 +68,7 @@ version = "5.5.1" description = "Extensible memoizing collections and decorators" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "cachetools-5.5.1-py3-none-any.whl", hash = "sha256:b76651fdc3b24ead3c648bbdeeb940c1b04d365b38b4af66788f9ec4a81d42bb"}, {file = "cachetools-5.5.1.tar.gz", hash = "sha256:70f238fbba50383ef62e55c6aff6d9673175fe59f7c6782c7a0b9e38f4a9df95"}, @@ -77,6 +80,7 @@ version = "2024.12.14" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56"}, {file = "certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db"}, @@ -88,6 +92,7 @@ version = "3.4.0" description = "Validate configuration and produce human readable error messages." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, @@ -99,6 +104,7 @@ version = "3.4.1" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"}, {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"}, @@ -200,6 +206,7 @@ version = "8.1.8" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, @@ -214,6 +221,8 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["dev"] +markers = "sys_platform == \"win32\" or platform_system == \"Windows\"" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, @@ -225,6 +234,7 @@ version = "7.6.10" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "coverage-7.6.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5c912978f7fbf47ef99cec50c4401340436d200d41d714c7a4766f377c5b7b78"}, {file = "coverage-7.6.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a01ec4af7dfeb96ff0078ad9a48810bb0cc8abcb0115180c6013a6b26237626c"}, @@ -302,6 +312,7 @@ version = "0.3.9" description = "Distribution utilities" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87"}, {file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"}, @@ -313,6 +324,8 @@ version = "1.2.2" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" +groups = ["dev"] +markers = "python_version < \"3.11\"" files = [ {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, @@ -327,6 +340,7 @@ version = "3.17.0" description = "A platform independent file lock." optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "filelock-3.17.0-py3-none-any.whl", hash = "sha256:533dc2f7ba78dc2f0f531fc6c4940addf7b70a481e269a5a3b93be94ffbe8338"}, {file = "filelock-3.17.0.tar.gz", hash = "sha256:ee4e77401ef576ebb38cd7f13b9b28893194acc20a8e68e18730ba9c0e54660e"}, @@ -343,6 +357,7 @@ version = "2.24.1" description = "Google API client core library" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "google_api_core-2.24.1-py3-none-any.whl", hash = "sha256:bc78d608f5a5bf853b80bd70a795f703294de656c096c0968320830a4bc280f1"}, {file = "google_api_core-2.24.1.tar.gz", hash = "sha256:f8b36f5456ab0dd99a1b693a40a31d1e7757beea380ad1b38faaf8941eae9d8a"}, @@ -367,6 +382,7 @@ version = "2.38.0" description = "Google Authentication Library" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "google_auth-2.38.0-py2.py3-none-any.whl", hash = "sha256:e7dae6694313f434a2727bf2906f27ad259bae090d7aa896590d86feec3d9d4a"}, {file = "google_auth-2.38.0.tar.gz", hash = "sha256:8285113607d3b80a3f1543b75962447ba8a09fe85783432a784fdeef6ac094c4"}, @@ -391,6 +407,7 @@ version = "0.34.0" description = "API Client library for Google Cloud" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "google-cloud-0.34.0.tar.gz", hash = "sha256:01430187cf56df10a9ba775dd547393185d4b40741db0ea5889301f8e7a9d5d3"}, {file = "google_cloud-0.34.0-py2.py3-none-any.whl", hash = "sha256:fb1ab7b0548fe44b3d538041f0a374505b7f990d448a935ea36649c5ccab5acf"}, @@ -402,6 +419,7 @@ version = "1.66.0" description = "Common protobufs used in Google APIs" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "googleapis_common_protos-1.66.0-py2.py3-none-any.whl", hash = "sha256:d7abcd75fabb2e0ec9f74466401f6c119a0b498e27370e9be4c94cb7e382b8ed"}, {file = "googleapis_common_protos-1.66.0.tar.gz", hash = "sha256:c3e7b33d15fdca5374cc0a7346dd92ffa847425cc4ea941d970f13680052ec8c"}, @@ -419,6 +437,7 @@ version = "1.70.0" description = "HTTP/2-based RPC framework" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "grpcio-1.70.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:95469d1977429f45fe7df441f586521361e235982a0b39e33841549143ae2851"}, {file = "grpcio-1.70.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:ed9718f17fbdb472e33b869c77a16d0b55e166b100ec57b016dc7de9c8d236bf"}, @@ -480,12 +499,30 @@ files = [ [package.extras] protobuf = ["grpcio-tools (>=1.70.0)"] +[[package]] +name = "grpcio-status" +version = "1.62.3" +description = "Status proto mapping for gRPC" +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "grpcio-status-1.62.3.tar.gz", hash = "sha256:289bdd7b2459794a12cf95dc0cb727bd4a1742c37bd823f760236c937e53a485"}, + {file = "grpcio_status-1.62.3-py3-none-any.whl", hash = "sha256:f9049b762ba8de6b1086789d8315846e094edac2c50beaf462338b301a8fd4b8"}, +] + +[package.dependencies] +googleapis-common-protos = ">=1.5.5" +grpcio = ">=1.62.3" +protobuf = ">=4.21.6" + [[package]] name = "grpcio-testing" version = "1.62.3" description = "Testing utilities for gRPC Python" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "grpcio-testing-1.62.3.tar.gz", hash = "sha256:f63577f28aaa95ea525124a0fd63c3429d71f769f4179b13f5e6cbc54979bfab"}, {file = "grpcio_testing-1.62.3-py3-none-any.whl", hash = "sha256:06a4d7eb30d22f91368aa7f48bfc33563da13b9d951314455ca8c9c987fb75bb"}, @@ -501,6 +538,7 @@ version = "1.62.3" description = "Protobuf code generator for gRPC" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "grpcio-tools-1.62.3.tar.gz", hash = "sha256:7c7136015c3d62c3eef493efabaf9e3380e3e66d24ee8e94c01cb71377f57833"}, {file = "grpcio_tools-1.62.3-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:2f968b049c2849540751ec2100ab05e8086c24bead769ca734fdab58698408c1"}, @@ -563,6 +601,7 @@ version = "2.6.6" description = "File identification library for Python" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "identify-2.6.6-py2.py3-none-any.whl", hash = "sha256:cbd1810bce79f8b671ecb20f53ee0ae8e86ae84b557de31d89709dc2a48ba881"}, {file = "identify-2.6.6.tar.gz", hash = "sha256:7bec12768ed44ea4761efb47806f0a41f86e7c0a5fdf5950d4648c90eca7e251"}, @@ -577,6 +616,7 @@ version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, @@ -591,6 +631,7 @@ version = "2.0.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, @@ -602,6 +643,7 @@ version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." optional = false python-versions = ">=3.5" +groups = ["dev"] files = [ {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, @@ -613,6 +655,7 @@ version = "1.9.1" description = "Node.js virtual environment builder" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["dev"] files = [ {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, @@ -624,6 +667,7 @@ version = "24.2" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, @@ -635,6 +679,7 @@ version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, @@ -646,6 +691,7 @@ version = "4.3.6" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, @@ -662,6 +708,7 @@ version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, @@ -677,6 +724,7 @@ version = "3.8.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "pre_commit-3.8.0-py2.py3-none-any.whl", hash = "sha256:9a90a53bf82fdd8778d58085faf8d83df56e40dfe18f45b19446e26bf1b3a63f"}, {file = "pre_commit-3.8.0.tar.gz", hash = "sha256:8bb6494d4a20423842e198980c9ecf9f96607a07ea29549e180eef9ae80fe7af"}, @@ -695,6 +743,7 @@ version = "1.26.0" description = "Beautiful, Pythonic protocol buffers" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "proto_plus-1.26.0-py3-none-any.whl", hash = "sha256:bf2dfaa3da281fc3187d12d224c707cb57214fb2c22ba854eb0c105a3fb2d4d7"}, {file = "proto_plus-1.26.0.tar.gz", hash = "sha256:6e93d5f5ca267b54300880fff156b6a3386b3fa3f43b1da62e680fc0c586ef22"}, @@ -712,6 +761,7 @@ version = "4.25.6" description = "" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "protobuf-4.25.6-cp310-abi3-win32.whl", hash = "sha256:61df6b5786e2b49fc0055f636c1e8f0aff263808bb724b95b164685ac1bcc13a"}, {file = "protobuf-4.25.6-cp310-abi3-win_amd64.whl", hash = "sha256:b8f837bfb77513fe0e2f263250f423217a173b6d85135be4d81e96a4653bcd3c"}, @@ -732,6 +782,7 @@ version = "6.1.1" description = "Cross-platform lib for process and system monitoring in Python." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +groups = ["main"] files = [ {file = "psutil-6.1.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:9ccc4316f24409159897799b83004cb1e24f9819b0dcf9c0b68bdcb6cefee6a8"}, {file = "psutil-6.1.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ca9609c77ea3b8481ab005da74ed894035936223422dc591d6772b147421f777"}, @@ -762,6 +813,7 @@ version = "0.6.1" description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629"}, {file = "pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034"}, @@ -773,6 +825,7 @@ version = "0.4.1" description = "A collection of ASN.1-based protocols modules" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "pyasn1_modules-0.4.1-py3-none-any.whl", hash = "sha256:49bfa96b45a292b711e986f222502c1c9a5e1f4e568fc30e2574a6c7d07838fd"}, {file = "pyasn1_modules-0.4.1.tar.gz", hash = "sha256:c28e2dbf9c06ad61c71a075c7e0f9fd0f1b0bb2d2ad4377f240d33ac2ab60a7c"}, @@ -787,6 +840,7 @@ version = "7.4.4" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, @@ -809,6 +863,7 @@ version = "3.0.0" description = "Pytest plugin for measuring coverage." optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"}, {file = "pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"}, @@ -827,6 +882,7 @@ version = "6.0.2" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, @@ -889,6 +945,7 @@ version = "2.32.3" description = "Python HTTP for Humans." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, @@ -910,6 +967,7 @@ version = "4.9" description = "Pure-Python RSA implementation" optional = false python-versions = ">=3.6,<4" +groups = ["main"] files = [ {file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"}, {file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"}, @@ -924,6 +982,7 @@ version = "0.0.264" description = "An extremely fast Python linter, written in Rust." optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "ruff-0.0.264-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:ec2fa192c035b8b68cc2b91049c561cd69543e2b8c4d157d9aa7727320bedcca"}, {file = "ruff-0.0.264-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:d97ba8db0fb601ffe9ee996ebb97c698e427a2fd4514fefbe7b803111354f783"}, @@ -950,6 +1009,7 @@ version = "75.8.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "setuptools-75.8.0-py3-none-any.whl", hash = "sha256:e3982f444617239225d675215d51f6ba05f845d4eec313da4418fdbb56fb27e3"}, {file = "setuptools-75.8.0.tar.gz", hash = "sha256:c5afc8f407c626b8313a86e10311dd3f661c6cd9c09d4bf8c15c0e11f9f2b0e6"}, @@ -970,6 +1030,8 @@ version = "2.2.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" +groups = ["dev"] +markers = "python_full_version <= \"3.11.0a6\"" files = [ {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, @@ -1011,6 +1073,8 @@ version = "4.12.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" +groups = ["dev"] +markers = "python_version < \"3.11\"" files = [ {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, @@ -1022,6 +1086,7 @@ version = "2.3.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df"}, {file = "urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d"}, @@ -1039,6 +1104,7 @@ version = "0.19.0" description = "Fast implementation of asyncio event loop on top of libuv" optional = false python-versions = ">=3.8.0" +groups = ["main"] files = [ {file = "uvloop-0.19.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:de4313d7f575474c8f5a12e163f6d89c0a878bc49219641d49e6f1444369a90e"}, {file = "uvloop-0.19.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5588bd21cf1fcf06bded085f37e43ce0e00424197e7c10e77afd4bbefffef428"}, @@ -1083,6 +1149,7 @@ version = "20.29.1" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "virtualenv-20.29.1-py3-none-any.whl", hash = "sha256:4e4cb403c0b0da39e13b46b1b2476e505cb0046b25f242bee80f62bf990b2779"}, {file = "virtualenv-20.29.1.tar.gz", hash = "sha256:b8b8970138d32fb606192cb97f6cd4bb644fa486be9308fb9b63f81091b5dc35"}, @@ -1098,6 +1165,6 @@ docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "s test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] [metadata] -lock-version = "2.0" +lock-version = "2.1" python-versions = ">=3.9, <3.13" -content-hash = "9809652c1eca9c6948b7cce5dfb2d4b25f448994419a507c41aa15b9a1adf659" +content-hash = "afdf2080de75a2057f967d51fb0e96189ea51ce3a2970f3ee623701c6d4a70d7" diff --git a/pynumaflow/_constants.py b/pynumaflow/_constants.py index 174870ce..291d88b2 100644 --- a/pynumaflow/_constants.py +++ b/pynumaflow/_constants.py @@ -6,6 +6,16 @@ SIDE_INPUT_DIR_PATH = "/var/numaflow/side-inputs" +# UDF execution error prefixes +ERR_SOURCE_EXCEPTION = "UDF_EXECUTION_ERROR(source)" +ERR_TRANSFORMER_EXCEPTION = "UDF_EXECUTION_ERROR(transformer)" +ERR_SINK_EXCEPTION = "UDF_EXECUTION_ERROR(sink)" +ERR_MAP_STREAM_EXCEPTION = "UDF_EXECUTION_ERROR(mapstream)" +ERR_MAP_EXCEPTION = "UDF_EXECUTION_ERROR(map)" +ERR_BATCH_MAP_EXCEPTION = "UDF_EXECUTION_ERROR(batchmap)" +ERR_REDUCE_EXCEPTION = "UDF_EXECUTION_ERROR(reduce)" +ERR_SIDE_INPUT_RETRIEVAL_EXCEPTION = "UDF_EXECUTION_ERROR(sideinput)" + # Socket configs MAP_SOCK_PATH = "/var/run/numaflow/map.sock" MAP_STREAM_SOCK_PATH = "/var/run/numaflow/mapstream.sock" diff --git a/pynumaflow/batchmapper/servicer/async_servicer.py b/pynumaflow/batchmapper/servicer/async_servicer.py index d9220f5b..91b1c9f0 100644 --- a/pynumaflow/batchmapper/servicer/async_servicer.py +++ b/pynumaflow/batchmapper/servicer/async_servicer.py @@ -1,16 +1,15 @@ import asyncio from collections.abc import AsyncIterable -import grpc from google.protobuf import empty_pb2 as _empty_pb2 from pynumaflow.batchmapper import Datum from pynumaflow.batchmapper._dtypes import BatchMapCallable, BatchMapError from pynumaflow.proto.mapper import map_pb2, map_pb2_grpc from pynumaflow.shared.asynciter import NonBlockingIterator -from pynumaflow.shared.server import exit_on_error +from pynumaflow.shared.server import handle_async_error from pynumaflow.types import NumaflowServicerContext -from pynumaflow._constants import _LOGGER, STREAM_EOF +from pynumaflow._constants import _LOGGER, STREAM_EOF, ERR_BATCH_MAP_EXCEPTION class AsyncBatchMapServicer(map_pb2_grpc.MapServicer): @@ -99,10 +98,7 @@ async def MapFn( except BaseException as err: _LOGGER.critical("UDFError, re-raising the error", exc_info=True) - await asyncio.gather( - context.abort(grpc.StatusCode.UNKNOWN, details=repr(err)), return_exceptions=True - ) - exit_on_error(context, repr(err)) + await handle_async_error(context, err, ERR_BATCH_MAP_EXCEPTION) return async def IsReady( diff --git a/pynumaflow/mapper/_servicer/_async_servicer.py b/pynumaflow/mapper/_servicer/_async_servicer.py index fa90a7c5..843822fd 100644 --- a/pynumaflow/mapper/_servicer/_async_servicer.py +++ b/pynumaflow/mapper/_servicer/_async_servicer.py @@ -4,10 +4,10 @@ from google.protobuf import empty_pb2 as _empty_pb2 from pynumaflow.shared.asynciter import NonBlockingIterator -from pynumaflow._constants import _LOGGER, STREAM_EOF +from pynumaflow._constants import _LOGGER, STREAM_EOF, ERR_MAP_EXCEPTION from pynumaflow.mapper._dtypes import MapAsyncCallable, Datum, MapError from pynumaflow.proto.mapper import map_pb2, map_pb2_grpc -from pynumaflow.shared.server import exit_on_error, handle_async_error +from pynumaflow.shared.server import handle_async_error from pynumaflow.types import NumaflowServicerContext @@ -56,7 +56,7 @@ async def MapFn( async for msg in consumer: # If the message is an exception, we raise the exception if isinstance(msg, BaseException): - await handle_async_error(context, msg) + await handle_async_error(context, msg, ERR_MAP_EXCEPTION) return # Send window response back to the client else: @@ -65,7 +65,7 @@ async def MapFn( await producer except BaseException as e: _LOGGER.critical("UDFError, re-raising the error", exc_info=True) - exit_on_error(context, repr(e)) + await handle_async_error(context, e, ERR_MAP_EXCEPTION) return async def _process_inputs( @@ -92,9 +92,8 @@ async def _process_inputs( # send an EOF to result queue to indicate that all tasks have completed await result_queue.put(STREAM_EOF) - except BaseException as e: - await result_queue.put(e) - return + except BaseException: + _LOGGER.critical("MapFn Error, re-raising the error", exc_info=True) async def _invoke_map(self, req: map_pb2.MapRequest, result_queue: NonBlockingIterator): """ @@ -116,7 +115,7 @@ async def _invoke_map(self, req: map_pb2.MapRequest, result_queue: NonBlockingIt ) await result_queue.put(map_pb2.MapResponse(results=datums, id=req.id)) except BaseException as err: - _LOGGER.critical("UDFError, re-raising the error", exc_info=True) + _LOGGER.critical("MapFn handler error", exc_info=True) await result_queue.put(err) async def IsReady( diff --git a/pynumaflow/mapper/_servicer/_sync_servicer.py b/pynumaflow/mapper/_servicer/_sync_servicer.py index f3b07d32..6e8539b5 100644 --- a/pynumaflow/mapper/_servicer/_sync_servicer.py +++ b/pynumaflow/mapper/_servicer/_sync_servicer.py @@ -5,7 +5,7 @@ from google.protobuf import empty_pb2 as _empty_pb2 from pynumaflow.shared.server import exit_on_error -from pynumaflow._constants import NUM_THREADS_DEFAULT, STREAM_EOF, _LOGGER +from pynumaflow._constants import NUM_THREADS_DEFAULT, STREAM_EOF, _LOGGER, ERR_MAP_EXCEPTION from pynumaflow.mapper._dtypes import MapSyncCallable, Datum, MapError from pynumaflow.proto.mapper import map_pb2, map_pb2_grpc from pynumaflow.shared.synciter import SyncIterator @@ -57,7 +57,9 @@ def MapFn( # if error handler accordingly if isinstance(res, BaseException): # Terminate the current server process due to exception - exit_on_error(context, repr(res), parent=self.multiproc) + exit_on_error( + context, f"{ERR_MAP_EXCEPTION}: {repr(res)}", parent=self.multiproc + ) return # return the result yield res @@ -69,7 +71,7 @@ def MapFn( except BaseException as err: _LOGGER.critical("UDFError, re-raising the error", exc_info=True) # Terminate the current server process due to exception - exit_on_error(context, repr(err), parent=self.multiproc) + exit_on_error(context, f"{ERR_MAP_EXCEPTION}: {repr(err)}", parent=self.multiproc) return def _process_requests( @@ -87,9 +89,8 @@ def _process_requests( self.executor.shutdown(wait=True) # Indicate to the result queue that no more messages left to process result_queue.put(STREAM_EOF) - except BaseException as e: + except BaseException: _LOGGER.critical("MapFn Error, re-raising the error", exc_info=True) - result_queue.put(e) def _invoke_map( self, diff --git a/pynumaflow/mapstreamer/servicer/async_servicer.py b/pynumaflow/mapstreamer/servicer/async_servicer.py index f2e029e3..c0e77f4b 100644 --- a/pynumaflow/mapstreamer/servicer/async_servicer.py +++ b/pynumaflow/mapstreamer/servicer/async_servicer.py @@ -5,9 +5,9 @@ from pynumaflow.mapstreamer import Datum from pynumaflow.mapstreamer._dtypes import MapStreamCallable, MapStreamError from pynumaflow.proto.mapper import map_pb2_grpc, map_pb2 -from pynumaflow.shared.server import exit_on_error +from pynumaflow.shared.server import handle_async_error from pynumaflow.types import NumaflowServicerContext -from pynumaflow._constants import _LOGGER +from pynumaflow._constants import _LOGGER, ERR_MAP_STREAM_EXCEPTION class AsyncMapStreamServicer(map_pb2_grpc.MapServicer): @@ -59,7 +59,7 @@ async def MapFn( yield map_pb2.MapResponse(status=map_pb2.TransmissionStatus(eot=True), id=req.id) except BaseException as err: _LOGGER.critical("UDFError, re-raising the error", exc_info=True) - exit_on_error(context, repr(err)) + await handle_async_error(context, err, ERR_MAP_STREAM_EXCEPTION) return async def __invoke_map_stream(self, keys: list[str], req: Datum): @@ -68,7 +68,7 @@ async def __invoke_map_stream(self, keys: list[str], req: Datum): async for msg in self.__map_stream_handler(keys, req): yield map_pb2.MapResponse.Result(keys=msg.keys, value=msg.value, tags=msg.tags) except BaseException as err: - _LOGGER.critical("UDFError, re-raising the error", exc_info=True) + _LOGGER.critical("MapFn handler error", exc_info=True) raise err async def IsReady( diff --git a/pynumaflow/reducer/servicer/async_servicer.py b/pynumaflow/reducer/servicer/async_servicer.py index 73378196..8b7166e3 100644 --- a/pynumaflow/reducer/servicer/async_servicer.py +++ b/pynumaflow/reducer/servicer/async_servicer.py @@ -1,11 +1,9 @@ -import asyncio from collections.abc import AsyncIterable from typing import Union -import grpc from google.protobuf import empty_pb2 as _empty_pb2 -from pynumaflow._constants import _LOGGER +from pynumaflow._constants import _LOGGER, ERR_REDUCE_EXCEPTION from pynumaflow.proto.reducer import reduce_pb2, reduce_pb2_grpc from pynumaflow.reducer._dtypes import ( Datum, @@ -15,7 +13,7 @@ WindowOperation, ) from pynumaflow.reducer.servicer.task_manager import TaskManager -from pynumaflow.shared.server import exit_on_error +from pynumaflow.shared.server import handle_async_error from pynumaflow.types import NumaflowServicerContext @@ -107,10 +105,7 @@ async def ReduceFn( _LOGGER.critical("Reduce Error", exc_info=True) # Send a context abort signal for the rpc, this is required for numa container to get # the correct grpc error - await asyncio.gather( - context.abort(grpc.StatusCode.UNKNOWN, details=repr(e)), return_exceptions=True - ) - exit_on_error(err=repr(e), parent=False, context=context, update_context=False) + await handle_async_error(context, e, ERR_REDUCE_EXCEPTION) # send EOF to all the tasks once the request iterator is exhausted # This will signal the tasks to stop reading the data on their @@ -141,10 +136,7 @@ async def ReduceFn( _LOGGER.critical("Reduce Error", exc_info=True) # Send a context abort signal for the rpc, this is required for numa container to get # the correct grpc error - await asyncio.gather( - context.abort(grpc.StatusCode.UNKNOWN, details=repr(e)), return_exceptions=True - ) - exit_on_error(err=repr(e), parent=False, context=context, update_context=False) + await handle_async_error(context, e, ERR_REDUCE_EXCEPTION) async def IsReady( self, request: _empty_pb2.Empty, context: NumaflowServicerContext diff --git a/pynumaflow/reducestreamer/servicer/async_servicer.py b/pynumaflow/reducestreamer/servicer/async_servicer.py index 43b91986..ceb3d16b 100644 --- a/pynumaflow/reducestreamer/servicer/async_servicer.py +++ b/pynumaflow/reducestreamer/servicer/async_servicer.py @@ -4,6 +4,7 @@ from google.protobuf import empty_pb2 as _empty_pb2 +from pynumaflow._constants import ERR_REDUCE_EXCEPTION from pynumaflow.proto.reducer import reduce_pb2, reduce_pb2_grpc from pynumaflow.reducestreamer._dtypes import ( Datum, @@ -94,20 +95,20 @@ async def ReduceFn( async for msg in consumer: # If the message is an exception, we raise the exception if isinstance(msg, BaseException): - await handle_async_error(context, msg) + await handle_async_error(context, msg, ERR_REDUCE_EXCEPTION) return # Send window EOF response or Window result response # back to the client else: yield msg except BaseException as e: - await handle_async_error(context, e) + await handle_async_error(context, e, ERR_REDUCE_EXCEPTION) return # Wait for the process_input_stream task to finish for a clean exit try: await producer except BaseException as e: - await handle_async_error(context, e) + await handle_async_error(context, e, ERR_REDUCE_EXCEPTION) return async def IsReady( diff --git a/pynumaflow/shared/server.py b/pynumaflow/shared/server.py index ab86c9f0..128b8dc7 100644 --- a/pynumaflow/shared/server.py +++ b/pynumaflow/shared/server.py @@ -5,6 +5,10 @@ import os import socket import traceback + +from google.protobuf import any_pb2 +from google.rpc import code_pb2, status_pb2, error_details_pb2 +from grpc_status import rpc_status from abc import ABCMeta, abstractmethod from collections.abc import Iterator from concurrent.futures import ThreadPoolExecutor @@ -240,6 +244,21 @@ def check_instance(instance, callable_type) -> bool: return False +def get_grpc_status(err: str): + """ + Create a grpc status object with the error details. + """ + details = any_pb2.Any() + details.Pack( + error_details_pb2.DebugInfo( + detail="\n".join(traceback.format_stack()), + ) + ) + + status = status_pb2.Status(code=code_pb2.INTERNAL, message=err, details=[details]) + return rpc_status.to_status(status) + + def exit_on_error( context: NumaflowServicerContext, err: str, parent: bool = False, update_context=True ): @@ -255,8 +274,12 @@ def exit_on_error( the context with the error codes """ if update_context: - context.set_code(grpc.StatusCode.UNKNOWN) + # Create a status object with the error details + grpc_status = get_grpc_status(err) + + context.set_code(grpc.StatusCode.INTERNAL) context.set_details(err) + context.set_trailing_metadata(grpc_status.trailing_metadata) p = psutil.Process(os.getpid()) # If the parent flag is true, we exit from the parent process @@ -267,15 +290,19 @@ def exit_on_error( p.kill() -def update_context_err(context: NumaflowServicerContext, e: BaseException): +def update_context_err(context: NumaflowServicerContext, e: BaseException, err_msg: str): """ Update the context with the error and log the exception. """ trace = get_exception_traceback_str(e) _LOGGER.critical(trace) _LOGGER.critical(e.__str__()) - context.set_code(grpc.StatusCode.UNKNOWN) - context.set_details(e.__str__()) + + grpc_status = get_grpc_status(err_msg) + + context.set_code(grpc.StatusCode.INTERNAL) + context.set_details(err_msg) + context.set_trailing_metadata(grpc_status.trailing_metadata) def get_exception_traceback_str(exc) -> str: @@ -284,12 +311,15 @@ def get_exception_traceback_str(exc) -> str: return file.getvalue().rstrip() -async def handle_async_error(context: NumaflowServicerContext, exception: BaseException): +async def handle_async_error( + context: NumaflowServicerContext, exception: BaseException, exception_type: str +): """ Handle exceptions for async servers by updating the context and exiting. """ - update_context_err(context, exception) + err_msg = f"{exception_type}: {repr(exception)}" + update_context_err(context, exception, err_msg) await asyncio.gather( - context.abort(grpc.StatusCode.UNKNOWN, details=repr(exception)), return_exceptions=True + context.abort(grpc.StatusCode.INTERNAL, details=err_msg), return_exceptions=True ) - exit_on_error(err=repr(exception), parent=False, context=context, update_context=False) + exit_on_error(err=err_msg, parent=False, context=context, update_context=False) diff --git a/pynumaflow/sideinput/servicer/servicer.py b/pynumaflow/sideinput/servicer/servicer.py index 7b2d6955..835a46aa 100644 --- a/pynumaflow/sideinput/servicer/servicer.py +++ b/pynumaflow/sideinput/servicer/servicer.py @@ -2,6 +2,7 @@ from pynumaflow._constants import ( _LOGGER, + ERR_SIDE_INPUT_RETRIEVAL_EXCEPTION, ) from pynumaflow.proto.sideinput import sideinput_pb2_grpc, sideinput_pb2 from pynumaflow.shared.server import exit_on_error @@ -27,9 +28,9 @@ def RetrieveSideInput( try: rspn = self.__retrieve_handler() except BaseException as err: - err_msg = f"RetrieveSideInputErr: {repr(err)}" + err_msg = f"{ERR_SIDE_INPUT_RETRIEVAL_EXCEPTION}: {repr(err)}" _LOGGER.critical(err_msg, exc_info=True) - exit_on_error(context, repr(err)) + exit_on_error(context, err_msg) return return sideinput_pb2.SideInputResponse(value=rspn.value, no_broadcast=rspn.no_broadcast) diff --git a/pynumaflow/sinker/servicer/async_servicer.py b/pynumaflow/sinker/servicer/async_servicer.py index df9b6e7f..8c68e0d4 100644 --- a/pynumaflow/sinker/servicer/async_servicer.py +++ b/pynumaflow/sinker/servicer/async_servicer.py @@ -4,7 +4,7 @@ from google.protobuf import empty_pb2 as _empty_pb2 from pynumaflow.shared.asynciter import NonBlockingIterator -from pynumaflow.shared.server import exit_on_error +from pynumaflow.shared.server import handle_async_error from pynumaflow.sinker._dtypes import Datum, SinkAsyncCallable from pynumaflow.proto.sinker import sink_pb2_grpc, sink_pb2 from pynumaflow.sinker.servicer.utils import ( @@ -13,7 +13,7 @@ build_sink_resp_results, ) from pynumaflow.types import NumaflowServicerContext -from pynumaflow._constants import _LOGGER, STREAM_EOF +from pynumaflow._constants import _LOGGER, STREAM_EOF, ERR_SINK_EXCEPTION class AsyncSinkServicer(sink_pb2_grpc.SinkServicer): @@ -85,7 +85,7 @@ async def SinkFn( # if there is an exception, we will mark all the responses as a failure err_msg = f"UDSinkError: {repr(err)}" _LOGGER.critical(err_msg, exc_info=True) - exit_on_error(context, err_msg) + await handle_async_error(context, err, ERR_SINK_EXCEPTION) return async def __invoke_sink( @@ -98,7 +98,6 @@ async def __invoke_sink( except BaseException as err: err_msg = f"UDSinkError: {repr(err)}" _LOGGER.critical(err_msg, exc_info=True) - exit_on_error(context, err_msg) raise err async def IsReady( diff --git a/pynumaflow/sourcer/servicer/async_servicer.py b/pynumaflow/sourcer/servicer/async_servicer.py index 31c00f21..de47868b 100644 --- a/pynumaflow/sourcer/servicer/async_servicer.py +++ b/pynumaflow/sourcer/servicer/async_servicer.py @@ -5,13 +5,13 @@ from google.protobuf import empty_pb2 as _empty_pb2 from pynumaflow.shared.asynciter import NonBlockingIterator -from pynumaflow.shared.server import exit_on_error, handle_async_error +from pynumaflow.shared.server import handle_async_error from pynumaflow.sourcer._dtypes import ReadRequest, Offset from pynumaflow.sourcer._dtypes import AckRequest, SourceCallable from pynumaflow.proto.sourcer import source_pb2 from pynumaflow.proto.sourcer import source_pb2_grpc from pynumaflow.types import NumaflowServicerContext -from pynumaflow._constants import _LOGGER, STREAM_EOF +from pynumaflow._constants import _LOGGER, STREAM_EOF, ERR_SOURCE_EXCEPTION def _create_read_handshake_response(): @@ -119,7 +119,7 @@ async def ReadFn( yield _create_eot_response() except BaseException as err: _LOGGER.critical("User-Defined Source ReadFn error", exc_info=True) - exit_on_error(context, str(err)) + await handle_async_error(context, err, ERR_SOURCE_EXCEPTION) async def __invoke_read(self, req, niter): """Invoke the read handler and manage the iterator.""" @@ -165,7 +165,7 @@ async def AckFn( yield _create_ack_response() except BaseException as err: _LOGGER.critical("User-Defined Source AckFn error", exc_info=True) - exit_on_error(context, repr(err)) + await handle_async_error(context, err, ERR_SOURCE_EXCEPTION) async def IsReady( self, request: _empty_pb2.Empty, context: NumaflowServicerContext @@ -187,7 +187,7 @@ async def PendingFn( count = await self.__source_pending_handler() except BaseException as err: _LOGGER.critical("PendingFn Error", exc_info=True) - exit_on_error(context, repr(err)) + await handle_async_error(context, err, ERR_SOURCE_EXCEPTION) return resp = source_pb2.PendingResponse.Result(count=count.count) return source_pb2.PendingResponse(result=resp) @@ -202,7 +202,7 @@ async def PartitionsFn( partitions = await self.__source_partitions_handler() except BaseException as err: _LOGGER.critical("PartitionsFn Error", exc_info=True) - exit_on_error(context, repr(err)) + await handle_async_error(context, err, ERR_SOURCE_EXCEPTION) return resp = source_pb2.PartitionsResponse.Result(partitions=partitions.partitions) return source_pb2.PartitionsResponse(result=resp) diff --git a/pynumaflow/sourcetransformer/servicer/_servicer.py b/pynumaflow/sourcetransformer/servicer/_servicer.py index 1ee411bc..7c0f55c8 100644 --- a/pynumaflow/sourcetransformer/servicer/_servicer.py +++ b/pynumaflow/sourcetransformer/servicer/_servicer.py @@ -12,7 +12,12 @@ from pynumaflow.proto.sourcetransformer import transform_pb2 from pynumaflow.proto.sourcetransformer import transform_pb2_grpc from pynumaflow.types import NumaflowServicerContext -from pynumaflow._constants import _LOGGER, STREAM_EOF, NUM_THREADS_DEFAULT +from pynumaflow._constants import ( + _LOGGER, + STREAM_EOF, + NUM_THREADS_DEFAULT, + ERR_TRANSFORMER_EXCEPTION, +) def _create_read_handshake_response() -> transform_pb2.SourceTransformResponse: @@ -73,7 +78,9 @@ def SourceTransformFn( # if error handler accordingly if isinstance(res, BaseException): # Terminate the current server process due to exception - exit_on_error(context, repr(res), parent=self.multiproc) + exit_on_error( + context, f"{ERR_TRANSFORMER_EXCEPTION}: {repr(res)}", parent=self.multiproc + ) return # return the result yield res @@ -85,7 +92,9 @@ def SourceTransformFn( except BaseException as err: _LOGGER.critical("UDFError, re-raising the error", exc_info=True) # Terminate the current server process due to exception - exit_on_error(context, repr(err), parent=self.multiproc) + exit_on_error( + context, f"{ERR_TRANSFORMER_EXCEPTION}: {repr(err)}", parent=self.multiproc + ) return def _process_requests( @@ -103,9 +112,8 @@ def _process_requests( self.executor.shutdown(wait=True) # Indicate to the result queue that no more messages left to process result_queue.put(STREAM_EOF) - except BaseException as e: + except BaseException: _LOGGER.critical("SourceTransformFnError, re-raising the error", exc_info=True) - result_queue.put(e) def _invoke_transformer( self, context, request: transform_pb2.SourceTransformRequest, result_queue: SyncIterator diff --git a/pyproject.toml b/pyproject.toml index df93ffe2..309a300c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,7 +26,8 @@ grpcio = "^1.48.1" grpcio-tools = "^1.48.1" google-cloud = "^0.34.0" google-api-core = "^2.11.0" -protobuf = ">=3.20,<5.0" +grpcio-status = "^1.48.1" +protobuf = ">=3.20,<6.0" aiorun = "^2023.7" uvloop = "^0.19.0" psutil = "^6.0.0" diff --git a/tests/map/test_async_mapper.py b/tests/map/test_async_mapper.py index e974017e..85ff5a70 100644 --- a/tests/map/test_async_mapper.py +++ b/tests/map/test_async_mapper.py @@ -215,7 +215,7 @@ def test_map_grpc_error(self) -> None: except grpc.RpcError as e: logging.error(e) grpc_exception = e - self.assertEqual(grpc.StatusCode.UNKNOWN, e.code()) + self.assertEqual(grpc.StatusCode.INTERNAL, e.code()) self.assertTrue("Exception thrown from map" in e.__str__()) finally: raise_error_from_map = False diff --git a/tests/map/test_multiproc_mapper.py b/tests/map/test_multiproc_mapper.py index 39960269..418d0161 100644 --- a/tests/map/test_multiproc_mapper.py +++ b/tests/map/test_multiproc_mapper.py @@ -65,7 +65,7 @@ def test_udf_map_err_handshake(self): metadata, code, details = method.termination() self.assertTrue("MapFn: expected handshake as the first message" in details) - self.assertEqual(grpc.StatusCode.UNKNOWN, code) + self.assertEqual(grpc.StatusCode.INTERNAL, code) def test_udf_map_err(self): my_server = MapMultiprocServer(mapper_instance=err_map_handler) @@ -92,7 +92,7 @@ def test_udf_map_err(self): metadata, code, details = method.termination() self.assertTrue("Something is fishy!" in details) - self.assertEqual(grpc.StatusCode.UNKNOWN, code) + self.assertEqual(grpc.StatusCode.INTERNAL, code) def test_is_ready(self): method = self.test_server.invoke_unary_unary( diff --git a/tests/map/test_sync_mapper.py b/tests/map/test_sync_mapper.py index 9f6990be..4eb7149a 100644 --- a/tests/map/test_sync_mapper.py +++ b/tests/map/test_sync_mapper.py @@ -59,7 +59,7 @@ def test_udf_map_err_handshake(self): metadata, code, details = method.termination() self.assertTrue("MapFn: expected handshake as the first message" in details) - self.assertEqual(grpc.StatusCode.UNKNOWN, code) + self.assertEqual(grpc.StatusCode.INTERNAL, code) def test_udf_map_error_response(self): my_server = MapServer(mapper_instance=err_map_handler) @@ -87,7 +87,7 @@ def test_udf_map_error_response(self): metadata, code, details = method.termination() self.assertTrue("Something is fishy!" in details) - self.assertEqual(grpc.StatusCode.UNKNOWN, code) + self.assertEqual(grpc.StatusCode.INTERNAL, code) def test_is_ready(self): method = self.test_server.invoke_unary_unary( diff --git a/tests/sideinput/test_side_input_server.py b/tests/sideinput/test_side_input_server.py index 50886c79..aaec4c80 100644 --- a/tests/sideinput/test_side_input_server.py +++ b/tests/sideinput/test_side_input_server.py @@ -76,7 +76,7 @@ def test_side_input_err(self): timeout=1, ) response, metadata, code, details = method.termination() - self.assertEqual(grpc.StatusCode.UNKNOWN, code) + self.assertEqual(grpc.StatusCode.INTERNAL, code) def test_is_ready(self): method = self.test_server.invoke_unary_unary( diff --git a/tests/sink/test_async_sink.py b/tests/sink/test_async_sink.py index af29932c..0dc99e3c 100644 --- a/tests/sink/test_async_sink.py +++ b/tests/sink/test_async_sink.py @@ -182,7 +182,9 @@ def test_sink_err(self) -> None: for _ in generator_response: pass except BaseException as e: - self.assertTrue("UDSinkError: ValueError('test_mock_err_message')" in e.__str__()) + self.assertTrue( + "UDF_EXECUTION_ERROR(sink): ValueError('test_mock_err_message')" in e.__str__() + ) return except grpc.RpcError as e: grpc_exception = e diff --git a/tests/sink/test_server.py b/tests/sink/test_server.py index b67145c0..318226d9 100644 --- a/tests/sink/test_server.py +++ b/tests/sink/test_server.py @@ -124,7 +124,7 @@ def test_udsink_err_handshake(self): metadata, code, details = method.termination() self.assertTrue("UDSinkError: Exception('SinkFn: expected handshake message')" in details) - self.assertEqual(StatusCode.UNKNOWN, code) + self.assertEqual(StatusCode.INTERNAL, code) def test_udsink_err(self): server = SinkServer(sinker_instance=err_udsink_handler) @@ -183,7 +183,7 @@ def test_udsink_err(self): metadata, code, details = method.termination() print(code) - self.assertEqual(StatusCode.UNKNOWN, code) + self.assertEqual(StatusCode.INTERNAL, code) def test_forward_message(self): event_time_timestamp = _timestamp_pb2.Timestamp() diff --git a/tests/source/test_async_source_err.py b/tests/source/test_async_source_err.py index 8570470c..bb35b0f3 100644 --- a/tests/source/test_async_source_err.py +++ b/tests/source/test_async_source_err.py @@ -92,7 +92,11 @@ def test_read_error(self) -> None: for _ in generator_response: pass except BaseException as e: - self.assertTrue("Got a runtime error from read handler." in e.__str__()) + self.assertTrue( + "UDF_EXECUTION_ERROR(source): TypeError(" + '"handle_async_error() missing 1 required positional argument: ' + "'exception_type'\")" in e.__str__() + ) return except grpc.RpcError as e: grpc_exception = e diff --git a/tests/sourcetransform/test_multiproc.py b/tests/sourcetransform/test_multiproc.py index 865ab4e0..03e33bbc 100644 --- a/tests/sourcetransform/test_multiproc.py +++ b/tests/sourcetransform/test_multiproc.py @@ -76,7 +76,7 @@ def test_udf_mapt_err_handshake(self): metadata, code, details = method.termination() self.assertTrue("SourceTransformFn: expected handshake message" in details) - self.assertEqual(grpc.StatusCode.UNKNOWN, code) + self.assertEqual(grpc.StatusCode.INTERNAL, code) def test_udf_mapt_err(self): server = SourceTransformMultiProcServer(source_transform_instance=err_transform_handler) @@ -110,7 +110,7 @@ def test_udf_mapt_err(self): metadata, code, details = method.termination() self.assertTrue("Something is fishy" in details) - self.assertEqual(grpc.StatusCode.UNKNOWN, code) + self.assertEqual(grpc.StatusCode.INTERNAL, code) def test_is_ready(self): method = self.test_server.invoke_unary_unary( diff --git a/tests/sourcetransform/test_sync_server.py b/tests/sourcetransform/test_sync_server.py index 1f9545f6..9ec63cb6 100644 --- a/tests/sourcetransform/test_sync_server.py +++ b/tests/sourcetransform/test_sync_server.py @@ -67,7 +67,7 @@ def test_udf_mapt_err(self): metadata, code, details = method.termination() self.assertTrue("Something is fishy" in details) - self.assertEqual(grpc.StatusCode.UNKNOWN, code) + self.assertEqual(grpc.StatusCode.INTERNAL, code) def test_is_ready(self): method = self.test_server.invoke_unary_unary( @@ -118,7 +118,7 @@ def test_udf_mapt_err_handshake(self): metadata, code, details = method.termination() self.assertTrue("SourceTransformFn: expected handshake message" in details) - self.assertEqual(grpc.StatusCode.UNKNOWN, code) + self.assertEqual(grpc.StatusCode.INTERNAL, code) def test_mapt_assign_new_event_time(self): test_datums = get_test_datums()