diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0ff9626..5fdb32d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -66,4 +66,4 @@ jobs: - name: Execute test suite shell: bash - run: poetry run pytest tests/ + run: poetry run python -m pytest tests/ diff --git a/poetry.lock b/poetry.lock index f51529a..f136c52 100644 --- a/poetry.lock +++ b/poetry.lock @@ -229,7 +229,7 @@ docs = ["sphinx"] [[package]] name = "grpcio" -version = "1.36.1" +version = "1.40.0" description = "HTTP/2-based RPC framework" category = "dev" optional = false @@ -239,18 +239,18 @@ python-versions = "*" six = ">=1.5.2" [package.extras] -protobuf = ["grpcio-tools (>=1.36.1)"] +protobuf = ["grpcio-tools (>=1.40.0)"] [[package]] name = "grpcio-tools" -version = "1.36.1" +version = "1.40.0" description = "Protobuf code generator for gRPC" category = "dev" optional = false python-versions = "*" [package.dependencies] -grpcio = ">=1.36.1" +grpcio = ">=1.40.0" protobuf = ">=3.5.0.post1,<4.0dev" [[package]] @@ -470,15 +470,12 @@ tomlkit = ">=0.6.0,<1.0.0" [[package]] name = "protobuf" -version = "3.15.7" +version = "3.18.0" description = "Protocol Buffers" category = "dev" optional = false python-versions = "*" -[package.dependencies] -six = ">=1.9" - [[package]] name = "py" version = "1.10.0" @@ -1073,100 +1070,96 @@ greenlet = [ {file = "greenlet-1.0.0.tar.gz", hash = "sha256:719e169c79255816cdcf6dccd9ed2d089a72a9f6c42273aae12d55e8d35bdcf8"}, ] grpcio = [ - {file = "grpcio-1.36.1-cp27-cp27m-macosx_10_10_x86_64.whl", hash = "sha256:e3a83c5db16f95daac1d96cf3c9018d765579b5a29bb336758d793028e729921"}, - {file = "grpcio-1.36.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:c18739fecb90760b183bfcb4da1cf2c6bf57e38f7baa2c131d5f67d9a4c8365d"}, - {file = "grpcio-1.36.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:f6efa62ca1fe02cd34ec35f53446f04a15fe2c886a4e825f5679936a573d2cbf"}, - {file = "grpcio-1.36.1-cp27-cp27m-win32.whl", hash = "sha256:9a18299827a70be0507f98a65393b1c7f6c004fe2ca995fe23ffac534dd187a7"}, - {file = "grpcio-1.36.1-cp27-cp27m-win_amd64.whl", hash = "sha256:8a89190de1985a54ef311650cf9687ffb81de038973fd32e452636ddae36b29f"}, - {file = "grpcio-1.36.1-cp27-cp27mu-linux_armv7l.whl", hash = "sha256:3e75643d21db7d68acd541d3fec66faaa8061d12b511e101b529ff12a276bb9b"}, - {file = "grpcio-1.36.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:3c5204e05e18268dd6a1099ca6c106fd9d00bcae1e37d5a5186094c55044c941"}, - {file = "grpcio-1.36.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:24d4c2c5e540e666c52225953d6813afc8ccf9bf46db6a72edd4e8d606656248"}, - {file = "grpcio-1.36.1-cp35-cp35m-linux_armv7l.whl", hash = "sha256:4dc7295dc9673f7af22c1e38c2a2c24ecbd6773a4c5ed5a46ed38ad4dcf2bf6c"}, - {file = "grpcio-1.36.1-cp35-cp35m-macosx_10_10_intel.whl", hash = "sha256:f241116d4bf1a8037ff87f16914b606390824e50902bdbfa2262e855fbf07fe5"}, - {file = "grpcio-1.36.1-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:1056b558acfd575d774644826df449e1402a03e456a3192fafb6b06d1069bf80"}, - {file = "grpcio-1.36.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:52ec563da45d06319224ebbda53501d25594de64ee1b2786e119ba4a2f1ce40c"}, - {file = "grpcio-1.36.1-cp35-cp35m-manylinux2014_i686.whl", hash = "sha256:7cbeac9bbe6a4a7fce4a89c892c249135dd9f5f5219ede157174c34a456188f0"}, - {file = "grpcio-1.36.1-cp35-cp35m-manylinux2014_x86_64.whl", hash = "sha256:2abaa9f0d83bd0b26f6d0d1fc4b97d73bde3ceac36ab857f70d3cabcf31c5c79"}, - {file = "grpcio-1.36.1-cp35-cp35m-win32.whl", hash = "sha256:02030e1afd3247f2b159df9dff959ec79dd4047b1c4dd4eec9e3d1642efbd504"}, - {file = "grpcio-1.36.1-cp35-cp35m-win_amd64.whl", hash = "sha256:eafafc7e040e36aa926edc731ab52c23465981888779ae64bfc8ad85888ed4f3"}, - {file = "grpcio-1.36.1-cp36-cp36m-linux_armv7l.whl", hash = "sha256:1030e74ddd0fa6e3bad7944f0c68cf1251b15bcd70641f0ad3858fdf2b8602a0"}, - {file = "grpcio-1.36.1-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:b003e24339030ed356f59505d1065b89e1f443ef41ce71ca9069be944c0d2e6b"}, - {file = "grpcio-1.36.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:76daa3c4d58fcf40f7969bdb4270335e96ee0382a050cadcd97d7332cd0251a3"}, - {file = "grpcio-1.36.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:f591597bb25eae0094ead5a965555e911453e5f35fdbdaa83be11ef107865697"}, - {file = "grpcio-1.36.1-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:cbd82c479338fc1c0e5c3db09752b61fe47d40c6e38e4be8657153712fa76674"}, - {file = "grpcio-1.36.1-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:7e32bc01dfaa7a51c547379644ea619a2161d6969affdac3bbd173478d26673d"}, - {file = "grpcio-1.36.1-cp36-cp36m-win32.whl", hash = "sha256:5378189fb897567f4929f75ab67a3e0da4f8967806246cb9cfa1fa06bfbdb0d5"}, - {file = "grpcio-1.36.1-cp36-cp36m-win_amd64.whl", hash = "sha256:3a6295aa692806218e97bb687a71cd768450ed99e2acddc488f18d738edef463"}, - {file = "grpcio-1.36.1-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:6f6f8a8b57e40347d0bf32c2135037dae31d63d3b19007b4c426a11b76deaf65"}, - {file = "grpcio-1.36.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:4c05ed54b2a00df01e633bebec819b512bf0c60f8f5b3b36dd344dc673b02fea"}, - {file = "grpcio-1.36.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:e1b9e906aa6f7577016e86ed7f3a69cae7dab4e41356584dc7980f76ea65035f"}, - {file = "grpcio-1.36.1-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:a602d6b30760bbbb2fe776caaa914a0d404636cafc3f2322718bf8002d7b1e55"}, - {file = "grpcio-1.36.1-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:dee9971aef20fc09ed897420446c4d0926cd1d7630f343333288523ca5b44bb2"}, - {file = "grpcio-1.36.1-cp37-cp37m-win32.whl", hash = "sha256:ed16bfeda02268e75e038c58599d52afc7097d749916c079b26bc27a66900f7d"}, - {file = "grpcio-1.36.1-cp37-cp37m-win_amd64.whl", hash = "sha256:85a6035ae75ce964f78f19cf913938596ccf068b149fcd79f4371268bcb9aa7c"}, - {file = "grpcio-1.36.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:6b30682180053eebc87802c2f249d2f59b430e1a18e8808575dde0d22a968b2c"}, - {file = "grpcio-1.36.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:5e4920a8fb5d17b2c5ba980db0ac1c925bbee3e5d70e96da3ec4fb1c8600d68f"}, - {file = "grpcio-1.36.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:f7740d9d9451f3663df11b241ac05cafc0efaa052d2fdca6640c4d3748eaf6e2"}, - {file = "grpcio-1.36.1-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:20b7c4c5513e1135a2261e56830c0e710f205fee92019b92fe132d7f16a5cfd8"}, - {file = "grpcio-1.36.1-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:216fbd2a488e74c3b96e240e4054c85c4c99102a439bc9f556936991643f43bc"}, - {file = "grpcio-1.36.1-cp38-cp38-win32.whl", hash = "sha256:7863c2a140e829b1f4c6d67bf0bf15e5321ac4766d0a295e2682970d9dd4b091"}, - {file = "grpcio-1.36.1-cp38-cp38-win_amd64.whl", hash = "sha256:f214076eb13da9e65c1aa9877b51fca03f51a82bd8691358e1a1edd9ff341330"}, - {file = "grpcio-1.36.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:ec753c022b39656f88409fbf9f2d3b28497e3f17aa678f884d78776b41ebe6bd"}, - {file = "grpcio-1.36.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:0648a6d5d7ddcd9c8462d7d961660ee024dad6b88152ee3a521819e611830edf"}, - {file = "grpcio-1.36.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:45ea10dd133a43b10c0b4326834107ebccfee25dab59b312b78e018c2d72a1f0"}, - {file = "grpcio-1.36.1-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:bab743cdac1d6d8326c65d1d091d0740b39966dfab06519f74a03b3d128b8454"}, - {file = "grpcio-1.36.1-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:09af8ceb91860086216edc6e5ea15f9beb2cf81687faa43b7c03216f5b73e244"}, - {file = "grpcio-1.36.1-cp39-cp39-win32.whl", hash = "sha256:f3f70505207ee1cee65f60a799fd8e06e07861409aa0d55d834825a79b40c297"}, - {file = "grpcio-1.36.1-cp39-cp39-win_amd64.whl", hash = "sha256:f22c11772eff25ba1ca536e760b8c34ba56f2a9d66b6842cb11770a8f61f879d"}, - {file = "grpcio-1.36.1.tar.gz", hash = "sha256:a66ea59b20f3669df0f0c6a3bd57b985e5b2d1dcf3e4c29819bb8dc232d0fd38"}, + {file = "grpcio-1.40.0-cp35-cp35m-macosx_10_10_intel.whl", hash = "sha256:6f8f581787e739945e6cda101f312ea8a7e7082bdbb4993901eb828da6a49092"}, + {file = "grpcio-1.40.0-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:a4389e26a8f9338ca91effdc5436dfec67d6ecd296368dba115799ae8f8e5bdb"}, + {file = "grpcio-1.40.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:fb06708e3d173e387326abcd5182d52beb60e049db5c3d317bd85509e938afdc"}, + {file = "grpcio-1.40.0-cp35-cp35m-manylinux2014_i686.whl", hash = "sha256:f06e07161c21391682bfcac93a181a037a8aa3d561546690e9d0501189729aac"}, + {file = "grpcio-1.40.0-cp35-cp35m-manylinux2014_x86_64.whl", hash = "sha256:5ff0dcf66315f3f00e1a8eb7244c6a49bdb0cc59bef4fb65b9db8adbd78e6acb"}, + {file = "grpcio-1.40.0-cp35-cp35m-win32.whl", hash = "sha256:ba9dd97ea1738be3e81d34e6bab8ff91a0b80668a4ec81454b283d3c828cebde"}, + {file = "grpcio-1.40.0-cp35-cp35m-win_amd64.whl", hash = "sha256:e12d776a240fee3ebd002519c02d165d94ec636d3fe3d6185b361bfc9a2d3106"}, + {file = "grpcio-1.40.0-cp36-cp36m-linux_armv7l.whl", hash = "sha256:6b9b432f5665dfc802187384693b6338f05c7fc3707ebf003a89bd5132074e27"}, + {file = "grpcio-1.40.0-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:886d056f5101ac513f4aefe4d21a816d98ee3f9a8e77fc3bcb4ae1a3a24efe26"}, + {file = "grpcio-1.40.0-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:b1b34e5a6f1285d1576099c663dae28c07b474015ed21e35a243aff66a0c2aed"}, + {file = "grpcio-1.40.0-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:17ed13d43450ef9d1f9b78cc932bcf42844ca302235b93026dfd07fb5208d146"}, + {file = "grpcio-1.40.0-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:e19de138199502d575fcec5cf68ae48815a6efe7e5c0d0b8c97eba8c77ae9f0e"}, + {file = "grpcio-1.40.0-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:a812164ceb48cb62c3217bd6245274e693c624cc2ac0c1b11b4cea96dab054dd"}, + {file = "grpcio-1.40.0-cp36-cp36m-manylinux_2_24_aarch64.whl", hash = "sha256:eedc8c3514c10b6f11c6f406877e424ca29610883b97bb97e33b1dd2a9077f6c"}, + {file = "grpcio-1.40.0-cp36-cp36m-win32.whl", hash = "sha256:1708a0ba90c798b4313f541ffbcc25ed47e790adaafb02111204362723dabef0"}, + {file = "grpcio-1.40.0-cp36-cp36m-win_amd64.whl", hash = "sha256:d760a66c9773780837915be85a39d2cd4ab42ef32657c5f1d28475e23ab709fc"}, + {file = "grpcio-1.40.0-cp37-cp37m-linux_armv7l.whl", hash = "sha256:8a35b5f87247c893b01abf2f4f7493a18c2c5bf8eb3923b8dd1654d8377aa1a7"}, + {file = "grpcio-1.40.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:45704b9b5b85f9bcb027f90f2563d11d995c1b870a9ee4b3766f6c7ff6fc3505"}, + {file = "grpcio-1.40.0-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:4967949071c9e435f9565ec2f49700cebeda54836a04710fe21f7be028c0125a"}, + {file = "grpcio-1.40.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:1f9ccc9f5c0d5084d1cd917a0b5ff0142a8d269d0755592d751f8ce9e7d3d7f1"}, + {file = "grpcio-1.40.0-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:5729ca9540049f52c2e608ca110048cfabab3aeaa0d9f425361d9f8ba8506cac"}, + {file = "grpcio-1.40.0-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:edddc849bed3c5dfe215a9f9532a9bd9f670b57d7b8af603be80148b4c69e9a8"}, + {file = "grpcio-1.40.0-cp37-cp37m-manylinux_2_24_aarch64.whl", hash = "sha256:49155dfdf725c0862c428039123066b25ce61bd38ce50a21ce325f1735aac1bd"}, + {file = "grpcio-1.40.0-cp37-cp37m-win32.whl", hash = "sha256:913916823efa2e487b2ee9735b7759801d97fd1974bacdb1900e3bbd17f7d508"}, + {file = "grpcio-1.40.0-cp37-cp37m-win_amd64.whl", hash = "sha256:24277aab99c346ca36a1aa8589a0624e19a8e6f2b74c83f538f7bb1cc5ee8dbc"}, + {file = "grpcio-1.40.0-cp38-cp38-linux_armv7l.whl", hash = "sha256:a66a30513d2e080790244a7ac3d7a3f45001f936c5c2c9613e41e2a5d7a11794"}, + {file = "grpcio-1.40.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:e2367f2b18dd4ba64cdcd9f626a920f9ec2e8228630839dc8f4a424d461137ea"}, + {file = "grpcio-1.40.0-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:27dee6dcd1c04c4e9ceea49f6143003569292209d2c24ca100166660805e2440"}, + {file = "grpcio-1.40.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:d271e52038dec0db7c39ad9303442d6087c55e09b900e2931b86e837cf0cbc2e"}, + {file = "grpcio-1.40.0-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:41e250ec7cd7523bf49c815b5509d5821728c26fac33681d4b0d1f5f34f59f06"}, + {file = "grpcio-1.40.0-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:33dc4259fecb96e6eac20f760656b911bcb1616aa3e58b3a1d2f125714a2f5d3"}, + {file = "grpcio-1.40.0-cp38-cp38-manylinux_2_24_aarch64.whl", hash = "sha256:72b7b8075ee822dad4b39c150d73674c1398503d389e38981e9e35a894c476de"}, + {file = "grpcio-1.40.0-cp38-cp38-win32.whl", hash = "sha256:a93490e6eff5fce3748fb2757cb4273dc21eb1b56732b8c9640fd82c1997b215"}, + {file = "grpcio-1.40.0-cp38-cp38-win_amd64.whl", hash = "sha256:d3b4b41eb0148fca3e6e6fc61d1332a7e8e7c4074fb0d1543f0b255d7f5f1588"}, + {file = "grpcio-1.40.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:fbe3b66bfa2c2f94535f6063f6db62b5b150d55a120f2f9e1175d3087429c4d9"}, + {file = "grpcio-1.40.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:ecfd80e8ea03c46b3ea7ed37d2040fcbfe739004b9e4329b8b602d06ac6fb113"}, + {file = "grpcio-1.40.0-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:d487b4daf84a14741ca1dc1c061ffb11df49d13702cd169b5837fafb5e84d9c0"}, + {file = "grpcio-1.40.0-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:c26de909cfd54bacdb7e68532a1591a128486af47ee3a5f828df9aa2165ae457"}, + {file = "grpcio-1.40.0-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:1d9eabe2eb2f78208f9ae67a591f73b024488449d4e0a5b27c7fca2d6901a2d4"}, + {file = "grpcio-1.40.0-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:4c2baa438f51152c9b7d0835ff711add0b4bc5056c0f5df581a6112153010696"}, + {file = "grpcio-1.40.0-cp39-cp39-manylinux_2_24_aarch64.whl", hash = "sha256:bf114be0023b145f7101f392a344692c1efd6de38a610c54a65ed3cba035e669"}, + {file = "grpcio-1.40.0-cp39-cp39-win32.whl", hash = "sha256:5f6d6b638698fa6decf7f040819aade677b583eaa21b43366232cb254a2bbac8"}, + {file = "grpcio-1.40.0-cp39-cp39-win_amd64.whl", hash = "sha256:005fe14e67291498989da67d454d805be31d57a988af28ed3a2a0a7cabb05c53"}, + {file = "grpcio-1.40.0.tar.gz", hash = "sha256:3d172158fe886a2604db1b6e17c2de2ab465fe0fe36aba2ec810ca8441cefe3a"}, ] grpcio-tools = [ - {file = "grpcio-tools-1.36.1.tar.gz", hash = "sha256:80ef584f7b917f575e4b8f2ec59cd4a4d98c2046e801a735f3136b05742a36a6"}, - {file = "grpcio_tools-1.36.1-cp27-cp27m-macosx_10_10_x86_64.whl", hash = "sha256:ebbfdbff079bfc303a4e1d3da59302147d5cf4f1db2c412a074366149d93e89e"}, - {file = "grpcio_tools-1.36.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:d95dfefe156be02bcce4eb044ac7ff166c8a6c288d71bc3ed960d8b26bce2786"}, - {file = "grpcio_tools-1.36.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:7969915ac252d0e67f9cfd4f8b9d6bb546efc7b26bce34978a940e37ee4078d5"}, - {file = "grpcio_tools-1.36.1-cp27-cp27m-win32.whl", hash = "sha256:582b77e7a4905063d8071ac3685cefa38941799d5f4ea7b4519281a28cfc6752"}, - {file = "grpcio_tools-1.36.1-cp27-cp27m-win_amd64.whl", hash = "sha256:66d2a6237941199df0493e46b8a3123005b4dfde9af1b9572e8c54eb605a7567"}, - {file = "grpcio_tools-1.36.1-cp27-cp27mu-linux_armv7l.whl", hash = "sha256:702c3eb61a3cfddcaea04d2358c0390c2e189fe42b64a92239df8292366ab4df"}, - {file = "grpcio_tools-1.36.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:ce621375bc7dfaeac93e23e202771a6e567a8ea7e9a7cc690b87d8b1950e3da6"}, - {file = "grpcio_tools-1.36.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:ff1792b188183e977e2feccb1f3b3d4580f921df8f61385d7ae8eace10578a23"}, - {file = "grpcio_tools-1.36.1-cp35-cp35m-linux_armv7l.whl", hash = "sha256:a3a64797840fd4917ec98532d17b9b7c6a954dcfd7862657c750255556d369a5"}, - {file = "grpcio_tools-1.36.1-cp35-cp35m-macosx_10_10_intel.whl", hash = "sha256:0873697064cdbb116ba9f88ff524e13e9afd78bf7905ecd6a0f0f743bf40ca64"}, - {file = "grpcio_tools-1.36.1-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:6df49b402f387decaaf57784c3e74bea6f34cf446cc45d4bf7b9adb34f97fb20"}, - {file = "grpcio_tools-1.36.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:aec997dafa90a29b399bdb23d968ab43da223faeac005d384a1194f43ee0f46e"}, - {file = "grpcio_tools-1.36.1-cp35-cp35m-manylinux2014_i686.whl", hash = "sha256:92336c60db1052c865ab7c9936680187d16d2f565c470ba03199e817120714e8"}, - {file = "grpcio_tools-1.36.1-cp35-cp35m-manylinux2014_x86_64.whl", hash = "sha256:bbe8da70ccbe21c72599eb8de5ad26bd053c01f4f03c48ea16323f96f1ec7095"}, - {file = "grpcio_tools-1.36.1-cp35-cp35m-win32.whl", hash = "sha256:96e1c0d267eb03b819a31bcf973579220ec3b8b53178952daa9e2f1ad696783f"}, - {file = "grpcio_tools-1.36.1-cp35-cp35m-win_amd64.whl", hash = "sha256:f4326b1a5352e85480629bf888b132f0aec79bb791d29cd3e2322586cd70433a"}, - {file = "grpcio_tools-1.36.1-cp36-cp36m-linux_armv7l.whl", hash = "sha256:f2befead0395e8aaab1e8f76825c8c9fa93d69249a513c26107a55183f91ccd9"}, - {file = "grpcio_tools-1.36.1-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:125859be6458e65e348c50ddb7a964ba48945d521af3f46ce35aca9a2b752296"}, - {file = "grpcio_tools-1.36.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:c669f1ee5642631ad93fa51d298306124d26bccc76ce63a3bc143ddcf01c58af"}, - {file = "grpcio_tools-1.36.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:693dc16a65b1766037fca8cddc173c0f45e79dd14e05d61128a30dbfd02f6503"}, - {file = "grpcio_tools-1.36.1-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:add07eb3c79478b003ac3af7b636275c37fa6bac56e6a29f79128bea09b37488"}, - {file = "grpcio_tools-1.36.1-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:747b547c487231a6325eda820d1d6a7c6080eda2cd1f68a7d4b2f8d9cc0a3e95"}, - {file = "grpcio_tools-1.36.1-cp36-cp36m-win32.whl", hash = "sha256:fd5eed43f5764209a95a58db82c064c1958525f30ad8ebb57df38dd2c9e86aa7"}, - {file = "grpcio_tools-1.36.1-cp36-cp36m-win_amd64.whl", hash = "sha256:bc6257b5533c66143f4f084aea3ae52c1c01f99997a8b81d2259d0cf083176b5"}, - {file = "grpcio_tools-1.36.1-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:f35fad86d99743cc15fccf11ec74d8c9b76e997cd233dc1fd031457d3f0fd7fc"}, - {file = "grpcio_tools-1.36.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:dcdfe82237e7498eb49dd12751716c55d189a5e49b4bda0bb53f85acbe51bbb1"}, - {file = "grpcio_tools-1.36.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:d6b3c868c6ac15a0e288d3a5380ad5f01802cbbed8645333e496fa31ecea19af"}, - {file = "grpcio_tools-1.36.1-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:6898776449485feedb6d0fd98d3a36c8882c32a5603b86b2511e2557ee765d40"}, - {file = "grpcio_tools-1.36.1-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:6fee070c18be66a282ceb56245191dabf80986aee333e74d2fdea58118b452d4"}, - {file = "grpcio_tools-1.36.1-cp37-cp37m-win32.whl", hash = "sha256:55ed5c5de883defacd899123ba5a9f0077b7fb87d8f1778cb5996b4391604447"}, - {file = "grpcio_tools-1.36.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f74f0c3eedc0de72c402e82bb1199ffe5e359ccdac70bf789d65444042a84f42"}, - {file = "grpcio_tools-1.36.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:93e3ba4696b69fc4356a0823ecddd8b29ebb1fba0571f27574b1182ef5c262f6"}, - {file = "grpcio_tools-1.36.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:e730845677e45c6829d212be6e4fb69768979c3b35b5884293be02a7f436e18c"}, - {file = "grpcio_tools-1.36.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:f7ba8d631f8f5c089958285545bd9e307fd752cdd1fa31515a51cfc1e04b833d"}, - {file = "grpcio_tools-1.36.1-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:af392594ba30b5ee470b7538cf792df970e2097edc299685d8e0d76b2b1bef7b"}, - {file = "grpcio_tools-1.36.1-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:facda541209a0b0edfccf6a5b18ce344c4e90bc8950c995482c85936a23ba939"}, - {file = "grpcio_tools-1.36.1-cp38-cp38-win32.whl", hash = "sha256:9fa491aaccd455e3aec35d12bcef5dce307c674f08e98bbbf33bf6774e6e2ec5"}, - {file = "grpcio_tools-1.36.1-cp38-cp38-win_amd64.whl", hash = "sha256:76900dde111192900c6eb5ed491cf0d8a13403e502c74859f2e2c3116842668a"}, - {file = "grpcio_tools-1.36.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0a7b85758e44d9585f27fc7692b58e63952a2e9130cfbbd16defda8c2ffbb2ad"}, - {file = "grpcio_tools-1.36.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:9b8556e2938ef9437ef07d028b46198f299533497df878f96785502e6f74250d"}, - {file = "grpcio_tools-1.36.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:cd44135fb8b45acc79424e7354bb4548911a6202ca2fac384574099f8d998f06"}, - {file = "grpcio_tools-1.36.1-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:c02b5b6d185b1af86342381ddd1ad3d0482c4116e203e52a7145636fb1b2ad12"}, - {file = "grpcio_tools-1.36.1-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:377cd9b8d2098d2ced48d3dee466fd73fb19128aa0edc6f1799077cf4dbda606"}, - {file = "grpcio_tools-1.36.1-cp39-cp39-win32.whl", hash = "sha256:120bad5a3f3288ae8acd07d839a13d7873304ae35a1d717033295e90ed9bd8ac"}, - {file = "grpcio_tools-1.36.1-cp39-cp39-win_amd64.whl", hash = "sha256:5cec989d219164312bdfa9389aedaea5887fb8133bb1a247fcde5901775b5427"}, + {file = "grpcio-tools-1.40.0.tar.gz", hash = "sha256:d440f2bc089ff628618c536904d5bc39d0b44f7afdda4c4c1ecd15fcf385bfba"}, + {file = "grpcio_tools-1.40.0-cp35-cp35m-macosx_10_10_intel.whl", hash = "sha256:ba6cc607a9cbff6a45969f6eca730612f6413634cbf70303af56e3db66021f48"}, + {file = "grpcio_tools-1.40.0-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:0531233bfa6599412ffcb711ee02a3302fcc38bd1edc03b4e2443394eea22960"}, + {file = "grpcio_tools-1.40.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:e89722df3a263acd7bb84802e556f5e5616be4e5292b0b7551a740b93360743e"}, + {file = "grpcio_tools-1.40.0-cp35-cp35m-manylinux2014_i686.whl", hash = "sha256:cea28f8fc02fdae07501aa51bf8d89f972f5bcbd87ae56755d71b1b6f9140e4d"}, + {file = "grpcio_tools-1.40.0-cp35-cp35m-manylinux2014_x86_64.whl", hash = "sha256:664ad975e16be1f4c8bfba0c7c7e24c73b5b7c6cf74a3d18825beb720841f775"}, + {file = "grpcio_tools-1.40.0-cp35-cp35m-win32.whl", hash = "sha256:8aa8a081cd8ee094231d7e263266b731bcbedd4a05cd17a5084fc5bcc6cfd7fe"}, + {file = "grpcio_tools-1.40.0-cp35-cp35m-win_amd64.whl", hash = "sha256:45794ebdec5bf7e9623806def361e61ae83f3acda5a53ff9744c13b5657a055a"}, + {file = "grpcio_tools-1.40.0-cp36-cp36m-linux_armv7l.whl", hash = "sha256:4805382c2f57bf393c2f7ec3c89391790f606249bf03cd722a875d3ba46e1b60"}, + {file = "grpcio_tools-1.40.0-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:4b85c4e8037c382dacaf65defbdb4fafee5884b614ef19e03f22026ae2c4735e"}, + {file = "grpcio_tools-1.40.0-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:849ccab9b043518fe38fe2c6c6bd4c6f6a87bad9184378f5269b0158b6d35453"}, + {file = "grpcio_tools-1.40.0-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:cf9d79ed7dea8363c7769324d87e35f2ac67f361c98ac960e45edd76b35df679"}, + {file = "grpcio_tools-1.40.0-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:224d427ff0aacbbdd353721f545340c99d16c204d77b638a688160b331943162"}, + {file = "grpcio_tools-1.40.0-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:07e769dcf8938b5a355802d18b9bc0d7a7372276abce1b624e0af3b2b92e69c3"}, + {file = "grpcio_tools-1.40.0-cp36-cp36m-manylinux_2_24_aarch64.whl", hash = "sha256:ff9ae29f77f9061a9239a93c53535d7f43dfd82a1a08a96c26bf76a232c4fa46"}, + {file = "grpcio_tools-1.40.0-cp36-cp36m-win32.whl", hash = "sha256:00930e260a4900b68f8856c3813a32ec1db94666e140081831869fc92aedce46"}, + {file = "grpcio_tools-1.40.0-cp36-cp36m-win_amd64.whl", hash = "sha256:5f026c6a73476ac1506d8a4c9aa33aaecdc6ac103ed592c98e9b6cc7a123f714"}, + {file = "grpcio_tools-1.40.0-cp37-cp37m-linux_armv7l.whl", hash = "sha256:d540128b3f4cc3a6ec16f0d48019dfa325e979e6a9d96c83979d7dd0adbf50c2"}, + {file = "grpcio_tools-1.40.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:fe262f5cc30eff3bd5a0ca93883a7fe9b1ade5c29282b86714140d6d084ab500"}, + {file = "grpcio_tools-1.40.0-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:9eb0926a21eff5a0e4444a35309a8d2fc18bb53242253f66e5a465a6adecaf48"}, + {file = "grpcio_tools-1.40.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:986dc7a49e6dc2ad2f54cb6637d18c27d2feb96e32a904cc7e861750bda1b46d"}, + {file = "grpcio_tools-1.40.0-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:168c4444f44e386d8b387825cee09278f9311ca125c32207d54c498934ce9bf0"}, + {file = "grpcio_tools-1.40.0-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:bd212d363909b36e32227e0bf734e49909d4e8ba8ddff934b54649e5ba6ef938"}, + {file = "grpcio_tools-1.40.0-cp37-cp37m-manylinux_2_24_aarch64.whl", hash = "sha256:01a3652601ff39027083c004fc8662beebc40ce92e6d77ed96403d3980190508"}, + {file = "grpcio_tools-1.40.0-cp37-cp37m-win32.whl", hash = "sha256:1f3f5d931815059f179c529df3dfd61040e39894f21bcc4c412f35cbefceff15"}, + {file = "grpcio_tools-1.40.0-cp37-cp37m-win_amd64.whl", hash = "sha256:05e10caf77717c59bb70282a8c7013db6ae9c1fbc3fbdf14f7d84dfd5ff18f24"}, + {file = "grpcio_tools-1.40.0-cp38-cp38-linux_armv7l.whl", hash = "sha256:47348f4f61a3a73e5304d096d22412d0ba00738307a5be0793930225c4034c18"}, + {file = "grpcio_tools-1.40.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:6b4235450b51f73db83237fdf54cca1235257eb8a84da0784513352f884bbb59"}, + {file = "grpcio_tools-1.40.0-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:791084528308a9ad403801527eb79074bb45cf71321204bdb12bcd58fcd810f8"}, + {file = "grpcio_tools-1.40.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:4235c3ed899c44a8b528b07a8d98261b369d2072445cea0759310dcb9be5c329"}, + {file = "grpcio_tools-1.40.0-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:47c9e396636a64198d4fcdc9bc8d8ed8835bcc5cca28786f5f24de9feddbd937"}, + {file = "grpcio_tools-1.40.0-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:1d1065d74bbfd955cdacdfc88f4e728d651ccf546c8f2703ab4de4fb864bf073"}, + {file = "grpcio_tools-1.40.0-cp38-cp38-manylinux_2_24_aarch64.whl", hash = "sha256:27c10291fc41465e7931a214b6fbede38a497af766bab71fcbb9851f67a2c49f"}, + {file = "grpcio_tools-1.40.0-cp38-cp38-win32.whl", hash = "sha256:aa681d1986c112d716586bcd51a6bb1ac459fa8a74975e9c8070123c39731221"}, + {file = "grpcio_tools-1.40.0-cp38-cp38-win_amd64.whl", hash = "sha256:ce75b5c56439f6d438f67a0c9dfeec9b4295628447a98e7fe921e26bb321a50e"}, + {file = "grpcio_tools-1.40.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:93372ee8fc6e74af1db7128042ca21d8ceaa32b5de9cff73bed8dfaebdaac7ed"}, + {file = "grpcio_tools-1.40.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:f3a4bcc8bd57dbb53ec97634c5db9e459076dba8baf2c5d3a7e9e59397f0ee83"}, + {file = "grpcio_tools-1.40.0-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:687ec1aa210ac6bdbf7880b34f5f61bc85c3b25db2fc323a946ff570a6a984ea"}, + {file = "grpcio_tools-1.40.0-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:7f6ab2a9ccf50cf20e7be48531bf675b41b153cc1dcfe530c3fcbd430c44bbc9"}, + {file = "grpcio_tools-1.40.0-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:a36c8ebeb348ee95376f2cf10d7530ae34593ab1132f7215e867a9e82ed8117f"}, + {file = "grpcio_tools-1.40.0-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:f5eea943c0524a59113dd2747a13de17a7550e838f59e3fb09c1b4fa131efe27"}, + {file = "grpcio_tools-1.40.0-cp39-cp39-manylinux_2_24_aarch64.whl", hash = "sha256:76063203802e9858ec35cb454b96673a1336a8ae9e7313cc8de1a66ce3c357cf"}, + {file = "grpcio_tools-1.40.0-cp39-cp39-win32.whl", hash = "sha256:0d707e34dd86b4368391df6bbf9f7cb3649517f10655bc9ef4bc638a9c88caa5"}, + {file = "grpcio_tools-1.40.0-cp39-cp39-win_amd64.whl", hash = "sha256:fe6a3ee6667c44432859ee151da766f32fc01f91228ea5a529ae121ba44fef6f"}, ] grpclib = [ {file = "grpclib-0.4.1.tar.gz", hash = "sha256:8c0021cd038634c268249e4cd168d9f3570e66ceceec1c9416094b788ebc8372"}, @@ -1345,26 +1338,27 @@ poethepoet = [ {file = "poethepoet-0.10.0.tar.gz", hash = "sha256:70b97cb194b978dc464c70793e85e6f746cddf82b84a38bfb135946ad71ae19c"}, ] protobuf = [ - {file = "protobuf-3.15.7-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a14141d5c967362d2eedff8825d2b69cc36a5b3ed6b1f618557a04e58a3cf787"}, - {file = "protobuf-3.15.7-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d54d78f621852ec4fdd1484d1263ca04d4bf5ffdf7abffdbb939e444b6ff3385"}, - {file = "protobuf-3.15.7-cp35-cp35m-macosx_10_9_intel.whl", hash = "sha256:462085acdb410b06335315fe7e63cb281a1902856e0f4657f341c283cedc1d56"}, - {file = "protobuf-3.15.7-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:849c92ce112e1ef648705c29ce044248e350f71d9d54a2026830623198f0bd38"}, - {file = "protobuf-3.15.7-cp35-cp35m-win32.whl", hash = "sha256:1f6083382f7714700deadf3014e921711e2f807de7f27e40c32b744701ae5b99"}, - {file = "protobuf-3.15.7-cp35-cp35m-win_amd64.whl", hash = "sha256:e17f60f00081adcb32068ee0bb51e418f6474acf83424244ff3512ffd2166385"}, - {file = "protobuf-3.15.7-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6c75e563c6fb2ca5b8f21dd75c15659aa2c4a0025b9da3a7711ae661cd6a488d"}, - {file = "protobuf-3.15.7-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d939f41b4108350841c4790ebbadb61729e1363522fdb8434eb4e6f2065d0db1"}, - {file = "protobuf-3.15.7-cp36-cp36m-win32.whl", hash = "sha256:24f14c09d4c0a3641f1b0e9b552d026361de65b01686fdd3e5fdf8f9512cd79b"}, - {file = "protobuf-3.15.7-cp36-cp36m-win_amd64.whl", hash = "sha256:1247170191bcb2a8d978d11a58afe391004ec6c2184e4d961baf8102d43ff500"}, - {file = "protobuf-3.15.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:364cadaeec0756afdc099cbd88cb5659bd1bb7d547168d063abcb0272ccbb2f6"}, - {file = "protobuf-3.15.7-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:0c3a6941b1e6e6e22d812a8e5c46bfe83082ea60d262a46f2cfb22d9b9fb17db"}, - {file = "protobuf-3.15.7-cp37-cp37m-win32.whl", hash = "sha256:eb5668f3f6a83b6603ca2e09be5b20de89521ea5914aabe032cce981e4129cc8"}, - {file = "protobuf-3.15.7-cp37-cp37m-win_amd64.whl", hash = "sha256:1001e671cf8476edce7fb72778358d026390649cc35a79d47b2a291684ccfbb2"}, - {file = "protobuf-3.15.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a5ba7dd6f97964655aa7b234c95d80886425a31b7010764f042cdeb985314d18"}, - {file = "protobuf-3.15.7-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:46674bd6fcf8c63b4b9869ba579685db67cf51ae966443dd6bd9a8fa00fcef62"}, - {file = "protobuf-3.15.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4c4399156fb27e3768313b7a59352c861a893252bda6fb9f3643beb3ebb7047e"}, - {file = "protobuf-3.15.7-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:85cd29faf056036167d87445d5a5059034c298881c044e71a73d3b61a4be1c23"}, - {file = "protobuf-3.15.7-py2.py3-none-any.whl", hash = "sha256:22054432b923c0086f9cf1e1c0c52d39bf3c6e31014ea42eec2dabc22ee26d78"}, - {file = "protobuf-3.15.7.tar.gz", hash = "sha256:2d03fc2591543cd2456d0b72230b50c4519546a8d379ac6fd3ecd84c6df61e5d"}, + {file = "protobuf-3.18.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9072cb18fca8998b77f969fb74d25a11d7f4a39a8b1ddc3cf76cd5abda8499cb"}, + {file = "protobuf-3.18.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f589346b5b3f702c1d30e2343c9897e6c35e7bd495c10a0e17d11ecb5ee5bd06"}, + {file = "protobuf-3.18.0-cp36-cp36m-win32.whl", hash = "sha256:93c077fd83879cf48f327a2491c24da447a09da6a7ab3cc311a6f5a61fcb5de0"}, + {file = "protobuf-3.18.0-cp36-cp36m-win_amd64.whl", hash = "sha256:3b5b81bb665aac548b413480f4e0d8c38a74bc4dea57835f288a3ce74f63dfe9"}, + {file = "protobuf-3.18.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d11465040cadcea8ecf5f0b131af5099a9696f9d0bef6f88148b372bacc1c52d"}, + {file = "protobuf-3.18.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:f6138462643adce0ed6e49007a63b7fd7dc4fda1ef4e15a70fcebe76c1407a71"}, + {file = "protobuf-3.18.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:877664b1b8d1e23553634f625e4e12aae4ff16cbbef473f8118c239d478f422a"}, + {file = "protobuf-3.18.0-cp37-cp37m-win32.whl", hash = "sha256:5201333b7aa711965c5769b250f8565a9924e8e27f8b622bbc5e6847aeaab1b1"}, + {file = "protobuf-3.18.0-cp37-cp37m-win_amd64.whl", hash = "sha256:1f3ecec3038c2fb4dad952d3d6cb9ca301999903a09e43794fb348da48f7577f"}, + {file = "protobuf-3.18.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:17181fc0814655812aac108e755bd5185d71aa8d81bd241cec6e232c84097918"}, + {file = "protobuf-3.18.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:7646c20605fbee57e77fdbc4a90175538281b152f46ba17019916593f8062c2a"}, + {file = "protobuf-3.18.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80b0a5157f3a53043daf8eb7cfa1220b27a5a63dd6655dbd8e1e6f7b5dcd6347"}, + {file = "protobuf-3.18.0-cp38-cp38-win32.whl", hash = "sha256:5730de255c95b3403eedd1a568eb28203b913b6192ff5a3fdc3ff30f37107a38"}, + {file = "protobuf-3.18.0-cp38-cp38-win_amd64.whl", hash = "sha256:9147565f93e6699d7512747766598afe63205f226ac7b61f47954974c9aab852"}, + {file = "protobuf-3.18.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:568c049ff002a7523ed33fb612e6b97da002bf87ffb619a1fc3eadf2257a3b31"}, + {file = "protobuf-3.18.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:7e791a94db391ae22b3943fc88f6ba0e1f62b6ad58b33db7517df576c7834d23"}, + {file = "protobuf-3.18.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42c04e66ec5a38ad2171639dc9860c2f9594668f709ea3a4a192acf7346853a7"}, + {file = "protobuf-3.18.0-cp39-cp39-win32.whl", hash = "sha256:0a59ea8da307118372750e2fdfe0961622e675b8dd35e05c42384d618189a938"}, + {file = "protobuf-3.18.0-cp39-cp39-win_amd64.whl", hash = "sha256:f7c8193ec805324ff6024242b00f64a24b94d56b895f62bf28a9d72a228d4fca"}, + {file = "protobuf-3.18.0-py2.py3-none-any.whl", hash = "sha256:615099e52e9fbc9fde00177267a94ca820ecf4e80093e390753568b7d8cb3c1a"}, + {file = "protobuf-3.18.0.tar.gz", hash = "sha256:18b308946a592e245299391e53c01b5b8efc2794f49986e80f37d7b5e60a270f"}, ] py = [ {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, diff --git a/pyproject.toml b/pyproject.toml index 0b650cc..201eb64 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,7 @@ python-dateutil = "^2.8" asv = "^0.4.2" black = "^21.11b0" bpython = "^0.19" -grpcio-tools = "^1.30.0" +grpcio-tools = "^1.40.0" jinja2 = "^2.11.2" mypy = "^0.770" poethepoet = ">=0.9.0" diff --git a/src/betterproto/__init__.py b/src/betterproto/__init__.py index c894aff..3cee888 100644 --- a/src/betterproto/__init__.py +++ b/src/betterproto/__init__.py @@ -145,6 +145,8 @@ class FieldMetadata: group: Optional[str] = None # Describes the wrapped type (e.g. when using google.protobuf.BoolValue) wraps: Optional[str] = None + # Is the field optional + optional: Optional[bool] = False @staticmethod def get(field: dataclasses.Field) -> "FieldMetadata": @@ -159,12 +161,15 @@ def dataclass_field( map_types: Optional[Tuple[str, str]] = None, group: Optional[str] = None, wraps: Optional[str] = None, + optional: bool = False, ) -> dataclasses.Field: """Creates a dataclass field with attached protobuf metadata.""" return dataclasses.field( - default=PLACEHOLDER, + default=None if optional else PLACEHOLDER, metadata={ - "betterproto": FieldMetadata(number, proto_type, map_types, group, wraps) + "betterproto": FieldMetadata( + number, proto_type, map_types, group, wraps, optional + ) }, ) @@ -174,74 +179,107 @@ def dataclass_field( # out at runtime. The generated dataclass variables are still typed correctly. -def enum_field(number: int, group: Optional[str] = None) -> Any: - return dataclass_field(number, TYPE_ENUM, group=group) +def enum_field(number: int, group: Optional[str] = None, optional: bool = False) -> Any: + return dataclass_field(number, TYPE_ENUM, group=group, optional=optional) -def bool_field(number: int, group: Optional[str] = None) -> Any: - return dataclass_field(number, TYPE_BOOL, group=group) +def bool_field(number: int, group: Optional[str] = None, optional: bool = False) -> Any: + return dataclass_field(number, TYPE_BOOL, group=group, optional=optional) -def int32_field(number: int, group: Optional[str] = None) -> Any: - return dataclass_field(number, TYPE_INT32, group=group) +def int32_field( + number: int, group: Optional[str] = None, optional: bool = False +) -> Any: + return dataclass_field(number, TYPE_INT32, group=group, optional=optional) -def int64_field(number: int, group: Optional[str] = None) -> Any: - return dataclass_field(number, TYPE_INT64, group=group) +def int64_field( + number: int, group: Optional[str] = None, optional: bool = False +) -> Any: + return dataclass_field(number, TYPE_INT64, group=group, optional=optional) -def uint32_field(number: int, group: Optional[str] = None) -> Any: - return dataclass_field(number, TYPE_UINT32, group=group) +def uint32_field( + number: int, group: Optional[str] = None, optional: bool = False +) -> Any: + return dataclass_field(number, TYPE_UINT32, group=group, optional=optional) -def uint64_field(number: int, group: Optional[str] = None) -> Any: - return dataclass_field(number, TYPE_UINT64, group=group) +def uint64_field( + number: int, group: Optional[str] = None, optional: bool = False +) -> Any: + return dataclass_field(number, TYPE_UINT64, group=group, optional=optional) -def sint32_field(number: int, group: Optional[str] = None) -> Any: - return dataclass_field(number, TYPE_SINT32, group=group) +def sint32_field( + number: int, group: Optional[str] = None, optional: bool = False +) -> Any: + return dataclass_field(number, TYPE_SINT32, group=group, optional=optional) -def sint64_field(number: int, group: Optional[str] = None) -> Any: - return dataclass_field(number, TYPE_SINT64, group=group) +def sint64_field( + number: int, group: Optional[str] = None, optional: bool = False +) -> Any: + return dataclass_field(number, TYPE_SINT64, group=group, optional=optional) -def float_field(number: int, group: Optional[str] = None) -> Any: - return dataclass_field(number, TYPE_FLOAT, group=group) +def float_field( + number: int, group: Optional[str] = None, optional: bool = False +) -> Any: + return dataclass_field(number, TYPE_FLOAT, group=group, optional=optional) -def double_field(number: int, group: Optional[str] = None) -> Any: - return dataclass_field(number, TYPE_DOUBLE, group=group) +def double_field( + number: int, group: Optional[str] = None, optional: bool = False +) -> Any: + return dataclass_field(number, TYPE_DOUBLE, group=group, optional=optional) -def fixed32_field(number: int, group: Optional[str] = None) -> Any: - return dataclass_field(number, TYPE_FIXED32, group=group) +def fixed32_field( + number: int, group: Optional[str] = None, optional: bool = False +) -> Any: + return dataclass_field(number, TYPE_FIXED32, group=group, optional=optional) -def fixed64_field(number: int, group: Optional[str] = None) -> Any: - return dataclass_field(number, TYPE_FIXED64, group=group) +def fixed64_field( + number: int, group: Optional[str] = None, optional: bool = False +) -> Any: + return dataclass_field(number, TYPE_FIXED64, group=group, optional=optional) -def sfixed32_field(number: int, group: Optional[str] = None) -> Any: - return dataclass_field(number, TYPE_SFIXED32, group=group) +def sfixed32_field( + number: int, group: Optional[str] = None, optional: bool = False +) -> Any: + return dataclass_field(number, TYPE_SFIXED32, group=group, optional=optional) -def sfixed64_field(number: int, group: Optional[str] = None) -> Any: - return dataclass_field(number, TYPE_SFIXED64, group=group) +def sfixed64_field( + number: int, group: Optional[str] = None, optional: bool = False +) -> Any: + return dataclass_field(number, TYPE_SFIXED64, group=group, optional=optional) -def string_field(number: int, group: Optional[str] = None) -> Any: - return dataclass_field(number, TYPE_STRING, group=group) +def string_field( + number: int, group: Optional[str] = None, optional: bool = False +) -> Any: + return dataclass_field(number, TYPE_STRING, group=group, optional=optional) -def bytes_field(number: int, group: Optional[str] = None) -> Any: - return dataclass_field(number, TYPE_BYTES, group=group) +def bytes_field( + number: int, group: Optional[str] = None, optional: bool = False +) -> Any: + return dataclass_field(number, TYPE_BYTES, group=group, optional=optional) def message_field( - number: int, group: Optional[str] = None, wraps: Optional[str] = None + number: int, + group: Optional[str] = None, + wraps: Optional[str] = None, + optional: bool = False, ) -> Any: - return dataclass_field(number, TYPE_MESSAGE, group=group, wraps=wraps) + return dataclass_field( + number, TYPE_MESSAGE, group=group, wraps=wraps, optional=optional + ) def map_field( @@ -586,7 +624,8 @@ class Message(ABC): if meta.group: group_current.setdefault(meta.group) - if self.__raw_get(field_name) != PLACEHOLDER: + value = self.__raw_get(field_name) + if value != PLACEHOLDER and not (meta.optional and value is None): # Found a non-sentinel value all_sentinel = False @@ -701,12 +740,16 @@ class Message(ABC): if value is None: # Optional items should be skipped. This is used for the Google - # wrapper types. + # wrapper types and proto3 field presence/optional fields. continue # Being selected in a a group means this field is the one that is # currently set in a `oneof` group, so it must be serialized even # if the value is the default zero value. + # + # Note that proto3 field presence/optional fields are put in a + # synthetic single-item oneof by protoc, which helps us ensure we + # send the value even if the value is the default zero value. selected_in_group = ( meta.group and self._group_current[meta.group] == field_name ) @@ -829,8 +872,9 @@ class Message(ABC): # This is some kind of list (repeated) field. return list elif t.__origin__ is Union and t.__args__[1] is type(None): - # This is an optional (wrapped) field. For setting the default we - # really don't care what kind of field it is. + # This is an optional field (either wrapped, or using proto3 + # field presence). For setting the default we really don't care + # what kind of field it is. return type(None) else: return t @@ -1041,6 +1085,9 @@ class Message(ABC): ] if value or include_default_values: output[cased_name] = value + elif value is None: + if include_default_values: + output[cased_name] = value elif ( value._serialized_on_wire or include_default_values @@ -1066,6 +1113,9 @@ class Message(ABC): if meta.proto_type in INT_64_TYPES: if field_is_repeated: output[cased_name] = [str(n) for n in value] + elif value is None: + if include_default_values: + output[cased_name] = value else: output[cased_name] = str(value) elif meta.proto_type == TYPE_BYTES: @@ -1073,6 +1123,8 @@ class Message(ABC): output[cased_name] = [ b64encode(b).decode("utf8") for b in value ] + elif value is None and include_default_values: + output[cased_name] = value else: output[cased_name] = b64encode(value).decode("utf8") elif meta.proto_type == TYPE_ENUM: @@ -1085,6 +1137,12 @@ class Message(ABC): else: # transparently upgrade single value to repeated output[cased_name] = [enum_class(value).name] + elif value is None: + if include_default_values: + output[cased_name] = value + elif meta.optional: + enum_class = field_types[field_name].__args__[0] + output[cased_name] = enum_class(value).name else: enum_class = field_types[field_name] # noqa output[cased_name] = enum_class(value).name @@ -1141,6 +1199,9 @@ class Message(ABC): setattr(self, field_name, v) elif meta.wraps: setattr(self, field_name, value[key]) + elif v is None: + cls = self._betterproto.cls_by_field[field_name] + setattr(self, field_name, cls().from_dict(value[key])) else: # NOTE: `from_dict` mutates the underlying message, so no # assignment here is necessary. diff --git a/src/betterproto/lib/google/protobuf/__init__.py b/src/betterproto/lib/google/protobuf/__init__.py index b361c04..e9b6de7 100644 --- a/src/betterproto/lib/google/protobuf/__init__.py +++ b/src/betterproto/lib/google/protobuf/__init__.py @@ -6,6 +6,7 @@ from dataclasses import dataclass from typing import Dict, List import betterproto +from betterproto.grpc.grpclib_server import ServiceBase class Syntax(betterproto.Enum): @@ -46,17 +47,6 @@ class FieldCardinality(betterproto.Enum): CARDINALITY_REPEATED = 3 -class NullValue(betterproto.Enum): - """ - `NullValue` is a singleton enumeration to represent the null value for the - `Value` type union. The JSON representation for `NullValue` is JSON - `null`. - """ - - # Null value. - NULL_VALUE = 0 - - class FieldDescriptorProtoType(betterproto.Enum): TYPE_DOUBLE = 1 TYPE_FLOAT = 2 @@ -108,165 +98,15 @@ class MethodOptionsIdempotencyLevel(betterproto.Enum): IDEMPOTENT = 2 -@dataclass(eq=False, repr=False) -class Timestamp(betterproto.Message): +class NullValue(betterproto.Enum): """ - A Timestamp represents a point in time independent of any time zone or - local calendar, encoded as a count of seconds and fractions of seconds at - nanosecond resolution. The count is relative to an epoch at UTC midnight on - January 1, 1970, in the proleptic Gregorian calendar which extends the - Gregorian calendar backwards to year one. All minutes are 60 seconds long. - Leap seconds are "smeared" so that no leap second table is needed for - interpretation, using a [24-hour linear - smear](https://developers.google.com/time/smear). The range is from - 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By restricting to - that range, we ensure that we can convert to and from [RFC - 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings. # Examples - Example 1: Compute Timestamp from POSIX `time()`. Timestamp timestamp; - timestamp.set_seconds(time(NULL)); timestamp.set_nanos(0); Example 2: - Compute Timestamp from POSIX `gettimeofday()`. struct timeval tv; - gettimeofday(&tv, NULL); Timestamp timestamp; - timestamp.set_seconds(tv.tv_sec); timestamp.set_nanos(tv.tv_usec * - 1000); Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`. - FILETIME ft; GetSystemTimeAsFileTime(&ft); UINT64 ticks = - (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime; // A Windows - tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z // is - 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z. Timestamp - timestamp; timestamp.set_seconds((INT64) ((ticks / 10000000) - - 11644473600LL)); timestamp.set_nanos((INT32) ((ticks % 10000000) * - 100)); Example 4: Compute Timestamp from Java `System.currentTimeMillis()`. - long millis = System.currentTimeMillis(); Timestamp timestamp = - Timestamp.newBuilder().setSeconds(millis / 1000) .setNanos((int) - ((millis % 1000) * 1000000)).build(); Example 5: Compute Timestamp from - current time in Python. timestamp = Timestamp() - timestamp.GetCurrentTime() # JSON Mapping In JSON format, the Timestamp - type is encoded as a string in the [RFC - 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the format is - "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z" where {year} is - always expressed using four digits while {month}, {day}, {hour}, {min}, and - {sec} are zero-padded to two digits each. The fractional seconds, which can - go up to 9 digits (i.e. up to 1 nanosecond resolution), are optional. The - "Z" suffix indicates the timezone ("UTC"); the timezone is required. A - proto3 JSON serializer should always use UTC (as indicated by "Z") when - printing the Timestamp type and a proto3 JSON parser should be able to - accept both UTC and other timezones (as indicated by an offset). For - example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past 01:30 UTC on - January 15, 2017. In JavaScript, one can convert a Date object to this - format using the standard [toISOString()](https://developer.mozilla.org/en- - US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString) method. - In Python, a standard `datetime.datetime` object can be converted to this - format using - [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) - with the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one - can use the Joda Time's [`ISODateTimeFormat.dateTime()`]( - http://www.joda.org/joda- - time/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime%2D%2D ) - to obtain a formatter capable of generating timestamps in this format. + `NullValue` is a singleton enumeration to represent the null value for the + `Value` type union. The JSON representation for `NullValue` is JSON + `null`. """ - # Represents seconds of UTC time since Unix epoch 1970-01-01T00:00:00Z. Must - # be from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59Z inclusive. - seconds: int = betterproto.int64_field(1) - # Non-negative fractions of a second at nanosecond resolution. Negative - # second values with fractions must still have non-negative nanos values that - # count forward in time. Must be from 0 to 999,999,999 inclusive. - nanos: int = betterproto.int32_field(2) - - -@dataclass(eq=False, repr=False) -class FieldMask(betterproto.Message): - """ - `FieldMask` represents a set of symbolic field paths, for example: - paths: "f.a" paths: "f.b.d" Here `f` represents a field in some root - message, `a` and `b` fields in the message found in `f`, and `d` a field - found in the message in `f.b`. Field masks are used to specify a subset of - fields that should be returned by a get operation or modified by an update - operation. Field masks also have a custom JSON encoding (see below). # - Field Masks in Projections When used in the context of a projection, a - response message or sub-message is filtered by the API to only contain - those fields as specified in the mask. For example, if the mask in the - previous example is applied to a response message as follows: f { - a : 22 b { d : 1 x : 2 } y : 13 } - z: 8 The result will not contain specific values for fields x,y and z - (their value will be set to the default, and omitted in proto text output): - f { a : 22 b { d : 1 } } A repeated field is - not allowed except at the last position of a paths string. If a FieldMask - object is not present in a get operation, the operation applies to all - fields (as if a FieldMask of all fields had been specified). Note that a - field mask does not necessarily apply to the top-level response message. In - case of a REST get operation, the field mask applies directly to the - response, but in case of a REST list operation, the mask instead applies to - each individual message in the returned resource list. In case of a REST - custom method, other definitions may be used. Where the mask applies will - be clearly documented together with its declaration in the API. In any - case, the effect on the returned resource/resources is required behavior - for APIs. # Field Masks in Update Operations A field mask in update - operations specifies which fields of the targeted resource are going to be - updated. The API is required to only change the values of the fields as - specified in the mask and leave the others untouched. If a resource is - passed in to describe the updated values, the API ignores the values of all - fields not covered by the mask. If a repeated field is specified for an - update operation, new values will be appended to the existing repeated - field in the target resource. Note that a repeated field is only allowed in - the last position of a `paths` string. If a sub-message is specified in the - last position of the field mask for an update operation, then new value - will be merged into the existing sub-message in the target resource. For - example, given the target message: f { b { d: 1 - x: 2 } c: [1] } And an update message: f { b { - d: 10 } c: [2] } then if the field mask is: paths: ["f.b", - "f.c"] then the result will be: f { b { d: 10 x: - 2 } c: [1, 2] } An implementation may provide options to - override this default behavior for repeated and message fields. In order to - reset a field's value to the default, the field must be in the mask and set - to the default value in the provided resource. Hence, in order to reset all - fields of a resource, provide a default instance of the resource and set - all fields in the mask, or do not provide a mask as described below. If a - field mask is not present on update, the operation applies to all fields - (as if a field mask of all fields has been specified). Note that in the - presence of schema evolution, this may mean that fields the client does not - know and has therefore not filled into the request will be reset to their - default. If this is unwanted behavior, a specific service may require a - client to always specify a field mask, producing an error if not. As with - get operations, the location of the resource which describes the updated - values in the request message depends on the operation kind. In any case, - the effect of the field mask is required to be honored by the API. ## - Considerations for HTTP REST The HTTP kind of an update operation which - uses a field mask must be set to PATCH instead of PUT in order to satisfy - HTTP semantics (PUT must only be used for full updates). # JSON Encoding of - Field Masks In JSON, a field mask is encoded as a single string where paths - are separated by a comma. Fields name in each path are converted to/from - lower-camel naming conventions. As an example, consider the following - message declarations: message Profile { User user = 1; - Photo photo = 2; } message User { string display_name = 1; - string address = 2; } In proto a field mask for `Profile` may look as - such: mask { paths: "user.display_name" paths: "photo" - } In JSON, the same mask is represented as below: { mask: - "user.displayName,photo" } # Field Masks and Oneof Fields Field masks - treat fields in oneofs just as regular fields. Consider the following - message: message SampleMessage { oneof test_oneof { - string name = 4; SubMessage sub_message = 9; } } The - field mask can be: mask { paths: "name" } Or: mask { - paths: "sub_message" } Note that oneof type names ("test_oneof" in this - case) cannot be used in paths. ## Field Mask Verification The - implementation of any API method which has a FieldMask type field in the - request should verify the included field paths, and return an - `INVALID_ARGUMENT` error if any path is unmappable. - """ - - # The set of field mask paths. - paths: List[str] = betterproto.string_field(1) - - -@dataclass(eq=False, repr=False) -class SourceContext(betterproto.Message): - """ - `SourceContext` represents information about the source of a protobuf - element, like the file in which it is defined. - """ - - # The path-qualified name of the .proto file that contained the associated - # protobuf element. For example: `"google/protobuf/source_context.proto"`. - file_name: str = betterproto.string_field(1) + # Null value. + NULL_VALUE = 0 @dataclass(eq=False, repr=False) @@ -283,24 +123,25 @@ class Any(betterproto.Message): Example 3: Pack and unpack a message in Python. foo = Foo(...) any = Any() any.Pack(foo) ... if any.Is(Foo.DESCRIPTOR): any.Unpack(foo) ... Example 4: Pack and unpack a message in Go - foo := &pb.Foo{...} any, err := ptypes.MarshalAny(foo) ... - foo := &pb.Foo{} if err := ptypes.UnmarshalAny(any, foo); err != nil { - ... } The pack methods provided by protobuf library will by default - use 'type.googleapis.com/full.type.name' as the type URL and the unpack - methods only use the fully qualified type name after the last '/' in the - type URL, for example "foo.bar.com/x/y.z" will yield type name "y.z". JSON - ==== The JSON representation of an `Any` value uses the regular - representation of the deserialized, embedded message, with an additional - field `@type` which contains the type URL. Example: package - google.profile; message Person { string first_name = 1; - string last_name = 2; } { "@type": - "type.googleapis.com/google.profile.Person", "firstName": , - "lastName": } If the embedded message type is well-known and - has a custom JSON representation, that representation will be embedded - adding a field `value` which holds the custom JSON in addition to the - `@type` field. Example (for message [google.protobuf.Duration][]): { - "@type": "type.googleapis.com/google.protobuf.Duration", "value": - "1.212s" } + foo := &pb.Foo{...} any, err := anypb.New(foo) if err != nil { + ... } ... foo := &pb.Foo{} if err := + any.UnmarshalTo(foo); err != nil { ... } The pack methods + provided by protobuf library will by default use + 'type.googleapis.com/full.type.name' as the type URL and the unpack methods + only use the fully qualified type name after the last '/' in the type URL, + for example "foo.bar.com/x/y.z" will yield type name "y.z". JSON ==== The + JSON representation of an `Any` value uses the regular representation of + the deserialized, embedded message, with an additional field `@type` which + contains the type URL. Example: package google.profile; message + Person { string first_name = 1; string last_name = 2; } + { "@type": "type.googleapis.com/google.profile.Person", + "firstName": , "lastName": } If the embedded + message type is well-known and has a custom JSON representation, that + representation will be embedded adding a field `value` which holds the + custom JSON in addition to the `@type` field. Example (for message + [google.protobuf.Duration][]): { "@type": + "type.googleapis.com/google.protobuf.Duration", "value": "1.212s" + } """ # A URL/resource name that uniquely identifies the type of the serialized @@ -327,6 +168,18 @@ class Any(betterproto.Message): value: bytes = betterproto.bytes_field(2) +@dataclass(eq=False, repr=False) +class SourceContext(betterproto.Message): + """ + `SourceContext` represents information about the source of a protobuf + element, like the file in which it is defined. + """ + + # The path-qualified name of the .proto file that contained the associated + # protobuf element. For example: `"google/protobuf/source_context.proto"`. + file_name: str = betterproto.string_field(1) + + @dataclass(eq=False, repr=False) class Type(betterproto.Message): """A protocol buffer message type.""" @@ -510,7 +363,7 @@ class Mixin(betterproto.Message): implies that all methods in `AccessControl` are also declared with same name and request/response types in `Storage`. A documentation generator or annotation processor will see the effective `Storage.GetAcl` method after - inherting documentation and annotations as follows: service Storage { + inheriting documentation and annotations as follows: service Storage { // Get the underlying ACL object. rpc GetAcl(GetAclRequest) returns (Acl) { option (google.api.http).get = "/v2/{resource=**}:getAcl"; } ... } Note how the version in the path pattern changed from @@ -530,215 +383,6 @@ class Mixin(betterproto.Message): root: str = betterproto.string_field(2) -@dataclass(eq=False, repr=False) -class Duration(betterproto.Message): - """ - A Duration represents a signed, fixed-length span of time represented as a - count of seconds and fractions of seconds at nanosecond resolution. It is - independent of any calendar and concepts like "day" or "month". It is - related to Timestamp in that the difference between two Timestamp values is - a Duration and it can be added or subtracted from a Timestamp. Range is - approximately +-10,000 years. # Examples Example 1: Compute Duration from - two Timestamps in pseudo code. Timestamp start = ...; Timestamp end - = ...; Duration duration = ...; duration.seconds = end.seconds - - start.seconds; duration.nanos = end.nanos - start.nanos; if - (duration.seconds < 0 && duration.nanos > 0) { duration.seconds += 1; - duration.nanos -= 1000000000; } else if (duration.seconds > 0 && - duration.nanos < 0) { duration.seconds -= 1; duration.nanos += - 1000000000; } Example 2: Compute Timestamp from Timestamp + Duration in - pseudo code. Timestamp start = ...; Duration duration = ...; - Timestamp end = ...; end.seconds = start.seconds + duration.seconds; - end.nanos = start.nanos + duration.nanos; if (end.nanos < 0) { - end.seconds -= 1; end.nanos += 1000000000; } else if (end.nanos - >= 1000000000) { end.seconds += 1; end.nanos -= 1000000000; - } Example 3: Compute Duration from datetime.timedelta in Python. td = - datetime.timedelta(days=3, minutes=10) duration = Duration() - duration.FromTimedelta(td) # JSON Mapping In JSON format, the Duration type - is encoded as a string rather than an object, where the string ends in the - suffix "s" (indicating seconds) and is preceded by the number of seconds, - with nanoseconds expressed as fractional seconds. For example, 3 seconds - with 0 nanoseconds should be encoded in JSON format as "3s", while 3 - seconds and 1 nanosecond should be expressed in JSON format as - "3.000000001s", and 3 seconds and 1 microsecond should be expressed in JSON - format as "3.000001s". - """ - - # Signed seconds of the span of time. Must be from -315,576,000,000 to - # +315,576,000,000 inclusive. Note: these bounds are computed from: 60 - # sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years - seconds: int = betterproto.int64_field(1) - # Signed fractions of a second at nanosecond resolution of the span of time. - # Durations less than one second are represented with a 0 `seconds` field and - # a positive or negative `nanos` field. For durations of one second or more, - # a non-zero value for the `nanos` field must be of the same sign as the - # `seconds` field. Must be from -999,999,999 to +999,999,999 inclusive. - nanos: int = betterproto.int32_field(2) - - -@dataclass(eq=False, repr=False) -class Struct(betterproto.Message): - """ - `Struct` represents a structured data value, consisting of fields which map - to dynamically typed values. In some languages, `Struct` might be supported - by a native representation. For example, in scripting languages like JS a - struct is represented as an object. The details of that representation are - described together with the proto support for the language. The JSON - representation for `Struct` is JSON object. - """ - - # Unordered map of dynamically typed values. - fields: Dict[str, "Value"] = betterproto.map_field( - 1, betterproto.TYPE_STRING, betterproto.TYPE_MESSAGE - ) - - -@dataclass(eq=False, repr=False) -class Value(betterproto.Message): - """ - `Value` represents a dynamically typed value which can be either null, a - number, a string, a boolean, a recursive struct value, or a list of values. - A producer of value is expected to set one of that variants, absence of any - variant indicates an error. The JSON representation for `Value` is JSON - value. - """ - - # Represents a null value. - null_value: "NullValue" = betterproto.enum_field(1, group="kind") - # Represents a double value. - number_value: float = betterproto.double_field(2, group="kind") - # Represents a string value. - string_value: str = betterproto.string_field(3, group="kind") - # Represents a boolean value. - bool_value: bool = betterproto.bool_field(4, group="kind") - # Represents a structured value. - struct_value: "Struct" = betterproto.message_field(5, group="kind") - # Represents a repeated `Value`. - list_value: "ListValue" = betterproto.message_field(6, group="kind") - - -@dataclass(eq=False, repr=False) -class ListValue(betterproto.Message): - """ - `ListValue` is a wrapper around a repeated field of values. The JSON - representation for `ListValue` is JSON array. - """ - - # Repeated field of dynamically typed values. - values: List["Value"] = betterproto.message_field(1) - - -@dataclass(eq=False, repr=False) -class DoubleValue(betterproto.Message): - """ - Wrapper message for `double`. The JSON representation for `DoubleValue` is - JSON number. - """ - - # The double value. - value: float = betterproto.double_field(1) - - -@dataclass(eq=False, repr=False) -class FloatValue(betterproto.Message): - """ - Wrapper message for `float`. The JSON representation for `FloatValue` is - JSON number. - """ - - # The float value. - value: float = betterproto.float_field(1) - - -@dataclass(eq=False, repr=False) -class Int64Value(betterproto.Message): - """ - Wrapper message for `int64`. The JSON representation for `Int64Value` is - JSON string. - """ - - # The int64 value. - value: int = betterproto.int64_field(1) - - -@dataclass(eq=False, repr=False) -class UInt64Value(betterproto.Message): - """ - Wrapper message for `uint64`. The JSON representation for `UInt64Value` is - JSON string. - """ - - # The uint64 value. - value: int = betterproto.uint64_field(1) - - -@dataclass(eq=False, repr=False) -class Int32Value(betterproto.Message): - """ - Wrapper message for `int32`. The JSON representation for `Int32Value` is - JSON number. - """ - - # The int32 value. - value: int = betterproto.int32_field(1) - - -@dataclass(eq=False, repr=False) -class UInt32Value(betterproto.Message): - """ - Wrapper message for `uint32`. The JSON representation for `UInt32Value` is - JSON number. - """ - - # The uint32 value. - value: int = betterproto.uint32_field(1) - - -@dataclass(eq=False, repr=False) -class BoolValue(betterproto.Message): - """ - Wrapper message for `bool`. The JSON representation for `BoolValue` is JSON - `true` and `false`. - """ - - # The bool value. - value: bool = betterproto.bool_field(1) - - -@dataclass(eq=False, repr=False) -class StringValue(betterproto.Message): - """ - Wrapper message for `string`. The JSON representation for `StringValue` is - JSON string. - """ - - # The string value. - value: str = betterproto.string_field(1) - - -@dataclass(eq=False, repr=False) -class BytesValue(betterproto.Message): - """ - Wrapper message for `bytes`. The JSON representation for `BytesValue` is - JSON string. - """ - - # The bytes value. - value: bytes = betterproto.bytes_field(1) - - -@dataclass(eq=False, repr=False) -class Empty(betterproto.Message): - """ - A generic empty message that you can re-use to avoid defining duplicated - empty messages in your APIs. A typical example is to use it as the request - or the response type of an API method. For instance: service Foo { - rpc Bar(google.protobuf.Empty) returns (google.protobuf.Empty); } The - JSON representation for `Empty` is empty JSON object `{}`. - """ - - pass - - @dataclass(eq=False, repr=False) class FileDescriptorSet(betterproto.Message): """ @@ -855,6 +499,23 @@ class FieldDescriptorProto(betterproto.Message): # camelCase. json_name: str = betterproto.string_field(10) options: "FieldOptions" = betterproto.message_field(8) + # If true, this is a proto3 "optional". When a proto3 field is optional, it + # tracks presence regardless of field type. When proto3_optional is true, + # this field must be belong to a oneof to signal to old proto3 clients that + # presence is tracked for this field. This oneof is known as a "synthetic" + # oneof, and this field must be its sole member (each proto3 optional field + # gets its own synthetic oneof). Synthetic oneofs exist in the descriptor + # only, and do not generate any API. Synthetic oneofs must be ordered after + # all "real" oneofs. For message fields, proto3_optional doesn't create any + # semantic change, since non-repeated message fields always track presence. + # However it still indicates the semantic detail of whether the user wrote + # "optional" or not. This can be useful for round-tripping the .proto file. + # For consistency we give message fields a synthetic oneof also, even though + # it is not required to track presence. This is especially important because + # the parser can't tell if a field is a message or an enum, so it must always + # create a synthetic oneof. Proto2 optional fields do not set this flag, + # because they already indicate optional with `LABEL_OPTIONAL`. + proto3_optional: bool = betterproto.bool_field(17) @dataclass(eq=False, repr=False) @@ -937,17 +598,18 @@ class FileOptions(betterproto.Message): # inappropriate because proto packages do not normally start with backwards # domain names. java_package: str = betterproto.string_field(1) - # If set, all the classes from the .proto file are wrapped in a single outer - # class with the given name. This applies to both Proto1 (equivalent to the - # old "--one_java_file" option) and Proto2 (where a .proto always translates - # to a single class, but you may want to explicitly choose the class name). + # Controls the name of the wrapper Java class generated for the .proto file. + # That class will always contain the .proto file's getDescriptor() method as + # well as any top-level extensions defined in the .proto file. If + # java_multiple_files is disabled, then all the other classes from the .proto + # file will be nested inside the single wrapper outer class. java_outer_classname: str = betterproto.string_field(8) - # If set true, then the Java code generator will generate a separate .java + # If enabled, then the Java code generator will generate a separate .java # file for each top-level message, enum, and service defined in the .proto - # file. Thus, these types will *not* be nested inside the outer class named - # by java_outer_classname. However, the outer class will still be generated - # to contain the file's getDescriptor() method as well as any top-level - # extensions defined in the file. + # file. Thus, these types will *not* be nested inside the wrapper class + # named by java_outer_classname. However, the wrapper class will still be + # generated to contain the file's getDescriptor() method as well as any top- + # level extensions defined in the file. java_multiple_files: bool = betterproto.bool_field(10) # This option does nothing. java_generate_equals_and_hash: bool = betterproto.bool_field(20) @@ -1315,3 +977,363 @@ class GeneratedCodeInfoAnnotation(betterproto.Message): # the identified offset. The end offset should be one past the last relevant # byte (so the length of the text = end - begin). end: int = betterproto.int32_field(4) + + +@dataclass(eq=False, repr=False) +class Duration(betterproto.Message): + """ + A Duration represents a signed, fixed-length span of time represented as a + count of seconds and fractions of seconds at nanosecond resolution. It is + independent of any calendar and concepts like "day" or "month". It is + related to Timestamp in that the difference between two Timestamp values is + a Duration and it can be added or subtracted from a Timestamp. Range is + approximately +-10,000 years. # Examples Example 1: Compute Duration from + two Timestamps in pseudo code. Timestamp start = ...; Timestamp end + = ...; Duration duration = ...; duration.seconds = end.seconds - + start.seconds; duration.nanos = end.nanos - start.nanos; if + (duration.seconds < 0 && duration.nanos > 0) { duration.seconds += 1; + duration.nanos -= 1000000000; } else if (duration.seconds > 0 && + duration.nanos < 0) { duration.seconds -= 1; duration.nanos += + 1000000000; } Example 2: Compute Timestamp from Timestamp + Duration in + pseudo code. Timestamp start = ...; Duration duration = ...; + Timestamp end = ...; end.seconds = start.seconds + duration.seconds; + end.nanos = start.nanos + duration.nanos; if (end.nanos < 0) { + end.seconds -= 1; end.nanos += 1000000000; } else if (end.nanos + >= 1000000000) { end.seconds += 1; end.nanos -= 1000000000; + } Example 3: Compute Duration from datetime.timedelta in Python. td = + datetime.timedelta(days=3, minutes=10) duration = Duration() + duration.FromTimedelta(td) # JSON Mapping In JSON format, the Duration type + is encoded as a string rather than an object, where the string ends in the + suffix "s" (indicating seconds) and is preceded by the number of seconds, + with nanoseconds expressed as fractional seconds. For example, 3 seconds + with 0 nanoseconds should be encoded in JSON format as "3s", while 3 + seconds and 1 nanosecond should be expressed in JSON format as + "3.000000001s", and 3 seconds and 1 microsecond should be expressed in JSON + format as "3.000001s". + """ + + # Signed seconds of the span of time. Must be from -315,576,000,000 to + # +315,576,000,000 inclusive. Note: these bounds are computed from: 60 + # sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years + seconds: int = betterproto.int64_field(1) + # Signed fractions of a second at nanosecond resolution of the span of time. + # Durations less than one second are represented with a 0 `seconds` field and + # a positive or negative `nanos` field. For durations of one second or more, + # a non-zero value for the `nanos` field must be of the same sign as the + # `seconds` field. Must be from -999,999,999 to +999,999,999 inclusive. + nanos: int = betterproto.int32_field(2) + + +@dataclass(eq=False, repr=False) +class Empty(betterproto.Message): + """ + A generic empty message that you can re-use to avoid defining duplicated + empty messages in your APIs. A typical example is to use it as the request + or the response type of an API method. For instance: service Foo { + rpc Bar(google.protobuf.Empty) returns (google.protobuf.Empty); } The + JSON representation for `Empty` is empty JSON object `{}`. + """ + + pass + + +@dataclass(eq=False, repr=False) +class FieldMask(betterproto.Message): + """ + `FieldMask` represents a set of symbolic field paths, for example: + paths: "f.a" paths: "f.b.d" Here `f` represents a field in some root + message, `a` and `b` fields in the message found in `f`, and `d` a field + found in the message in `f.b`. Field masks are used to specify a subset of + fields that should be returned by a get operation or modified by an update + operation. Field masks also have a custom JSON encoding (see below). # + Field Masks in Projections When used in the context of a projection, a + response message or sub-message is filtered by the API to only contain + those fields as specified in the mask. For example, if the mask in the + previous example is applied to a response message as follows: f { + a : 22 b { d : 1 x : 2 } y : 13 } + z: 8 The result will not contain specific values for fields x,y and z + (their value will be set to the default, and omitted in proto text output): + f { a : 22 b { d : 1 } } A repeated field is + not allowed except at the last position of a paths string. If a FieldMask + object is not present in a get operation, the operation applies to all + fields (as if a FieldMask of all fields had been specified). Note that a + field mask does not necessarily apply to the top-level response message. In + case of a REST get operation, the field mask applies directly to the + response, but in case of a REST list operation, the mask instead applies to + each individual message in the returned resource list. In case of a REST + custom method, other definitions may be used. Where the mask applies will + be clearly documented together with its declaration in the API. In any + case, the effect on the returned resource/resources is required behavior + for APIs. # Field Masks in Update Operations A field mask in update + operations specifies which fields of the targeted resource are going to be + updated. The API is required to only change the values of the fields as + specified in the mask and leave the others untouched. If a resource is + passed in to describe the updated values, the API ignores the values of all + fields not covered by the mask. If a repeated field is specified for an + update operation, new values will be appended to the existing repeated + field in the target resource. Note that a repeated field is only allowed in + the last position of a `paths` string. If a sub-message is specified in the + last position of the field mask for an update operation, then new value + will be merged into the existing sub-message in the target resource. For + example, given the target message: f { b { d: 1 + x: 2 } c: [1] } And an update message: f { b { + d: 10 } c: [2] } then if the field mask is: paths: ["f.b", + "f.c"] then the result will be: f { b { d: 10 x: + 2 } c: [1, 2] } An implementation may provide options to + override this default behavior for repeated and message fields. In order to + reset a field's value to the default, the field must be in the mask and set + to the default value in the provided resource. Hence, in order to reset all + fields of a resource, provide a default instance of the resource and set + all fields in the mask, or do not provide a mask as described below. If a + field mask is not present on update, the operation applies to all fields + (as if a field mask of all fields has been specified). Note that in the + presence of schema evolution, this may mean that fields the client does not + know and has therefore not filled into the request will be reset to their + default. If this is unwanted behavior, a specific service may require a + client to always specify a field mask, producing an error if not. As with + get operations, the location of the resource which describes the updated + values in the request message depends on the operation kind. In any case, + the effect of the field mask is required to be honored by the API. ## + Considerations for HTTP REST The HTTP kind of an update operation which + uses a field mask must be set to PATCH instead of PUT in order to satisfy + HTTP semantics (PUT must only be used for full updates). # JSON Encoding of + Field Masks In JSON, a field mask is encoded as a single string where paths + are separated by a comma. Fields name in each path are converted to/from + lower-camel naming conventions. As an example, consider the following + message declarations: message Profile { User user = 1; + Photo photo = 2; } message User { string display_name = 1; + string address = 2; } In proto a field mask for `Profile` may look as + such: mask { paths: "user.display_name" paths: "photo" + } In JSON, the same mask is represented as below: { mask: + "user.displayName,photo" } # Field Masks and Oneof Fields Field masks + treat fields in oneofs just as regular fields. Consider the following + message: message SampleMessage { oneof test_oneof { + string name = 4; SubMessage sub_message = 9; } } The + field mask can be: mask { paths: "name" } Or: mask { + paths: "sub_message" } Note that oneof type names ("test_oneof" in this + case) cannot be used in paths. ## Field Mask Verification The + implementation of any API method which has a FieldMask type field in the + request should verify the included field paths, and return an + `INVALID_ARGUMENT` error if any path is unmappable. + """ + + # The set of field mask paths. + paths: List[str] = betterproto.string_field(1) + + +@dataclass(eq=False, repr=False) +class Struct(betterproto.Message): + """ + `Struct` represents a structured data value, consisting of fields which map + to dynamically typed values. In some languages, `Struct` might be supported + by a native representation. For example, in scripting languages like JS a + struct is represented as an object. The details of that representation are + described together with the proto support for the language. The JSON + representation for `Struct` is JSON object. + """ + + # Unordered map of dynamically typed values. + fields: Dict[str, "Value"] = betterproto.map_field( + 1, betterproto.TYPE_STRING, betterproto.TYPE_MESSAGE + ) + + +@dataclass(eq=False, repr=False) +class Value(betterproto.Message): + """ + `Value` represents a dynamically typed value which can be either null, a + number, a string, a boolean, a recursive struct value, or a list of values. + A producer of value is expected to set one of that variants, absence of any + variant indicates an error. The JSON representation for `Value` is JSON + value. + """ + + # Represents a null value. + null_value: "NullValue" = betterproto.enum_field(1, group="kind") + # Represents a double value. + number_value: float = betterproto.double_field(2, group="kind") + # Represents a string value. + string_value: str = betterproto.string_field(3, group="kind") + # Represents a boolean value. + bool_value: bool = betterproto.bool_field(4, group="kind") + # Represents a structured value. + struct_value: "Struct" = betterproto.message_field(5, group="kind") + # Represents a repeated `Value`. + list_value: "ListValue" = betterproto.message_field(6, group="kind") + + +@dataclass(eq=False, repr=False) +class ListValue(betterproto.Message): + """ + `ListValue` is a wrapper around a repeated field of values. The JSON + representation for `ListValue` is JSON array. + """ + + # Repeated field of dynamically typed values. + values: List["Value"] = betterproto.message_field(1) + + +@dataclass(eq=False, repr=False) +class Timestamp(betterproto.Message): + """ + A Timestamp represents a point in time independent of any time zone or + local calendar, encoded as a count of seconds and fractions of seconds at + nanosecond resolution. The count is relative to an epoch at UTC midnight on + January 1, 1970, in the proleptic Gregorian calendar which extends the + Gregorian calendar backwards to year one. All minutes are 60 seconds long. + Leap seconds are "smeared" so that no leap second table is needed for + interpretation, using a [24-hour linear + smear](https://developers.google.com/time/smear). The range is from + 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By restricting to + that range, we ensure that we can convert to and from [RFC + 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings. # Examples + Example 1: Compute Timestamp from POSIX `time()`. Timestamp timestamp; + timestamp.set_seconds(time(NULL)); timestamp.set_nanos(0); Example 2: + Compute Timestamp from POSIX `gettimeofday()`. struct timeval tv; + gettimeofday(&tv, NULL); Timestamp timestamp; + timestamp.set_seconds(tv.tv_sec); timestamp.set_nanos(tv.tv_usec * + 1000); Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`. + FILETIME ft; GetSystemTimeAsFileTime(&ft); UINT64 ticks = + (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime; // A Windows + tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z // is + 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z. Timestamp + timestamp; timestamp.set_seconds((INT64) ((ticks / 10000000) - + 11644473600LL)); timestamp.set_nanos((INT32) ((ticks % 10000000) * + 100)); Example 4: Compute Timestamp from Java `System.currentTimeMillis()`. + long millis = System.currentTimeMillis(); Timestamp timestamp = + Timestamp.newBuilder().setSeconds(millis / 1000) .setNanos((int) + ((millis % 1000) * 1000000)).build(); Example 5: Compute Timestamp from + Java `Instant.now()`. Instant now = Instant.now(); Timestamp + timestamp = Timestamp.newBuilder().setSeconds(now.getEpochSecond()) + .setNanos(now.getNano()).build(); Example 6: Compute Timestamp from current + time in Python. timestamp = Timestamp() timestamp.GetCurrentTime() + # JSON Mapping In JSON format, the Timestamp type is encoded as a string in + the [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the + format is "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z" where + {year} is always expressed using four digits while {month}, {day}, {hour}, + {min}, and {sec} are zero-padded to two digits each. The fractional + seconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution), + are optional. The "Z" suffix indicates the timezone ("UTC"); the timezone + is required. A proto3 JSON serializer should always use UTC (as indicated + by "Z") when printing the Timestamp type and a proto3 JSON parser should be + able to accept both UTC and other timezones (as indicated by an offset). + For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past 01:30 UTC + on January 15, 2017. In JavaScript, one can convert a Date object to this + format using the standard [toISOString()](https://developer.mozilla.org/en- + US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString) method. + In Python, a standard `datetime.datetime` object can be converted to this + format using + [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) + with the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one + can use the Joda Time's [`ISODateTimeFormat.dateTime()`]( + http://www.joda.org/joda- + time/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime%2D%2D ) + to obtain a formatter capable of generating timestamps in this format. + """ + + # Represents seconds of UTC time since Unix epoch 1970-01-01T00:00:00Z. Must + # be from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59Z inclusive. + seconds: int = betterproto.int64_field(1) + # Non-negative fractions of a second at nanosecond resolution. Negative + # second values with fractions must still have non-negative nanos values that + # count forward in time. Must be from 0 to 999,999,999 inclusive. + nanos: int = betterproto.int32_field(2) + + +@dataclass(eq=False, repr=False) +class DoubleValue(betterproto.Message): + """ + Wrapper message for `double`. The JSON representation for `DoubleValue` is + JSON number. + """ + + # The double value. + value: float = betterproto.double_field(1) + + +@dataclass(eq=False, repr=False) +class FloatValue(betterproto.Message): + """ + Wrapper message for `float`. The JSON representation for `FloatValue` is + JSON number. + """ + + # The float value. + value: float = betterproto.float_field(1) + + +@dataclass(eq=False, repr=False) +class Int64Value(betterproto.Message): + """ + Wrapper message for `int64`. The JSON representation for `Int64Value` is + JSON string. + """ + + # The int64 value. + value: int = betterproto.int64_field(1) + + +@dataclass(eq=False, repr=False) +class UInt64Value(betterproto.Message): + """ + Wrapper message for `uint64`. The JSON representation for `UInt64Value` is + JSON string. + """ + + # The uint64 value. + value: int = betterproto.uint64_field(1) + + +@dataclass(eq=False, repr=False) +class Int32Value(betterproto.Message): + """ + Wrapper message for `int32`. The JSON representation for `Int32Value` is + JSON number. + """ + + # The int32 value. + value: int = betterproto.int32_field(1) + + +@dataclass(eq=False, repr=False) +class UInt32Value(betterproto.Message): + """ + Wrapper message for `uint32`. The JSON representation for `UInt32Value` is + JSON number. + """ + + # The uint32 value. + value: int = betterproto.uint32_field(1) + + +@dataclass(eq=False, repr=False) +class BoolValue(betterproto.Message): + """ + Wrapper message for `bool`. The JSON representation for `BoolValue` is JSON + `true` and `false`. + """ + + # The bool value. + value: bool = betterproto.bool_field(1) + + +@dataclass(eq=False, repr=False) +class StringValue(betterproto.Message): + """ + Wrapper message for `string`. The JSON representation for `StringValue` is + JSON string. + """ + + # The string value. + value: str = betterproto.string_field(1) + + +@dataclass(eq=False, repr=False) +class BytesValue(betterproto.Message): + """ + Wrapper message for `bytes`. The JSON representation for `BytesValue` is + JSON string. + """ + + # The bytes value. + value: bytes = betterproto.bytes_field(1) diff --git a/src/betterproto/lib/google/protobuf/compiler/__init__.py b/src/betterproto/lib/google/protobuf/compiler/__init__.py index e5c8b60..cb2396d 100644 --- a/src/betterproto/lib/google/protobuf/compiler/__init__.py +++ b/src/betterproto/lib/google/protobuf/compiler/__init__.py @@ -5,6 +5,12 @@ from dataclasses import dataclass from typing import List import betterproto +from betterproto.grpc.grpclib_server import ServiceBase + + +class CodeGeneratorResponseFeature(betterproto.Enum): + FEATURE_NONE = 0 + FEATURE_PROTO3_OPTIONAL = 1 @dataclass(eq=False, repr=False) @@ -59,6 +65,9 @@ class CodeGeneratorResponse(betterproto.Message): # unparseable -- should be reported by writing a message to stderr and # exiting with a non-zero status code. error: str = betterproto.string_field(1) + # A bitmask of supported features that the code generator supports. This is a + # bitwise "or" of values from the Feature enum. + supported_features: int = betterproto.uint64_field(2) file: List["CodeGeneratorResponseFile"] = betterproto.message_field(15) @@ -108,6 +117,12 @@ class CodeGeneratorResponseFile(betterproto.Message): insertion_point: str = betterproto.string_field(2) # The file contents. content: str = betterproto.string_field(15) + # Information describing the file content being inserted. If an insertion + # point is used, this information will be appropriately offset and inserted + # into the code generation metadata for the generated files. + generated_code_info: "betterproto_lib_google_protobuf.GeneratedCodeInfo" = ( + betterproto.message_field(16) + ) import betterproto.lib.google.protobuf as betterproto_lib_google_protobuf diff --git a/src/betterproto/plugin/main.py b/src/betterproto/plugin/main.py index e0b2557..8982321 100755 --- a/src/betterproto/plugin/main.py +++ b/src/betterproto/plugin/main.py @@ -28,11 +28,8 @@ def main() -> None: if dump_file: dump_request(dump_file, request) - # Create response - response = CodeGeneratorResponse() - # Generate code - generate_code(request, response) + response = generate_code(request) # Serialise response message output = response.SerializeToString() diff --git a/src/betterproto/plugin/models.py b/src/betterproto/plugin/models.py index e58092f..8401400 100644 --- a/src/betterproto/plugin/models.py +++ b/src/betterproto/plugin/models.py @@ -389,6 +389,8 @@ class FieldCompiler(MessageCompiler): args = [] if self.field_wraps: args.append(f"wraps={self.field_wraps}") + if self.optional: + args.append(f"optional=True") return args @property @@ -444,6 +446,10 @@ class FieldCompiler(MessageCompiler): and not is_map(self.proto_obj, self.parent) ) + @property + def optional(self) -> bool: + return self.proto_obj.proto3_optional + @property def mutable(self) -> bool: """True if the field is a mutable type, otherwise False.""" @@ -463,6 +469,8 @@ class FieldCompiler(MessageCompiler): """Python representation of the default proto value.""" if self.repeated: return "[]" + if self.optional: + return "None" if self.py_type == "int": return "0" if self.py_type == "float": @@ -530,6 +538,8 @@ class FieldCompiler(MessageCompiler): py_type = f"builtins.{py_type}" if self.repeated: return f"List[{py_type}]" + if self.optional: + return f"Optional[{py_type}]" return py_type diff --git a/src/betterproto/plugin/parser.py b/src/betterproto/plugin/parser.py index 5e929d8..21a2caf 100644 --- a/src/betterproto/plugin/parser.py +++ b/src/betterproto/plugin/parser.py @@ -8,6 +8,7 @@ from betterproto.lib.google.protobuf import ( from betterproto.lib.google.protobuf.compiler import ( CodeGeneratorRequest, CodeGeneratorResponse, + CodeGeneratorResponseFeature, CodeGeneratorResponseFile, ) import itertools @@ -60,10 +61,11 @@ def traverse( ) -def generate_code( - request: CodeGeneratorRequest, response: CodeGeneratorResponse -) -> None: +def generate_code(request: CodeGeneratorRequest) -> CodeGeneratorResponse: + response = CodeGeneratorResponse() + plugin_options = request.parameter.split(",") if request.parameter else [] + response.supported_features = CodeGeneratorResponseFeature.FEATURE_PROTO3_OPTIONAL request_data = PluginRequestCompiler(plugin_request_obj=request) # Gather output packages @@ -133,6 +135,8 @@ def generate_code( for output_package_name in sorted(output_paths.union(init_files)): print(f"Writing {output_package_name}", file=sys.stderr) + return response + def read_protobuf_type( item: DescriptorProto, diff --git a/tests/inputs/proto3_field_presence/proto3_field_presence.json b/tests/inputs/proto3_field_presence/proto3_field_presence.json new file mode 100644 index 0000000..3cc3ccb --- /dev/null +++ b/tests/inputs/proto3_field_presence/proto3_field_presence.json @@ -0,0 +1,12 @@ +{ + "test1": 128, + "test2": true, + "test3": "A value", + "test4": "aGVsbG8=", + "test5": { + "test": "Hello" + }, + "test6": "B", + "test7": "8589934592", + "test8": 2.5 +} diff --git a/tests/inputs/proto3_field_presence/proto3_field_presence.proto b/tests/inputs/proto3_field_presence/proto3_field_presence.proto new file mode 100644 index 0000000..0ccb700 --- /dev/null +++ b/tests/inputs/proto3_field_presence/proto3_field_presence.proto @@ -0,0 +1,21 @@ +syntax = "proto3"; + +message InnerTest { + string test = 1; +} + +message Test { + optional uint32 test1 = 1; + optional bool test2 = 2; + optional string test3 = 3; + optional bytes test4 = 4; + optional InnerTest test5 = 5; + optional TestEnum test6 = 6; + optional uint64 test7 = 7; + optional float test8 = 8; +} + +enum TestEnum { + A = 0; + B = 1; +} diff --git a/tests/inputs/proto3_field_presence/proto3_field_presence_default.json b/tests/inputs/proto3_field_presence/proto3_field_presence_default.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/tests/inputs/proto3_field_presence/proto3_field_presence_default.json @@ -0,0 +1 @@ +{} diff --git a/tests/inputs/proto3_field_presence/proto3_field_presence_missing.json b/tests/inputs/proto3_field_presence/proto3_field_presence_missing.json new file mode 100644 index 0000000..b19ae98 --- /dev/null +++ b/tests/inputs/proto3_field_presence/proto3_field_presence_missing.json @@ -0,0 +1,9 @@ +{ + "test1": 0, + "test2": false, + "test3": "", + "test4": "", + "test6": "A", + "test7": "0", + "test8": 0 +} diff --git a/tests/inputs/proto3_field_presence/test_proto3_field_presence.py b/tests/inputs/proto3_field_presence/test_proto3_field_presence.py new file mode 100644 index 0000000..364e0cc --- /dev/null +++ b/tests/inputs/proto3_field_presence/test_proto3_field_presence.py @@ -0,0 +1,38 @@ +import json + +from tests.output_betterproto.proto3_field_presence import Test, InnerTest, TestEnum + + +def test_null_fields_json(): + """Ensure that using "null" in JSON is equivalent to not specifying a + field, for fields with explicit presence""" + + def test_json(ref_json: str, obj_json: str) -> None: + """`ref_json` and `obj_json` are JSON strings describing a `Test` object. + Test that deserializing both leads to the same object, and that + `ref_json` is the normalized format.""" + ref_obj = Test().from_json(ref_json) + obj = Test().from_json(obj_json) + + assert obj == ref_obj + assert json.loads(obj.to_json(0)) == json.loads(ref_json) + + test_json("{}", '{ "test1": null, "test2": null, "test3": null }') + test_json("{}", '{ "test4": null, "test5": null, "test6": null }') + test_json("{}", '{ "test7": null, "test8": null }') + test_json('{ "test5": {} }', '{ "test3": null, "test5": {} }') + + # Make sure that if include_default_values is set, None values are + # exported. + obj = Test() + assert obj.to_dict() == {} + assert obj.to_dict(include_default_values=True) == { + "test1": None, + "test2": None, + "test3": None, + "test4": None, + "test5": None, + "test6": None, + "test7": None, + "test8": None, + } diff --git a/tests/inputs/proto3_field_presence_oneof/proto3_field_presence_oneof.json b/tests/inputs/proto3_field_presence_oneof/proto3_field_presence_oneof.json new file mode 100644 index 0000000..da08192 --- /dev/null +++ b/tests/inputs/proto3_field_presence_oneof/proto3_field_presence_oneof.json @@ -0,0 +1,3 @@ +{ + "nested": {} +} diff --git a/tests/inputs/proto3_field_presence_oneof/proto3_field_presence_oneof.proto b/tests/inputs/proto3_field_presence_oneof/proto3_field_presence_oneof.proto new file mode 100644 index 0000000..c4dc9d4 --- /dev/null +++ b/tests/inputs/proto3_field_presence_oneof/proto3_field_presence_oneof.proto @@ -0,0 +1,20 @@ +syntax = "proto3"; + +message Test { + oneof kind { + Nested nested = 1; + WithOptional with_optional = 2; + } +} + +message InnerNested { + optional bool a = 1; +} + +message Nested { + InnerNested inner = 1; +} + +message WithOptional { + optional bool b = 2; +} diff --git a/tests/inputs/proto3_field_presence_oneof/test_proto3_field_presence_oneof.py b/tests/inputs/proto3_field_presence_oneof/test_proto3_field_presence_oneof.py new file mode 100644 index 0000000..0092a23 --- /dev/null +++ b/tests/inputs/proto3_field_presence_oneof/test_proto3_field_presence_oneof.py @@ -0,0 +1,29 @@ +from tests.output_betterproto.proto3_field_presence_oneof import ( + Test, + InnerNested, + Nested, + WithOptional, +) + + +def test_serialization(): + """Ensure that serialization of fields unset but with explicit field + presence do not bloat the serialized payload with length-delimited fields + with length 0""" + + def test_empty_nested(message: Test) -> None: + # '0a' => tag 1, length delimited + # '00' => length: 0 + assert bytes(message) == bytearray.fromhex("0a 00") + + test_empty_nested(Test(nested=Nested())) + test_empty_nested(Test(nested=Nested(inner=None))) + test_empty_nested(Test(nested=Nested(inner=InnerNested(a=None)))) + + def test_empty_with_optional(message: Test) -> None: + # '12' => tag 2, length delimited + # '00' => length: 0 + assert bytes(message) == bytearray.fromhex("12 00") + + test_empty_with_optional(Test(with_optional=WithOptional())) + test_empty_with_optional(Test(with_optional=WithOptional(b=None)))