diff --git a/README.md b/README.md index 41d6c7c5..c450177e 100644 --- a/README.md +++ b/README.md @@ -248,20 +248,6 @@ For compatibility the default is to convert field names to `camelCase`. You can MyMessage().to_dict(casing=betterproto.Casing.SNAKE) ``` -### Determining if a message was sent - -Sometimes it is useful to be able to determine whether a message has been sent on the wire. This is how the Google wrapper types work to let you know whether a value is unset, set as the default (zero value), or set as something else, for example. - -Use `betterproto.serialized_on_wire(message)` to determine if it was sent. This is a little bit different from the official Google generated Python code, and it lives outside the generated `Message` class to prevent name clashes. Note that it **only** supports Proto 3 and thus can **only** be used to check if `Message` fields are set. You cannot check if a scalar was sent on the wire. - -```py -# Old way (official Google Protobuf package) ->>> mymessage.HasField('myfield') - -# New way (this project) ->>> betterproto.serialized_on_wire(mymessage.myfield) -``` - ### One-of Support Protobuf supports grouping fields in a `oneof` clause. Only one of the fields in the group may be set at a given time. For example, given the proto: @@ -283,11 +269,11 @@ On Python 3.10 and later, you can use a `match` statement to access the provided ```py test = Test() match test: - case Test(on=value): + case Test(on=bool(value)): print(value) # value: bool - case Test(count=value): + case Test(count=int(value)): print(value) # value: int - case Test(name=value): + case Test(name=str(value)): print(value) # value: str case _: print("No value provided") diff --git a/docs/migrating.rst b/docs/migrating.rst index 3d02650d..e455618e 100644 --- a/docs/migrating.rst +++ b/docs/migrating.rst @@ -19,30 +19,6 @@ regenerating your protobufs of course) although there are some minor differences :meth:`betterproto.Message.__bytes__` respectively. -Determining if a message was sent -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Sometimes it is useful to be able to determine whether a message has been sent on -the wire. This is how the Google wrapper types work to let you know whether a value is -unset (set as the default/zero value), or set as something else, for example. - -Use ``betterproto.serialized_on_wire(message)`` to determine if it was sent. This is -a little bit different from the official Google generated Python code, and it lives -outside the generated ``Message`` class to prevent name clashes. Note that it only -supports Proto 3 and thus can only be used to check if ``Message`` fields are set. -You cannot check if a scalar was sent on the wire. - -.. code-block:: python - - # Old way (official Google Protobuf package) - >>> mymessage.HasField('myfield') - True - - # New way (this project) - >>> betterproto.serialized_on_wire(mymessage.myfield) - True - - One-of Support ~~~~~~~~~~~~~~ diff --git a/poetry.lock b/poetry.lock index f3a0790f..f8148fb1 100644 --- a/poetry.lock +++ b/poetry.lock @@ -604,70 +604,70 @@ test = ["objgraph", "psutil"] [[package]] name = "grpcio" -version = "1.67.1" +version = "1.68.1" description = "HTTP/2-based RPC framework" optional = false python-versions = ">=3.8" files = [ - {file = "grpcio-1.67.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:8b0341d66a57f8a3119b77ab32207072be60c9bf79760fa609c5609f2deb1f3f"}, - {file = "grpcio-1.67.1-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:f5a27dddefe0e2357d3e617b9079b4bfdc91341a91565111a21ed6ebbc51b22d"}, - {file = "grpcio-1.67.1-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:43112046864317498a33bdc4797ae6a268c36345a910de9b9c17159d8346602f"}, - {file = "grpcio-1.67.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9b929f13677b10f63124c1a410994a401cdd85214ad83ab67cc077fc7e480f0"}, - {file = "grpcio-1.67.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7d1797a8a3845437d327145959a2c0c47c05947c9eef5ff1a4c80e499dcc6fa"}, - {file = "grpcio-1.67.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0489063974d1452436139501bf6b180f63d4977223ee87488fe36858c5725292"}, - {file = "grpcio-1.67.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9fd042de4a82e3e7aca44008ee2fb5da01b3e5adb316348c21980f7f58adc311"}, - {file = "grpcio-1.67.1-cp310-cp310-win32.whl", hash = "sha256:638354e698fd0c6c76b04540a850bf1db27b4d2515a19fcd5cf645c48d3eb1ed"}, - {file = "grpcio-1.67.1-cp310-cp310-win_amd64.whl", hash = "sha256:608d87d1bdabf9e2868b12338cd38a79969eaf920c89d698ead08f48de9c0f9e"}, - {file = "grpcio-1.67.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:7818c0454027ae3384235a65210bbf5464bd715450e30a3d40385453a85a70cb"}, - {file = "grpcio-1.67.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ea33986b70f83844cd00814cee4451055cd8cab36f00ac64a31f5bb09b31919e"}, - {file = "grpcio-1.67.1-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:c7a01337407dd89005527623a4a72c5c8e2894d22bead0895306b23c6695698f"}, - {file = "grpcio-1.67.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80b866f73224b0634f4312a4674c1be21b2b4afa73cb20953cbbb73a6b36c3cc"}, - {file = "grpcio-1.67.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9fff78ba10d4250bfc07a01bd6254a6d87dc67f9627adece85c0b2ed754fa96"}, - {file = "grpcio-1.67.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:8a23cbcc5bb11ea7dc6163078be36c065db68d915c24f5faa4f872c573bb400f"}, - {file = "grpcio-1.67.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1a65b503d008f066e994f34f456e0647e5ceb34cfcec5ad180b1b44020ad4970"}, - {file = "grpcio-1.67.1-cp311-cp311-win32.whl", hash = "sha256:e29ca27bec8e163dca0c98084040edec3bc49afd10f18b412f483cc68c712744"}, - {file = "grpcio-1.67.1-cp311-cp311-win_amd64.whl", hash = "sha256:786a5b18544622bfb1e25cc08402bd44ea83edfb04b93798d85dca4d1a0b5be5"}, - {file = "grpcio-1.67.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:267d1745894200e4c604958da5f856da6293f063327cb049a51fe67348e4f953"}, - {file = "grpcio-1.67.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:85f69fdc1d28ce7cff8de3f9c67db2b0ca9ba4449644488c1e0303c146135ddb"}, - {file = "grpcio-1.67.1-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:f26b0b547eb8d00e195274cdfc63ce64c8fc2d3e2d00b12bf468ece41a0423a0"}, - {file = "grpcio-1.67.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4422581cdc628f77302270ff839a44f4c24fdc57887dc2a45b7e53d8fc2376af"}, - {file = "grpcio-1.67.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d7616d2ded471231c701489190379e0c311ee0a6c756f3c03e6a62b95a7146e"}, - {file = "grpcio-1.67.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8a00efecde9d6fcc3ab00c13f816313c040a28450e5e25739c24f432fc6d3c75"}, - {file = "grpcio-1.67.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:699e964923b70f3101393710793289e42845791ea07565654ada0969522d0a38"}, - {file = "grpcio-1.67.1-cp312-cp312-win32.whl", hash = "sha256:4e7b904484a634a0fff132958dabdb10d63e0927398273917da3ee103e8d1f78"}, - {file = "grpcio-1.67.1-cp312-cp312-win_amd64.whl", hash = "sha256:5721e66a594a6c4204458004852719b38f3d5522082be9061d6510b455c90afc"}, - {file = "grpcio-1.67.1-cp313-cp313-linux_armv7l.whl", hash = "sha256:aa0162e56fd10a5547fac8774c4899fc3e18c1aa4a4759d0ce2cd00d3696ea6b"}, - {file = "grpcio-1.67.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:beee96c8c0b1a75d556fe57b92b58b4347c77a65781ee2ac749d550f2a365dc1"}, - {file = "grpcio-1.67.1-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:a93deda571a1bf94ec1f6fcda2872dad3ae538700d94dc283c672a3b508ba3af"}, - {file = "grpcio-1.67.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e6f255980afef598a9e64a24efce87b625e3e3c80a45162d111a461a9f92955"}, - {file = "grpcio-1.67.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e838cad2176ebd5d4a8bb03955138d6589ce9e2ce5d51c3ada34396dbd2dba8"}, - {file = "grpcio-1.67.1-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:a6703916c43b1d468d0756c8077b12017a9fcb6a1ef13faf49e67d20d7ebda62"}, - {file = "grpcio-1.67.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:917e8d8994eed1d86b907ba2a61b9f0aef27a2155bca6cbb322430fc7135b7bb"}, - {file = "grpcio-1.67.1-cp313-cp313-win32.whl", hash = "sha256:e279330bef1744040db8fc432becc8a727b84f456ab62b744d3fdb83f327e121"}, - {file = "grpcio-1.67.1-cp313-cp313-win_amd64.whl", hash = "sha256:fa0c739ad8b1996bd24823950e3cb5152ae91fca1c09cc791190bf1627ffefba"}, - {file = "grpcio-1.67.1-cp38-cp38-linux_armv7l.whl", hash = "sha256:178f5db771c4f9a9facb2ab37a434c46cb9be1a75e820f187ee3d1e7805c4f65"}, - {file = "grpcio-1.67.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0f3e49c738396e93b7ba9016e153eb09e0778e776df6090c1b8c91877cc1c426"}, - {file = "grpcio-1.67.1-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:24e8a26dbfc5274d7474c27759b54486b8de23c709d76695237515bc8b5baeab"}, - {file = "grpcio-1.67.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b6c16489326d79ead41689c4b84bc40d522c9a7617219f4ad94bc7f448c5085"}, - {file = "grpcio-1.67.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60e6a4dcf5af7bbc36fd9f81c9f372e8ae580870a9e4b6eafe948cd334b81cf3"}, - {file = "grpcio-1.67.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:95b5f2b857856ed78d72da93cd7d09b6db8ef30102e5e7fe0961fe4d9f7d48e8"}, - {file = "grpcio-1.67.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b49359977c6ec9f5d0573ea4e0071ad278ef905aa74e420acc73fd28ce39e9ce"}, - {file = "grpcio-1.67.1-cp38-cp38-win32.whl", hash = "sha256:f5b76ff64aaac53fede0cc93abf57894ab2a7362986ba22243d06218b93efe46"}, - {file = "grpcio-1.67.1-cp38-cp38-win_amd64.whl", hash = "sha256:804c6457c3cd3ec04fe6006c739579b8d35c86ae3298ffca8de57b493524b771"}, - {file = "grpcio-1.67.1-cp39-cp39-linux_armv7l.whl", hash = "sha256:a25bdea92b13ff4d7790962190bf6bf5c4639876e01c0f3dda70fc2769616335"}, - {file = "grpcio-1.67.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:cdc491ae35a13535fd9196acb5afe1af37c8237df2e54427be3eecda3653127e"}, - {file = "grpcio-1.67.1-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:85f862069b86a305497e74d0dc43c02de3d1d184fc2c180993aa8aa86fbd19b8"}, - {file = "grpcio-1.67.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ec74ef02010186185de82cc594058a3ccd8d86821842bbac9873fd4a2cf8be8d"}, - {file = "grpcio-1.67.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01f616a964e540638af5130469451cf580ba8c7329f45ca998ab66e0c7dcdb04"}, - {file = "grpcio-1.67.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:299b3d8c4f790c6bcca485f9963b4846dd92cf6f1b65d3697145d005c80f9fe8"}, - {file = "grpcio-1.67.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:60336bff760fbb47d7e86165408126f1dded184448e9a4c892189eb7c9d3f90f"}, - {file = "grpcio-1.67.1-cp39-cp39-win32.whl", hash = "sha256:5ed601c4c6008429e3d247ddb367fe8c7259c355757448d7c1ef7bd4a6739e8e"}, - {file = "grpcio-1.67.1-cp39-cp39-win_amd64.whl", hash = "sha256:5db70d32d6703b89912af16d6d45d78406374a8b8ef0d28140351dd0ec610e98"}, - {file = "grpcio-1.67.1.tar.gz", hash = "sha256:3dc2ed4cabea4dc14d5e708c2b426205956077cc5de419b4d4079315017e9732"}, + {file = "grpcio-1.68.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:d35740e3f45f60f3c37b1e6f2f4702c23867b9ce21c6410254c9c682237da68d"}, + {file = "grpcio-1.68.1-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:d99abcd61760ebb34bdff37e5a3ba333c5cc09feda8c1ad42547bea0416ada78"}, + {file = "grpcio-1.68.1-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:f8261fa2a5f679abeb2a0a93ad056d765cdca1c47745eda3f2d87f874ff4b8c9"}, + {file = "grpcio-1.68.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0feb02205a27caca128627bd1df4ee7212db051019a9afa76f4bb6a1a80ca95e"}, + {file = "grpcio-1.68.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:919d7f18f63bcad3a0f81146188e90274fde800a94e35d42ffe9eadf6a9a6330"}, + {file = "grpcio-1.68.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:963cc8d7d79b12c56008aabd8b457f400952dbea8997dd185f155e2f228db079"}, + {file = "grpcio-1.68.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ccf2ebd2de2d6661e2520dae293298a3803a98ebfc099275f113ce1f6c2a80f1"}, + {file = "grpcio-1.68.1-cp310-cp310-win32.whl", hash = "sha256:2cc1fd04af8399971bcd4f43bd98c22d01029ea2e56e69c34daf2bf8470e47f5"}, + {file = "grpcio-1.68.1-cp310-cp310-win_amd64.whl", hash = "sha256:ee2e743e51cb964b4975de572aa8fb95b633f496f9fcb5e257893df3be854746"}, + {file = "grpcio-1.68.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:55857c71641064f01ff0541a1776bfe04a59db5558e82897d35a7793e525774c"}, + {file = "grpcio-1.68.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4b177f5547f1b995826ef529d2eef89cca2f830dd8b2c99ffd5fde4da734ba73"}, + {file = "grpcio-1.68.1-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:3522c77d7e6606d6665ec8d50e867f13f946a4e00c7df46768f1c85089eae515"}, + {file = "grpcio-1.68.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9d1fae6bbf0816415b81db1e82fb3bf56f7857273c84dcbe68cbe046e58e1ccd"}, + {file = "grpcio-1.68.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:298ee7f80e26f9483f0b6f94cc0a046caf54400a11b644713bb5b3d8eb387600"}, + {file = "grpcio-1.68.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cbb5780e2e740b6b4f2d208e90453591036ff80c02cc605fea1af8e6fc6b1bbe"}, + {file = "grpcio-1.68.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ddda1aa22495d8acd9dfbafff2866438d12faec4d024ebc2e656784d96328ad0"}, + {file = "grpcio-1.68.1-cp311-cp311-win32.whl", hash = "sha256:b33bd114fa5a83f03ec6b7b262ef9f5cac549d4126f1dc702078767b10c46ed9"}, + {file = "grpcio-1.68.1-cp311-cp311-win_amd64.whl", hash = "sha256:7f20ebec257af55694d8f993e162ddf0d36bd82d4e57f74b31c67b3c6d63d8b2"}, + {file = "grpcio-1.68.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:8829924fffb25386995a31998ccbbeaa7367223e647e0122043dfc485a87c666"}, + {file = "grpcio-1.68.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:3aed6544e4d523cd6b3119b0916cef3d15ef2da51e088211e4d1eb91a6c7f4f1"}, + {file = "grpcio-1.68.1-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:4efac5481c696d5cb124ff1c119a78bddbfdd13fc499e3bc0ca81e95fc573684"}, + {file = "grpcio-1.68.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ab2d912ca39c51f46baf2a0d92aa265aa96b2443266fc50d234fa88bf877d8e"}, + {file = "grpcio-1.68.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95c87ce2a97434dffe7327a4071839ab8e8bffd0054cc74cbe971fba98aedd60"}, + {file = "grpcio-1.68.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:e4842e4872ae4ae0f5497bf60a0498fa778c192cc7a9e87877abd2814aca9475"}, + {file = "grpcio-1.68.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:255b1635b0ed81e9f91da4fcc8d43b7ea5520090b9a9ad9340d147066d1d3613"}, + {file = "grpcio-1.68.1-cp312-cp312-win32.whl", hash = "sha256:7dfc914cc31c906297b30463dde0b9be48e36939575eaf2a0a22a8096e69afe5"}, + {file = "grpcio-1.68.1-cp312-cp312-win_amd64.whl", hash = "sha256:a0c8ddabef9c8f41617f213e527254c41e8b96ea9d387c632af878d05db9229c"}, + {file = "grpcio-1.68.1-cp313-cp313-linux_armv7l.whl", hash = "sha256:a47faedc9ea2e7a3b6569795c040aae5895a19dde0c728a48d3c5d7995fda385"}, + {file = "grpcio-1.68.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:390eee4225a661c5cd133c09f5da1ee3c84498dc265fd292a6912b65c421c78c"}, + {file = "grpcio-1.68.1-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:66a24f3d45c33550703f0abb8b656515b0ab777970fa275693a2f6dc8e35f1c1"}, + {file = "grpcio-1.68.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c08079b4934b0bf0a8847f42c197b1d12cba6495a3d43febd7e99ecd1cdc8d54"}, + {file = "grpcio-1.68.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8720c25cd9ac25dd04ee02b69256d0ce35bf8a0f29e20577427355272230965a"}, + {file = "grpcio-1.68.1-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:04cfd68bf4f38f5bb959ee2361a7546916bd9a50f78617a346b3aeb2b42e2161"}, + {file = "grpcio-1.68.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c28848761a6520c5c6071d2904a18d339a796ebe6b800adc8b3f474c5ce3c3ad"}, + {file = "grpcio-1.68.1-cp313-cp313-win32.whl", hash = "sha256:77d65165fc35cff6e954e7fd4229e05ec76102d4406d4576528d3a3635fc6172"}, + {file = "grpcio-1.68.1-cp313-cp313-win_amd64.whl", hash = "sha256:a8040f85dcb9830d8bbb033ae66d272614cec6faceee88d37a88a9bd1a7a704e"}, + {file = "grpcio-1.68.1-cp38-cp38-linux_armv7l.whl", hash = "sha256:eeb38ff04ab6e5756a2aef6ad8d94e89bb4a51ef96e20f45c44ba190fa0bcaad"}, + {file = "grpcio-1.68.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8a3869a6661ec8f81d93f4597da50336718bde9eb13267a699ac7e0a1d6d0bea"}, + {file = "grpcio-1.68.1-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:2c4cec6177bf325eb6faa6bd834d2ff6aa8bb3b29012cceb4937b86f8b74323c"}, + {file = "grpcio-1.68.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12941d533f3cd45d46f202e3667be8ebf6bcb3573629c7ec12c3e211d99cfccf"}, + {file = "grpcio-1.68.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80af6f1e69c5e68a2be529990684abdd31ed6622e988bf18850075c81bb1ad6e"}, + {file = "grpcio-1.68.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:e8dbe3e00771bfe3d04feed8210fc6617006d06d9a2679b74605b9fed3e8362c"}, + {file = "grpcio-1.68.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:83bbf5807dc3ee94ce1de2dfe8a356e1d74101e4b9d7aa8c720cc4818a34aded"}, + {file = "grpcio-1.68.1-cp38-cp38-win32.whl", hash = "sha256:8cb620037a2fd9eeee97b4531880e439ebfcd6d7d78f2e7dcc3726428ab5ef63"}, + {file = "grpcio-1.68.1-cp38-cp38-win_amd64.whl", hash = "sha256:52fbf85aa71263380d330f4fce9f013c0798242e31ede05fcee7fbe40ccfc20d"}, + {file = "grpcio-1.68.1-cp39-cp39-linux_armv7l.whl", hash = "sha256:cb400138e73969eb5e0535d1d06cae6a6f7a15f2cc74add320e2130b8179211a"}, + {file = "grpcio-1.68.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a1b988b40f2fd9de5c820f3a701a43339d8dcf2cb2f1ca137e2c02671cc83ac1"}, + {file = "grpcio-1.68.1-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:96f473cdacfdd506008a5d7579c9f6a7ff245a9ade92c3c0265eb76cc591914f"}, + {file = "grpcio-1.68.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:37ea3be171f3cf3e7b7e412a98b77685eba9d4fd67421f4a34686a63a65d99f9"}, + {file = "grpcio-1.68.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ceb56c4285754e33bb3c2fa777d055e96e6932351a3082ce3559be47f8024f0"}, + {file = "grpcio-1.68.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:dffd29a2961f3263a16d73945b57cd44a8fd0b235740cb14056f0612329b345e"}, + {file = "grpcio-1.68.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:025f790c056815b3bf53da850dd70ebb849fd755a4b1ac822cb65cd631e37d43"}, + {file = "grpcio-1.68.1-cp39-cp39-win32.whl", hash = "sha256:1098f03dedc3b9810810568060dea4ac0822b4062f537b0f53aa015269be0a76"}, + {file = "grpcio-1.68.1-cp39-cp39-win_amd64.whl", hash = "sha256:334ab917792904245a028f10e803fcd5b6f36a7b2173a820c0b5b076555825e1"}, + {file = "grpcio-1.68.1.tar.gz", hash = "sha256:44a8502dd5de653ae6a73e2de50a401d84184f0331d0ac3daeb044e66d5c5054"}, ] [package.extras] -protobuf = ["grpcio-tools (>=1.67.1)"] +protobuf = ["grpcio-tools (>=1.68.1)"] [[package]] name = "grpcio-tools" @@ -1237,22 +1237,19 @@ files = [ [[package]] name = "pydantic" -version = "2.9.2" +version = "2.10.3" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12"}, - {file = "pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f"}, + {file = "pydantic-2.10.3-py3-none-any.whl", hash = "sha256:be04d85bbc7b65651c5f8e6b9976ed9c6f41782a55524cef079a34a0bb82144d"}, + {file = "pydantic-2.10.3.tar.gz", hash = "sha256:cb5ac360ce894ceacd69c403187900a02c4b20b693a9dd1d643e1effab9eadf9"}, ] [package.dependencies] annotated-types = ">=0.6.0" -pydantic-core = "2.23.4" -typing-extensions = [ - {version = ">=4.12.2", markers = "python_version >= \"3.13\""}, - {version = ">=4.6.1", markers = "python_version < \"3.13\""}, -] +pydantic-core = "2.27.1" +typing-extensions = ">=4.12.2" [package.extras] email = ["email-validator (>=2.0.0)"] @@ -1260,100 +1257,111 @@ timezone = ["tzdata"] [[package]] name = "pydantic-core" -version = "2.23.4" +version = "2.27.1" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic_core-2.23.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b10bd51f823d891193d4717448fab065733958bdb6a6b351967bd349d48d5c9b"}, - {file = "pydantic_core-2.23.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4fc714bdbfb534f94034efaa6eadd74e5b93c8fa6315565a222f7b6f42ca1166"}, - {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63e46b3169866bd62849936de036f901a9356e36376079b05efa83caeaa02ceb"}, - {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed1a53de42fbe34853ba90513cea21673481cd81ed1be739f7f2efb931b24916"}, - {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cfdd16ab5e59fc31b5e906d1a3f666571abc367598e3e02c83403acabc092e07"}, - {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255a8ef062cbf6674450e668482456abac99a5583bbafb73f9ad469540a3a232"}, - {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a7cd62e831afe623fbb7aabbb4fe583212115b3ef38a9f6b71869ba644624a2"}, - {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f09e2ff1f17c2b51f2bc76d1cc33da96298f0a036a137f5440ab3ec5360b624f"}, - {file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e38e63e6f3d1cec5a27e0afe90a085af8b6806ee208b33030e65b6516353f1a3"}, - {file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0dbd8dbed2085ed23b5c04afa29d8fd2771674223135dc9bc937f3c09284d071"}, - {file = "pydantic_core-2.23.4-cp310-none-win32.whl", hash = "sha256:6531b7ca5f951d663c339002e91aaebda765ec7d61b7d1e3991051906ddde119"}, - {file = "pydantic_core-2.23.4-cp310-none-win_amd64.whl", hash = "sha256:7c9129eb40958b3d4500fa2467e6a83356b3b61bfff1b414c7361d9220f9ae8f"}, - {file = "pydantic_core-2.23.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:77733e3892bb0a7fa797826361ce8a9184d25c8dffaec60b7ffe928153680ba8"}, - {file = "pydantic_core-2.23.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b84d168f6c48fabd1f2027a3d1bdfe62f92cade1fb273a5d68e621da0e44e6d"}, - {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df49e7a0861a8c36d089c1ed57d308623d60416dab2647a4a17fe050ba85de0e"}, - {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff02b6d461a6de369f07ec15e465a88895f3223eb75073ffea56b84d9331f607"}, - {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:996a38a83508c54c78a5f41456b0103c30508fed9abcad0a59b876d7398f25fd"}, - {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d97683ddee4723ae8c95d1eddac7c192e8c552da0c73a925a89fa8649bf13eea"}, - {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:216f9b2d7713eb98cb83c80b9c794de1f6b7e3145eef40400c62e86cee5f4e1e"}, - {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6f783e0ec4803c787bcea93e13e9932edab72068f68ecffdf86a99fd5918878b"}, - {file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d0776dea117cf5272382634bd2a5c1b6eb16767c223c6a5317cd3e2a757c61a0"}, - {file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5f7a395a8cf1621939692dba2a6b6a830efa6b3cee787d82c7de1ad2930de64"}, - {file = "pydantic_core-2.23.4-cp311-none-win32.whl", hash = "sha256:74b9127ffea03643e998e0c5ad9bd3811d3dac8c676e47db17b0ee7c3c3bf35f"}, - {file = "pydantic_core-2.23.4-cp311-none-win_amd64.whl", hash = "sha256:98d134c954828488b153d88ba1f34e14259284f256180ce659e8d83e9c05eaa3"}, - {file = "pydantic_core-2.23.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f3e0da4ebaef65158d4dfd7d3678aad692f7666877df0002b8a522cdf088f231"}, - {file = "pydantic_core-2.23.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f69a8e0b033b747bb3e36a44e7732f0c99f7edd5cea723d45bc0d6e95377ffee"}, - {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723314c1d51722ab28bfcd5240d858512ffd3116449c557a1336cbe3919beb87"}, - {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb2802e667b7051a1bebbfe93684841cc9351004e2badbd6411bf357ab8d5ac8"}, - {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18ca8148bebe1b0a382a27a8ee60350091a6ddaf475fa05ef50dc35b5df6327"}, - {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33e3d65a85a2a4a0dc3b092b938a4062b1a05f3a9abde65ea93b233bca0e03f2"}, - {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:128585782e5bfa515c590ccee4b727fb76925dd04a98864182b22e89a4e6ed36"}, - {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:68665f4c17edcceecc112dfed5dbe6f92261fb9d6054b47d01bf6371a6196126"}, - {file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20152074317d9bed6b7a95ade3b7d6054845d70584216160860425f4fbd5ee9e"}, - {file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9261d3ce84fa1d38ed649c3638feefeae23d32ba9182963e465d58d62203bd24"}, - {file = "pydantic_core-2.23.4-cp312-none-win32.whl", hash = "sha256:4ba762ed58e8d68657fc1281e9bb72e1c3e79cc5d464be146e260c541ec12d84"}, - {file = "pydantic_core-2.23.4-cp312-none-win_amd64.whl", hash = "sha256:97df63000f4fea395b2824da80e169731088656d1818a11b95f3b173747b6cd9"}, - {file = "pydantic_core-2.23.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7530e201d10d7d14abce4fb54cfe5b94a0aefc87da539d0346a484ead376c3cc"}, - {file = "pydantic_core-2.23.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df933278128ea1cd77772673c73954e53a1c95a4fdf41eef97c2b779271bd0bd"}, - {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cb3da3fd1b6a5d0279a01877713dbda118a2a4fc6f0d821a57da2e464793f05"}, - {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c6dcb030aefb668a2b7009c85b27f90e51e6a3b4d5c9bc4c57631292015b0d"}, - {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:696dd8d674d6ce621ab9d45b205df149399e4bb9aa34102c970b721554828510"}, - {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2971bb5ffe72cc0f555c13e19b23c85b654dd2a8f7ab493c262071377bfce9f6"}, - {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8394d940e5d400d04cad4f75c0598665cbb81aecefaca82ca85bd28264af7f9b"}, - {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0dff76e0602ca7d4cdaacc1ac4c005e0ce0dcfe095d5b5259163a80d3a10d327"}, - {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7d32706badfe136888bdea71c0def994644e09fff0bfe47441deaed8e96fdbc6"}, - {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed541d70698978a20eb63d8c5d72f2cc6d7079d9d90f6b50bad07826f1320f5f"}, - {file = "pydantic_core-2.23.4-cp313-none-win32.whl", hash = "sha256:3d5639516376dce1940ea36edf408c554475369f5da2abd45d44621cb616f769"}, - {file = "pydantic_core-2.23.4-cp313-none-win_amd64.whl", hash = "sha256:5a1504ad17ba4210df3a045132a7baeeba5a200e930f57512ee02909fc5c4cb5"}, - {file = "pydantic_core-2.23.4-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d4488a93b071c04dc20f5cecc3631fc78b9789dd72483ba15d423b5b3689b555"}, - {file = "pydantic_core-2.23.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:81965a16b675b35e1d09dd14df53f190f9129c0202356ed44ab2728b1c905658"}, - {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ffa2ebd4c8530079140dd2d7f794a9d9a73cbb8e9d59ffe24c63436efa8f271"}, - {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:61817945f2fe7d166e75fbfb28004034b48e44878177fc54d81688e7b85a3665"}, - {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29d2c342c4bc01b88402d60189f3df065fb0dda3654744d5a165a5288a657368"}, - {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5e11661ce0fd30a6790e8bcdf263b9ec5988e95e63cf901972107efc49218b13"}, - {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d18368b137c6295db49ce7218b1a9ba15c5bc254c96d7c9f9e924a9bc7825ad"}, - {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec4e55f79b1c4ffb2eecd8a0cfba9955a2588497d96851f4c8f99aa4a1d39b12"}, - {file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:374a5e5049eda9e0a44c696c7ade3ff355f06b1fe0bb945ea3cac2bc336478a2"}, - {file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5c364564d17da23db1106787675fc7af45f2f7b58b4173bfdd105564e132e6fb"}, - {file = "pydantic_core-2.23.4-cp38-none-win32.whl", hash = "sha256:d7a80d21d613eec45e3d41eb22f8f94ddc758a6c4720842dc74c0581f54993d6"}, - {file = "pydantic_core-2.23.4-cp38-none-win_amd64.whl", hash = "sha256:5f5ff8d839f4566a474a969508fe1c5e59c31c80d9e140566f9a37bba7b8d556"}, - {file = "pydantic_core-2.23.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a4fa4fc04dff799089689f4fd502ce7d59de529fc2f40a2c8836886c03e0175a"}, - {file = "pydantic_core-2.23.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0a7df63886be5e270da67e0966cf4afbae86069501d35c8c1b3b6c168f42cb36"}, - {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcedcd19a557e182628afa1d553c3895a9f825b936415d0dbd3cd0bbcfd29b4b"}, - {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f54b118ce5de9ac21c363d9b3caa6c800341e8c47a508787e5868c6b79c9323"}, - {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86d2f57d3e1379a9525c5ab067b27dbb8a0642fb5d454e17a9ac434f9ce523e3"}, - {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:de6d1d1b9e5101508cb37ab0d972357cac5235f5c6533d1071964c47139257df"}, - {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1278e0d324f6908e872730c9102b0112477a7f7cf88b308e4fc36ce1bdb6d58c"}, - {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9a6b5099eeec78827553827f4c6b8615978bb4b6a88e5d9b93eddf8bb6790f55"}, - {file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e55541f756f9b3ee346b840103f32779c695a19826a4c442b7954550a0972040"}, - {file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a5c7ba8ffb6d6f8f2ab08743be203654bb1aaa8c9dcb09f82ddd34eadb695605"}, - {file = "pydantic_core-2.23.4-cp39-none-win32.whl", hash = "sha256:37b0fe330e4a58d3c58b24d91d1eb102aeec675a3db4c292ec3928ecd892a9a6"}, - {file = "pydantic_core-2.23.4-cp39-none-win_amd64.whl", hash = "sha256:1498bec4c05c9c787bde9125cfdcc63a41004ff167f495063191b863399b1a29"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f455ee30a9d61d3e1a15abd5068827773d6e4dc513e795f380cdd59932c782d5"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1e90d2e3bd2c3863d48525d297cd143fe541be8bbf6f579504b9712cb6b643ec"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e203fdf807ac7e12ab59ca2bfcabb38c7cf0b33c41efeb00f8e5da1d86af480"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e08277a400de01bc72436a0ccd02bdf596631411f592ad985dcee21445bd0068"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f220b0eea5965dec25480b6333c788fb72ce5f9129e8759ef876a1d805d00801"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d06b0c8da4f16d1d1e352134427cb194a0a6e19ad5db9161bf32b2113409e728"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ba1a0996f6c2773bd83e63f18914c1de3c9dd26d55f4ac302a7efe93fb8e7433"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:9a5bce9d23aac8f0cf0836ecfc033896aa8443b501c58d0602dbfd5bd5b37753"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:78ddaaa81421a29574a682b3179d4cf9e6d405a09b99d93ddcf7e5239c742e21"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:883a91b5dd7d26492ff2f04f40fbb652de40fcc0afe07e8129e8ae779c2110eb"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88ad334a15b32a791ea935af224b9de1bf99bcd62fabf745d5f3442199d86d59"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:233710f069d251feb12a56da21e14cca67994eab08362207785cf8c598e74577"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:19442362866a753485ba5e4be408964644dd6a09123d9416c54cd49171f50744"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:624e278a7d29b6445e4e813af92af37820fafb6dcc55c012c834f9e26f9aaaef"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f5ef8f42bec47f21d07668a043f077d507e5bf4e668d5c6dfe6aaba89de1a5b8"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:aea443fffa9fbe3af1a9ba721a87f926fe548d32cab71d188a6ede77d0ff244e"}, - {file = "pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863"}, + {file = "pydantic_core-2.27.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:71a5e35c75c021aaf400ac048dacc855f000bdfed91614b4a726f7432f1f3d6a"}, + {file = "pydantic_core-2.27.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f82d068a2d6ecfc6e054726080af69a6764a10015467d7d7b9f66d6ed5afa23b"}, + {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:121ceb0e822f79163dd4699e4c54f5ad38b157084d97b34de8b232bcaad70278"}, + {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4603137322c18eaf2e06a4495f426aa8d8388940f3c457e7548145011bb68e05"}, + {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a33cd6ad9017bbeaa9ed78a2e0752c5e250eafb9534f308e7a5f7849b0b1bfb4"}, + {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15cc53a3179ba0fcefe1e3ae50beb2784dede4003ad2dfd24f81bba4b23a454f"}, + {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45d9c5eb9273aa50999ad6adc6be5e0ecea7e09dbd0d31bd0c65a55a2592ca08"}, + {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8bf7b66ce12a2ac52d16f776b31d16d91033150266eb796967a7e4621707e4f6"}, + {file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:655d7dd86f26cb15ce8a431036f66ce0318648f8853d709b4167786ec2fa4807"}, + {file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:5556470f1a2157031e676f776c2bc20acd34c1990ca5f7e56f1ebf938b9ab57c"}, + {file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f69ed81ab24d5a3bd93861c8c4436f54afdf8e8cc421562b0c7504cf3be58206"}, + {file = "pydantic_core-2.27.1-cp310-none-win32.whl", hash = "sha256:f5a823165e6d04ccea61a9f0576f345f8ce40ed533013580e087bd4d7442b52c"}, + {file = "pydantic_core-2.27.1-cp310-none-win_amd64.whl", hash = "sha256:57866a76e0b3823e0b56692d1a0bf722bffb324839bb5b7226a7dbd6c9a40b17"}, + {file = "pydantic_core-2.27.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ac3b20653bdbe160febbea8aa6c079d3df19310d50ac314911ed8cc4eb7f8cb8"}, + {file = "pydantic_core-2.27.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a5a8e19d7c707c4cadb8c18f5f60c843052ae83c20fa7d44f41594c644a1d330"}, + {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f7059ca8d64fea7f238994c97d91f75965216bcbe5f695bb44f354893f11d52"}, + {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bed0f8a0eeea9fb72937ba118f9db0cb7e90773462af7962d382445f3005e5a4"}, + {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a3cb37038123447cf0f3ea4c74751f6a9d7afef0eb71aa07bf5f652b5e6a132c"}, + {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84286494f6c5d05243456e04223d5a9417d7f443c3b76065e75001beb26f88de"}, + {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acc07b2cfc5b835444b44a9956846b578d27beeacd4b52e45489e93276241025"}, + {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4fefee876e07a6e9aad7a8c8c9f85b0cdbe7df52b8a9552307b09050f7512c7e"}, + {file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:258c57abf1188926c774a4c94dd29237e77eda19462e5bb901d88adcab6af919"}, + {file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:35c14ac45fcfdf7167ca76cc80b2001205a8d5d16d80524e13508371fb8cdd9c"}, + {file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d1b26e1dff225c31897696cab7d4f0a315d4c0d9e8666dbffdb28216f3b17fdc"}, + {file = "pydantic_core-2.27.1-cp311-none-win32.whl", hash = "sha256:2cdf7d86886bc6982354862204ae3b2f7f96f21a3eb0ba5ca0ac42c7b38598b9"}, + {file = "pydantic_core-2.27.1-cp311-none-win_amd64.whl", hash = "sha256:3af385b0cee8df3746c3f406f38bcbfdc9041b5c2d5ce3e5fc6637256e60bbc5"}, + {file = "pydantic_core-2.27.1-cp311-none-win_arm64.whl", hash = "sha256:81f2ec23ddc1b476ff96563f2e8d723830b06dceae348ce02914a37cb4e74b89"}, + {file = "pydantic_core-2.27.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9cbd94fc661d2bab2bc702cddd2d3370bbdcc4cd0f8f57488a81bcce90c7a54f"}, + {file = "pydantic_core-2.27.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5f8c4718cd44ec1580e180cb739713ecda2bdee1341084c1467802a417fe0f02"}, + {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15aae984e46de8d376df515f00450d1522077254ef6b7ce189b38ecee7c9677c"}, + {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1ba5e3963344ff25fc8c40da90f44b0afca8cfd89d12964feb79ac1411a260ac"}, + {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:992cea5f4f3b29d6b4f7f1726ed8ee46c8331c6b4eed6db5b40134c6fe1768bb"}, + {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0325336f348dbee6550d129b1627cb8f5351a9dc91aad141ffb96d4937bd9529"}, + {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7597c07fbd11515f654d6ece3d0e4e5093edc30a436c63142d9a4b8e22f19c35"}, + {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3bbd5d8cc692616d5ef6fbbbd50dbec142c7e6ad9beb66b78a96e9c16729b089"}, + {file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:dc61505e73298a84a2f317255fcc72b710b72980f3a1f670447a21efc88f8381"}, + {file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:e1f735dc43da318cad19b4173dd1ffce1d84aafd6c9b782b3abc04a0d5a6f5bb"}, + {file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f4e5658dbffe8843a0f12366a4c2d1c316dbe09bb4dfbdc9d2d9cd6031de8aae"}, + {file = "pydantic_core-2.27.1-cp312-none-win32.whl", hash = "sha256:672ebbe820bb37988c4d136eca2652ee114992d5d41c7e4858cdd90ea94ffe5c"}, + {file = "pydantic_core-2.27.1-cp312-none-win_amd64.whl", hash = "sha256:66ff044fd0bb1768688aecbe28b6190f6e799349221fb0de0e6f4048eca14c16"}, + {file = "pydantic_core-2.27.1-cp312-none-win_arm64.whl", hash = "sha256:9a3b0793b1bbfd4146304e23d90045f2a9b5fd5823aa682665fbdaf2a6c28f3e"}, + {file = "pydantic_core-2.27.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f216dbce0e60e4d03e0c4353c7023b202d95cbaeff12e5fd2e82ea0a66905073"}, + {file = "pydantic_core-2.27.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a2e02889071850bbfd36b56fd6bc98945e23670773bc7a76657e90e6b6603c08"}, + {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42b0e23f119b2b456d07ca91b307ae167cc3f6c846a7b169fca5326e32fdc6cf"}, + {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:764be71193f87d460a03f1f7385a82e226639732214b402f9aa61f0d025f0737"}, + {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c00666a3bd2f84920a4e94434f5974d7bbc57e461318d6bb34ce9cdbbc1f6b2"}, + {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ccaa88b24eebc0f849ce0a4d09e8a408ec5a94afff395eb69baf868f5183107"}, + {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c65af9088ac534313e1963443d0ec360bb2b9cba6c2909478d22c2e363d98a51"}, + {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:206b5cf6f0c513baffaeae7bd817717140770c74528f3e4c3e1cec7871ddd61a"}, + {file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:062f60e512fc7fff8b8a9d680ff0ddaaef0193dba9fa83e679c0c5f5fbd018bc"}, + {file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:a0697803ed7d4af5e4c1adf1670af078f8fcab7a86350e969f454daf598c4960"}, + {file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:58ca98a950171f3151c603aeea9303ef6c235f692fe555e883591103da709b23"}, + {file = "pydantic_core-2.27.1-cp313-none-win32.whl", hash = "sha256:8065914ff79f7eab1599bd80406681f0ad08f8e47c880f17b416c9f8f7a26d05"}, + {file = "pydantic_core-2.27.1-cp313-none-win_amd64.whl", hash = "sha256:ba630d5e3db74c79300d9a5bdaaf6200172b107f263c98a0539eeecb857b2337"}, + {file = "pydantic_core-2.27.1-cp313-none-win_arm64.whl", hash = "sha256:45cf8588c066860b623cd11c4ba687f8d7175d5f7ef65f7129df8a394c502de5"}, + {file = "pydantic_core-2.27.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:5897bec80a09b4084aee23f9b73a9477a46c3304ad1d2d07acca19723fb1de62"}, + {file = "pydantic_core-2.27.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d0165ab2914379bd56908c02294ed8405c252250668ebcb438a55494c69f44ab"}, + {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b9af86e1d8e4cfc82c2022bfaa6f459381a50b94a29e95dcdda8442d6d83864"}, + {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f6c8a66741c5f5447e047ab0ba7a1c61d1e95580d64bce852e3df1f895c4067"}, + {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a42d6a8156ff78981f8aa56eb6394114e0dedb217cf8b729f438f643608cbcd"}, + {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64c65f40b4cd8b0e049a8edde07e38b476da7e3aaebe63287c899d2cff253fa5"}, + {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdcf339322a3fae5cbd504edcefddd5a50d9ee00d968696846f089b4432cf78"}, + {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bf99c8404f008750c846cb4ac4667b798a9f7de673ff719d705d9b2d6de49c5f"}, + {file = "pydantic_core-2.27.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8f1edcea27918d748c7e5e4d917297b2a0ab80cad10f86631e488b7cddf76a36"}, + {file = "pydantic_core-2.27.1-cp38-cp38-musllinux_1_1_armv7l.whl", hash = "sha256:159cac0a3d096f79ab6a44d77a961917219707e2a130739c64d4dd46281f5c2a"}, + {file = "pydantic_core-2.27.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:029d9757eb621cc6e1848fa0b0310310de7301057f623985698ed7ebb014391b"}, + {file = "pydantic_core-2.27.1-cp38-none-win32.whl", hash = "sha256:a28af0695a45f7060e6f9b7092558a928a28553366519f64083c63a44f70e618"}, + {file = "pydantic_core-2.27.1-cp38-none-win_amd64.whl", hash = "sha256:2d4567c850905d5eaaed2f7a404e61012a51caf288292e016360aa2b96ff38d4"}, + {file = "pydantic_core-2.27.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:e9386266798d64eeb19dd3677051f5705bf873e98e15897ddb7d76f477131967"}, + {file = "pydantic_core-2.27.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4228b5b646caa73f119b1ae756216b59cc6e2267201c27d3912b592c5e323b60"}, + {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b3dfe500de26c52abe0477dde16192ac39c98f05bf2d80e76102d394bd13854"}, + {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:aee66be87825cdf72ac64cb03ad4c15ffef4143dbf5c113f64a5ff4f81477bf9"}, + {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b748c44bb9f53031c8cbc99a8a061bc181c1000c60a30f55393b6e9c45cc5bd"}, + {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ca038c7f6a0afd0b2448941b6ef9d5e1949e999f9e5517692eb6da58e9d44be"}, + {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e0bd57539da59a3e4671b90a502da9a28c72322a4f17866ba3ac63a82c4498e"}, + {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ac6c2c45c847bbf8f91930d88716a0fb924b51e0c6dad329b793d670ec5db792"}, + {file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b94d4ba43739bbe8b0ce4262bcc3b7b9f31459ad120fb595627eaeb7f9b9ca01"}, + {file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:00e6424f4b26fe82d44577b4c842d7df97c20be6439e8e685d0d715feceb9fb9"}, + {file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:38de0a70160dd97540335b7ad3a74571b24f1dc3ed33f815f0880682e6880131"}, + {file = "pydantic_core-2.27.1-cp39-none-win32.whl", hash = "sha256:7ccebf51efc61634f6c2344da73e366c75e735960b5654b63d7e6f69a5885fa3"}, + {file = "pydantic_core-2.27.1-cp39-none-win_amd64.whl", hash = "sha256:a57847b090d7892f123726202b7daa20df6694cbd583b67a592e856bff603d6c"}, + {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3fa80ac2bd5856580e242dbc202db873c60a01b20309c8319b5c5986fbe53ce6"}, + {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d950caa237bb1954f1b8c9227b5065ba6875ac9771bb8ec790d956a699b78676"}, + {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e4216e64d203e39c62df627aa882f02a2438d18a5f21d7f721621f7a5d3611d"}, + {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02a3d637bd387c41d46b002f0e49c52642281edacd2740e5a42f7017feea3f2c"}, + {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:161c27ccce13b6b0c8689418da3885d3220ed2eae2ea5e9b2f7f3d48f1d52c27"}, + {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:19910754e4cc9c63bc1c7f6d73aa1cfee82f42007e407c0f413695c2f7ed777f"}, + {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:e173486019cc283dc9778315fa29a363579372fe67045e971e89b6365cc035ed"}, + {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:af52d26579b308921b73b956153066481f064875140ccd1dfd4e77db89dbb12f"}, + {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:981fb88516bd1ae8b0cbbd2034678a39dedc98752f264ac9bc5839d3923fa04c"}, + {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5fde892e6c697ce3e30c61b239330fc5d569a71fefd4eb6512fc6caec9dd9e2f"}, + {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:816f5aa087094099fff7edabb5e01cc370eb21aa1a1d44fe2d2aefdfb5599b31"}, + {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c10c309e18e443ddb108f0ef64e8729363adbfd92d6d57beec680f6261556f3"}, + {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98476c98b02c8e9b2eec76ac4156fd006628b1b2d0ef27e548ffa978393fd154"}, + {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c3027001c28434e7ca5a6e1e527487051136aa81803ac812be51802150d880dd"}, + {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:7699b1df36a48169cdebda7ab5a2bac265204003f153b4bd17276153d997670a"}, + {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1c39b07d90be6b48968ddc8c19e7585052088fd7ec8d568bb31ff64c70ae3c97"}, + {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:46ccfe3032b3915586e469d4972973f893c0a2bb65669194a5bdea9bacc088c2"}, + {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:62ba45e21cf6571d7f716d903b5b7b6d2617e2d5d67c0923dc47b9d41369f840"}, + {file = "pydantic_core-2.27.1.tar.gz", hash = "sha256:62a763352879b84aa31058fc931884055fd75089cccbd9d58bb6afd01141b235"}, ] [package.dependencies] @@ -1625,13 +1633,13 @@ type = ["importlib-metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.12 [[package]] name = "six" -version = "1.16.0" +version = "1.17.0" description = "Python 2 and 3 compatibility utilities" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, + {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, + {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, ] [[package]] @@ -1799,13 +1807,43 @@ files = [ [[package]] name = "tomli" -version = "2.1.0" +version = "2.2.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" files = [ - {file = "tomli-2.1.0-py3-none-any.whl", hash = "sha256:a5c57c3d1c56f5ccdf89f6523458f60ef716e210fc47c4cfb188c5ba473e0391"}, - {file = "tomli-2.1.0.tar.gz", hash = "sha256:3f646cae2aec94e17d04973e4249548320197cfabdf130015d023de4b74d8ab8"}, + {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, + {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"}, + {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"}, + {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"}, + {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"}, + {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"}, + {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"}, + {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"}, + {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, + {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, ] [[package]] @@ -1876,13 +1914,13 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" -version = "20.27.1" +version = "20.28.0" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.8" files = [ - {file = "virtualenv-20.27.1-py3-none-any.whl", hash = "sha256:f11f1b8a29525562925f745563bfd48b189450f61fb34c4f9cc79dd5aa32a1f4"}, - {file = "virtualenv-20.27.1.tar.gz", hash = "sha256:142c6be10212543b32c6c45d3d3893dff89112cc588b7d0879ae5a1ec03a47ba"}, + {file = "virtualenv-20.28.0-py3-none-any.whl", hash = "sha256:23eae1b4516ecd610481eda647f3a7c09aea295055337331bb4e6892ecce47b0"}, + {file = "virtualenv-20.28.0.tar.gz", hash = "sha256:2c9c3262bb8e7b87ea801d715fae4495e6032450c71d2309be9550e7364049aa"}, ] [package.dependencies] diff --git a/src/betterproto/__init__.py b/src/betterproto/__init__.py index 7ed3785e..82c0c934 100644 --- a/src/betterproto/__init__.py +++ b/src/betterproto/__init__.py @@ -168,24 +168,6 @@ class Casing(builtin_enum.Enum): SNAKE = snake_case #: A snake_case sterilization function. -class Placeholder: - __slots__ = () - - def __repr__(self) -> str: - return "" - - def __copy__(self) -> Self: - return self - - def __deepcopy__(self, _) -> Self: - return self - - -# We can't simply use object() here because pydantic automatically performs deep-copy of mutable default values -# See #606 -PLACEHOLDER: Any = Placeholder() - - @dataclasses.dataclass(frozen=True) class FieldMetadata: """Stores internal metadata used for parsing & serialization.""" @@ -212,15 +194,23 @@ def get(field: dataclasses.Field) -> "FieldMetadata": def dataclass_field( number: int, proto_type: str, + default_factory: Callable[[], Any], *, map_types: Optional[Tuple[str, str]] = None, group: Optional[str] = None, wraps: Optional[str] = None, optional: bool = False, + repeated: bool = False, ) -> dataclasses.Field: """Creates a dataclass field with attached protobuf metadata.""" + if repeated: + default_factory = list + + elif optional or group: + default_factory = type(None) + return dataclasses.field( - default=None if optional else PLACEHOLDER, # type: ignore + default_factory=default_factory, metadata={ "betterproto": FieldMetadata( number, proto_type, map_types, group, wraps, optional @@ -234,96 +224,251 @@ def dataclass_field( # out at runtime. The generated dataclass variables are still typed correctly. -def enum_field(number: int, group: Optional[str] = None, optional: bool = False) -> Any: - return dataclass_field(number, TYPE_ENUM, group=group, optional=optional) +def enum_field( + number: int, + enum_default_value: Callable[[], Enum], + group: Optional[str] = None, + optional: bool = False, + repeated: bool = False, +) -> Any: + return dataclass_field( + number, + TYPE_ENUM, + enum_default_value, + group=group, + optional=optional, + repeated=repeated, + ) -def bool_field(number: int, group: Optional[str] = None, optional: bool = False) -> Any: - return dataclass_field(number, TYPE_BOOL, group=group, optional=optional) +def bool_field( + number: int, + group: Optional[str] = None, + optional: bool = False, + repeated: bool = False, +) -> Any: + return dataclass_field( + number, + TYPE_BOOL, + bool, + group=group, + optional=optional, + repeated=repeated, + ) def int32_field( - number: int, group: Optional[str] = None, optional: bool = False + number: int, + group: Optional[str] = None, + optional: bool = False, + repeated: bool = False, ) -> Any: - return dataclass_field(number, TYPE_INT32, group=group, optional=optional) + return dataclass_field( + number, TYPE_INT32, int, group=group, optional=optional, repeated=repeated + ) def int64_field( - number: int, group: Optional[str] = None, optional: bool = False + number: int, + group: Optional[str] = None, + optional: bool = False, + repeated: bool = False, ) -> Any: - return dataclass_field(number, TYPE_INT64, group=group, optional=optional) + return dataclass_field( + number, TYPE_INT64, int, group=group, optional=optional, repeated=repeated + ) def uint32_field( - number: int, group: Optional[str] = None, optional: bool = False + number: int, + group: Optional[str] = None, + optional: bool = False, + repeated: bool = False, ) -> Any: - return dataclass_field(number, TYPE_UINT32, group=group, optional=optional) + return dataclass_field( + number, + TYPE_UINT32, + int, + group=group, + optional=optional, + repeated=repeated, + ) def uint64_field( - number: int, group: Optional[str] = None, optional: bool = False + number: int, + group: Optional[str] = None, + optional: bool = False, + repeated: bool = False, ) -> Any: - return dataclass_field(number, TYPE_UINT64, group=group, optional=optional) + return dataclass_field( + number, + TYPE_UINT64, + int, + group=group, + optional=optional, + repeated=repeated, + ) def sint32_field( - number: int, group: Optional[str] = None, optional: bool = False + number: int, + group: Optional[str] = None, + optional: bool = False, + repeated: bool = False, ) -> Any: - return dataclass_field(number, TYPE_SINT32, group=group, optional=optional) + return dataclass_field( + number, + TYPE_SINT32, + int, + group=group, + optional=optional, + repeated=repeated, + ) def sint64_field( - number: int, group: Optional[str] = None, optional: bool = False + number: int, + group: Optional[str] = None, + optional: bool = False, + repeated: bool = False, ) -> Any: - return dataclass_field(number, TYPE_SINT64, group=group, optional=optional) + return dataclass_field( + number, + TYPE_SINT64, + int, + group=group, + optional=optional, + repeated=repeated, + ) def float_field( - number: int, group: Optional[str] = None, optional: bool = False + number: int, + group: Optional[str] = None, + optional: bool = False, + repeated: bool = False, ) -> Any: - return dataclass_field(number, TYPE_FLOAT, group=group, optional=optional) + return dataclass_field( + number, + TYPE_FLOAT, + float, + group=group, + optional=optional, + repeated=repeated, + ) def double_field( - number: int, group: Optional[str] = None, optional: bool = False + number: int, + group: Optional[str] = None, + optional: bool = False, + repeated: bool = False, ) -> Any: - return dataclass_field(number, TYPE_DOUBLE, group=group, optional=optional) + return dataclass_field( + number, + TYPE_DOUBLE, + float, + group=group, + optional=optional, + repeated=repeated, + ) def fixed32_field( - number: int, group: Optional[str] = None, optional: bool = False + number: int, + group: Optional[str] = None, + optional: bool = False, + repeated: bool = False, ) -> Any: - return dataclass_field(number, TYPE_FIXED32, group=group, optional=optional) + return dataclass_field( + number, + TYPE_FIXED32, + float, + group=group, + optional=optional, + repeated=repeated, + ) def fixed64_field( - number: int, group: Optional[str] = None, optional: bool = False + number: int, + group: Optional[str] = None, + optional: bool = False, + repeated: bool = False, ) -> Any: - return dataclass_field(number, TYPE_FIXED64, group=group, optional=optional) + return dataclass_field( + number, + TYPE_FIXED64, + float, + group=group, + optional=optional, + repeated=repeated, + ) def sfixed32_field( - number: int, group: Optional[str] = None, optional: bool = False + number: int, + group: Optional[str] = None, + optional: bool = False, + repeated: bool = False, ) -> Any: - return dataclass_field(number, TYPE_SFIXED32, group=group, optional=optional) + return dataclass_field( + number, + TYPE_SFIXED32, + float, + group=group, + optional=optional, + repeated=repeated, + ) def sfixed64_field( - number: int, group: Optional[str] = None, optional: bool = False + number: int, + group: Optional[str] = None, + optional: bool = False, + repeated: bool = False, ) -> Any: - return dataclass_field(number, TYPE_SFIXED64, group=group, optional=optional) + return dataclass_field( + number, + TYPE_SFIXED64, + float, + group=group, + optional=optional, + repeated=repeated, + ) def string_field( - number: int, group: Optional[str] = None, optional: bool = False + number: int, + group: Optional[str] = None, + optional: bool = False, + repeated: bool = False, ) -> Any: - return dataclass_field(number, TYPE_STRING, group=group, optional=optional) + return dataclass_field( + number, + TYPE_STRING, + str, + group=group, + optional=optional, + repeated=repeated, + ) def bytes_field( - number: int, group: Optional[str] = None, optional: bool = False + number: int, + group: Optional[str] = None, + optional: bool = False, + repeated: bool = False, ) -> Any: - return dataclass_field(number, TYPE_BYTES, group=group, optional=optional) + return dataclass_field( + number, + TYPE_BYTES, + bytes, + group=group, + optional=optional, + repeated=repeated, + ) def message_field( @@ -331,9 +476,16 @@ def message_field( group: Optional[str] = None, wraps: Optional[str] = None, optional: bool = False, + repeated: bool = False, ) -> Any: return dataclass_field( - number, TYPE_MESSAGE, group=group, wraps=wraps, optional=optional + number, + TYPE_MESSAGE, + type(None), + group=group, + wraps=wraps, + optional=optional, + repeated=repeated, ) @@ -341,7 +493,7 @@ def map_field( number: int, key_type: str, value_type: str, group: Optional[str] = None ) -> Any: return dataclass_field( - number, TYPE_MAP, map_types=(key_type, value_type), group=group + number, TYPE_MAP, dict, map_types=(key_type, value_type), group=group ) @@ -726,8 +878,16 @@ def _get_cls_by_field( field_cls[field.name] = dataclasses.make_dataclass( "Entry", [ - ("key", kt, dataclass_field(1, meta.map_types[0])), - ("value", vt, dataclass_field(2, meta.map_types[1])), + ( + "key", + kt, + dataclass_field(1, meta.map_types[0], default_factory=kt), + ), + ( + "value", + vt, + dataclass_field(2, meta.map_types[1], default_factory=vt), + ), ], bases=(Message,), ) @@ -755,14 +915,13 @@ class Message(ABC): Calls :meth:`__bool__`. """ - _serialized_on_wire: bool _unknown_fields: bytes _group_current: Dict[str, str] _betterproto_meta: ClassVar[ProtoClassMetadata] def __post_init__(self) -> None: - # Keep track of whether every field was default - all_sentinel = True + # # Keep track of whether every field was default + # all_sentinel = True # Set current field of each group after `__init__` has already been run. group_current: Dict[str, Optional[str]] = {} @@ -770,17 +929,11 @@ def __post_init__(self) -> None: if meta.group: group_current.setdefault(meta.group) - value = self.__raw_get(field_name) - if value is not PLACEHOLDER and not (meta.optional and value is None): - # Found a non-sentinel value - all_sentinel = False - - if meta.group: - # This was set, so make it the selected value of the one-of. + value = self.__raw_get(field_name) + if value is not None: group_current[meta.group] = field_name # Now that all the defaults are set, reset it! - self.__dict__["_serialized_on_wire"] = not all_sentinel self.__dict__["_unknown_fields"] = b"" self.__dict__["_group_current"] = group_current @@ -794,12 +947,6 @@ def __eq__(self, other) -> bool: for field_name in self._betterproto.meta_by_field_name: self_val = self.__raw_get(field_name) other_val = other.__raw_get(field_name) - if self_val is PLACEHOLDER: - if other_val is PLACEHOLDER: - continue - self_val = self._get_field_default(field_name) - elif other_val is PLACEHOLDER: - other_val = other._get_field_default(field_name) if self_val != other_val: # We consider two nan values to be the same for the @@ -822,61 +969,15 @@ def __repr__(self) -> str: f"{field_name}={value!r}" for field_name in self._betterproto.sorted_field_names for value in (self.__raw_get(field_name),) - if value is not PLACEHOLDER + if value != self._get_field_default(field_name) ] return f"{self.__class__.__name__}({', '.join(parts)})" - def __rich_repr__(self) -> Iterable[Tuple[str, Any, Any]]: - for field_name in self._betterproto.sorted_field_names: - yield field_name, self.__raw_get(field_name), PLACEHOLDER - - if not TYPE_CHECKING: - - def __getattribute__(self, name: str) -> Any: - """ - Lazily initialize default values to avoid infinite recursion for recursive - message types. - Raise :class:`AttributeError` on attempts to access unset ``oneof`` fields. - """ - try: - group_current = super().__getattribute__("_group_current") - except AttributeError: - pass - else: - if name not in {"__class__", "_betterproto"}: - group = self._betterproto.oneof_group_by_field.get(name) - if group is not None and group_current[group] != name: - if sys.version_info < (3, 10): - raise AttributeError( - f"{group!r} is set to {group_current[group]!r}, not {name!r}" - ) - else: - raise AttributeError( - f"{group!r} is set to {group_current[group]!r}, not {name!r}", - name=name, - obj=self, - ) - - value = super().__getattribute__(name) - if value is not PLACEHOLDER: - return value - - value = self._get_field_default(name) - super().__setattr__(name, value) - return value + # def __rich_repr__(self) -> Iterable[Tuple[str, Any, Any]]: + # for field_name in self._betterproto.sorted_field_names: + # yield field_name, self.__raw_get(field_name), PLACEHOLDER def __setattr__(self, attr: str, value: Any) -> None: - if ( - isinstance(value, Message) - and hasattr(value, "_betterproto") - and not value._betterproto.meta_by_field_name - ): - value._serialized_on_wire = True - - if attr != "_serialized_on_wire": - # Track when a field has been set. - self.__dict__["_serialized_on_wire"] = True - if hasattr(self, "_group_current"): # __post_init__ had already run if attr in self._betterproto.oneof_group_by_field: group = self._betterproto.oneof_group_by_field[attr] @@ -884,15 +985,14 @@ def __setattr__(self, attr: str, value: Any) -> None: if field.name == attr: self._group_current[group] = field.name else: - super().__setattr__(field.name, PLACEHOLDER) + super().__setattr__(field.name, None) super().__setattr__(attr, value) def __bool__(self) -> bool: """True if the Message has any fields with non-default values.""" return any( - self.__raw_get(field_name) - not in (PLACEHOLDER, self._get_field_default(field_name)) + self.__raw_get(field_name) != self._get_field_default(field_name) for field_name in self._betterproto.meta_by_field_name ) @@ -900,16 +1000,14 @@ def __deepcopy__(self: T, _: Any = {}) -> T: kwargs = {} for name in self._betterproto.sorted_field_names: value = self.__raw_get(name) - if value is not PLACEHOLDER: - kwargs[name] = deepcopy(value) + kwargs[name] = deepcopy(value) return self.__class__(**kwargs) # type: ignore def __copy__(self: T, _: Any = {}) -> T: kwargs = {} for name in self._betterproto.sorted_field_names: value = self.__raw_get(name) - if value is not PLACEHOLDER: - kwargs[name] = value + kwargs[name] = value return self.__class__(**kwargs) # type: ignore @classproperty @@ -940,10 +1038,7 @@ def dump(self, stream: "SupportsWrite[bytes]", delimit: bool = False) -> None: dump_varint(len(self), stream) for field_name, meta in self._betterproto.meta_by_field_name.items(): - try: - value = getattr(self, field_name) - except AttributeError: - continue + value = getattr(self, field_name) if value is None: # Optional items should be skipped. This is used for the Google @@ -961,7 +1056,8 @@ def dump(self, stream: "SupportsWrite[bytes]", delimit: bool = False) -> None: # Empty messages can still be sent on the wire if they were # set (or received empty). - serialize_empty = isinstance(value, Message) and value._serialized_on_wire + # TODO this is here for historical reasons, now if a message is defined (ie not None), it should be sent + serialize_empty = isinstance(value, Message) include_default_value_for_oneof = self._include_default_value_for_oneof( field_name=field_name, meta=meta @@ -1046,10 +1142,7 @@ def __len__(self) -> int: """ size = 0 for field_name, meta in self._betterproto.meta_by_field_name.items(): - try: - value = getattr(self, field_name) - except AttributeError: - continue + value = getattr(self, field_name) if value is None: # Optional items should be skipped. This is used for the Google @@ -1067,7 +1160,7 @@ def __len__(self) -> int: # Empty messages can still be sent on the wire if they were # set (or received empty). - serialize_empty = isinstance(value, Message) and value._serialized_on_wire + serialize_empty = isinstance(value, Message) include_default_value_for_oneof = self._include_default_value_for_oneof( field_name=field_name, meta=meta @@ -1208,6 +1301,9 @@ def _get_field_default_gen(cls, field: dataclasses.Field) -> Any: if t is datetime: # Offsets are relative to 1970-01-01T00:00:00Z return datetime_default_gen + # In proto 3, message fields are always optional + if issubclass(t, Message): + return type(None) # This is either a primitive scalar or another message type. Calling # it should result in its zero value. return t @@ -1250,7 +1346,6 @@ def _postprocess_single( value = _get_wrapper(meta.wraps)().parse(value).value else: value = cls().parse(value) - value._serialized_on_wire = True elif meta.proto_type == TYPE_MAP: value = self._betterproto.cls_by_field[field_name]().parse(value) @@ -1291,7 +1386,6 @@ def load( size, _ = load_varint(stream) # Got some data over the wire - self._serialized_on_wire = True proto_meta = self._betterproto read = 0 for parsed in load_fields(stream): @@ -1326,11 +1420,7 @@ def load( parsed.wire_type, meta, field_name, parsed.value ) - try: - current = getattr(self, field_name) - except AttributeError: - current = self._get_field_default(field_name) - setattr(self, field_name, current) + current = getattr(self, field_name) if meta.proto_type == TYPE_MAP: # Value represents a single key/value pair entry in the map. @@ -1430,10 +1520,7 @@ def to_dict( defaults = self._betterproto.default_gen for field_name, meta in self._betterproto.meta_by_field_name.items(): field_is_repeated = defaults[field_name] is list - try: - value = getattr(self, field_name) - except AttributeError: - value = self._get_field_default(field_name) + value = getattr(self, field_name) cased_name = casing(field_name).rstrip("_") # type: ignore if meta.proto_type == TYPE_MESSAGE: if isinstance(value, datetime): @@ -1473,13 +1560,7 @@ def to_dict( elif value is None: if include_default_values: output[cased_name] = value - elif ( - value._serialized_on_wire - or include_default_values - or self._include_default_value_for_oneof( - field_name=field_name, meta=meta - ) - ): + else: output[cased_name] = value.to_dict(casing, include_default_values) elif meta.proto_type == TYPE_MAP: output_map = {**value} @@ -1620,9 +1701,7 @@ def from_dict(cls: type[Self], value: Mapping[str, Any]) -> Self: # type: ignor :class:`Message` The initialized message. """ - self = cls(**cls._from_dict_init(value)) - self._serialized_on_wire = True - return self + return cls(**cls._from_dict_init(value)) @from_dict.instancemethod def from_dict(self, value: Mapping[str, Any]) -> Self: @@ -1640,7 +1719,6 @@ def from_dict(self, value: Mapping[str, Any]) -> Self: :class:`Message` The initialized message. """ - self._serialized_on_wire = True for field, value in self._from_dict_init(value).items(): setattr(self, field, value) return self @@ -1760,13 +1838,7 @@ def to_pydict( elif value is None: if include_default_values: output[cased_name] = None - elif ( - value._serialized_on_wire - or include_default_values - or self._include_default_value_for_oneof( - field_name=field_name, meta=meta - ) - ): + else: output[cased_name] = value.to_pydict(casing, include_default_values) elif meta.proto_type == TYPE_MAP: for k in value: @@ -1800,7 +1872,6 @@ def from_pydict(self: T, value: Mapping[str, Any]) -> T: :class:`Message` The initialized message. """ - self._serialized_on_wire = True for key in value: field_name = safe_snake_case(key) meta = self._betterproto.meta_by_field_name.get(field_name) @@ -1810,20 +1881,18 @@ def from_pydict(self: T, value: Mapping[str, Any]) -> T: if value[key] is not None: if meta.proto_type == TYPE_MESSAGE: v = getattr(self, field_name) - if isinstance(v, list): - cls = self._betterproto.cls_by_field[field_name] + cls = self._betterproto.cls_by_field[field_name] + if issubclass(cls, list): for item in value[key]: v.append(cls().from_pydict(item)) - elif isinstance(v, datetime): + elif issubclass(cls, datetime): v = value[key] - elif isinstance(v, timedelta): + elif issubclass(cls, timedelta): v = value[key] elif meta.wraps: v = value[key] else: - # NOTE: `from_pydict` mutates the underlying message, so no - # assignment here is necessary. - v.from_pydict(value[key]) + v = cls().from_pydict(value[key]) elif meta.map_types and meta.map_types[1] == TYPE_MESSAGE: v = getattr(self, field_name) cls = self._betterproto.cls_by_field[f"{field_name}.value"] @@ -1850,12 +1919,7 @@ def is_set(self, name: str) -> bool: :class:`bool` `True` if field has been set, otherwise `False`. """ - default = ( - PLACEHOLDER - if not self._betterproto.meta_by_field_name[name].optional - else None - ) - return self.__raw_get(name) is not default + return self.__raw_get(name) is not self._get_field_default(name) @classmethod def _validate_field_groups(cls, values): @@ -1889,37 +1953,6 @@ def _validate_field_groups(cls, values): Message.__annotations__ = {} # HACK to avoid typing.get_type_hints breaking :) -# monkey patch (de-)serialization functions of class `Message` -# with functions from `betterproto-rust-codec` if available -try: - import betterproto_rust_codec - - def __parse_patch(self: T, data: bytes) -> T: - betterproto_rust_codec.deserialize(self, data) - return self - - def __bytes_patch(self) -> bytes: - return betterproto_rust_codec.serialize(self) - - Message.parse = __parse_patch - Message.__bytes__ = __bytes_patch -except ModuleNotFoundError: - pass - - -def serialized_on_wire(message: Message) -> bool: - """ - If this message was or should be serialized on the wire. This can be used to detect - presence (e.g. optional wrapper message) and is used internally during - parsing/serialization. - - Returns - -------- - :class:`bool` - Whether this message was or should be serialized on the wire. - """ - return message._serialized_on_wire - def which_one_of(message: Message, group_name: str) -> Tuple[str, Optional[Any]]: """ diff --git a/src/betterproto/lib/pydantic/google/protobuf/__init__.py b/src/betterproto/lib/pydantic/google/protobuf/__init__.py index d147dece..1d60f5b2 100644 --- a/src/betterproto/lib/pydantic/google/protobuf/__init__.py +++ b/src/betterproto/lib/pydantic/google/protobuf/__init__.py @@ -663,7 +663,9 @@ class Type(betterproto.Message): source_context: "SourceContext" = betterproto.message_field(5) """The source context.""" - syntax: "Syntax" = betterproto.enum_field(6) + syntax: "Syntax" = betterproto.enum_field( + 6, enum_default_value=lambda: Syntax.try_value(0) + ) """The source syntax.""" edition: str = betterproto.string_field(7) @@ -676,10 +678,14 @@ class Type(betterproto.Message): class Field(betterproto.Message): """A single field of a message type.""" - kind: "FieldKind" = betterproto.enum_field(1) + kind: "FieldKind" = betterproto.enum_field( + 1, enum_default_value=lambda: FieldKind.try_value(0) + ) """The field type.""" - cardinality: "FieldCardinality" = betterproto.enum_field(2) + cardinality: "FieldCardinality" = betterproto.enum_field( + 2, enum_default_value=lambda: FieldCardinality.try_value(0) + ) """The field cardinality.""" number: int = betterproto.int32_field(3) @@ -733,7 +739,9 @@ class Enum(betterproto.Message): source_context: "SourceContext" = betterproto.message_field(4) """The source context.""" - syntax: "Syntax" = betterproto.enum_field(5) + syntax: "Syntax" = betterproto.enum_field( + 5, enum_default_value=lambda: Syntax.try_value(0) + ) """The source syntax.""" edition: str = betterproto.string_field(6) @@ -838,7 +846,9 @@ class Api(betterproto.Message): mixins: List["Mixin"] = betterproto.message_field(6) """Included interfaces. See [Mixin][].""" - syntax: "Syntax" = betterproto.enum_field(7) + syntax: "Syntax" = betterproto.enum_field( + 7, enum_default_value=lambda: Syntax.try_value(0) + ) """The source syntax of the service.""" @@ -864,7 +874,9 @@ class Method(betterproto.Message): options: List["Option"] = betterproto.message_field(6) """Any metadata attached to the method.""" - syntax: "Syntax" = betterproto.enum_field(7) + syntax: "Syntax" = betterproto.enum_field( + 7, enum_default_value=lambda: Syntax.try_value(0) + ) """The source syntax of this method.""" @@ -1012,7 +1024,9 @@ class FileDescriptorProto(betterproto.Message): If `edition` is present, this value must be "editions". """ - edition: "Edition" = betterproto.enum_field(14) + edition: "Edition" = betterproto.enum_field( + 14, enum_default_value=lambda: Edition.try_value(0) + ) """The edition of the proto file.""" @@ -1072,7 +1086,10 @@ class ExtensionRangeOptions(betterproto.Message): features: "FeatureSet" = betterproto.message_field(50) """Any features defined in the specific edition.""" - verification: "ExtensionRangeOptionsVerificationState" = betterproto.enum_field(3) + verification: "ExtensionRangeOptionsVerificationState" = betterproto.enum_field( + 3, + enum_default_value=lambda: ExtensionRangeOptionsVerificationState.try_value(0), + ) """ The verification state of the range. TODO: flip the default to DECLARATION once all empty ranges @@ -1118,8 +1135,12 @@ class FieldDescriptorProto(betterproto.Message): name: str = betterproto.string_field(1) number: int = betterproto.int32_field(3) - label: "FieldDescriptorProtoLabel" = betterproto.enum_field(4) - type: "FieldDescriptorProtoType" = betterproto.enum_field(5) + label: "FieldDescriptorProtoLabel" = betterproto.enum_field( + 4, enum_default_value=lambda: FieldDescriptorProtoLabel.try_value(0) + ) + type: "FieldDescriptorProtoType" = betterproto.enum_field( + 5, enum_default_value=lambda: FieldDescriptorProtoType.try_value(0) + ) """ If type_name is set, this need not be set. If both this and type_name are set, this must be one of TYPE_ENUM, TYPE_MESSAGE or TYPE_GROUP. @@ -1315,7 +1336,9 @@ class FileOptions(betterproto.Message): This option has no effect on when used with the lite runtime. """ - optimize_for: "FileOptionsOptimizeMode" = betterproto.enum_field(9) + optimize_for: "FileOptionsOptimizeMode" = betterproto.enum_field( + 9, enum_default_value=lambda: FileOptionsOptimizeMode.try_value(0) + ) go_package: str = betterproto.string_field(11) """ Sets the Go package where structs generated from this .proto will be @@ -1512,7 +1535,9 @@ def __post_init__(self) -> None: @dataclass(eq=False, repr=False) class FieldOptions(betterproto.Message): - ctype: "FieldOptionsCType" = betterproto.enum_field(1) + ctype: "FieldOptionsCType" = betterproto.enum_field( + 1, enum_default_value=lambda: FieldOptionsCType.try_value(0) + ) """ The ctype option instructs the C++ code generator to use a different representation of the field than it normally would. See the specific @@ -1533,7 +1558,9 @@ class FieldOptions(betterproto.Message): the behavior. """ - jstype: "FieldOptionsJsType" = betterproto.enum_field(6) + jstype: "FieldOptionsJsType" = betterproto.enum_field( + 6, enum_default_value=lambda: FieldOptionsJsType.try_value(0) + ) """ The jstype option determines the JavaScript type used for values of the field. The option is permitted only for 64 bit integral and fixed types @@ -1598,8 +1625,12 @@ class FieldOptions(betterproto.Message): formats, e.g. when the field contains sensitive credentials. """ - retention: "FieldOptionsOptionRetention" = betterproto.enum_field(17) - targets: List["FieldOptionsOptionTargetType"] = betterproto.enum_field(19) + retention: "FieldOptionsOptionRetention" = betterproto.enum_field( + 17, enum_default_value=lambda: FieldOptionsOptionRetention.try_value(0) + ) + targets: List["FieldOptionsOptionTargetType"] = betterproto.enum_field( + 19, enum_default_value=lambda: FieldOptionsOptionTargetType.try_value(0) + ) edition_defaults: List["FieldOptionsEditionDefault"] = betterproto.message_field(20) features: "FeatureSet" = betterproto.message_field(21) """Any features defined in the specific edition.""" @@ -1610,7 +1641,9 @@ class FieldOptions(betterproto.Message): @dataclass(eq=False, repr=False) class FieldOptionsEditionDefault(betterproto.Message): - edition: "Edition" = betterproto.enum_field(3) + edition: "Edition" = betterproto.enum_field( + 3, enum_default_value=lambda: Edition.try_value(0) + ) value: str = betterproto.string_field(2) @@ -1715,7 +1748,9 @@ class MethodOptions(betterproto.Message): this is a formalization for deprecating methods. """ - idempotency_level: "MethodOptionsIdempotencyLevel" = betterproto.enum_field(34) + idempotency_level: "MethodOptionsIdempotencyLevel" = betterproto.enum_field( + 34, enum_default_value=lambda: MethodOptionsIdempotencyLevel.try_value(0) + ) features: "FeatureSet" = betterproto.message_field(35) """Any features defined in the specific edition.""" @@ -1773,14 +1808,24 @@ class FeatureSet(betterproto.Message): conflict here. """ - field_presence: "FeatureSetFieldPresence" = betterproto.enum_field(1) - enum_type: "FeatureSetEnumType" = betterproto.enum_field(2) + field_presence: "FeatureSetFieldPresence" = betterproto.enum_field( + 1, enum_default_value=lambda: FeatureSetFieldPresence.try_value(0) + ) + enum_type: "FeatureSetEnumType" = betterproto.enum_field( + 2, enum_default_value=lambda: FeatureSetEnumType.try_value(0) + ) repeated_field_encoding: "FeatureSetRepeatedFieldEncoding" = betterproto.enum_field( - 3 + 3, enum_default_value=lambda: FeatureSetRepeatedFieldEncoding.try_value(0) + ) + utf8_validation: "FeatureSetUtf8Validation" = betterproto.enum_field( + 4, enum_default_value=lambda: FeatureSetUtf8Validation.try_value(0) + ) + message_encoding: "FeatureSetMessageEncoding" = betterproto.enum_field( + 5, enum_default_value=lambda: FeatureSetMessageEncoding.try_value(0) + ) + json_format: "FeatureSetJsonFormat" = betterproto.enum_field( + 6, enum_default_value=lambda: FeatureSetJsonFormat.try_value(0) ) - utf8_validation: "FeatureSetUtf8Validation" = betterproto.enum_field(4) - message_encoding: "FeatureSetMessageEncoding" = betterproto.enum_field(5) - json_format: "FeatureSetJsonFormat" = betterproto.enum_field(6) @dataclass(eq=False, repr=False) @@ -1795,13 +1840,17 @@ class FeatureSetDefaults(betterproto.Message): defaults: List["FeatureSetDefaultsFeatureSetEditionDefault"] = ( betterproto.message_field(1) ) - minimum_edition: "Edition" = betterproto.enum_field(4) + minimum_edition: "Edition" = betterproto.enum_field( + 4, enum_default_value=lambda: Edition.try_value(0) + ) """ The minimum supported edition (inclusive) when this was constructed. Editions before this will not have defaults. """ - maximum_edition: "Edition" = betterproto.enum_field(5) + maximum_edition: "Edition" = betterproto.enum_field( + 5, enum_default_value=lambda: Edition.try_value(0) + ) """ The maximum known edition (inclusive) when this was constructed. Editions after this will not have reliable defaults. @@ -1817,7 +1866,9 @@ class FeatureSetDefaultsFeatureSetEditionDefault(betterproto.Message): be used. This field must be in strict ascending order by edition. """ - edition: "Edition" = betterproto.enum_field(3) + edition: "Edition" = betterproto.enum_field( + 3, enum_default_value=lambda: Edition.try_value(0) + ) features: "FeatureSet" = betterproto.message_field(2) @@ -2008,7 +2059,9 @@ class GeneratedCodeInfoAnnotation(betterproto.Message): the last relevant byte (so the length of the text = end - begin). """ - semantic: "GeneratedCodeInfoAnnotationSemantic" = betterproto.enum_field(5) + semantic: "GeneratedCodeInfoAnnotationSemantic" = betterproto.enum_field( + 5, enum_default_value=lambda: GeneratedCodeInfoAnnotationSemantic.try_value(0) + ) @dataclass(eq=False, repr=False) @@ -2372,7 +2425,10 @@ class Value(betterproto.Message): """ null_value: Optional["NullValue"] = betterproto.enum_field( - 1, optional=True, group="kind" + 1, + enum_default_value=lambda: NullValue.try_value(0), + optional=True, + group="kind", ) """Represents a null value.""" diff --git a/src/betterproto/lib/std/google/protobuf/__init__.py b/src/betterproto/lib/std/google/protobuf/__init__.py index 2b87b3bc..76770f4b 100644 --- a/src/betterproto/lib/std/google/protobuf/__init__.py +++ b/src/betterproto/lib/std/google/protobuf/__init__.py @@ -1,6 +1,73 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # sources: google/protobuf/any.proto, google/protobuf/api.proto, google/protobuf/descriptor.proto, google/protobuf/duration.proto, google/protobuf/empty.proto, google/protobuf/field_mask.proto, google/protobuf/source_context.proto, google/protobuf/struct.proto, google/protobuf/timestamp.proto, google/protobuf/type.proto, google/protobuf/wrappers.proto # plugin: python-betterproto +# This file has been @generated + +__all__ = ( + "Syntax", + "FieldKind", + "FieldCardinality", + "FieldDescriptorProtoType", + "FieldDescriptorProtoLabel", + "FileOptionsOptimizeMode", + "FieldOptionsCType", + "FieldOptionsJsType", + "MethodOptionsIdempotencyLevel", + "NullValue", + "Any", + "SourceContext", + "Type", + "Field", + "Enum", + "EnumValue", + "Option", + "Api", + "Method", + "Mixin", + "FileDescriptorSet", + "FileDescriptorProto", + "DescriptorProto", + "DescriptorProtoExtensionRange", + "DescriptorProtoReservedRange", + "ExtensionRangeOptions", + "FieldDescriptorProto", + "OneofDescriptorProto", + "EnumDescriptorProto", + "EnumDescriptorProtoEnumReservedRange", + "EnumValueDescriptorProto", + "ServiceDescriptorProto", + "MethodDescriptorProto", + "FileOptions", + "MessageOptions", + "FieldOptions", + "OneofOptions", + "EnumOptions", + "EnumValueOptions", + "ServiceOptions", + "MethodOptions", + "UninterpretedOption", + "UninterpretedOptionNamePart", + "SourceCodeInfo", + "SourceCodeInfoLocation", + "GeneratedCodeInfo", + "GeneratedCodeInfoAnnotation", + "Duration", + "Empty", + "FieldMask", + "Struct", + "Value", + "ListValue", + "Timestamp", + "DoubleValue", + "FloatValue", + "Int64Value", + "UInt64Value", + "Int32Value", + "UInt32Value", + "BoolValue", + "StringValue", + "BytesValue", +) import warnings from dataclasses import dataclass @@ -8,6 +75,7 @@ Dict, List, Mapping, + Optional, ) from typing_extensions import Self @@ -25,9 +93,6 @@ class Syntax(betterproto.Enum): PROTO3 = 1 """Syntax `proto3`.""" - EDITIONS = 2 - """Syntax `editions`.""" - class FieldKind(betterproto.Enum): """Basic field types.""" @@ -106,112 +171,112 @@ class FieldCardinality(betterproto.Enum): """For repeated fields.""" -class Edition(betterproto.Enum): - """The full set of known editions.""" - - UNKNOWN = 0 - """A placeholder for an unknown edition value.""" +class FieldDescriptorProtoType(betterproto.Enum): + """ """ - PROTO2 = 998 + TYPE_DOUBLE = 1 """ - Legacy syntax "editions". These pre-date editions, but behave much like - distinct editions. These can't be used to specify the edition of proto - files, but feature definitions must supply proto2/proto3 defaults for - backwards compatibility. + 0 is reserved for errors. + Order is weird for historical reasons. """ - PROTO3 = 999 - _2023 = 1000 + TYPE_FLOAT = 2 """ - Editions that have been released. The specific values are arbitrary and - should not be depended on, but they will always be time-ordered for easy - comparison. + """ - _2024 = 1001 - _1_TEST_ONLY = 1 + TYPE_INT64 = 3 """ - Placeholder editions for testing feature resolution. These should not be - used or relyed on outside of tests. + Not ZigZag encoded. Negative numbers take 10 bytes. Use TYPE_SINT64 if + negative values are likely. """ - _2_TEST_ONLY = 2 - _99997_TEST_ONLY = 99997 - _99998_TEST_ONLY = 99998 - _99999_TEST_ONLY = 99999 - MAX = 2147483647 + TYPE_UINT64 = 4 """ - Placeholder for specifying unbounded edition support. This should only - ever be used by plugins that can expect to never require any changes to - support a new edition. + """ - -class ExtensionRangeOptionsVerificationState(betterproto.Enum): - """The verification state of the extension range.""" - - DECLARATION = 0 - """All the extensions of the range must be declared.""" - - UNVERIFIED = 1 - - -class FieldDescriptorProtoType(betterproto.Enum): - TYPE_DOUBLE = 1 + TYPE_INT32 = 5 """ - 0 is reserved for errors. - Order is weird for historical reasons. + Not ZigZag encoded. Negative numbers take 10 bytes. Use TYPE_SINT32 if + negative values are likely. """ - TYPE_FLOAT = 2 - TYPE_INT64 = 3 + TYPE_FIXED64 = 6 """ - Not ZigZag encoded. Negative numbers take 10 bytes. Use TYPE_SINT64 if - negative values are likely. + """ - TYPE_UINT64 = 4 - TYPE_INT32 = 5 + TYPE_FIXED32 = 7 """ - Not ZigZag encoded. Negative numbers take 10 bytes. Use TYPE_SINT32 if - negative values are likely. + """ - TYPE_FIXED64 = 6 - TYPE_FIXED32 = 7 TYPE_BOOL = 8 + """ + + """ + TYPE_STRING = 9 + """ + + """ + TYPE_GROUP = 10 """ Tag-delimited aggregate. - Group type is deprecated and not supported after google.protobuf. However, Proto3 - implementations should still be able to parse the group wire format and - treat group fields as unknown fields. In Editions, the group wire format - can be enabled via the `message_encoding` feature. + Group type is deprecated and not supported in proto3. However, Proto3 + implementations should still be able to parse the group wire format and + treat group fields as unknown fields. """ TYPE_MESSAGE = 11 + """Length-delimited aggregate.""" + TYPE_BYTES = 12 """New in version 2.""" TYPE_UINT32 = 13 + """ + + """ + TYPE_ENUM = 14 + """ + + """ + TYPE_SFIXED32 = 15 + """ + + """ + TYPE_SFIXED64 = 16 + """ + + """ + TYPE_SINT32 = 17 + """Uses ZigZag encoding.""" + TYPE_SINT64 = 18 + """Uses ZigZag encoding.""" class FieldDescriptorProtoLabel(betterproto.Enum): + """ """ + LABEL_OPTIONAL = 1 """0 is reserved for errors""" - LABEL_REPEATED = 3 LABEL_REQUIRED = 2 """ - The required label is only allowed in google.protobuf. In proto3 and Editions - it's explicitly prohibited. In Editions, the `field_presence` feature - can be used to get this behavior. + + """ + + LABEL_REPEATED = 3 + """ + """ @@ -219,30 +284,39 @@ class FileOptionsOptimizeMode(betterproto.Enum): """Generated classes can be optimized for speed or code size.""" SPEED = 1 + """Generate complete code for parsing, serialization,""" + CODE_SIZE = 2 - """etc.""" + """ + etc. + + Use ReflectionOps to implement these methods. + """ LITE_RUNTIME = 3 + """Generate code using MessageLite and the lite runtime.""" class FieldOptionsCType(betterproto.Enum): + """ """ + STRING = 0 """Default mode.""" CORD = 1 """ - The option [ctype=CORD] may be applied to a non-repeated field of type - "bytes". It indicates that in C++, the data should be stored in a Cord - instead of a string. For very large strings, this may reduce memory - fragmentation. It may also allow better performance when parsing from a - Cord, or when parsing with aliasing enabled, as the parsed Cord may then - alias the original buffer. + """ STRING_PIECE = 2 + """ + + """ class FieldOptionsJsType(betterproto.Enum): + """ """ + JS_NORMAL = 0 """Use the default type.""" @@ -253,107 +327,29 @@ class FieldOptionsJsType(betterproto.Enum): """Use JavaScript numbers.""" -class FieldOptionsOptionRetention(betterproto.Enum): - """ - If set to RETENTION_SOURCE, the option will be omitted from the binary. - Note: as of January 2023, support for this is in progress and does not yet - have an effect (b/264593489). - """ - - RETENTION_UNKNOWN = 0 - RETENTION_RUNTIME = 1 - RETENTION_SOURCE = 2 - - -class FieldOptionsOptionTargetType(betterproto.Enum): - """ - This indicates the types of entities that the field may apply to when used - as an option. If it is unset, then the field may be freely used as an - option on any kind of entity. Note: as of January 2023, support for this is - in progress and does not yet have an effect (b/264593489). - """ - - TARGET_TYPE_UNKNOWN = 0 - TARGET_TYPE_FILE = 1 - TARGET_TYPE_EXTENSION_RANGE = 2 - TARGET_TYPE_MESSAGE = 3 - TARGET_TYPE_FIELD = 4 - TARGET_TYPE_ONEOF = 5 - TARGET_TYPE_ENUM = 6 - TARGET_TYPE_ENUM_ENTRY = 7 - TARGET_TYPE_SERVICE = 8 - TARGET_TYPE_METHOD = 9 - - class MethodOptionsIdempotencyLevel(betterproto.Enum): """ Is this method side-effect-free (or safe in HTTP parlance), or idempotent, - or neither? HTTP based RPC implementation may choose GET verb for safe - methods, and PUT verb for idempotent methods instead of the default POST. + or neither? HTTP based RPC implementation may choose GET verb for safe + methods, and PUT verb for idempotent methods instead of the default POST. """ IDEMPOTENCY_UNKNOWN = 0 - NO_SIDE_EFFECTS = 1 - IDEMPOTENT = 2 - - -class FeatureSetFieldPresence(betterproto.Enum): - FIELD_PRESENCE_UNKNOWN = 0 - EXPLICIT = 1 - IMPLICIT = 2 - LEGACY_REQUIRED = 3 - - -class FeatureSetEnumType(betterproto.Enum): - ENUM_TYPE_UNKNOWN = 0 - OPEN = 1 - CLOSED = 2 - - -class FeatureSetRepeatedFieldEncoding(betterproto.Enum): - REPEATED_FIELD_ENCODING_UNKNOWN = 0 - PACKED = 1 - EXPANDED = 2 - - -class FeatureSetUtf8Validation(betterproto.Enum): - UTF8_VALIDATION_UNKNOWN = 0 - VERIFY = 2 - NONE = 3 - - -class FeatureSetMessageEncoding(betterproto.Enum): - MESSAGE_ENCODING_UNKNOWN = 0 - LENGTH_PREFIXED = 1 - DELIMITED = 2 - - -class FeatureSetJsonFormat(betterproto.Enum): - JSON_FORMAT_UNKNOWN = 0 - ALLOW = 1 - LEGACY_BEST_EFFORT = 2 - - -class GeneratedCodeInfoAnnotationSemantic(betterproto.Enum): """ - Represents the identified object's effect on the element in the original - .proto file. + """ - NONE = 0 - """There is no effect or the effect is indescribable.""" - - SET = 1 - """The element is set or otherwise mutated.""" + NO_SIDE_EFFECTS = 1 + """implies idempotent""" - ALIAS = 2 - """An alias to the element is returned.""" + IDEMPOTENT = 2 + """idempotent, but may have side effects""" class NullValue(betterproto.Enum): """ `NullValue` is a singleton enumeration to represent the null value for the - `Value` type union. + `Value` type union. The JSON representation for `NullValue` is JSON `null`. """ @@ -366,122 +362,114 @@ class NullValue(betterproto.Enum): class Any(betterproto.Message): """ `Any` contains an arbitrary serialized protocol buffer message along with a - URL that describes the type of the serialized message. + URL that describes the type of the serialized message. - Protobuf library provides support to pack/unpack Any values in the form - of utility functions or additional generated methods of the Any type. + Protobuf library provides support to pack/unpack Any values in the form + of utility functions or additional generated methods of the Any type. - Example 1: Pack and unpack a message in C++. + Example 1: Pack and unpack a message in C++. - Foo foo = ...; - Any any; - any.PackFrom(foo); - ... - if (any.UnpackTo(&foo)) { - ... - } - - Example 2: Pack and unpack a message in Java. - - Foo foo = ...; - Any any = Any.pack(foo); - ... - if (any.is(Foo.class)) { - foo = any.unpack(Foo.class); - } - // or ... - if (any.isSameTypeAs(Foo.getDefaultInstance())) { - foo = any.unpack(Foo.getDefaultInstance()); - } + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } - Example 3: Pack and unpack a message in Python. + Example 2: Pack and unpack a message in Java. - foo = Foo(...) - any = Any() - any.Pack(foo) - ... - if any.Is(Foo.DESCRIPTOR): - any.Unpack(foo) - ... + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } - Example 4: Pack and unpack a message in Go + Example 3: Pack and unpack a message in Python. - foo := &pb.Foo{...} - any, err := anypb.New(foo) - if err != nil { - ... - } + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) ... - 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; - } + Example 4: Pack and unpack a message in Go - { - "@type": "type.googleapis.com/google.profile.Person", - "firstName": , - "lastName": + foo := &pb.Foo{...} + any, err := ptypes.MarshalAny(foo) + ... + foo := &pb.Foo{} + if err := ptypes.UnmarshalAny(any, foo); err != nil { + ... } - 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" - } + The pack methods provided by protobuf library will by default use + 'type.googleapis.com/full.type.name' as the type URL and the unpack + methods only use the fully qualified type name after the last '/' + in the type URL, for example "foo.bar.com/x/y.z" will yield type + name "y.z". + + JSON + ==== + The JSON representation of an `Any` value uses the regular + representation of the deserialized, embedded message, with an + additional field `@type` which contains the type URL. Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom JSON + representation, that representation will be embedded adding a field + `value` which holds the custom JSON in addition to the `@type` + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } """ type_url: str = betterproto.string_field(1) """ A URL/resource name that uniquely identifies the type of the serialized - protocol buffer message. This string must contain at least - one "/" character. The last segment of the URL's path must represent - the fully qualified name of the type (as in - `path/google.protobuf.Duration`). The name should be in a canonical form - (e.g., leading "." is not accepted). - - In practice, teams usually precompile into the binary all types that they - expect it to use in the context of Any. However, for URLs which use the - scheme `http`, `https`, or no scheme, one can optionally set up a type - server that maps type URLs to message definitions as follows: - - * If no scheme is provided, `https` is assumed. - * An HTTP GET on the URL must yield a [google.protobuf.Type][] - value in binary format, or produce an error. - * Applications are allowed to cache lookup results based on the - URL, or have them precompiled into a binary to avoid any - lookup. Therefore, binary compatibility needs to be preserved - on changes to types. (Use versioned type names to manage - breaking changes.) - - Note: this functionality is not currently available in the official - protobuf release, and it is not used for type URLs beginning with - type.googleapis.com. As of May 2023, there are no widely used type server - implementations and no plans to implement one. - - Schemes other than `http`, `https` (or the empty scheme) might be - used with implementation specific semantics. + protocol buffer message. This string must contain at least + one "/" character. The last segment of the URL's path must represent + the fully qualified name of the type (as in + `path/google.protobuf.Duration`). The name should be in a canonical form + (e.g., leading "." is not accepted). + + In practice, teams usually precompile into the binary all types that they + expect it to use in the context of Any. However, for URLs which use the + scheme `http`, `https`, or no scheme, one can optionally set up a type + server that maps type URLs to message definitions as follows: + + * If no scheme is provided, `https` is assumed. + * An HTTP GET on the URL must yield a [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in the official + protobuf release, and it is not used for type URLs beginning with + type.googleapis.com. + + Schemes other than `http`, `https` (or the empty scheme) might be + used with implementation specific semantics. """ value: bytes = betterproto.bytes_field(2) @@ -494,13 +482,13 @@ class Any(betterproto.Message): class SourceContext(betterproto.Message): """ `SourceContext` represents information about the source of a - protobuf element, like the file in which it is defined. + protobuf element, like the file in which it is defined. """ file_name: str = betterproto.string_field(1) """ The path-qualified name of the .proto file that contained the associated - protobuf element. For example: `"google/protobuf/source_context.proto"`. + protobuf element. For example: `"google/protobuf/source_context.proto"`. """ @@ -511,35 +499,36 @@ class Type(betterproto.Message): name: str = betterproto.string_field(1) """The fully qualified message name.""" - fields: List["Field"] = betterproto.message_field(2) + fields: List["Field"] = betterproto.message_field(2, repeated=True) """The list of fields.""" - oneofs: List[str] = betterproto.string_field(3) + oneofs: List[str] = betterproto.string_field(3, repeated=True) """The list of types appearing in `oneof` definitions in this type.""" - options: List["Option"] = betterproto.message_field(4) + options: List["Option"] = betterproto.message_field(4, repeated=True) """The protocol buffer options.""" source_context: "SourceContext" = betterproto.message_field(5) """The source context.""" - syntax: "Syntax" = betterproto.enum_field(6) + syntax: "Syntax" = betterproto.enum_field( + 6, enum_default_value=lambda: Syntax.try_value(0) + ) """The source syntax.""" - edition: str = betterproto.string_field(7) - """ - The source edition string, only valid when syntax is SYNTAX_EDITIONS. - """ - @dataclass(eq=False, repr=False) class Field(betterproto.Message): """A single field of a message type.""" - kind: "FieldKind" = betterproto.enum_field(1) + kind: "FieldKind" = betterproto.enum_field( + 1, enum_default_value=lambda: FieldKind.try_value(0) + ) """The field type.""" - cardinality: "FieldCardinality" = betterproto.enum_field(2) + cardinality: "FieldCardinality" = betterproto.enum_field( + 2, enum_default_value=lambda: FieldCardinality.try_value(0) + ) """The field cardinality.""" number: int = betterproto.int32_field(3) @@ -551,19 +540,19 @@ class Field(betterproto.Message): type_url: str = betterproto.string_field(6) """ The field type URL, without the scheme, for message or enumeration - types. Example: `"type.googleapis.com/google.protobuf.Timestamp"`. + types. Example: `"type.googleapis.com/google.protobuf.Timestamp"`. """ oneof_index: int = betterproto.int32_field(7) """ The index of the field type in `Type.oneofs`, for message or enumeration - types. The first type has index 1; zero means the type is not in the list. + types. The first type has index 1; zero means the type is not in the list. """ packed: bool = betterproto.bool_field(8) """Whether to use alternative packed wire representation.""" - options: List["Option"] = betterproto.message_field(9) + options: List["Option"] = betterproto.message_field(9, repeated=True) """The protocol buffer options.""" json_name: str = betterproto.string_field(10) @@ -583,24 +572,21 @@ class Enum(betterproto.Message): """Enum type name.""" enumvalue: List["EnumValue"] = betterproto.message_field( - 2, wraps=betterproto.TYPE_ENUM + 2, wraps=betterproto.TYPE_ENUM, repeated=True ) """Enum value definitions.""" - options: List["Option"] = betterproto.message_field(3) + options: List["Option"] = betterproto.message_field(3, repeated=True) """Protocol buffer options.""" source_context: "SourceContext" = betterproto.message_field(4) """The source context.""" - syntax: "Syntax" = betterproto.enum_field(5) + syntax: "Syntax" = betterproto.enum_field( + 5, enum_default_value=lambda: Syntax.try_value(0) + ) """The source syntax.""" - edition: str = betterproto.string_field(6) - """ - The source edition string, only valid when syntax is SYNTAX_EDITIONS. - """ - @dataclass(eq=False, repr=False) class EnumValue(betterproto.Message): @@ -612,7 +598,7 @@ class EnumValue(betterproto.Message): number: int = betterproto.int32_field(2) """Enum value number.""" - options: List["Option"] = betterproto.message_field(3) + options: List["Option"] = betterproto.message_field(3, repeated=True) """Protocol buffer options.""" @@ -620,23 +606,23 @@ class EnumValue(betterproto.Message): class Option(betterproto.Message): """ A protocol buffer option, which can be attached to a message, field, - enumeration, etc. + enumeration, etc. """ name: str = betterproto.string_field(1) """ The option's name. For protobuf built-in options (options defined in - descriptor.proto), this is the short name. For example, `"map_entry"`. - For custom options, it should be the fully-qualified name. For example, - `"google.api.http"`. + descriptor.proto), this is the short name. For example, `"map_entry"`. + For custom options, it should be the fully-qualified name. For example, + `"google.api.http"`. """ value: "Any" = betterproto.message_field(2) """ The option's value packed in an Any message. If the value is a primitive, - the corresponding wrapper type defined in google/protobuf/wrappers.proto - should be used. If the value is an enum, it should be stored as an int32 - value using the google.protobuf.Int32Value type. + the corresponding wrapper type defined in google/protobuf/wrappers.proto + should be used. If the value is an enum, it should be stored as an int32 + value using the google.protobuf.Int32Value type. """ @@ -645,60 +631,62 @@ class Api(betterproto.Message): """ Api is a light-weight descriptor for an API Interface. - Interfaces are also described as "protocol buffer services" in some contexts, - such as by the "service" keyword in a .proto file, but they are different - from API Services, which represent a concrete implementation of an interface - as opposed to simply a description of methods and bindings. They are also - sometimes simply referred to as "APIs" in other contexts, such as the name of - this message itself. See https://cloud.google.com/apis/design/glossary for - detailed terminology. + Interfaces are also described as "protocol buffer services" in some contexts, + such as by the "service" keyword in a .proto file, but they are different + from API Services, which represent a concrete implementation of an interface + as opposed to simply a description of methods and bindings. They are also + sometimes simply referred to as "APIs" in other contexts, such as the name of + this message itself. See https://cloud.google.com/apis/design/glossary for + detailed terminology. """ name: str = betterproto.string_field(1) """ The fully qualified name of this interface, including package name - followed by the interface's simple name. + followed by the interface's simple name. """ - methods: List["Method"] = betterproto.message_field(2) + methods: List["Method"] = betterproto.message_field(2, repeated=True) """The methods of this interface, in unspecified order.""" - options: List["Option"] = betterproto.message_field(3) + options: List["Option"] = betterproto.message_field(3, repeated=True) """Any metadata attached to the interface.""" version: str = betterproto.string_field(4) """ A version string for this interface. If specified, must have the form - `major-version.minor-version`, as in `1.10`. If the minor version is - omitted, it defaults to zero. If the entire version field is empty, the - major version is derived from the package name, as outlined below. If the - field is not empty, the version in the package name will be verified to be - consistent with what is provided here. - - The versioning schema uses [semantic - versioning](http://semver.org) where the major version number - indicates a breaking change and the minor version an additive, - non-breaking change. Both version numbers are signals to users - what to expect from different versions, and should be carefully - chosen based on the product plan. - - The major version is also reflected in the package name of the - interface, which must end in `v`, as in - `google.feature.v1`. For major versions 0 and 1, the suffix can - be omitted. Zero major versions must only be used for - experimental, non-GA interfaces. + `major-version.minor-version`, as in `1.10`. If the minor version is + omitted, it defaults to zero. If the entire version field is empty, the + major version is derived from the package name, as outlined below. If the + field is not empty, the version in the package name will be verified to be + consistent with what is provided here. + + The versioning schema uses [semantic + versioning](http://semver.org) where the major version number + indicates a breaking change and the minor version an additive, + non-breaking change. Both version numbers are signals to users + what to expect from different versions, and should be carefully + chosen based on the product plan. + + The major version is also reflected in the package name of the + interface, which must end in `v`, as in + `google.feature.v1`. For major versions 0 and 1, the suffix can + be omitted. Zero major versions must only be used for + experimental, non-GA interfaces. """ source_context: "SourceContext" = betterproto.message_field(5) """ Source context for the protocol buffer service represented by this - message. + message. """ - mixins: List["Mixin"] = betterproto.message_field(6) + mixins: List["Mixin"] = betterproto.message_field(6, repeated=True) """Included interfaces. See [Mixin][].""" - syntax: "Syntax" = betterproto.enum_field(7) + syntax: "Syntax" = betterproto.enum_field( + 7, enum_default_value=lambda: Syntax.try_value(0) + ) """The source syntax of the service.""" @@ -721,10 +709,12 @@ class Method(betterproto.Message): response_streaming: bool = betterproto.bool_field(5) """If true, the response is streamed.""" - options: List["Option"] = betterproto.message_field(6) + options: List["Option"] = betterproto.message_field(6, repeated=True) """Any metadata attached to the method.""" - syntax: "Syntax" = betterproto.enum_field(7) + syntax: "Syntax" = betterproto.enum_field( + 7, enum_default_value=lambda: Syntax.try_value(0) + ) """The source syntax of this method.""" @@ -732,83 +722,83 @@ class Method(betterproto.Message): class Mixin(betterproto.Message): """ Declares an API Interface to be included in this interface. The including - interface must redeclare all the methods from the included interface, but - documentation and options are inherited as follows: - - - If after comment and whitespace stripping, the documentation - string of the redeclared method is empty, it will be inherited - from the original method. - - - Each annotation belonging to the service config (http, - visibility) which is not set in the redeclared method will be - inherited. - - - If an http annotation is inherited, the path pattern will be - modified as follows. Any version prefix will be replaced by the - version of the including interface plus the [root][] path if - specified. - - Example of a simple mixin: - - package google.acl.v1; - service AccessControl { - // Get the underlying ACL object. - rpc GetAcl(GetAclRequest) returns (Acl) { - option (google.api.http).get = "/v1/{resource=**}:getAcl"; - } - } + interface must redeclare all the methods from the included interface, but + documentation and options are inherited as follows: - package google.storage.v2; - service Storage { - rpc GetAcl(GetAclRequest) returns (Acl); + - If after comment and whitespace stripping, the documentation + string of the redeclared method is empty, it will be inherited + from the original method. - // Get a data record. - rpc GetData(GetDataRequest) returns (Data) { - option (google.api.http).get = "/v2/{resource=**}"; - } - } + - Each annotation belonging to the service config (http, + visibility) which is not set in the redeclared method will be + inherited. - Example of a mixin configuration: + - If an http annotation is inherited, the path pattern will be + modified as follows. Any version prefix will be replaced by the + version of the including interface plus the [root][] path if + specified. - apis: - - name: google.storage.v2.Storage - mixins: - - name: google.acl.v1.AccessControl + Example of a simple mixin: - The mixin construct 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: + package google.acl.v1; + service AccessControl { + // Get the underlying ACL object. + rpc GetAcl(GetAclRequest) returns (Acl) { + option (google.api.http).get = "/v1/{resource=**}:getAcl"; + } + } - service Storage { - // Get the underlying ACL object. - rpc GetAcl(GetAclRequest) returns (Acl) { - option (google.api.http).get = "/v2/{resource=**}:getAcl"; - } - ... - } + package google.storage.v2; + service Storage { + rpc GetAcl(GetAclRequest) returns (Acl); + + // Get a data record. + rpc GetData(GetDataRequest) returns (Data) { + option (google.api.http).get = "/v2/{resource=**}"; + } + } - Note how the version in the path pattern changed from `v1` to `v2`. + Example of a mixin configuration: - If the `root` field in the mixin is specified, it should be a - relative path under which inherited HTTP paths are placed. Example: + apis: + - name: google.storage.v2.Storage + mixins: + - name: google.acl.v1.AccessControl - apis: - - name: google.storage.v2.Storage - mixins: - - name: google.acl.v1.AccessControl - root: acls + The mixin construct 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: - This implies the following inherited HTTP annotation: + service Storage { + // Get the underlying ACL object. + rpc GetAcl(GetAclRequest) returns (Acl) { + option (google.api.http).get = "/v2/{resource=**}:getAcl"; + } + ... + } - service Storage { - // Get the underlying ACL object. - rpc GetAcl(GetAclRequest) returns (Acl) { - option (google.api.http).get = "/v2/acls/{resource=**}:getAcl"; - } - ... - } + Note how the version in the path pattern changed from `v1` to `v2`. + + If the `root` field in the mixin is specified, it should be a + relative path under which inherited HTTP paths are placed. Example: + + apis: + - name: google.storage.v2.Storage + mixins: + - name: google.acl.v1.AccessControl + root: acls + + This implies the following inherited HTTP annotation: + + service Storage { + // Get the underlying ACL object. + rpc GetAcl(GetAclRequest) returns (Acl) { + option (google.api.http).get = "/v2/acls/{resource=**}:getAcl"; + } + ... + } """ name: str = betterproto.string_field(1) @@ -817,7 +807,7 @@ class Mixin(betterproto.Message): root: str = betterproto.string_field(2) """ If non-empty specifies a path under which inherited HTTP paths - are rooted. + are rooted. """ @@ -825,10 +815,13 @@ class Mixin(betterproto.Message): class FileDescriptorSet(betterproto.Message): """ The protocol compiler can output a FileDescriptorSet containing the .proto - files it parses. + files it parses. """ - file: List["FileDescriptorProto"] = betterproto.message_field(1) + file: List["FileDescriptorProto"] = betterproto.message_field(1, repeated=True) + """ + + """ @dataclass(eq=False, repr=False) @@ -836,216 +829,265 @@ class FileDescriptorProto(betterproto.Message): """Describes a complete .proto file.""" name: str = betterproto.string_field(1) + """file name, relative to root of source tree""" + package: str = betterproto.string_field(2) - dependency: List[str] = betterproto.string_field(3) + """e.g. "foo", "foo.bar", etc.""" + + dependency: List[str] = betterproto.string_field(3, repeated=True) """Names of files imported by this file.""" - public_dependency: List[int] = betterproto.int32_field(10) + public_dependency: List[int] = betterproto.int32_field(10, repeated=True) """Indexes of the public imported files in the dependency list above.""" - weak_dependency: List[int] = betterproto.int32_field(11) + weak_dependency: List[int] = betterproto.int32_field(11, repeated=True) """ Indexes of the weak imported files in the dependency list. - For Google-internal migration only. Do not use. + For Google-internal migration only. Do not use. """ - message_type: List["DescriptorProto"] = betterproto.message_field(4) + message_type: List["DescriptorProto"] = betterproto.message_field(4, repeated=True) """All top-level definitions in this file.""" - enum_type: List["EnumDescriptorProto"] = betterproto.message_field(5) - service: List["ServiceDescriptorProto"] = betterproto.message_field(6) - extension: List["FieldDescriptorProto"] = betterproto.message_field(7) + enum_type: List["EnumDescriptorProto"] = betterproto.message_field(5, repeated=True) + """ + + """ + + service: List["ServiceDescriptorProto"] = betterproto.message_field( + 6, repeated=True + ) + """ + + """ + + extension: List["FieldDescriptorProto"] = betterproto.message_field( + 7, repeated=True + ) + """ + + """ + options: "FileOptions" = betterproto.message_field(8) + """ + + """ + source_code_info: "SourceCodeInfo" = betterproto.message_field(9) """ This field contains optional information about the original source code. - You may safely remove this entire field without harming runtime - functionality of the descriptors -- the information is needed only by - development tools. + You may safely remove this entire field without harming runtime + functionality of the descriptors -- the information is needed only by + development tools. """ syntax: str = betterproto.string_field(12) """ The syntax of the proto file. - The supported values are "proto2", "proto3", and "editions". - - If `edition` is present, this value must be "editions". + The supported values are "proto2" and "proto3". """ - edition: "Edition" = betterproto.enum_field(14) - """The edition of the proto file.""" - @dataclass(eq=False, repr=False) class DescriptorProto(betterproto.Message): """Describes a message type.""" name: str = betterproto.string_field(1) - field: List["FieldDescriptorProto"] = betterproto.message_field(2) - extension: List["FieldDescriptorProto"] = betterproto.message_field(6) - nested_type: List["DescriptorProto"] = betterproto.message_field(3) - enum_type: List["EnumDescriptorProto"] = betterproto.message_field(4) + """ + + """ + + field: List["FieldDescriptorProto"] = betterproto.message_field(2, repeated=True) + """ + + """ + + extension: List["FieldDescriptorProto"] = betterproto.message_field( + 6, repeated=True + ) + """ + + """ + + nested_type: List["DescriptorProto"] = betterproto.message_field(3, repeated=True) + """ + + """ + + enum_type: List["EnumDescriptorProto"] = betterproto.message_field(4, repeated=True) + """ + + """ + extension_range: List["DescriptorProtoExtensionRange"] = betterproto.message_field( - 5 + 5, repeated=True + ) + """ + + """ + + oneof_decl: List["OneofDescriptorProto"] = betterproto.message_field( + 8, repeated=True ) - oneof_decl: List["OneofDescriptorProto"] = betterproto.message_field(8) + """ + + """ + options: "MessageOptions" = betterproto.message_field(7) - reserved_range: List["DescriptorProtoReservedRange"] = betterproto.message_field(9) - reserved_name: List[str] = betterproto.string_field(10) + """ + + """ + + reserved_range: List["DescriptorProtoReservedRange"] = betterproto.message_field( + 9, repeated=True + ) + """ + + """ + + reserved_name: List[str] = betterproto.string_field(10, repeated=True) """ Reserved field names, which may not be used by fields in the same message. - A given name may only be reserved once. + A given name may only be reserved once. """ @dataclass(eq=False, repr=False) class DescriptorProtoExtensionRange(betterproto.Message): + """ """ + start: int = betterproto.int32_field(1) + """Inclusive.""" + end: int = betterproto.int32_field(2) + """Exclusive.""" + options: "ExtensionRangeOptions" = betterproto.message_field(3) + """ + + """ @dataclass(eq=False, repr=False) class DescriptorProtoReservedRange(betterproto.Message): """ Range of reserved tag numbers. Reserved tag numbers may not be used by - fields or extension ranges in the same message. Reserved ranges may - not overlap. + fields or extension ranges in the same message. Reserved ranges may + not overlap. """ start: int = betterproto.int32_field(1) + """Inclusive.""" + end: int = betterproto.int32_field(2) + """Exclusive.""" @dataclass(eq=False, repr=False) class ExtensionRangeOptions(betterproto.Message): - uninterpreted_option: List["UninterpretedOption"] = betterproto.message_field(999) - """The parser stores options it doesn't recognize here. See above.""" - - declaration: List["ExtensionRangeOptionsDeclaration"] = betterproto.message_field(2) - """ - For external users: DO NOT USE. We are in the process of open sourcing - extension declaration and executing internal cleanups before it can be - used externally. - """ - - features: "FeatureSet" = betterproto.message_field(50) - """Any features defined in the specific edition.""" + """ """ - verification: "ExtensionRangeOptionsVerificationState" = betterproto.enum_field(3) - """ - The verification state of the range. - TODO: flip the default to DECLARATION once all empty ranges - are marked as UNVERIFIED. - """ + uninterpreted_option: List["UninterpretedOption"] = betterproto.message_field( + 999, repeated=True + ) + """The parser stores options it doesn't recognize here. See above.""" @dataclass(eq=False, repr=False) -class ExtensionRangeOptionsDeclaration(betterproto.Message): - number: int = betterproto.int32_field(1) - """The extension number declared within the extension range.""" - - full_name: str = betterproto.string_field(2) - """ - The fully-qualified name of the extension field. There must be a leading - dot in front of the full name. - """ +class FieldDescriptorProto(betterproto.Message): + """Describes a field within a message.""" - type: str = betterproto.string_field(3) + name: str = betterproto.string_field(1) """ - The fully-qualified type name of the extension field. Unlike - Metadata.type, Declaration.type must have a leading dot for messages - and enums. + """ - reserved: bool = betterproto.bool_field(5) + number: int = betterproto.int32_field(3) """ - If true, indicates that the number is reserved in the extension range, - and any extension field with the number will fail to compile. Set this - when a declared extension field is deleted. + """ - repeated: bool = betterproto.bool_field(6) + label: "FieldDescriptorProtoLabel" = betterproto.enum_field( + 4, enum_default_value=lambda: FieldDescriptorProtoLabel.try_value(0) + ) """ - If true, indicates that the extension must be defined as repeated. - Otherwise the extension must be defined as optional. + """ - -@dataclass(eq=False, repr=False) -class FieldDescriptorProto(betterproto.Message): - """Describes a field within a message.""" - - name: str = betterproto.string_field(1) - number: int = betterproto.int32_field(3) - label: "FieldDescriptorProtoLabel" = betterproto.enum_field(4) - type: "FieldDescriptorProtoType" = betterproto.enum_field(5) + type: "FieldDescriptorProtoType" = betterproto.enum_field( + 5, enum_default_value=lambda: FieldDescriptorProtoType.try_value(0) + ) """ If type_name is set, this need not be set. If both this and type_name - are set, this must be one of TYPE_ENUM, TYPE_MESSAGE or TYPE_GROUP. + are set, this must be one of TYPE_ENUM, TYPE_MESSAGE or TYPE_GROUP. """ type_name: str = betterproto.string_field(6) """ For message and enum types, this is the name of the type. If the name - starts with a '.', it is fully-qualified. Otherwise, C++-like scoping - rules are used to find the type (i.e. first the nested types within this - message are searched, then within the parent, on up to the root - namespace). + starts with a '.', it is fully-qualified. Otherwise, C++-like scoping + rules are used to find the type (i.e. first the nested types within this + message are searched, then within the parent, on up to the root + namespace). """ extendee: str = betterproto.string_field(2) """ For extensions, this is the name of the type being extended. It is - resolved in the same manner as type_name. + resolved in the same manner as type_name. """ default_value: str = betterproto.string_field(7) """ For numeric types, contains the original text representation of the value. - For booleans, "true" or "false". - For strings, contains the default text contents (not escaped in any way). - For bytes, contains the C escaped value. All bytes >= 128 are escaped. + For booleans, "true" or "false". + For strings, contains the default text contents (not escaped in any way). + For bytes, contains the C escaped value. All bytes >= 128 are escaped. + TODO(kenton): Base-64 encode? """ - oneof_index: int = betterproto.int32_field(9) + oneof_index: Optional[int] = betterproto.int32_field(9, optional=True) """ If set, gives the index of a oneof in the containing type's oneof_decl - list. This field is a member of that oneof. + list. This field is a member of that oneof. """ json_name: str = betterproto.string_field(10) """ JSON name of this field. The value is set by protocol compiler. If the - user has set a "json_name" option on this field, that option's value - will be used. Otherwise, it's deduced from the field's name by converting - it to camelCase. + user has set a "json_name" option on this field, that option's value + will be used. Otherwise, it's deduced from the field's name by converting + it to camelCase. """ options: "FieldOptions" = betterproto.message_field(8) + """ + + """ + proto3_optional: bool = betterproto.bool_field(17) """ If true, this is a proto3 "optional". When a proto3 field is optional, it - tracks presence regardless of field type. + tracks presence regardless of field type. - When proto3_optional is true, this field must 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. + 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. + 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`. + Proto2 optional fields do not set this flag, because they already indicate + optional with `LABEL_OPTIONAL`. """ @@ -1054,7 +1096,14 @@ class OneofDescriptorProto(betterproto.Message): """Describes a oneof.""" name: str = betterproto.string_field(1) + """ + + """ + options: "OneofOptions" = betterproto.message_field(2) + """ + + """ @dataclass(eq=False, repr=False) @@ -1062,21 +1111,35 @@ class EnumDescriptorProto(betterproto.Message): """Describes an enum type.""" name: str = betterproto.string_field(1) - value: List["EnumValueDescriptorProto"] = betterproto.message_field(2) + """ + + """ + + value: List["EnumValueDescriptorProto"] = betterproto.message_field( + 2, repeated=True + ) + """ + + """ + options: "EnumOptions" = betterproto.message_field(3) + """ + + """ + reserved_range: List["EnumDescriptorProtoEnumReservedRange"] = ( - betterproto.message_field(4) + betterproto.message_field(4, repeated=True) ) """ Range of reserved numeric values. Reserved numeric values may not be used - by enum values in the same enum declaration. Reserved ranges may not - overlap. + by enum values in the same enum declaration. Reserved ranges may not + overlap. """ - reserved_name: List[str] = betterproto.string_field(5) + reserved_name: List[str] = betterproto.string_field(5, repeated=True) """ Reserved enum value names, which may not be reused. A given name may only - be reserved once. + be reserved once. """ @@ -1084,15 +1147,18 @@ class EnumDescriptorProto(betterproto.Message): class EnumDescriptorProtoEnumReservedRange(betterproto.Message): """ Range of reserved numeric values. Reserved values may not be used by - entries in the same enum. Reserved ranges may not overlap. + entries in the same enum. Reserved ranges may not overlap. - Note that this is distinct from DescriptorProto.ReservedRange in that it - is inclusive such that it can appropriately represent the entire int32 - domain. + Note that this is distinct from DescriptorProto.ReservedRange in that it + is inclusive such that it can appropriately represent the entire int32 + domain. """ start: int = betterproto.int32_field(1) + """Inclusive.""" + end: int = betterproto.int32_field(2) + """Inclusive.""" @dataclass(eq=False, repr=False) @@ -1100,8 +1166,19 @@ class EnumValueDescriptorProto(betterproto.Message): """Describes a value within an enum.""" name: str = betterproto.string_field(1) + """ + + """ + number: int = betterproto.int32_field(2) + """ + + """ + options: "EnumValueOptions" = betterproto.message_field(3) + """ + + """ @dataclass(eq=False, repr=False) @@ -1109,8 +1186,19 @@ class ServiceDescriptorProto(betterproto.Message): """Describes a service.""" name: str = betterproto.string_field(1) - method: List["MethodDescriptorProto"] = betterproto.message_field(2) + """ + + """ + + method: List["MethodDescriptorProto"] = betterproto.message_field(2, repeated=True) + """ + + """ + options: "ServiceOptions" = betterproto.message_field(3) + """ + + """ @dataclass(eq=False, repr=False) @@ -1118,14 +1206,26 @@ class MethodDescriptorProto(betterproto.Message): """Describes a method of a service.""" name: str = betterproto.string_field(1) + """ + + """ + input_type: str = betterproto.string_field(2) """ Input and output type names. These are resolved in the same way as - FieldDescriptorProto.type_name, but must refer to a message type. + FieldDescriptorProto.type_name, but must refer to a message type. """ output_type: str = betterproto.string_field(3) + """ + + """ + options: "MethodOptions" = betterproto.message_field(4) + """ + + """ + client_streaming: bool = betterproto.bool_field(5) """Identifies if client streams multiple client messages""" @@ -1135,31 +1235,65 @@ class MethodDescriptorProto(betterproto.Message): @dataclass(eq=False, repr=False) class FileOptions(betterproto.Message): + """ + =================================================================== + Options + + Each of the definitions above may have "options" attached. These are + just annotations which may cause code to be generated slightly differently + or may contain hints for code that manipulates protocol messages. + + Clients may define custom options as extensions of the *Options messages. + These extensions may not yet be known at parsing time, so the parser cannot + store the values in them. Instead it stores them in a field in the *Options + message called uninterpreted_option. This field must have the same name + across all *Options messages. We then use this field to populate the + extensions when we build a descriptor, at which point all protos have been + parsed and so all extensions are known. + + Extension numbers for custom options may be chosen as follows: + * For options which will only be used within a single application or + organization, or for experimental options, use field numbers 50000 + through 99999. It is up to you to ensure that you do not use the + same number for multiple options. + * For options which will be published and used publicly by multiple + independent entities, e-mail protobuf-global-extension-registry@google.com + to reserve extension numbers. Simply provide your project name (e.g. + Objective-C plugin) and your project website (if available) -- there's no + need to explain how you intend to use them. Usually you only need one + extension number. You can declare multiple options with only one extension + number by putting them in a sub-message. See the Custom Options section of + the docs for examples: + https://developers.google.com/protocol-buffers/docs/proto#options + If this turns out to be popular, a web service will be set up + to automatically assign option numbers. + """ + java_package: str = betterproto.string_field(1) """ Sets the Java package where classes generated from this .proto will be - placed. By default, the proto package is used, but this is often - inappropriate because proto packages do not normally start with backwards - domain names. + placed. By default, the proto package is used, but this is often + inappropriate because proto packages do not normally start with backwards + domain names. """ java_outer_classname: str = betterproto.string_field(8) """ - 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. + 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). """ java_multiple_files: bool = betterproto.bool_field(10) """ - 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 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. + If set true, 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. """ java_generate_equals_and_hash: bool = betterproto.bool_field(20) @@ -1167,62 +1301,77 @@ class FileOptions(betterproto.Message): java_string_check_utf8: bool = betterproto.bool_field(27) """ - A proto2 file can set this to true to opt in to UTF-8 checking for Java, - which will throw an exception if invalid UTF-8 is parsed from the wire or - assigned to a string field. - - TODO: clarify exactly what kinds of field types this option - applies to, and update these docs accordingly. + If set true, then the Java2 code generator will generate code that + throws an exception whenever an attempt is made to assign a non-UTF-8 + byte sequence to a string field. + Message reflection will do the same. + However, an extension field still accepts non-UTF-8 byte sequences. + This option has no effect on when used with the lite runtime. + """ + + optimize_for: "FileOptionsOptimizeMode" = betterproto.enum_field( + 9, enum_default_value=lambda: FileOptionsOptimizeMode.try_value(0) + ) + """ - Proto3 files already perform these checks. Setting the option explicitly to - false has no effect: it cannot be used to opt proto3 files out of UTF-8 - checks. """ - optimize_for: "FileOptionsOptimizeMode" = betterproto.enum_field(9) go_package: str = betterproto.string_field(11) """ Sets the Go package where structs generated from this .proto will be - placed. If omitted, the Go package will be derived from the following: - - The basename of the package import path, if provided. - - Otherwise, the package statement in the .proto file, if present. - - Otherwise, the basename of the .proto file, without extension. + placed. If omitted, the Go package will be derived from the following: + - The basename of the package import path, if provided. + - Otherwise, the package statement in the .proto file, if present. + - Otherwise, the basename of the .proto file, without extension. """ cc_generic_services: bool = betterproto.bool_field(16) """ Should generic services be generated in each language? "Generic" services - are not specific to any particular RPC system. They are generated by the - main code generators in each language (without additional plugins). - Generic services were the only kind of service generation supported by - early versions of google.protobuf. + are not specific to any particular RPC system. They are generated by the + main code generators in each language (without additional plugins). + Generic services were the only kind of service generation supported by + early versions of google.protobuf. - Generic services are now considered deprecated in favor of using plugins - that generate code specific to your particular RPC system. Therefore, - these default to false. Old code which depends on generic services should - explicitly set them to true. + Generic services are now considered deprecated in favor of using plugins + that generate code specific to your particular RPC system. Therefore, + these default to false. Old code which depends on generic services should + explicitly set them to true. """ java_generic_services: bool = betterproto.bool_field(17) + """ + + """ + py_generic_services: bool = betterproto.bool_field(18) + """ + + """ + + php_generic_services: bool = betterproto.bool_field(42) + """ + + """ + deprecated: bool = betterproto.bool_field(23) """ Is this file deprecated? - Depending on the target platform, this can emit Deprecated annotations - for everything in the file, or it will be completely ignored; in the very - least, this is a formalization for deprecating files. + Depending on the target platform, this can emit Deprecated annotations + for everything in the file, or it will be completely ignored; in the very + least, this is a formalization for deprecating files. """ cc_enable_arenas: bool = betterproto.bool_field(31) """ Enables the use of arenas for the proto messages in this file. This applies - only to generated classes for C++. + only to generated classes for C++. """ objc_class_prefix: str = betterproto.string_field(36) """ Sets the objective c class prefix which is prepended to all objective c - generated classes from this .proto. There is no default. + generated classes from this .proto. There is no default. """ csharp_namespace: str = betterproto.string_field(37) @@ -1231,45 +1380,44 @@ class FileOptions(betterproto.Message): swift_prefix: str = betterproto.string_field(39) """ By default Swift generators will take the proto package and CamelCase it - replacing '.' with underscore and use that to prefix the types/symbols - defined. When this options is provided, they will use this value instead - to prefix the types/symbols defined. + replacing '.' with underscore and use that to prefix the types/symbols + defined. When this options is provided, they will use this value instead + to prefix the types/symbols defined. """ php_class_prefix: str = betterproto.string_field(40) """ Sets the php class prefix which is prepended to all php generated classes - from this .proto. Default is empty. + from this .proto. Default is empty. """ php_namespace: str = betterproto.string_field(41) """ Use this option to change the namespace of php generated classes. Default - is empty. When this option is empty, the package name will be used for - determining the namespace. + is empty. When this option is empty, the package name will be used for + determining the namespace. """ php_metadata_namespace: str = betterproto.string_field(44) """ Use this option to change the namespace of php generated metadata classes. - Default is empty. When this option is empty, the proto file name will be - used for determining the namespace. + Default is empty. When this option is empty, the proto file name will be + used for determining the namespace. """ ruby_package: str = betterproto.string_field(45) """ Use this option to change the package of ruby generated classes. Default - is empty. When this option is not set, the package name will be used for - determining the ruby package. + is empty. When this option is not set, the package name will be used for + determining the ruby package. """ - features: "FeatureSet" = betterproto.message_field(50) - """Any features defined in the specific edition.""" - - uninterpreted_option: List["UninterpretedOption"] = betterproto.message_field(999) + uninterpreted_option: List["UninterpretedOption"] = betterproto.message_field( + 999, repeated=True + ) """ The parser stores options it doesn't recognize here. - See the documentation for the "Options" section above. + See the documentation for the "Options" section above. """ def __post_init__(self) -> None: @@ -1283,339 +1431,266 @@ def __post_init__(self) -> None: @dataclass(eq=False, repr=False) class MessageOptions(betterproto.Message): + """ """ + message_set_wire_format: bool = betterproto.bool_field(1) """ Set true to use the old proto1 MessageSet wire format for extensions. - This is provided for backwards-compatibility with the MessageSet wire - format. You should not use this for any other reason: It's less - efficient, has fewer features, and is more complicated. + This is provided for backwards-compatibility with the MessageSet wire + format. You should not use this for any other reason: It's less + efficient, has fewer features, and is more complicated. - The message must be defined exactly as follows: - message Foo { - option message_set_wire_format = true; - extensions 4 to max; - } - Note that the message cannot have any defined fields; MessageSets only - have extensions. + The message must be defined exactly as follows: + message Foo { + option message_set_wire_format = true; + extensions 4 to max; + } + Note that the message cannot have any defined fields; MessageSets only + have extensions. - All extensions of your type must be singular messages; e.g. they cannot - be int32s, enums, or repeated messages. + All extensions of your type must be singular messages; e.g. they cannot + be int32s, enums, or repeated messages. - Because this is an option, the above two restrictions are not enforced by - the protocol compiler. + Because this is an option, the above two restrictions are not enforced by + the protocol compiler. """ no_standard_descriptor_accessor: bool = betterproto.bool_field(2) """ Disables the generation of the standard "descriptor()" accessor, which can - conflict with a field of the same name. This is meant to make migration - from proto1 easier; new code should avoid fields named "descriptor". + conflict with a field of the same name. This is meant to make migration + from proto1 easier; new code should avoid fields named "descriptor". """ deprecated: bool = betterproto.bool_field(3) """ Is this message deprecated? - Depending on the target platform, this can emit Deprecated annotations - for the message, or it will be completely ignored; in the very least, - this is a formalization for deprecating messages. + Depending on the target platform, this can emit Deprecated annotations + for the message, or it will be completely ignored; in the very least, + this is a formalization for deprecating messages. """ map_entry: bool = betterproto.bool_field(7) """ Whether the message is an automatically generated map entry type for the - maps field. - - For maps fields: - map map_field = 1; - The parsed descriptor looks like: - message MapFieldEntry { - option map_entry = true; - optional KeyType key = 1; - optional ValueType value = 2; - } - repeated MapFieldEntry map_field = 1; + maps field. - Implementations may choose not to generate the map_entry=true message, but - use a native map in the target language to hold the keys and values. - The reflection APIs in such implementations still need to work as - if the field is a repeated message field. - - NOTE: Do not set the option in .proto files. Always use the maps syntax - instead. The option should only be implicitly set by the proto compiler - parser. - """ - - deprecated_legacy_json_field_conflicts: bool = betterproto.bool_field(11) - """ - Enable the legacy handling of JSON field name conflicts. This lowercases - and strips underscored from the fields before comparison in proto3 only. - The new behavior takes `json_name` into account and applies to proto2 as - well. + For maps fields: + map map_field = 1; + The parsed descriptor looks like: + message MapFieldEntry { + option map_entry = true; + optional KeyType key = 1; + optional ValueType value = 2; + } + repeated MapFieldEntry map_field = 1; - This should only be used as a temporary measure against broken builds due - to the change in behavior for JSON field name conflicts. + Implementations may choose not to generate the map_entry=true message, but + use a native map in the target language to hold the keys and values. + The reflection APIs in such implementations still need to work as + if the field is a repeated message field. - TODO This is legacy behavior we plan to remove once downstream - teams have had time to migrate. + NOTE: Do not set the option in .proto files. Always use the maps syntax + instead. The option should only be implicitly set by the proto compiler + parser. """ - features: "FeatureSet" = betterproto.message_field(12) - """Any features defined in the specific edition.""" - - uninterpreted_option: List["UninterpretedOption"] = betterproto.message_field(999) + uninterpreted_option: List["UninterpretedOption"] = betterproto.message_field( + 999, repeated=True + ) """The parser stores options it doesn't recognize here. See above.""" - def __post_init__(self) -> None: - super().__post_init__() - if self.is_set("deprecated_legacy_json_field_conflicts"): - warnings.warn( - "MessageOptions.deprecated_legacy_json_field_conflicts is deprecated", - DeprecationWarning, - ) - @dataclass(eq=False, repr=False) class FieldOptions(betterproto.Message): - ctype: "FieldOptionsCType" = betterproto.enum_field(1) + """ """ + + ctype: "FieldOptionsCType" = betterproto.enum_field( + 1, enum_default_value=lambda: FieldOptionsCType.try_value(0) + ) """ The ctype option instructs the C++ code generator to use a different - representation of the field than it normally would. See the specific - options below. This option is only implemented to support use of - [ctype=CORD] and [ctype=STRING] (the default) on non-repeated fields of - type "bytes" in the open source release -- sorry, we'll try to include - other types in a future version! + representation of the field than it normally would. See the specific + options below. This option is not yet implemented in the open source + release -- sorry, we'll try to include it in a future version! """ packed: bool = betterproto.bool_field(2) """ The packed option can be enabled for repeated primitive fields to enable - a more efficient representation on the wire. Rather than repeatedly - writing the tag and type for each element, the entire array is encoded as - a single length-delimited blob. In proto3, only explicit setting it to - false will avoid using packed encoding. This option is prohibited in - Editions, but the `repeated_field_encoding` feature can be used to control - the behavior. + a more efficient representation on the wire. Rather than repeatedly + writing the tag and type for each element, the entire array is encoded as + a single length-delimited blob. In proto3, only explicit setting it to + false will avoid using packed encoding. """ - jstype: "FieldOptionsJsType" = betterproto.enum_field(6) + jstype: "FieldOptionsJsType" = betterproto.enum_field( + 6, enum_default_value=lambda: FieldOptionsJsType.try_value(0) + ) """ The jstype option determines the JavaScript type used for values of the - field. The option is permitted only for 64 bit integral and fixed types - (int64, uint64, sint64, fixed64, sfixed64). A field with jstype JS_STRING - is represented as JavaScript string, which avoids loss of precision that - can happen when a large value is converted to a floating point JavaScript. - Specifying JS_NUMBER for the jstype causes the generated JavaScript code to - use the JavaScript "number" type. The behavior of the default option - JS_NORMAL is implementation dependent. + field. The option is permitted only for 64 bit integral and fixed types + (int64, uint64, sint64, fixed64, sfixed64). A field with jstype JS_STRING + is represented as JavaScript string, which avoids loss of precision that + can happen when a large value is converted to a floating point JavaScript. + Specifying JS_NUMBER for the jstype causes the generated JavaScript code to + use the JavaScript "number" type. The behavior of the default option + JS_NORMAL is implementation dependent. - This option is an enum to permit additional types to be added, e.g. - goog.math.Integer. + This option is an enum to permit additional types to be added, e.g. + goog.math.Integer. """ lazy: bool = betterproto.bool_field(5) """ Should this field be parsed lazily? Lazy applies only to message-type - fields. It means that when the outer message is initially parsed, the - inner message's contents will not be parsed but instead stored in encoded - form. The inner message will actually be parsed when it is first accessed. + fields. It means that when the outer message is initially parsed, the + inner message's contents will not be parsed but instead stored in encoded + form. The inner message will actually be parsed when it is first accessed. - This is only a hint. Implementations are free to choose whether to use - eager or lazy parsing regardless of the value of this option. However, - setting this option true suggests that the protocol author believes that - using lazy parsing on this field is worth the additional bookkeeping - overhead typically needed to implement it. + This is only a hint. Implementations are free to choose whether to use + eager or lazy parsing regardless of the value of this option. However, + setting this option true suggests that the protocol author believes that + using lazy parsing on this field is worth the additional bookkeeping + overhead typically needed to implement it. - This option does not affect the public interface of any generated code; - all method signatures remain the same. Furthermore, thread-safety of the - interface is not affected by this option; const methods remain safe to - call from multiple threads concurrently, while non-const methods continue - to require exclusive access. + This option does not affect the public interface of any generated code; + all method signatures remain the same. Furthermore, thread-safety of the + interface is not affected by this option; const methods remain safe to + call from multiple threads concurrently, while non-const methods continue + to require exclusive access. - Note that lazy message fields are still eagerly verified to check - ill-formed wireformat or missing required fields. Calling IsInitialized() - on the outer message would fail if the inner message has missing required - fields. Failed verification would result in parsing failure (except when - uninitialized messages are acceptable). - """ - - unverified_lazy: bool = betterproto.bool_field(15) - """ - unverified_lazy does no correctness checks on the byte stream. This should - only be used where lazy with verification is prohibitive for performance - reasons. + Note that implementations may choose not to check required fields within + a lazy sub-message. That is, calling IsInitialized() on the outer message + may return true even if the inner message has missing required fields. + This is necessary because otherwise the inner message would have to be + parsed in order to perform the check, defeating the purpose of lazy + parsing. An implementation which chooses not to check required fields + must be consistent about it. That is, for any particular sub-message, the + implementation must either *always* check its required fields, or *never* + check its required fields, regardless of whether or not the message has + been parsed. """ deprecated: bool = betterproto.bool_field(3) """ Is this field deprecated? - Depending on the target platform, this can emit Deprecated annotations - for accessors, or it will be completely ignored; in the very least, this - is a formalization for deprecating fields. + Depending on the target platform, this can emit Deprecated annotations + for accessors, or it will be completely ignored; in the very least, this + is a formalization for deprecating fields. """ weak: bool = betterproto.bool_field(10) """For Google-internal migration only. Do not use.""" - debug_redact: bool = betterproto.bool_field(16) - """ - Indicate that the field value should not be printed out when using debug - formats, e.g. when the field contains sensitive credentials. - """ - - retention: "FieldOptionsOptionRetention" = betterproto.enum_field(17) - targets: List["FieldOptionsOptionTargetType"] = betterproto.enum_field(19) - edition_defaults: List["FieldOptionsEditionDefault"] = betterproto.message_field(20) - features: "FeatureSet" = betterproto.message_field(21) - """Any features defined in the specific edition.""" - - feature_support: "FieldOptionsFeatureSupport" = betterproto.message_field(22) - uninterpreted_option: List["UninterpretedOption"] = betterproto.message_field(999) + uninterpreted_option: List["UninterpretedOption"] = betterproto.message_field( + 999, repeated=True + ) """The parser stores options it doesn't recognize here. See above.""" -@dataclass(eq=False, repr=False) -class FieldOptionsEditionDefault(betterproto.Message): - edition: "Edition" = betterproto.enum_field(3) - value: str = betterproto.string_field(2) - - -@dataclass(eq=False, repr=False) -class FieldOptionsFeatureSupport(betterproto.Message): - """Information about the support window of a feature.""" - - edition_introduced: "Edition" = betterproto.enum_field(1) - """ - The edition that this feature was first available in. In editions - earlier than this one, the default assigned to EDITION_LEGACY will be - used, and proto files will not be able to override it. - """ - - edition_deprecated: "Edition" = betterproto.enum_field(2) - """ - The edition this feature becomes deprecated in. Using this after this - edition may trigger warnings. - """ - - deprecation_warning: str = betterproto.string_field(3) - """ - The deprecation warning text if this feature is used after the edition it - was marked deprecated in. - """ - - edition_removed: "Edition" = betterproto.enum_field(4) - """ - The edition this feature is no longer available in. In editions after - this one, the last default assigned will be used, and proto files will - not be able to override it. - """ - - @dataclass(eq=False, repr=False) class OneofOptions(betterproto.Message): - features: "FeatureSet" = betterproto.message_field(1) - """Any features defined in the specific edition.""" + """ """ - uninterpreted_option: List["UninterpretedOption"] = betterproto.message_field(999) + uninterpreted_option: List["UninterpretedOption"] = betterproto.message_field( + 999, repeated=True + ) """The parser stores options it doesn't recognize here. See above.""" @dataclass(eq=False, repr=False) class EnumOptions(betterproto.Message): + """ """ + allow_alias: bool = betterproto.bool_field(2) """ Set this option to true to allow mapping different tag names to the same - value. + value. """ deprecated: bool = betterproto.bool_field(3) """ Is this enum deprecated? - Depending on the target platform, this can emit Deprecated annotations - for the enum, or it will be completely ignored; in the very least, this - is a formalization for deprecating enums. + Depending on the target platform, this can emit Deprecated annotations + for the enum, or it will be completely ignored; in the very least, this + is a formalization for deprecating enums. """ - deprecated_legacy_json_field_conflicts: bool = betterproto.bool_field(6) - """ - Enable the legacy handling of JSON field name conflicts. This lowercases - and strips underscored from the fields before comparison in proto3 only. - The new behavior takes `json_name` into account and applies to proto2 as - well. - TODO Remove this legacy behavior once downstream teams have - had time to migrate. - """ - - features: "FeatureSet" = betterproto.message_field(7) - """Any features defined in the specific edition.""" - - uninterpreted_option: List["UninterpretedOption"] = betterproto.message_field(999) + uninterpreted_option: List["UninterpretedOption"] = betterproto.message_field( + 999, repeated=True + ) """The parser stores options it doesn't recognize here. See above.""" - def __post_init__(self) -> None: - super().__post_init__() - if self.is_set("deprecated_legacy_json_field_conflicts"): - warnings.warn( - "EnumOptions.deprecated_legacy_json_field_conflicts is deprecated", - DeprecationWarning, - ) - @dataclass(eq=False, repr=False) class EnumValueOptions(betterproto.Message): + """ """ + deprecated: bool = betterproto.bool_field(1) """ Is this enum value deprecated? - Depending on the target platform, this can emit Deprecated annotations - for the enum value, or it will be completely ignored; in the very least, - this is a formalization for deprecating enum values. - """ - - features: "FeatureSet" = betterproto.message_field(2) - """Any features defined in the specific edition.""" - - debug_redact: bool = betterproto.bool_field(3) - """ - Indicate that fields annotated with this enum value should not be printed - out when using debug formats, e.g. when the field contains sensitive - credentials. + Depending on the target platform, this can emit Deprecated annotations + for the enum value, or it will be completely ignored; in the very least, + this is a formalization for deprecating enum values. """ - uninterpreted_option: List["UninterpretedOption"] = betterproto.message_field(999) + uninterpreted_option: List["UninterpretedOption"] = betterproto.message_field( + 999, repeated=True + ) """The parser stores options it doesn't recognize here. See above.""" @dataclass(eq=False, repr=False) class ServiceOptions(betterproto.Message): - features: "FeatureSet" = betterproto.message_field(34) - """Any features defined in the specific edition.""" + """ """ deprecated: bool = betterproto.bool_field(33) """ + Note: Field numbers 1 through 32 are reserved for Google's internal RPC + framework. We apologize for hoarding these numbers to ourselves, but + we were already using them long before we decided to release Protocol + Buffers. + Is this service deprecated? - Depending on the target platform, this can emit Deprecated annotations - for the service, or it will be completely ignored; in the very least, - this is a formalization for deprecating services. + Depending on the target platform, this can emit Deprecated annotations + for the service, or it will be completely ignored; in the very least, + this is a formalization for deprecating services. """ - uninterpreted_option: List["UninterpretedOption"] = betterproto.message_field(999) + uninterpreted_option: List["UninterpretedOption"] = betterproto.message_field( + 999, repeated=True + ) """The parser stores options it doesn't recognize here. See above.""" @dataclass(eq=False, repr=False) class MethodOptions(betterproto.Message): + """ """ + deprecated: bool = betterproto.bool_field(33) """ + Note: Field numbers 1 through 32 are reserved for Google's internal RPC + framework. We apologize for hoarding these numbers to ourselves, but + we were already using them long before we decided to release Protocol + Buffers. + Is this method deprecated? - Depending on the target platform, this can emit Deprecated annotations - for the method, or it will be completely ignored; in the very least, - this is a formalization for deprecating methods. + Depending on the target platform, this can emit Deprecated annotations + for the method, or it will be completely ignored; in the very least, + this is a formalization for deprecating methods. """ - idempotency_level: "MethodOptionsIdempotencyLevel" = betterproto.enum_field(34) - features: "FeatureSet" = betterproto.message_field(35) - """Any features defined in the specific edition.""" + idempotency_level: "MethodOptionsIdempotencyLevel" = betterproto.enum_field( + 34, enum_default_value=lambda: MethodOptionsIdempotencyLevel.try_value(0) + ) + """ + + """ - uninterpreted_option: List["UninterpretedOption"] = betterproto.message_field(999) + uninterpreted_option: List["UninterpretedOption"] = betterproto.message_field( + 999, repeated=True + ) """The parser stores options it doesn't recognize here. See above.""" @@ -1623,279 +1698,260 @@ class MethodOptions(betterproto.Message): class UninterpretedOption(betterproto.Message): """ A message representing a option the parser does not recognize. This only - appears in options protos created by the compiler::Parser class. - DescriptorPool resolves these when building Descriptor objects. Therefore, - options protos in descriptor objects (e.g. returned by Descriptor::options(), - or produced by Descriptor::CopyTo()) will never have UninterpretedOptions - in them. + appears in options protos created by the compiler::Parser class. + DescriptorPool resolves these when building Descriptor objects. Therefore, + options protos in descriptor objects (e.g. returned by Descriptor::options(), + or produced by Descriptor::CopyTo()) will never have UninterpretedOptions + in them. + """ + + name: List["UninterpretedOptionNamePart"] = betterproto.message_field( + 2, repeated=True + ) + """ + """ - name: List["UninterpretedOptionNamePart"] = betterproto.message_field(2) identifier_value: str = betterproto.string_field(3) """ The value of the uninterpreted option, in whatever type the tokenizer - identified it as during parsing. Exactly one of these should be set. + identified it as during parsing. Exactly one of these should be set. """ positive_int_value: int = betterproto.uint64_field(4) - negative_int_value: int = betterproto.int64_field(5) - double_value: float = betterproto.double_field(6) - string_value: bytes = betterproto.bytes_field(7) - aggregate_value: str = betterproto.string_field(8) - - -@dataclass(eq=False, repr=False) -class UninterpretedOptionNamePart(betterproto.Message): """ - The name of the uninterpreted option. Each string represents a segment in - a dot-separated name. is_extension is true iff a segment represents an - extension (denoted with parentheses in options specs in .proto files). - E.g.,{ ["foo", false], ["bar.baz", true], ["moo", false] } represents - "foo.(bar.baz).moo". + """ - name_part: str = betterproto.string_field(1) - is_extension: bool = betterproto.bool_field(2) - - -@dataclass(eq=False, repr=False) -class FeatureSet(betterproto.Message): + negative_int_value: int = betterproto.int64_field(5) """ - TODO Enums in C++ gencode (and potentially other languages) are - not well scoped. This means that each of the feature enums below can clash - with each other. The short names we've chosen maximize call-site - readability, but leave us very open to this scenario. A future feature will - be designed and implemented to handle this, hopefully before we ever hit a - conflict here. + """ - field_presence: "FeatureSetFieldPresence" = betterproto.enum_field(1) - enum_type: "FeatureSetEnumType" = betterproto.enum_field(2) - repeated_field_encoding: "FeatureSetRepeatedFieldEncoding" = betterproto.enum_field( - 3 - ) - utf8_validation: "FeatureSetUtf8Validation" = betterproto.enum_field(4) - message_encoding: "FeatureSetMessageEncoding" = betterproto.enum_field(5) - json_format: "FeatureSetJsonFormat" = betterproto.enum_field(6) - - -@dataclass(eq=False, repr=False) -class FeatureSetDefaults(betterproto.Message): + double_value: float = betterproto.double_field(6) """ - A compiled specification for the defaults of a set of features. These - messages are generated from FeatureSet extensions and can be used to seed - feature resolution. The resolution with this object becomes a simple search - for the closest matching edition, followed by proto merges. + """ - defaults: List["FeatureSetDefaultsFeatureSetEditionDefault"] = ( - betterproto.message_field(1) - ) - minimum_edition: "Edition" = betterproto.enum_field(4) + string_value: bytes = betterproto.bytes_field(7) """ - The minimum supported edition (inclusive) when this was constructed. - Editions before this will not have defaults. + """ - maximum_edition: "Edition" = betterproto.enum_field(5) + aggregate_value: str = betterproto.string_field(8) """ - The maximum known edition (inclusive) when this was constructed. Editions - after this will not have reliable defaults. + """ @dataclass(eq=False, repr=False) -class FeatureSetDefaultsFeatureSetEditionDefault(betterproto.Message): +class UninterpretedOptionNamePart(betterproto.Message): """ - A map from every known edition with a unique set of defaults to its - defaults. Not all editions may be contained here. For a given edition, - the defaults at the closest matching edition ordered at or before it should - be used. This field must be in strict ascending order by edition. + The name of the uninterpreted option. Each string represents a segment in + a dot-separated name. is_extension is true iff a segment represents an + extension (denoted with parentheses in options specs in .proto files). + E.g.,{ ["foo", false], ["bar.baz", true], ["qux", false] } represents + "foo.(bar.baz).qux". """ - edition: "Edition" = betterproto.enum_field(3) - overridable_features: "FeatureSet" = betterproto.message_field(4) - """Defaults of features that can be overridden in this edition.""" - - fixed_features: "FeatureSet" = betterproto.message_field(5) - """Defaults of features that can't be overridden in this edition.""" + name_part: str = betterproto.string_field(1) + """ + + """ - features: "FeatureSet" = betterproto.message_field(2) + is_extension: bool = betterproto.bool_field(2) """ - TODO Deprecate and remove this field, which is just the - above two merged. + """ @dataclass(eq=False, repr=False) class SourceCodeInfo(betterproto.Message): """ + =================================================================== + Optional source code info + Encapsulates information about the original source file from which a - FileDescriptorProto was generated. + FileDescriptorProto was generated. """ - location: List["SourceCodeInfoLocation"] = betterproto.message_field(1) + location: List["SourceCodeInfoLocation"] = betterproto.message_field( + 1, repeated=True + ) """ A Location identifies a piece of source code in a .proto file which - corresponds to a particular definition. This information is intended - to be useful to IDEs, code indexers, documentation generators, and similar - tools. - - For example, say we have a file like: - message Foo { - optional string foo = 1; - } - Let's look at just the field definition: - optional string foo = 1; - ^ ^^ ^^ ^ ^^^ - a bc de f ghi - We have the following locations: - span path represents - [a,i) [ 4, 0, 2, 0 ] The whole field definition. - [a,b) [ 4, 0, 2, 0, 4 ] The label (optional). - [c,d) [ 4, 0, 2, 0, 5 ] The type (string). - [e,f) [ 4, 0, 2, 0, 1 ] The name (foo). - [g,h) [ 4, 0, 2, 0, 3 ] The number (1). - - Notes: - - A location may refer to a repeated field itself (i.e. not to any - particular index within it). This is used whenever a set of elements are - logically enclosed in a single code segment. For example, an entire - extend block (possibly containing multiple extension definitions) will - have an outer location whose path refers to the "extensions" repeated - field without an index. - - Multiple locations may have the same path. This happens when a single - logical declaration is spread out across multiple places. The most - obvious example is the "extend" block again -- there may be multiple - extend blocks in the same scope, each of which will have the same path. - - A location's span is not always a subset of its parent's span. For - example, the "extendee" of an extension declaration appears at the - beginning of the "extend" block and is shared by all extensions within - the block. - - Just because a location's span is a subset of some other location's span - does not mean that it is a descendant. For example, a "group" defines - both a type and a field in a single declaration. Thus, the locations - corresponding to the type and field and their components will overlap. - - Code which tries to interpret locations should probably be designed to - ignore those that it doesn't understand, as more types of locations could - be recorded in the future. + corresponds to a particular definition. This information is intended + to be useful to IDEs, code indexers, documentation generators, and similar + tools. + + For example, say we have a file like: + message Foo { + optional string foo = 1; + } + Let's look at just the field definition: + optional string foo = 1; + ^ ^^ ^^ ^ ^^^ + a bc de f ghi + We have the following locations: + span path represents + [a,i) [ 4, 0, 2, 0 ] The whole field definition. + [a,b) [ 4, 0, 2, 0, 4 ] The label (optional). + [c,d) [ 4, 0, 2, 0, 5 ] The type (string). + [e,f) [ 4, 0, 2, 0, 1 ] The name (foo). + [g,h) [ 4, 0, 2, 0, 3 ] The number (1). + + Notes: + - A location may refer to a repeated field itself (i.e. not to any + particular index within it). This is used whenever a set of elements are + logically enclosed in a single code segment. For example, an entire + extend block (possibly containing multiple extension definitions) will + have an outer location whose path refers to the "extensions" repeated + field without an index. + - Multiple locations may have the same path. This happens when a single + logical declaration is spread out across multiple places. The most + obvious example is the "extend" block again -- there may be multiple + extend blocks in the same scope, each of which will have the same path. + - A location's span is not always a subset of its parent's span. For + example, the "extendee" of an extension declaration appears at the + beginning of the "extend" block and is shared by all extensions within + the block. + - Just because a location's span is a subset of some other location's span + does not mean that it is a descendant. For example, a "group" defines + both a type and a field in a single declaration. Thus, the locations + corresponding to the type and field and their components will overlap. + - Code which tries to interpret locations should probably be designed to + ignore those that it doesn't understand, as more types of locations could + be recorded in the future. """ @dataclass(eq=False, repr=False) class SourceCodeInfoLocation(betterproto.Message): - path: List[int] = betterproto.int32_field(1) + """ """ + + path: List[int] = betterproto.int32_field(1, repeated=True) """ Identifies which part of the FileDescriptorProto was defined at this - location. - - Each element is a field number or an index. They form a path from - the root FileDescriptorProto to the place where the definition appears. - For example, this path: - [ 4, 3, 2, 7, 1 ] - refers to: - file.message_type(3) // 4, 3 - .field(7) // 2, 7 - .name() // 1 - This is because FileDescriptorProto.message_type has field number 4: - repeated DescriptorProto message_type = 4; - and DescriptorProto.field has field number 2: - repeated FieldDescriptorProto field = 2; - and FieldDescriptorProto.name has field number 1: - optional string name = 1; - - Thus, the above path gives the location of a field name. If we removed - the last element: - [ 4, 3, 2, 7 ] - this path refers to the whole field declaration (from the beginning - of the label to the terminating semicolon). - """ - - span: List[int] = betterproto.int32_field(2) + location. + + Each element is a field number or an index. They form a path from + the root FileDescriptorProto to the place where the definition. For + example, this path: + [ 4, 3, 2, 7, 1 ] + refers to: + file.message_type(3) // 4, 3 + .field(7) // 2, 7 + .name() // 1 + This is because FileDescriptorProto.message_type has field number 4: + repeated DescriptorProto message_type = 4; + and DescriptorProto.field has field number 2: + repeated FieldDescriptorProto field = 2; + and FieldDescriptorProto.name has field number 1: + optional string name = 1; + + Thus, the above path gives the location of a field name. If we removed + the last element: + [ 4, 3, 2, 7 ] + this path refers to the whole field declaration (from the beginning + of the label to the terminating semicolon). + """ + + span: List[int] = betterproto.int32_field(2, repeated=True) """ Always has exactly three or four elements: start line, start column, - end line (optional, otherwise assumed same as start line), end column. - These are packed into a single field for efficiency. Note that line - and column numbers are zero-based -- typically you will want to add - 1 to each before displaying to a user. + end line (optional, otherwise assumed same as start line), end column. + These are packed into a single field for efficiency. Note that line + and column numbers are zero-based -- typically you will want to add + 1 to each before displaying to a user. """ leading_comments: str = betterproto.string_field(3) """ If this SourceCodeInfo represents a complete declaration, these are any - comments appearing before and after the declaration which appear to be - attached to the declaration. + comments appearing before and after the declaration which appear to be + attached to the declaration. - A series of line comments appearing on consecutive lines, with no other - tokens appearing on those lines, will be treated as a single comment. + A series of line comments appearing on consecutive lines, with no other + tokens appearing on those lines, will be treated as a single comment. - leading_detached_comments will keep paragraphs of comments that appear - before (but not connected to) the current element. Each paragraph, - separated by empty lines, will be one comment element in the repeated - field. + leading_detached_comments will keep paragraphs of comments that appear + before (but not connected to) the current element. Each paragraph, + separated by empty lines, will be one comment element in the repeated + field. - Only the comment content is provided; comment markers (e.g. //) are - stripped out. For block comments, leading whitespace and an asterisk - will be stripped from the beginning of each line other than the first. - Newlines are included in the output. + Only the comment content is provided; comment markers (e.g. //) are + stripped out. For block comments, leading whitespace and an asterisk + will be stripped from the beginning of each line other than the first. + Newlines are included in the output. - Examples: + Examples: - optional int32 foo = 1; // Comment attached to foo. - // Comment attached to bar. - optional int32 bar = 2; + optional int32 foo = 1; // Comment attached to foo. + // Comment attached to bar. + optional int32 bar = 2; - optional string baz = 3; - // Comment attached to baz. - // Another line attached to baz. + optional string baz = 3; + // Comment attached to baz. + // Another line attached to baz. - // Comment attached to moo. - // - // Another line attached to moo. - optional double moo = 4; + // Comment attached to qux. + // + // Another line attached to qux. + optional double qux = 4; - // Detached comment for corge. This is not leading or trailing comments - // to moo or corge because there are blank lines separating it from - // both. + // Detached comment for corge. This is not leading or trailing comments + // to qux or corge because there are blank lines separating it from + // both. - // Detached comment for corge paragraph 2. + // Detached comment for corge paragraph 2. - optional string corge = 5; - /* Block comment attached - * to corge. Leading asterisks - * will be removed. */ - /* Block comment attached to - * grault. */ - optional int32 grault = 6; + optional string corge = 5; + /* Block comment attached + * to corge. Leading asterisks + * will be removed. */ + /* Block comment attached to + * grault. */ + optional int32 grault = 6; - // ignored detached comments. + // ignored detached comments. """ trailing_comments: str = betterproto.string_field(4) - leading_detached_comments: List[str] = betterproto.string_field(6) + """ + + """ + + leading_detached_comments: List[str] = betterproto.string_field(6, repeated=True) + """ + + """ @dataclass(eq=False, repr=False) class GeneratedCodeInfo(betterproto.Message): """ Describes the relationship between generated code and its original source - file. A GeneratedCodeInfo message is associated with only one generated - source file, but may contain references to different source .proto files. + file. A GeneratedCodeInfo message is associated with only one generated + source file, but may contain references to different source .proto files. """ - annotation: List["GeneratedCodeInfoAnnotation"] = betterproto.message_field(1) + annotation: List["GeneratedCodeInfoAnnotation"] = betterproto.message_field( + 1, repeated=True + ) """ An Annotation connects some span of text in generated code to an element - of its generating .proto file. + of its generating .proto file. """ @dataclass(eq=False, repr=False) class GeneratedCodeInfoAnnotation(betterproto.Message): - path: List[int] = betterproto.int32_field(1) + """ """ + + path: List[int] = betterproto.int32_field(1, repeated=True) """ Identifies the element in the original source .proto file. This field - is formatted the same as SourceCodeInfo.Location.path. + is formatted the same as SourceCodeInfo.Location.path. """ source_file: str = betterproto.string_field(2) @@ -1904,97 +1960,95 @@ class GeneratedCodeInfoAnnotation(betterproto.Message): begin: int = betterproto.int32_field(3) """ Identifies the starting offset in bytes in the generated code - that relates to the identified object. + that relates to the identified object. """ end: int = betterproto.int32_field(4) """ Identifies the ending offset in bytes in the generated code that - relates to the identified object. The end offset should be one past - the last relevant byte (so the length of the text = end - begin). + relates to the identified offset. The end offset should be one past + the last relevant byte (so the length of the text = end - begin). """ - semantic: "GeneratedCodeInfoAnnotationSemantic" = betterproto.enum_field(5) - @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. + 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 + # Examples - Example 1: Compute Duration from two Timestamps in pseudo code. + Example 1: Compute Duration from two Timestamps in pseudo code. - Timestamp start = ...; - Timestamp end = ...; - Duration duration = ...; + Timestamp start = ...; + Timestamp end = ...; + Duration duration = ...; - duration.seconds = end.seconds - start.seconds; - duration.nanos = end.nanos - start.nanos; + 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; - } + 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. + Example 2: Compute Timestamp from Timestamp + Duration in pseudo code. - Timestamp start = ...; - Duration duration = ...; - Timestamp end = ...; + Timestamp start = ...; + Duration duration = ...; + Timestamp end = ...; - end.seconds = start.seconds + duration.seconds; - end.nanos = start.nanos + duration.nanos; + 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; - } + 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. + Example 3: Compute Duration from datetime.timedelta in Python. - td = datetime.timedelta(days=3, minutes=10) - duration = Duration() - duration.FromTimedelta(td) + td = datetime.timedelta(days=3, minutes=10) + duration = Duration() + duration.FromTimedelta(td) - # JSON Mapping + # 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". + 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". """ seconds: int = betterproto.int64_field(1) """ 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 + 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 """ nanos: int = betterproto.int32_field(2) """ 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. + 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. """ @@ -2002,12 +2056,14 @@ class Duration(betterproto.Message): 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: + 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); - } + service Foo { + rpc Bar(google.protobuf.Empty) returns (google.protobuf.Empty); + } + + The JSON representation for `Empty` is empty JSON object `{}`. """ pass @@ -2018,206 +2074,205 @@ 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 + paths: "f.a" + paths: "f.b.d" - 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: + 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`. - 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): + 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 - f { - a : 22 - b { - d : 1 - } - } + 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: - 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] - } + 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: + And an update message: - f { - b { - d: 10 - } - c: [2] - } + f { + b { + d: 10 + } + c: [2] + } - then if the field mask is: + then if the field mask is: - paths: ["f.b", "f.c"] + paths: ["f.b", "f.c"] - then the result will be: + then the result will be: - f { - b { - d: 10 - x: 2 - } - c: [1, 2] - } + f { + b { + d: 10 + x: 2 + } + c: [1, 2] + } - An implementation may provide options to override this default behavior for - repeated and message fields. + 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. + 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. + 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. + 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 + ## 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). + 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 + # 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. + 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: + 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; - } + 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: + In proto a field mask for `Profile` may look as such: - mask { - paths: "user.display_name" - paths: "photo" - } + mask { + paths: "user.display_name" + paths: "photo" + } - In JSON, the same mask is represented as below: + In JSON, the same mask is represented as below: - { - mask: "user.displayName,photo" - } + { + mask: "user.displayName,photo" + } - # Field Masks and Oneof Fields + # Field Masks and Oneof Fields - Field masks treat fields in oneofs just as regular fields. Consider the - following message: + 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; - } - } + message SampleMessage { + oneof test_oneof { + string name = 4; + SubMessage sub_message = 9; + } + } - The field mask can be: + The field mask can be: - mask { - paths: "name" - } + mask { + paths: "name" + } - Or: + Or: - mask { - paths: "sub_message" - } + mask { + paths: "sub_message" + } - Note that oneof type names ("test_oneof" in this case) cannot be used in - paths. + Note that oneof type names ("test_oneof" in this case) cannot be used in + paths. - ## Field Mask Verification + ## 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 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. """ - paths: List[str] = betterproto.string_field(1) + paths: List[str] = betterproto.string_field(1, repeated=True) """The set of field mask paths.""" @@ -2225,13 +2280,13 @@ class FieldMask(betterproto.Message): 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. + 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. + The JSON representation for `Struct` is JSON object. """ fields: Dict[str, "Value"] = betterproto.map_field( @@ -2270,14 +2325,16 @@ def to_dict( 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 these - variants. Absence of any variant indicates an error. + 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. + The JSON representation for `Value` is JSON value. """ - null_value: "NullValue" = betterproto.enum_field(1, group="kind") + null_value: "NullValue" = betterproto.enum_field( + 1, enum_default_value=lambda: NullValue.try_value(0), group="kind" + ) """Represents a null value.""" number_value: float = betterproto.double_field(2, group="kind") @@ -2301,10 +2358,10 @@ class ListValue(betterproto.Message): """ `ListValue` is a wrapper around a repeated field of values. - The JSON representation for `ListValue` is JSON array. + The JSON representation for `ListValue` is JSON array. """ - values: List["Value"] = betterproto.message_field(1) + values: List["Value"] = betterproto.message_field(1, repeated=True) """Repeated field of dynamically typed values.""" @@ -2312,109 +2369,101 @@ class ListValue(betterproto.Message): 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 + 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. - Example 1: Compute Timestamp from POSIX `time()`. + 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). - Timestamp timestamp; - timestamp.set_seconds(time(NULL)); - timestamp.set_nanos(0); + 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. - Example 2: Compute Timestamp from POSIX `gettimeofday()`. + # Examples - struct timeval tv; - gettimeofday(&tv, NULL); + Example 1: Compute Timestamp from POSIX `time()`. - Timestamp timestamp; - timestamp.set_seconds(tv.tv_sec); - timestamp.set_nanos(tv.tv_usec * 1000); + Timestamp timestamp; + timestamp.set_seconds(time(NULL)); + timestamp.set_nanos(0); - Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`. + Example 2: Compute Timestamp from POSIX `gettimeofday()`. - FILETIME ft; - GetSystemTimeAsFileTime(&ft); - UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime; + struct timeval tv; + gettimeofday(&tv, NULL); - // 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)); + Timestamp timestamp; + timestamp.set_seconds(tv.tv_sec); + timestamp.set_nanos(tv.tv_usec * 1000); - Example 4: Compute Timestamp from Java `System.currentTimeMillis()`. + Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`. - long millis = System.currentTimeMillis(); + FILETIME ft; + GetSystemTimeAsFileTime(&ft); + UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime; - Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000) - .setNanos((int) ((millis % 1000) * 1000000)).build(); + // 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 5: Compute Timestamp from Java `Instant.now()`. + Example 4: Compute Timestamp from Java `System.currentTimeMillis()`. - Instant now = Instant.now(); + long millis = System.currentTimeMillis(); - Timestamp timestamp = - Timestamp.newBuilder().setSeconds(now.getEpochSecond()) - .setNanos(now.getNano()).build(); + Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000) + .setNanos((int) ((millis % 1000) * 1000000)).build(); - Example 6: Compute Timestamp from current time in Python. + Example 5: Compute Timestamp from current time in Python. - timestamp = Timestamp() - timestamp.GetCurrentTime() + timestamp = Timestamp() + timestamp.GetCurrentTime() - # JSON Mapping + # 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). + 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. + 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://joda-time.sourceforge.net/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime() - ) to obtain a formatter capable of generating timestamps in this format. + 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. """ seconds: int = betterproto.int64_field(1) """ 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. + 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to + 9999-12-31T23:59:59Z inclusive. """ nanos: int = betterproto.int32_field(2) """ 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. + 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. """ @@ -2423,7 +2472,7 @@ class DoubleValue(betterproto.Message): """ Wrapper message for `double`. - The JSON representation for `DoubleValue` is JSON number. + The JSON representation for `DoubleValue` is JSON number. """ value: float = betterproto.double_field(1) @@ -2435,7 +2484,7 @@ class FloatValue(betterproto.Message): """ Wrapper message for `float`. - The JSON representation for `FloatValue` is JSON number. + The JSON representation for `FloatValue` is JSON number. """ value: float = betterproto.float_field(1) @@ -2447,7 +2496,7 @@ class Int64Value(betterproto.Message): """ Wrapper message for `int64`. - The JSON representation for `Int64Value` is JSON string. + The JSON representation for `Int64Value` is JSON string. """ value: int = betterproto.int64_field(1) @@ -2459,7 +2508,7 @@ class UInt64Value(betterproto.Message): """ Wrapper message for `uint64`. - The JSON representation for `UInt64Value` is JSON string. + The JSON representation for `UInt64Value` is JSON string. """ value: int = betterproto.uint64_field(1) @@ -2471,7 +2520,7 @@ class Int32Value(betterproto.Message): """ Wrapper message for `int32`. - The JSON representation for `Int32Value` is JSON number. + The JSON representation for `Int32Value` is JSON number. """ value: int = betterproto.int32_field(1) @@ -2483,7 +2532,7 @@ class UInt32Value(betterproto.Message): """ Wrapper message for `uint32`. - The JSON representation for `UInt32Value` is JSON number. + The JSON representation for `UInt32Value` is JSON number. """ value: int = betterproto.uint32_field(1) @@ -2495,7 +2544,7 @@ class BoolValue(betterproto.Message): """ Wrapper message for `bool`. - The JSON representation for `BoolValue` is JSON `true` and `false`. + The JSON representation for `BoolValue` is JSON `true` and `false`. """ value: bool = betterproto.bool_field(1) @@ -2507,7 +2556,7 @@ class StringValue(betterproto.Message): """ Wrapper message for `string`. - The JSON representation for `StringValue` is JSON string. + The JSON representation for `StringValue` is JSON string. """ value: str = betterproto.string_field(1) @@ -2519,7 +2568,7 @@ class BytesValue(betterproto.Message): """ Wrapper message for `bytes`. - The JSON representation for `BytesValue` is JSON string. + The JSON representation for `BytesValue` is JSON string. """ value: bytes = betterproto.bytes_field(1) diff --git a/src/betterproto/lib/std/google/protobuf/compiler/__init__.py b/src/betterproto/lib/std/google/protobuf/compiler/__init__.py index acacce43..dc5334a3 100644 --- a/src/betterproto/lib/std/google/protobuf/compiler/__init__.py +++ b/src/betterproto/lib/std/google/protobuf/compiler/__init__.py @@ -36,7 +36,7 @@ class Version(betterproto.Message): class CodeGeneratorRequest(betterproto.Message): """An encoded CodeGeneratorRequest is written to the plugin's stdin.""" - file_to_generate: List[str] = betterproto.string_field(1) + file_to_generate: List[str] = betterproto.string_field(1, repeated=True) """ The .proto files that were explicitly listed on the command-line. The code generator should generate code only for these files. Each file's @@ -47,7 +47,7 @@ class CodeGeneratorRequest(betterproto.Message): """The generator parameter passed on the command-line.""" proto_file: List["betterproto_lib_google_protobuf.FileDescriptorProto"] = ( - betterproto.message_field(15) + betterproto.message_field(15, repeated=True) ) """ FileDescriptorProtos for all files in files_to_generate and everything @@ -73,7 +73,7 @@ class CodeGeneratorRequest(betterproto.Message): source_file_descriptors: List[ "betterproto_lib_google_protobuf.FileDescriptorProto" - ] = betterproto.message_field(17) + ] = betterproto.message_field(17, repeated=True) """ File descriptors with all options, including source-retention options. These descriptors are only provided for the files listed in @@ -122,7 +122,9 @@ class CodeGeneratorResponse(betterproto.Message): effect for plugins that have FEATURE_SUPPORTS_EDITIONS set. """ - file: List["CodeGeneratorResponseFile"] = betterproto.message_field(15) + file: List["CodeGeneratorResponseFile"] = betterproto.message_field( + 15, repeated=True + ) @dataclass(eq=False, repr=False) diff --git a/src/betterproto/plugin/main.py b/src/betterproto/plugin/main.py index 62382e2e..29079204 100755 --- a/src/betterproto/plugin/main.py +++ b/src/betterproto/plugin/main.py @@ -7,7 +7,8 @@ CodeGeneratorRequest, CodeGeneratorResponse, ) -from betterproto.plugin.models import monkey_patch_oneof_index + +# from betterproto.plugin.models import monkey_patch_oneof_index from betterproto.plugin.parser import generate_code @@ -17,7 +18,7 @@ def main() -> None: data = sys.stdin.buffer.read() # Apply Work around for proto2/3 difference in protoc messages - monkey_patch_oneof_index() + # monkey_patch_oneof_index() # Parse request request = CodeGeneratorRequest() diff --git a/src/betterproto/plugin/models.py b/src/betterproto/plugin/models.py index 826fc781..7cfbdbc7 100644 --- a/src/betterproto/plugin/models.py +++ b/src/betterproto/plugin/models.py @@ -124,25 +124,26 @@ ) -def monkey_patch_oneof_index(): - """ - The compiler message types are written for proto2, but we read them as proto3. - For this to work in the case of the oneof_index fields, which depend on being able - to tell whether they were set, we have to treat them as oneof fields. This method - monkey patches the generated classes after the fact to force this behaviour. - """ - object.__setattr__( - FieldDescriptorProto.__dataclass_fields__["oneof_index"].metadata[ - "betterproto" - ], - "group", - "oneof_index", - ) - object.__setattr__( - Field.__dataclass_fields__["oneof_index"].metadata["betterproto"], - "group", - "oneof_index", - ) +# TODO patch again to make field optional +# def monkey_patch_oneof_index(): +# """ +# The compiler message types are written for proto2, but we read them as proto3. +# For this to work in the case of the oneof_index fields, which depend on being able +# to tell whether they were set, we have to treat them as oneof fields. This method +# monkey patches the generated classes after the fact to force this behaviour. +# """ +# object.__setattr__( +# FieldDescriptorProto.__dataclass_fields__["oneof_index"].metadata[ +# "betterproto" +# ], +# "group", +# "oneof_index", +# ) +# object.__setattr__( +# Field.__dataclass_fields__["oneof_index"].metadata["betterproto"], +# "group", +# "oneof_index", +# ) def get_comment( @@ -230,7 +231,7 @@ def comment(self) -> str: @property def deprecated(self) -> bool: - return self.proto_obj.options.deprecated + return self.proto_obj.options and self.proto_obj.options.deprecated @dataclass @@ -310,7 +311,10 @@ def python_module_imports(self) -> Set[str]: if any(x for x in self.messages.values() if any(x.deprecated_fields)): has_deprecated = True if any( - any(m.proto_obj.options.deprecated for m in s.methods) + any( + m.proto_obj.options and m.proto_obj.options.deprecated + for m in s.methods + ) for s in self.services.values() ): has_deprecated = True @@ -405,6 +409,7 @@ def is_oneof(proto_field_obj: FieldDescriptorProto) -> bool: True if proto_field_obj is a OneOf, otherwise False. .. warning:: + TODO update comment Becuase the message from protoc is defined in proto2, and betterproto works with proto3, and interpreting the FieldDescriptorProto.oneof_index field requires distinguishing between default and unset values (which proto3 doesn't support), @@ -415,8 +420,7 @@ def is_oneof(proto_field_obj: FieldDescriptorProto) -> bool: """ return ( - not proto_field_obj.proto3_optional - and which_one_of(proto_field_obj, "oneof_index")[0] == "oneof_index" + not proto_field_obj.proto3_optional and proto_field_obj.oneof_index is not None ) @@ -460,7 +464,12 @@ def betterproto_field_args(self) -> List[str]: if self.field_wraps: args.append(f"wraps={self.field_wraps}") if self.optional: - args.append(f"optional=True") + args.append("optional=True") + if self.repeated: + args.append("repeated=True") + if self.field_type == "enum": + t = self.py_type.strip('"') + args.append(f"enum_default_value=lambda: {t}.try_value(0)") return args @property @@ -510,7 +519,7 @@ def repeated(self) -> bool: @property def optional(self) -> bool: - return self.proto_obj.proto3_optional + return self.proto_obj.proto3_optional or self.field_type == "message" @property def field_type(self) -> str: @@ -576,6 +585,10 @@ def annotation(self) -> str: @dataclass class OneOfFieldCompiler(FieldCompiler): + @property + def optional(self) -> bool: + return True + @property def betterproto_field_args(self) -> List[str]: args = super().betterproto_field_args @@ -586,13 +599,6 @@ def betterproto_field_args(self) -> List[str]: @dataclass class PydanticOneOfFieldCompiler(OneOfFieldCompiler): - @property - def optional(self) -> bool: - # Force the optional to be True. This will allow the pydantic dataclass - # to validate the object correctly by allowing the field to be let empty. - # We add a pydantic validator later to ensure exactly one field is defined. - return True - @property def pydantic_imports(self) -> Set[str]: return {"model_validator"} diff --git a/src/betterproto/plugin/parser.py b/src/betterproto/plugin/parser.py index cf2a8e3e..01ea1dd7 100644 --- a/src/betterproto/plugin/parser.py +++ b/src/betterproto/plugin/parser.py @@ -61,7 +61,9 @@ def _traverse( # Adjust the name since we flatten the hierarchy. # Todo: don't change the name, but include full name in returned tuple should_rename = ( - not isinstance(item, DescriptorProto) or not item.options.map_entry + not isinstance(item, DescriptorProto) + or not item.options + or not item.options.map_entry ) item.name = next_prefix = ( @@ -225,7 +227,7 @@ def read_protobuf_type( output_package: OutputTemplate, ) -> None: if isinstance(item, DescriptorProto): - if item.options.map_entry: + if item.options and item.options.map_entry: # Skip generated map entry messages since we just use dicts return # Process Message diff --git a/src/betterproto/templates/template.py.j2 b/src/betterproto/templates/template.py.j2 index 8230819a..7517def7 100644 --- a/src/betterproto/templates/template.py.j2 +++ b/src/betterproto/templates/template.py.j2 @@ -89,7 +89,7 @@ class {{ service.py_name }}Stub(betterproto.ServiceStub): {{ method.comment }} {% endif %} - {% if method.proto_obj.options.deprecated %} + {% if method.proto_obj.options and method.proto_obj.options.deprecated %} warnings.warn("{{ service.py_name }}.{{ method.py_name }} is deprecated", DeprecationWarning) {% endif %} diff --git a/tests/inputs/casing_inner_class/test_casing_inner_class.py b/tests/inputs/casing_inner_class/test_casing_inner_class.py index 267b1056..50103c92 100644 --- a/tests/inputs/casing_inner_class/test_casing_inner_class.py +++ b/tests/inputs/casing_inner_class/test_casing_inner_class.py @@ -8,7 +8,7 @@ def test_message_casing_inner_class_name(): def test_message_casing_inner_class_attributes(): - message = casing_inner_class.Test() + message = casing_inner_class.Test(inner=casing_inner_class.TestInnerClass()) assert hasattr( message.inner, "old_exp" ), "Inline defined Message attribute is snake_case" diff --git a/tests/inputs/google_impl_behavior_equivalence/test_google_impl_behavior_equivalence.py b/tests/inputs/google_impl_behavior_equivalence/test_google_impl_behavior_equivalence.py index 6d2991bd..97587cfa 100644 --- a/tests/inputs/google_impl_behavior_equivalence/test_google_impl_behavior_equivalence.py +++ b/tests/inputs/google_impl_behavior_equivalence/test_google_impl_behavior_equivalence.py @@ -58,10 +58,8 @@ def test_bytes_are_the_same_for_oneof(): # None of these fields were explicitly set BUT they should not actually be null # themselves - assert not hasattr(message, "foo") - assert object.__getattribute__(message, "foo") == betterproto.PLACEHOLDER - assert not hasattr(message2, "foo") - assert object.__getattribute__(message2, "foo") == betterproto.PLACEHOLDER + assert message.foo is None + assert message2.foo is None assert isinstance(message_reference.foo, ReferenceFoo) assert isinstance(message_reference2.foo, ReferenceFoo) @@ -87,7 +85,4 @@ def test_empty_message_field(): message.foo = Empty() reference_message.foo.CopyFrom(ReferenceEmpty()) - assert betterproto.serialized_on_wire(message.foo) - assert reference_message.HasField("foo") - assert bytes(message) == reference_message.SerializeToString() diff --git a/tests/inputs/oneof_enum/test_oneof_enum.py b/tests/inputs/oneof_enum/test_oneof_enum.py index e54fa385..8dda1a97 100644 --- a/tests/inputs/oneof_enum/test_oneof_enum.py +++ b/tests/inputs/oneof_enum/test_oneof_enum.py @@ -1,5 +1,3 @@ -import pytest - import betterproto from tests.output_betterproto.oneof_enum import ( Move, @@ -18,8 +16,7 @@ def test_which_one_of_returns_enum_with_default_value(): get_test_case_json_data("oneof_enum", "oneof_enum-enum-0.json")[0].json ) - assert not hasattr(message, "move") - assert object.__getattribute__(message, "move") == betterproto.PLACEHOLDER + assert message.move is None assert message.signal == Signal.PASS assert betterproto.which_one_of(message, "action") == ("signal", Signal.PASS) @@ -32,8 +29,8 @@ def test_which_one_of_returns_enum_with_non_default_value(): message.from_json( get_test_case_json_data("oneof_enum", "oneof_enum-enum-1.json")[0].json ) - assert not hasattr(message, "move") - assert object.__getattribute__(message, "move") == betterproto.PLACEHOLDER + + assert message.move is None assert message.signal == Signal.RESIGN assert betterproto.which_one_of(message, "action") == ("signal", Signal.RESIGN) @@ -42,6 +39,5 @@ def test_which_one_of_returns_second_field_when_set(): message = Test() message.from_json(get_test_case_json_data("oneof_enum")[0].json) assert message.move == Move(x=2, y=3) - assert not hasattr(message, "signal") - assert object.__getattribute__(message, "signal") == betterproto.PLACEHOLDER + assert message.signal is None assert betterproto.which_one_of(message, "action") == ("move", Move(x=2, y=3)) diff --git a/tests/inputs/proto3_field_presence_oneof/test_proto3_field_presence_oneof.py b/tests/inputs/proto3_field_presence_oneof/test_proto3_field_presence_oneof.py index d5f69d01..6008e6a4 100644 --- a/tests/inputs/proto3_field_presence_oneof/test_proto3_field_presence_oneof.py +++ b/tests/inputs/proto3_field_presence_oneof/test_proto3_field_presence_oneof.py @@ -18,7 +18,6 @@ def test_empty_nested(message: Test) -> None: 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 diff --git a/tests/oneof_pattern_matching.py b/tests/oneof_pattern_matching.py index d4f18aab..4eab565a 100644 --- a/tests/oneof_pattern_matching.py +++ b/tests/oneof_pattern_matching.py @@ -1,4 +1,5 @@ from dataclasses import dataclass +from typing import Optional import pytest @@ -12,15 +13,15 @@ class Sub(betterproto.Message): @dataclass class Foo(betterproto.Message): - bar: int = betterproto.int32_field(1, group="group1") - baz: str = betterproto.string_field(2, group="group1") - sub: Sub = betterproto.message_field(3, group="group2") - abc: str = betterproto.string_field(4, group="group2") + bar: Optional[int] = betterproto.int32_field(1, group="group1") + baz: Optional[str] = betterproto.string_field(2, group="group1") + sub: Optional[Sub] = betterproto.message_field(3, group="group2") + abc: Optional[str] = betterproto.string_field(4, group="group2") foo = Foo(baz="test1", abc="test2") match foo: - case Foo(bar=_): + case Foo(bar=int(_)): pytest.fail("Matched 'bar' instead of 'baz'") case Foo(baz=v): assert v == "test1" @@ -28,7 +29,7 @@ class Foo(betterproto.Message): pytest.fail("Matched neither 'bar' nor 'baz'") match foo: - case Foo(sub=_): + case Foo(sub=Sub(_)): pytest.fail("Matched 'sub' instead of 'abc'") case Foo(abc=v): assert v == "test2" @@ -40,7 +41,7 @@ class Foo(betterproto.Message): match foo: case Foo(sub=Sub(val=v)): assert v == 1 - case Foo(abc=v): + case Foo(abc=str(v)): pytest.fail("Matched 'abc' instead of 'sub'") case _: pytest.fail("Matched neither 'sub' nor 'abc'") diff --git a/tests/test_features.py b/tests/test_features.py index 8ca44c67..323344aa 100644 --- a/tests/test_features.py +++ b/tests/test_features.py @@ -25,55 +25,6 @@ import betterproto -def test_has_field(): - @dataclass - class Bar(betterproto.Message): - baz: int = betterproto.int32_field(1) - - @dataclass - class Foo(betterproto.Message): - bar: Bar = betterproto.message_field(1) - - # Unset by default - foo = Foo() - assert betterproto.serialized_on_wire(foo.bar) is False - - # Serialized after setting something - foo.bar.baz = 1 - assert betterproto.serialized_on_wire(foo.bar) is True - - # Still has it after setting the default value - foo.bar.baz = 0 - assert betterproto.serialized_on_wire(foo.bar) is True - - # Manual override (don't do this) - foo.bar._serialized_on_wire = False - assert betterproto.serialized_on_wire(foo.bar) is False - - # Can manually set it but defaults to false - foo.bar = Bar() - assert betterproto.serialized_on_wire(foo.bar) is False - - @dataclass - class WithCollections(betterproto.Message): - test_list: List[str] = betterproto.string_field(1) - test_map: Dict[str, str] = betterproto.map_field( - 2, betterproto.TYPE_STRING, betterproto.TYPE_STRING - ) - - # Is always set from parse, even if all collections are empty - with_collections_empty = WithCollections().parse(bytes(WithCollections())) - assert betterproto.serialized_on_wire(with_collections_empty) == True - with_collections_list = WithCollections().parse( - bytes(WithCollections(test_list=["a", "b", "c"])) - ) - assert betterproto.serialized_on_wire(with_collections_list) == True - with_collections_map = WithCollections().parse( - bytes(WithCollections(test_map={"a": "b", "c": "d"})) - ) - assert betterproto.serialized_on_wire(with_collections_map) == True - - def test_class_init(): @dataclass class Bar(betterproto.Message): @@ -97,7 +48,9 @@ class TestEnum(betterproto.Enum): @dataclass class Foo(betterproto.Message): - bar: TestEnum = betterproto.enum_field(1) + bar: TestEnum = betterproto.enum_field( + 1, enum_default_value=lambda: TestEnum.try_value(0) + ) # JSON strings are supported, but ints should still be supported too. foo = Foo().from_dict({"bar": 1}) @@ -155,18 +108,15 @@ class Foo(betterproto.Message): foo.baz = "test" # Other oneof fields should now be unset - assert not hasattr(foo, "bar") - assert object.__getattribute__(foo, "bar") == betterproto.PLACEHOLDER + assert foo.bar is None assert betterproto.which_one_of(foo, "group1")[0] == "baz" foo.sub = Sub(val=1) - assert betterproto.serialized_on_wire(foo.sub) foo.abc = "test" # Group 1 shouldn't be touched, group 2 should have reset - assert not hasattr(foo, "sub") - assert object.__getattribute__(foo, "sub") == betterproto.PLACEHOLDER + assert foo.sub is None assert betterproto.which_one_of(foo, "group2")[0] == "abc" # Zero value should always serialize for one-of @@ -181,16 +131,6 @@ class Foo(betterproto.Message): assert betterproto.which_one_of(foo2, "group2")[0] == "" -@pytest.mark.skipif( - sys.version_info < (3, 10), - reason="pattern matching is only supported in python3.10+", -) -def test_oneof_pattern_matching(): - from .oneof_pattern_matching import test_oneof_pattern_matching - - test_oneof_pattern_matching() - - def test_json_casing(): @dataclass class CasingTest(betterproto.Message): @@ -346,28 +286,7 @@ class TestMessage(betterproto.Message): some_bool: bool = betterproto.bool_field(4) # Empty dict - test = TestMessage().from_dict({}) - - assert test.to_dict(include_default_values=True) == { - "someInt": 0, - "someDouble": 0.0, - "someStr": "", - "someBool": False, - } - - test = TestMessage().from_pydict({}) - - assert test.to_pydict(include_default_values=True) == { - "someInt": 0, - "someDouble": 0.0, - "someStr": "", - "someBool": False, - } - - # All default values - test = TestMessage().from_dict( - {"someInt": 0, "someDouble": 0.0, "someStr": "", "someBool": False} - ) + test = TestMessage() assert test.to_dict(include_default_values=True) == { "someInt": 0, @@ -376,10 +295,6 @@ class TestMessage(betterproto.Message): "someBool": False, } - test = TestMessage().from_pydict( - {"someInt": 0, "someDouble": 0.0, "someStr": "", "someBool": False} - ) - assert test.to_pydict(include_default_values=True) == { "someInt": 0, "someDouble": 0.0, @@ -456,14 +371,14 @@ class TestChildMessage(betterproto.Message): class TestParentMessage(betterproto.Message): some_int: int = betterproto.int32_field(1) some_double: float = betterproto.double_field(2) - some_message: TestChildMessage = betterproto.message_field(3) + some_message: Optional[TestChildMessage] = betterproto.message_field(3) test = TestParentMessage().from_dict({"someInt": 0, "someDouble": 1.2}) assert test.to_dict(include_default_values=True) == { "someInt": 0, "someDouble": 1.2, - "someMessage": {"someOtherInt": 0}, + "someMessage": None, } test = TestParentMessage().from_pydict({"someInt": 0, "someDouble": 1.2}) @@ -471,7 +386,7 @@ class TestParentMessage(betterproto.Message): assert test.to_pydict(include_default_values=True) == { "someInt": 0, "someDouble": 1.2, - "someMessage": {"someOtherInt": 0}, + "someMessage": None, } @@ -639,7 +554,7 @@ class Envelope(betterproto.Message): def test_iso_datetime_list(): @dataclass class Envelope(betterproto.Message): - timestamps: List[datetime] = betterproto.message_field(1) + timestamps: List[datetime] = betterproto.message_field(1, repeated=True) msg = Envelope() diff --git a/tests/test_inputs.py b/tests/test_inputs.py index 919bbc8c..f114a6ab 100644 --- a/tests/test_inputs.py +++ b/tests/test_inputs.py @@ -184,7 +184,7 @@ def test_message_json(test_data: TestData) -> None: message: betterproto.Message = plugin_module.Test() message.from_json(sample.json) - message_json = message.to_json(0) + message_json = message.to_json(indent=0) assert dict_replace_nans(json.loads(message_json)) == dict_replace_nans( json.loads(sample.json) diff --git a/tests/test_oneof_pattern_matching.py b/tests/test_oneof_pattern_matching.py new file mode 100644 index 00000000..186cfa3d --- /dev/null +++ b/tests/test_oneof_pattern_matching.py @@ -0,0 +1,13 @@ +import sys + +import pytest + + +@pytest.mark.skipif( + sys.version_info < (3, 10), + reason="pattern matching is only supported in python3.10+", +) +def test_oneof_pattern_matching(): + from tests.oneof_pattern_matching import test_oneof_pattern_matching + + test_oneof_pattern_matching() diff --git a/tests/test_pickling.py b/tests/test_pickling.py index 5650f750..d4d80946 100644 --- a/tests/test_pickling.py +++ b/tests/test_pickling.py @@ -101,21 +101,6 @@ def test_pickling_complex_message(): ) -def test_recursive_message(): - from tests.output_betterproto.recursivemessage import Test as RecursiveMessage - - msg = RecursiveMessage() - msg = unpickled(msg) - - assert msg.child == RecursiveMessage() - - # Lazily-created zero-value children must not affect equality. - assert msg == RecursiveMessage() - - # Lazily-created zero-value children must not affect serialization. - assert bytes(msg) == b"" - - def test_recursive_message_defaults(): from tests.output_betterproto.recursivemessage import ( Intermediate, @@ -132,23 +117,19 @@ def test_recursive_message_defaults(): assert msg != RecursiveMessage( name="bob", intermediate=Intermediate(42), child=RecursiveMessage(name="jude") ) - msg.child.child.name = "jude" + msg.child = RecursiveMessage(child=RecursiveMessage(name="jude")) assert msg == RecursiveMessage( name="bob", intermediate=Intermediate(42), child=RecursiveMessage(child=RecursiveMessage(name="jude")), ) - # lazily initialization recurses as needed - assert msg.child.child.child.child.child.child.child == RecursiveMessage() - assert msg.intermediate.child.intermediate == Intermediate() - @dataclass class PickledMessage(betterproto.Message): foo: bool = betterproto.bool_field(1) bar: int = betterproto.int32_field(2) - baz: List[str] = betterproto.string_field(3) + baz: List[str] = betterproto.string_field(3, repeated=True) def test_copyability():