Support proto3 field presence (#281)

* Update protobuf pregenerated files

* Update grpcio-tools to latest version

* Implement proto3 field presence

* Fix to_dict with None optional fields.

* Add test with optional enum

* Properly support optional enums

* Add tests for 64-bit ints and floats

* Support field presence for int64 types

* Fix oneof serialization with proto3 field presence (#292)

= Description

The serialization of a oneof message that contains a message with fields
with explicit presence was buggy.

For example:

```
message A {
    oneof kind {
        B b = 1;
        C c = 2;
    }
}

message B {}
message C {
    optional bool z = 1;
}
```

Serializing `A(b=B())` would lead to this payload:

```
0A # tag1, length delimited
00 # length: 0
12 # tag2, length delimited
00 # length: 0
```

Which when deserialized, leads to the message `A(c=C())`.

= Explanation

The issue lies in the post_init method. All fields are introspected, and
if different from PLACEHOLDER, the message is marked as having been
"serialized_on_wire".
Then, when serializing `A(b=B())`, we go through each field of the
oneof:

- field 'b': this is the selected field from the group, so it is
  serialized
- field 'c': marked as 'serialized_on_wire', so it is added as well.

= Fix

The issue is that support for explicit presence changed the default
value from PLACEHOLDER to None. This breaks the post_init method in that
case, which is relatively easy to fix: if a field is optional, and set
to None, this is considered as the default value (which it is).

This fix however has a side-effect: the group_current for this field (the
oneof trick for explicit presence) is no longer set. This changes the
behavior when serializing the message in JSON: as the value is the
default one (None), and the group is not set (which would force the
serialization of the field), so None fields are no longer serialized in
JSON. This break one test, and will be fixed in the next commit.

* fix: do not serialize None fields in JSON format

This is linked to the fix from the previous commit: after it, scalar
None fields were not included in the JSON format, but some were still
included.

This is all cleaned up: None fields are not added in JSON by default,
as they indicate the default value of fields with explicit presence.
However, if `include_default_values is set, they are included.

* Fix: use builtin annotation prefix

* Remove comment

Co-authored-by: roblabla <unfiltered@roblab.la>
Co-authored-by: Vincent Thiberville <vthib@pm.me>
This commit is contained in:
Kalan 2021-12-29 13:38:32 -08:00 committed by GitHub
parent 671c0ff4ac
commit d77f44ebb7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 809 additions and 573 deletions

View File

@ -66,4 +66,4 @@ jobs:
- name: Execute test suite
shell: bash
run: poetry run pytest tests/
run: poetry run python -m pytest tests/

234
poetry.lock generated
View File

@ -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"},

View File

@ -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"

View File

@ -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.

View File

@ -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": <string>,
"lastName": <string> } 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": <string>, "lastName": <string> } 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)

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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,

View File

@ -0,0 +1,12 @@
{
"test1": 128,
"test2": true,
"test3": "A value",
"test4": "aGVsbG8=",
"test5": {
"test": "Hello"
},
"test6": "B",
"test7": "8589934592",
"test8": 2.5
}

View File

@ -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;
}

View File

@ -0,0 +1,9 @@
{
"test1": 0,
"test2": false,
"test3": "",
"test4": "",
"test6": "A",
"test7": "0",
"test8": 0
}

View File

@ -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,
}

View File

@ -0,0 +1,3 @@
{
"nested": {}
}

View File

@ -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;
}

View File

@ -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)))