diff --git a/.github/.OwlBot.lock.yaml b/.github/.OwlBot.lock.yaml index 773c1dfd..81f87c56 100644 --- a/.github/.OwlBot.lock.yaml +++ b/.github/.OwlBot.lock.yaml @@ -1,4 +1,4 @@ -# Copyright 2023 Google LLC +# Copyright 2024 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -13,5 +13,5 @@ # limitations under the License. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:2f155882785883336b4468d5218db737bb1d10c9cea7cb62219ad16fe248c03c -# created: 2023-11-29T14:54:29.548172703Z + digest: sha256:5a4c19d17e597b92d786e569be101e636c9c2817731f80a5adec56b2aa8fe070 +# created: 2024-04-12T11:35:58.922854369Z diff --git a/.github/sync-repo-settings.yaml b/.github/sync-repo-settings.yaml index f1068589..d85818a5 100644 --- a/.github/sync-repo-settings.yaml +++ b/.github/sync-repo-settings.yaml @@ -7,24 +7,24 @@ branchProtectionRules: requiredStatusCheckContexts: - 'style-check' - 'docs' - - 'unit (3.6)' - - 'unit (3.6, cpp)' - - 'unit (3.7)' - 'unit (3.7, cpp)' + - 'unit (3.7, python)' - 'unit (3.7, upb)' - - 'unit (3.8)' - 'unit (3.8, cpp)' + - 'unit (3.8, python)' - 'unit (3.8, upb)' - - 'unit (3.9)' - 'unit (3.9, cpp)' + - 'unit (3.9, python)' - 'unit (3.9, upb)' - - 'unit (3.10)' - 'unit (3.10, cpp)' + - 'unit (3.10, python)' - 'unit (3.10, upb)' - - 'unit (3.11)' + - 'unit (3.11, python)' - 'unit (3.11, upb)' - - 'unit (3.12)' + - 'unit (3.12, python)' - 'unit (3.12, upb)' + - 'prerelease (3.12, python)' + - 'prerelease (3.12, upb)' - cover - OwlBot Post Processor - 'cla/google' diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e28374fe..edbc2141 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -19,7 +19,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Set up Python 3.8 - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: 3.8 - name: Install black @@ -31,7 +31,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Set up Python 3.9 - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.9" - name: Install nox. @@ -39,22 +39,30 @@ jobs: - name: Build the documentation. run: nox -s docs unit: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 strategy: matrix: - python: ['3.6', '3.7', '3.8', '3.9', '3.10', '3.11', '3.12'] - variant: ['', 'cpp', 'upb'] + python: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12', '3.13'] + variant: ['cpp', 'python', 'upb'] + # TODO(https://github.com/googleapis/proto-plus-python/issues/389): + # Remove the 'cpp' implementation once support for Protobuf 3.x is dropped. + # The 'cpp' implementation requires Protobuf == 3.x however version 3.x + # does not support Python 3.11 and newer. The 'cpp' implementation + # must be excluded from the test matrix for these runtimes. exclude: - variant: "cpp" python: 3.11 - variant: "cpp" python: 3.12 + - variant: "cpp" + python: 3.13 steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python }} + allow-prereleases: true - name: Install nox run: | pip install nox @@ -68,12 +76,38 @@ jobs: env: COVERAGE_FILE: .coverage-${{ matrix.variant }}-${{ env.PYTHON_VERSION_TRIMMED }} run: | - nox -s unit${{ matrix.variant }}-${{ env.PYTHON_VERSION_TRIMMED }} + nox -s "unit-${{ env.PYTHON_VERSION_TRIMMED }}(implementation='${{ matrix.variant }}')" - name: Upload coverage results - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: coverage-artifacts + name: coverage-artifact-${{ matrix.variant }}-${{ env.PYTHON_VERSION_TRIMMED }} path: .coverage-${{ matrix.variant }}-${{ env.PYTHON_VERSION_TRIMMED }} + prerelease: + runs-on: ubuntu-22.04 + strategy: + matrix: + python: ['3.12'] + variant: ['python', 'upb'] + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python }} + allow-prereleases: true + - name: Install nox + run: | + pip install nox + - name: Run unit tests + env: + COVERAGE_FILE: .coverage-prerelease-${{ matrix.variant }} + run: | + nox -s "prerelease_deps(implementation='${{ matrix.variant }}')" + - name: Upload coverage results + uses: actions/upload-artifact@v4 + with: + name: coverage-artifact-prerelease-${{ matrix.variant }} + path: .coverage-prerelease-${{ matrix.variant }} cover: runs-on: ubuntu-latest needs: @@ -82,7 +116,7 @@ jobs: - name: Checkout uses: actions/checkout@v4 - name: Setup Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.10" - name: Install coverage @@ -90,11 +124,11 @@ jobs: python -m pip install --upgrade setuptools pip wheel python -m pip install coverage - name: Download coverage results - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: - name: coverage-artifacts path: .coverage-results/ - name: Report coverage results run: | - coverage combine .coverage-results/.coverage* + find .coverage-results -type f -name '*.zip' -exec unzip {} \; + coverage combine .coverage-results/**/.coverage* coverage report --show-missing --fail-under=100 diff --git a/.kokoro/docker/docs/Dockerfile b/.kokoro/docker/docs/Dockerfile index 8e39a2cc..bdaf39fe 100644 --- a/.kokoro/docker/docs/Dockerfile +++ b/.kokoro/docker/docs/Dockerfile @@ -80,4 +80,8 @@ RUN wget -O /tmp/get-pip.py 'https://bootstrap.pypa.io/get-pip.py' \ # Test pip RUN python3 -m pip +# Install build requirements +COPY requirements.txt /requirements.txt +RUN python3 -m pip install --require-hashes -r requirements.txt + CMD ["python3.8"] diff --git a/.kokoro/docker/docs/requirements.in b/.kokoro/docker/docs/requirements.in new file mode 100644 index 00000000..816817c6 --- /dev/null +++ b/.kokoro/docker/docs/requirements.in @@ -0,0 +1 @@ +nox diff --git a/.kokoro/docker/docs/requirements.txt b/.kokoro/docker/docs/requirements.txt new file mode 100644 index 00000000..0e5d70f2 --- /dev/null +++ b/.kokoro/docker/docs/requirements.txt @@ -0,0 +1,38 @@ +# +# This file is autogenerated by pip-compile with Python 3.9 +# by the following command: +# +# pip-compile --allow-unsafe --generate-hashes requirements.in +# +argcomplete==3.2.3 \ + --hash=sha256:bf7900329262e481be5a15f56f19736b376df6f82ed27576fa893652c5de6c23 \ + --hash=sha256:c12355e0494c76a2a7b73e3a59b09024ca0ba1e279fb9ed6c1b82d5b74b6a70c + # via nox +colorlog==6.8.2 \ + --hash=sha256:3e3e079a41feb5a1b64f978b5ea4f46040a94f11f0e8bbb8261e3dbbeca64d44 \ + --hash=sha256:4dcbb62368e2800cb3c5abd348da7e53f6c362dda502ec27c560b2e58a66bd33 + # via nox +distlib==0.3.8 \ + --hash=sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784 \ + --hash=sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64 + # via virtualenv +filelock==3.13.1 \ + --hash=sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e \ + --hash=sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c + # via virtualenv +nox==2024.3.2 \ + --hash=sha256:e53514173ac0b98dd47585096a55572fe504fecede58ced708979184d05440be \ + --hash=sha256:f521ae08a15adbf5e11f16cb34e8d0e6ea521e0b92868f684e91677deb974553 + # via -r requirements.in +packaging==24.0 \ + --hash=sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5 \ + --hash=sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9 + # via nox +platformdirs==4.2.0 \ + --hash=sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068 \ + --hash=sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768 + # via virtualenv +virtualenv==20.25.1 \ + --hash=sha256:961c026ac520bac5f69acb8ea063e8a4f071bcc9457b9c1f28f6b085c511583a \ + --hash=sha256:e08e13ecdca7a0bd53798f356d5831434afa5b07b93f0abdf0797b7a06ffe197 + # via nox diff --git a/.kokoro/requirements.in b/.kokoro/requirements.in index ec867d9f..fff4d9ce 100644 --- a/.kokoro/requirements.in +++ b/.kokoro/requirements.in @@ -1,5 +1,5 @@ gcp-docuploader -gcp-releasetool>=1.10.5 # required for compatibility with cryptography>=39.x +gcp-releasetool>=2 # required for compatibility with cryptography>=42.x importlib-metadata typing-extensions twine @@ -8,3 +8,4 @@ setuptools nox>=2022.11.21 # required to remove dependency on py charset-normalizer<3 click<8.1.0 +cryptography>=42.0.5 diff --git a/.kokoro/requirements.txt b/.kokoro/requirements.txt index e5c1ffca..51f92b8e 100644 --- a/.kokoro/requirements.txt +++ b/.kokoro/requirements.txt @@ -93,31 +93,41 @@ colorlog==6.7.0 \ # via # gcp-docuploader # nox -cryptography==41.0.6 \ - --hash=sha256:068bc551698c234742c40049e46840843f3d98ad7ce265fd2bd4ec0d11306596 \ - --hash=sha256:0f27acb55a4e77b9be8d550d762b0513ef3fc658cd3eb15110ebbcbd626db12c \ - --hash=sha256:2132d5865eea673fe6712c2ed5fb4fa49dba10768bb4cc798345748380ee3660 \ - --hash=sha256:3288acccef021e3c3c10d58933f44e8602cf04dba96d9796d70d537bb2f4bbc4 \ - --hash=sha256:35f3f288e83c3f6f10752467c48919a7a94b7d88cc00b0668372a0d2ad4f8ead \ - --hash=sha256:398ae1fc711b5eb78e977daa3cbf47cec20f2c08c5da129b7a296055fbb22aed \ - --hash=sha256:422e3e31d63743855e43e5a6fcc8b4acab860f560f9321b0ee6269cc7ed70cc3 \ - --hash=sha256:48783b7e2bef51224020efb61b42704207dde583d7e371ef8fc2a5fb6c0aabc7 \ - --hash=sha256:4d03186af98b1c01a4eda396b137f29e4e3fb0173e30f885e27acec8823c1b09 \ - --hash=sha256:5daeb18e7886a358064a68dbcaf441c036cbdb7da52ae744e7b9207b04d3908c \ - --hash=sha256:60e746b11b937911dc70d164060d28d273e31853bb359e2b2033c9e93e6f3c43 \ - --hash=sha256:742ae5e9a2310e9dade7932f9576606836ed174da3c7d26bc3d3ab4bd49b9f65 \ - --hash=sha256:7e00fb556bda398b99b0da289ce7053639d33b572847181d6483ad89835115f6 \ - --hash=sha256:85abd057699b98fce40b41737afb234fef05c67e116f6f3650782c10862c43da \ - --hash=sha256:8efb2af8d4ba9dbc9c9dd8f04d19a7abb5b49eab1f3694e7b5a16a5fc2856f5c \ - --hash=sha256:ae236bb8760c1e55b7a39b6d4d32d2279bc6c7c8500b7d5a13b6fb9fc97be35b \ - --hash=sha256:afda76d84b053923c27ede5edc1ed7d53e3c9f475ebaf63c68e69f1403c405a8 \ - --hash=sha256:b27a7fd4229abef715e064269d98a7e2909ebf92eb6912a9603c7e14c181928c \ - --hash=sha256:b648fe2a45e426aaee684ddca2632f62ec4613ef362f4d681a9a6283d10e079d \ - --hash=sha256:c5a550dc7a3b50b116323e3d376241829fd326ac47bc195e04eb33a8170902a9 \ - --hash=sha256:da46e2b5df770070412c46f87bac0849b8d685c5f2679771de277a422c7d0b86 \ - --hash=sha256:f39812f70fc5c71a15aa3c97b2bbe213c3f2a460b79bd21c40d033bb34a9bf36 \ - --hash=sha256:ff369dd19e8fe0528b02e8df9f2aeb2479f89b1270d90f96a63500afe9af5cae +cryptography==42.0.5 \ + --hash=sha256:0270572b8bd2c833c3981724b8ee9747b3ec96f699a9665470018594301439ee \ + --hash=sha256:111a0d8553afcf8eb02a4fea6ca4f59d48ddb34497aa8706a6cf536f1a5ec576 \ + --hash=sha256:16a48c23a62a2f4a285699dba2e4ff2d1cff3115b9df052cdd976a18856d8e3d \ + --hash=sha256:1b95b98b0d2af784078fa69f637135e3c317091b615cd0905f8b8a087e86fa30 \ + --hash=sha256:1f71c10d1e88467126f0efd484bd44bca5e14c664ec2ede64c32f20875c0d413 \ + --hash=sha256:2424ff4c4ac7f6b8177b53c17ed5d8fa74ae5955656867f5a8affaca36a27abb \ + --hash=sha256:2bce03af1ce5a5567ab89bd90d11e7bbdff56b8af3acbbec1faded8f44cb06da \ + --hash=sha256:329906dcc7b20ff3cad13c069a78124ed8247adcac44b10bea1130e36caae0b4 \ + --hash=sha256:37dd623507659e08be98eec89323469e8c7b4c1407c85112634ae3dbdb926fdd \ + --hash=sha256:3eaafe47ec0d0ffcc9349e1708be2aaea4c6dd4978d76bf6eb0cb2c13636c6fc \ + --hash=sha256:5e6275c09d2badf57aea3afa80d975444f4be8d3bc58f7f80d2a484c6f9485c8 \ + --hash=sha256:6fe07eec95dfd477eb9530aef5bead34fec819b3aaf6c5bd6d20565da607bfe1 \ + --hash=sha256:7367d7b2eca6513681127ebad53b2582911d1736dc2ffc19f2c3ae49997496bc \ + --hash=sha256:7cde5f38e614f55e28d831754e8a3bacf9ace5d1566235e39d91b35502d6936e \ + --hash=sha256:9481ffe3cf013b71b2428b905c4f7a9a4f76ec03065b05ff499bb5682a8d9ad8 \ + --hash=sha256:98d8dc6d012b82287f2c3d26ce1d2dd130ec200c8679b6213b3c73c08b2b7940 \ + --hash=sha256:a011a644f6d7d03736214d38832e030d8268bcff4a41f728e6030325fea3e400 \ + --hash=sha256:a2913c5375154b6ef2e91c10b5720ea6e21007412f6437504ffea2109b5a33d7 \ + --hash=sha256:a30596bae9403a342c978fb47d9b0ee277699fa53bbafad14706af51fe543d16 \ + --hash=sha256:b03c2ae5d2f0fc05f9a2c0c997e1bc18c8229f392234e8a0194f202169ccd278 \ + --hash=sha256:b6cd2203306b63e41acdf39aa93b86fb566049aeb6dc489b70e34bcd07adca74 \ + --hash=sha256:b7ffe927ee6531c78f81aa17e684e2ff617daeba7f189f911065b2ea2d526dec \ + --hash=sha256:b8cac287fafc4ad485b8a9b67d0ee80c66bf3574f655d3b97ef2e1082360faf1 \ + --hash=sha256:ba334e6e4b1d92442b75ddacc615c5476d4ad55cc29b15d590cc6b86efa487e2 \ + --hash=sha256:ba3e4a42397c25b7ff88cdec6e2a16c2be18720f317506ee25210f6d31925f9c \ + --hash=sha256:c41fb5e6a5fe9ebcd58ca3abfeb51dffb5d83d6775405305bfa8715b76521922 \ + --hash=sha256:cd2030f6650c089aeb304cf093f3244d34745ce0cfcc39f20c6fbfe030102e2a \ + --hash=sha256:cd65d75953847815962c84a4654a84850b2bb4aed3f26fadcc1c13892e1e29f6 \ + --hash=sha256:e4985a790f921508f36f81831817cbc03b102d643b5fcb81cd33df3fa291a1a1 \ + --hash=sha256:e807b3188f9eb0eaa7bbb579b462c5ace579f1cedb28107ce8b48a9f7ad3679e \ + --hash=sha256:f12764b8fffc7a123f641d7d049d382b73f96a34117e0b637b80643169cec8ac \ + --hash=sha256:f8837fe1d6ac4a8052a9a8ddab256bc006242696f03368a4009be7ee3075cdb7 # via + # -r requirements.in # gcp-releasetool # secretstorage distlib==0.3.7 \ @@ -136,9 +146,9 @@ gcp-docuploader==0.6.5 \ --hash=sha256:30221d4ac3e5a2b9c69aa52fdbef68cc3f27d0e6d0d90e220fc024584b8d2318 \ --hash=sha256:b7458ef93f605b9d46a4bf3a8dc1755dad1f31d030c8679edf304e343b347eea # via -r requirements.in -gcp-releasetool==1.16.0 \ - --hash=sha256:27bf19d2e87aaa884096ff941aa3c592c482be3d6a2bfe6f06afafa6af2353e3 \ - --hash=sha256:a316b197a543fd036209d0caba7a8eb4d236d8e65381c80cbc6d7efaa7606d63 +gcp-releasetool==2.0.0 \ + --hash=sha256:3d73480b50ba243f22d7c7ec08b115a30e1c7817c4899781840c26f9c55b8277 \ + --hash=sha256:7aa9fd935ec61e581eb8458ad00823786d91756c25e492f372b2b30962f3c28f # via -r requirements.in google-api-core==2.12.0 \ --hash=sha256:c22e01b1e3c4dcd90998494879612c38d0a3411d1f7b679eb89e2abe3ce1f553 \ @@ -242,9 +252,9 @@ googleapis-common-protos==1.61.0 \ --hash=sha256:22f1915393bb3245343f6efe87f6fe868532efc12aa26b391b15132e1279f1c0 \ --hash=sha256:8a64866a97f6304a7179873a465d6eee97b7a24ec6cfd78e0f575e96b821240b # via google-api-core -idna==3.4 \ - --hash=sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4 \ - --hash=sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2 +idna==3.7 \ + --hash=sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc \ + --hash=sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0 # via requests importlib-metadata==6.8.0 \ --hash=sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb \ @@ -263,9 +273,9 @@ jeepney==0.8.0 \ # via # keyring # secretstorage -jinja2==3.1.2 \ - --hash=sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852 \ - --hash=sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61 +jinja2==3.1.3 \ + --hash=sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa \ + --hash=sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90 # via gcp-releasetool keyring==24.2.0 \ --hash=sha256:4901caaf597bfd3bbd78c9a0c7c4c29fcd8310dab2cffefe749e916b6527acd6 \ @@ -383,29 +393,18 @@ platformdirs==3.11.0 \ --hash=sha256:cf8ee52a3afdb965072dcc652433e0c7e3e40cf5ea1477cd4b3b1d2eb75495b3 \ --hash=sha256:e9d171d00af68be50e9202731309c4e658fd8bc76f55c11c7dd760d023bda68e # via virtualenv -protobuf==3.20.3 \ - --hash=sha256:03038ac1cfbc41aa21f6afcbcd357281d7521b4157926f30ebecc8d4ea59dcb7 \ - --hash=sha256:28545383d61f55b57cf4df63eebd9827754fd2dc25f80c5253f9184235db242c \ - --hash=sha256:2e3427429c9cffebf259491be0af70189607f365c2f41c7c3764af6f337105f2 \ - --hash=sha256:398a9e0c3eaceb34ec1aee71894ca3299605fa8e761544934378bbc6c97de23b \ - --hash=sha256:44246bab5dd4b7fbd3c0c80b6f16686808fab0e4aca819ade6e8d294a29c7050 \ - --hash=sha256:447d43819997825d4e71bf5769d869b968ce96848b6479397e29fc24c4a5dfe9 \ - --hash=sha256:67a3598f0a2dcbc58d02dd1928544e7d88f764b47d4a286202913f0b2801c2e7 \ - --hash=sha256:74480f79a023f90dc6e18febbf7b8bac7508420f2006fabd512013c0c238f454 \ - --hash=sha256:819559cafa1a373b7096a482b504ae8a857c89593cf3a25af743ac9ecbd23480 \ - --hash=sha256:899dc660cd599d7352d6f10d83c95df430a38b410c1b66b407a6b29265d66469 \ - --hash=sha256:8c0c984a1b8fef4086329ff8dd19ac77576b384079247c770f29cc8ce3afa06c \ - --hash=sha256:9aae4406ea63d825636cc11ffb34ad3379335803216ee3a856787bcf5ccc751e \ - --hash=sha256:a7ca6d488aa8ff7f329d4c545b2dbad8ac31464f1d8b1c87ad1346717731e4db \ - --hash=sha256:b6cc7ba72a8850621bfec987cb72623e703b7fe2b9127a161ce61e61558ad905 \ - --hash=sha256:bf01b5720be110540be4286e791db73f84a2b721072a3711efff6c324cdf074b \ - --hash=sha256:c02ce36ec760252242a33967d51c289fd0e1c0e6e5cc9397e2279177716add86 \ - --hash=sha256:d9e4432ff660d67d775c66ac42a67cf2453c27cb4d738fc22cb53b5d84c135d4 \ - --hash=sha256:daa564862dd0d39c00f8086f88700fdbe8bc717e993a21e90711acfed02f2402 \ - --hash=sha256:de78575669dddf6099a8a0f46a27e82a1783c557ccc38ee620ed8cc96d3be7d7 \ - --hash=sha256:e64857f395505ebf3d2569935506ae0dfc4a15cb80dc25261176c784662cdcc4 \ - --hash=sha256:f4bd856d702e5b0d96a00ec6b307b0f51c1982c2bf9c0052cf9019e9a544ba99 \ - --hash=sha256:f4c42102bc82a51108e449cbb32b19b180022941c727bac0cfd50170341f16ee +protobuf==4.25.3 \ + --hash=sha256:19b270aeaa0099f16d3ca02628546b8baefe2955bbe23224aaf856134eccf1e4 \ + --hash=sha256:209ba4cc916bab46f64e56b85b090607a676f66b473e6b762e6f1d9d591eb2e8 \ + --hash=sha256:25b5d0b42fd000320bd7830b349e3b696435f3b329810427a6bcce6a5492cc5c \ + --hash=sha256:7c8daa26095f82482307bc717364e7c13f4f1c99659be82890dcfc215194554d \ + --hash=sha256:c053062984e61144385022e53678fbded7aea14ebb3e0305ae3592fb219ccfa4 \ + --hash=sha256:d4198877797a83cbfe9bffa3803602bbe1625dc30d8a097365dbc762e5790faa \ + --hash=sha256:e3c97a1555fd6388f857770ff8b9703083de6bf1f9274a002a332d65fbb56c8c \ + --hash=sha256:e7cb0ae90dd83727f0c0718634ed56837bfeeee29a5f82a7514c03ee1364c019 \ + --hash=sha256:f0700d54bcf45424477e46a9f0944155b46fb0639d69728739c0e47bab83f2b9 \ + --hash=sha256:f1279ab38ecbfae7e456a108c5c0681e4956d5b1090027c1de0f934dfdb4b35c \ + --hash=sha256:f4f118245c4a087776e0a8408be33cf09f6c547442c00395fbfb116fac2f8ac2 # via # gcp-docuploader # gcp-releasetool @@ -509,7 +508,7 @@ zipp==3.17.0 \ # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: -setuptools==68.2.2 \ - --hash=sha256:4ac1475276d2f1c48684874089fefcd83bd7162ddaafb81fac866ba0db282a87 \ - --hash=sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a +setuptools==69.2.0 \ + --hash=sha256:0ff4183f8f42cd8fa3acea16c45205521a4ef28f73c6391d8a25e92893134f2e \ + --hash=sha256:c21c49fb1042386df081cb5d86759792ab89efca84cf114889191cd09aacc80c # via -r requirements.in diff --git a/.readthedocs.yml b/.readthedocs.yml index ebea21ff..309b5e76 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -3,4 +3,4 @@ build: image: latest python: pip_install: true - version: 3.6 + version: 3.9 diff --git a/.repo-metadata.json b/.repo-metadata.json index 477f07c7..cbe8a78e 100644 --- a/.repo-metadata.json +++ b/.repo-metadata.json @@ -6,7 +6,7 @@ "language": "python", "library_type": "CORE", "repo": "googleapis/proto-plus-python", - "distribution_name": "proto-plus-python", + "distribution_name": "proto-plus", "default_version": "", "codeowner_team": "" } diff --git a/CHANGELOG.md b/CHANGELOG.md index a22bc063..2171e424 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,25 @@ # Changelog +## [1.24.0](https://github.com/googleapis/proto-plus-python/compare/v1.23.0...v1.24.0) (2024-06-11) + + +### Features + +* Add `always_print_fields_with_no_presence` fields to `to_json` and `to_dict` ([0f89372](https://github.com/googleapis/proto-plus-python/commit/0f893724cabe513a5a9f9c8428dbd31f1b4f1d52)) + + +### Bug Fixes + +* Add compatibility with protobuf==5.x ([0f89372](https://github.com/googleapis/proto-plus-python/commit/0f893724cabe513a5a9f9c8428dbd31f1b4f1d52)) +* AttributeError module 'google._upb._message' has no attribute 'MessageMapContainer' ([0f89372](https://github.com/googleapis/proto-plus-python/commit/0f893724cabe513a5a9f9c8428dbd31f1b4f1d52)) +* **deps:** Allow protobuf 5.x ([#457](https://github.com/googleapis/proto-plus-python/issues/457)) ([62d74e3](https://github.com/googleapis/proto-plus-python/commit/62d74e3275476b82fed48f2119fc761fe2371292)) +* Drop python 3.6 ([#456](https://github.com/googleapis/proto-plus-python/issues/456)) ([5a7666c](https://github.com/googleapis/proto-plus-python/commit/5a7666c15002aee0fab44a9aa6d3279aab3f1f69)) + + +### Documentation + +* Deprecate field `including_default_value_fields` in `to_json` and `to_dict` ([0f89372](https://github.com/googleapis/proto-plus-python/commit/0f893724cabe513a5a9f9c8428dbd31f1b4f1d52)) + ## [1.23.0](https://github.com/googleapis/proto-plus-python/compare/v1.22.3...v1.23.0) (2023-12-01) diff --git a/noxfile.py b/noxfile.py index c6a850c2..19c80c95 100644 --- a/noxfile.py +++ b/noxfile.py @@ -13,23 +13,22 @@ # limitations under the License. from __future__ import absolute_import -import os -import pathlib import nox +import pathlib CURRENT_DIRECTORY = pathlib.Path(__file__).parent.absolute() PYTHON_VERSIONS = [ - "3.6", "3.7", "3.8", "3.9", "3.10", "3.11", "3.12", + "3.13", ] # Error if a python version is missing @@ -37,26 +36,30 @@ @nox.session(python=PYTHON_VERSIONS) -def unit(session, proto="python"): +@nox.parametrize("implementation", ["cpp", "upb", "python"]) +def unit(session, implementation): """Run the unit test suite.""" constraints_path = str( CURRENT_DIRECTORY / "testing" / f"constraints-{session.python}.txt" ) - session.env["PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION"] = proto + session.env["PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION"] = implementation session.install("coverage", "pytest", "pytest-cov", "pytz") session.install("-e", ".[testing]", "-c", constraints_path) - if proto == "cpp": # 4.20 does not have cpp. - session.install("protobuf==3.19.0") + # TODO(https://github.com/googleapis/proto-plus-python/issues/389): + # Remove the 'cpp' implementation once support for Protobuf 3.x is dropped. + # The 'cpp' implementation requires Protobuf<4. + if implementation == "cpp": + session.install("protobuf<4") # TODO(https://github.com/googleapis/proto-plus-python/issues/403): re-enable `-W=error` # The warnings-as-errors flag `-W=error` was removed in # https://github.com/googleapis/proto-plus-python/pull/400. # It should be re-added once issue - # https://github.com/protocolbuffers/protobuf/issues/12186 is fixed. + # https://github.com/protocolbuffers/protobuf/issues/15077 is fixed. session.run( - "py.test", + "pytest", "--quiet", *( session.posargs # Coverage info when running individual tests is annoying. @@ -71,24 +74,78 @@ def unit(session, proto="python"): ) -# Check if protobuf has released wheels for new python versions -# https://pypi.org/project/protobuf/#files -# This list will generally be shorter than 'unit' -@nox.session(python=["3.6", "3.7", "3.8", "3.9", "3.10"]) -def unitcpp(session): - return unit(session, proto="cpp") +# Only test upb and python implementation backends. +# As of protobuf 4.x, the "ccp" implementation is not available in the PyPI package as per +# https://github.com/protocolbuffers/protobuf/tree/main/python#implementation-backends +@nox.session(python=PYTHON_VERSIONS[-2]) +@nox.parametrize("implementation", ["python", "upb"]) +def prerelease_deps(session, implementation): + """Run the unit test suite against pre-release versions of dependencies.""" + session.env["PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION"] = implementation -@nox.session(python=PYTHON_VERSIONS) -def unitupb(session): - return unit(session, proto="upb") + # Install test environment dependencies + session.install("coverage", "pytest", "pytest-cov", "pytz") + + # Install the package without dependencies + session.install("-e", ".", "--no-deps") + + prerel_deps = [ + "google-api-core", + # dependency of google-api-core + "googleapis-common-protos", + ] + + for dep in prerel_deps: + session.install("--pre", "--no-deps", "--upgrade", dep) + + session.install("--pre", "--upgrade", "protobuf") + # Print out prerelease package versions + session.run( + "python", "-c", "import google.protobuf; print(google.protobuf.__version__)" + ) + session.run( + "python", "-c", "import google.api_core; print(google.api_core.__version__)" + ) + + # TODO(https://github.com/googleapis/proto-plus-python/issues/403): re-enable `-W=error` + # The warnings-as-errors flag `-W=error` was removed in + # https://github.com/googleapis/proto-plus-python/pull/400. + # It should be re-added once issue + # https://github.com/protocolbuffers/protobuf/issues/15077 is fixed. + session.run( + "pytest", + "--quiet", + *( + session.posargs # Coverage info when running individual tests is annoying. + or [ + "--cov=proto", + "--cov-config=.coveragerc", + "--cov-report=term", + "--cov-report=html", + "tests", + ] + ), + ) @nox.session(python="3.9") def docs(session): """Build the docs.""" - session.install("sphinx==4.2.0", "sphinx_rtd_theme") + session.install( + # We need to pin to specific versions of the `sphinxcontrib-*` packages + # which still support sphinx 4.x. + # See https://github.com/googleapis/sphinx-docfx-yaml/issues/344 + # and https://github.com/googleapis/sphinx-docfx-yaml/issues/345. + "sphinxcontrib-applehelp==1.0.4", + "sphinxcontrib-devhelp==1.0.2", + "sphinxcontrib-htmlhelp==2.0.1", + "sphinxcontrib-qthelp==1.0.3", + "sphinxcontrib-serializinghtml==1.1.5", + "sphinx==4.5.0", + "sphinx_rtd_theme", + ) session.install(".") # Build the docs! diff --git a/proto/marshal/compat.py b/proto/marshal/compat.py index e10999bb..dc77f3ac 100644 --- a/proto/marshal/compat.py +++ b/proto/marshal/compat.py @@ -19,6 +19,9 @@ # not be included. from google.protobuf.internal import containers +import google.protobuf + +PROTOBUF_VERSION = google.protobuf.__version__ # Import protobuf 4.xx first and fallback to earlier version # if not present. @@ -37,14 +40,28 @@ repeated_scalar_types = (containers.RepeatedScalarFieldContainer,) map_composite_types = (containers.MessageMap,) +# In `proto/marshal.py`, for compatibility with protobuf 5.x, +# we'll use `map_composite_type_names` to check whether +# the name of the class of a protobuf type is +# `MessageMapContainer`, and, if `True`, return a MapComposite. +# See https://github.com/protocolbuffers/protobuf/issues/16596 +map_composite_type_names = ("MessageMapContainer",) + if _message: repeated_composite_types += (_message.RepeatedCompositeContainer,) repeated_scalar_types += (_message.RepeatedScalarContainer,) - map_composite_types += (_message.MessageMapContainer,) + # In `proto/marshal.py`, for compatibility with protobuf 5.x, + # we'll use `map_composite_type_names` to check whether + # the name of the class of a protobuf type is + # `MessageMapContainer`, and, if `True`, return a MapComposite. + # See https://github.com/protocolbuffers/protobuf/issues/16596 + if PROTOBUF_VERSION[0:2] in ["3.", "4."]: + map_composite_types += (_message.MessageMapContainer,) __all__ = ( "repeated_composite_types", "repeated_scalar_types", "map_composite_types", + "map_composite_type_names", ) diff --git a/proto/marshal/marshal.py b/proto/marshal/marshal.py index e9fbbb9f..d278421a 100644 --- a/proto/marshal/marshal.py +++ b/proto/marshal/marshal.py @@ -188,7 +188,14 @@ def to_python(self, proto_type, value, *, absent: bool = None): return Repeated(value, marshal=self) # Same thing for maps of messages. - if value_type in compat.map_composite_types: + # See https://github.com/protocolbuffers/protobuf/issues/16596 + # We need to look up the name of the type in compat.map_composite_type_names + # as class `MessageMapContainer` is no longer exposed + # This is done to avoid taking a breaking change in proto-plus. + if ( + value_type in compat.map_composite_types + or value_type.__name__ in compat.map_composite_type_names + ): return MapComposite(value, marshal=self) return self.get_rule(proto_type=proto_type).to_python(value, absent=absent) diff --git a/proto/message.py b/proto/message.py index 7232d42f..2772fb42 100644 --- a/proto/message.py +++ b/proto/message.py @@ -16,8 +16,10 @@ import collections.abc import copy import re -from typing import List, Type +from typing import List, Optional, Type +import warnings +import google.protobuf from google.protobuf import descriptor_pb2 from google.protobuf import message from google.protobuf.json_format import MessageToDict, MessageToJson, Parse @@ -32,6 +34,8 @@ from proto.utils import has_upb +PROTOBUF_VERSION = google.protobuf.__version__ + _upb = has_upb() # Important to cache result here. @@ -369,16 +373,101 @@ def deserialize(cls, payload: bytes) -> "Message": """ return cls.wrap(cls.pb().FromString(payload)) + def _warn_if_including_default_value_fields_is_used_protobuf_5( + cls, including_default_value_fields: Optional[bool] + ) -> None: + """ + Warn Protobuf 5.x+ users that `including_default_value_fields` is deprecated if it is set. + + Args: + including_default_value_fields (Optional(bool)): The value of `including_default_value_fields` set by the user. + """ + if ( + PROTOBUF_VERSION[0] not in ("3", "4") + and including_default_value_fields is not None + ): + warnings.warn( + """The argument `including_default_value_fields` has been removed from + Protobuf 5.x. Please use `always_print_fields_with_no_presence` instead. + """, + DeprecationWarning, + ) + + def _raise_if_print_fields_values_are_set_and_differ( + cls, + always_print_fields_with_no_presence: Optional[bool], + including_default_value_fields: Optional[bool], + ) -> None: + """ + Raise Exception if both `always_print_fields_with_no_presence` and `including_default_value_fields` are set + and the values differ. + + Args: + always_print_fields_with_no_presence (Optional(bool)): The value of `always_print_fields_with_no_presence` set by the user. + including_default_value_fields (Optional(bool)): The value of `including_default_value_fields` set by the user. + Returns: + None + Raises: + ValueError: if both `always_print_fields_with_no_presence` and `including_default_value_fields` are set and + the values differ. + """ + if ( + always_print_fields_with_no_presence is not None + and including_default_value_fields is not None + and always_print_fields_with_no_presence != including_default_value_fields + ): + raise ValueError( + "Arguments `always_print_fields_with_no_presence` and `including_default_value_fields` must match" + ) + + def _normalize_print_fields_without_presence( + cls, + always_print_fields_with_no_presence: Optional[bool], + including_default_value_fields: Optional[bool], + ) -> bool: + """ + Return true if fields with no presence should be included in the results. + By default, fields with no presence will be included in the results + when both `always_print_fields_with_no_presence` and + `including_default_value_fields` are not set + + Args: + always_print_fields_with_no_presence (Optional(bool)): The value of `always_print_fields_with_no_presence` set by the user. + including_default_value_fields (Optional(bool)): The value of `including_default_value_fields` set by the user. + Returns: + None + Raises: + ValueError: if both `always_print_fields_with_no_presence` and `including_default_value_fields` are set and + the values differ. + """ + + cls._warn_if_including_default_value_fields_is_used_protobuf_5( + including_default_value_fields + ) + cls._raise_if_print_fields_values_are_set_and_differ( + always_print_fields_with_no_presence, including_default_value_fields + ) + # Default to True if neither `always_print_fields_with_no_presence` or `including_default_value_fields` is set + return ( + ( + always_print_fields_with_no_presence is None + and including_default_value_fields is None + ) + or always_print_fields_with_no_presence + or including_default_value_fields + ) + def to_json( cls, instance, *, use_integers_for_enums=True, - including_default_value_fields=True, + including_default_value_fields=None, preserving_proto_field_name=False, sort_keys=False, indent=2, float_precision=None, + always_print_fields_with_no_presence=None, ) -> str: """Given a message instance, serialize it to json @@ -388,6 +477,11 @@ def to_json( use_integers_for_enums (Optional(bool)): An option that determines whether enum values should be represented by strings (False) or integers (True). Default is True. + including_default_value_fields (Optional(bool)): Deprecated. Use argument + `always_print_fields_with_no_presence` instead. An option that + determines whether the default field values should be included in the results. + This value must match `always_print_fields_with_no_presence`, + if both arguments are explictly set. preserving_proto_field_name (Optional(bool)): An option that determines whether field name representations preserve proto case (snake_case) or use lowerCamelCase. Default is False. @@ -398,19 +492,46 @@ def to_json( Pass None for the most compact representation without newlines. float_precision (Optional(int)): If set, use this to specify float field valid digits. Default is None. + always_print_fields_with_no_presence (Optional(bool)): If True, fields without + presence (implicit presence scalars, repeated fields, and map fields) will + always be serialized. Any field that supports presence is not affected by + this option (including singular message fields and oneof fields). + This value must match `including_default_value_fields`, + if both arguments are explictly set. Returns: str: The json string representation of the protocol buffer. """ - return MessageToJson( - cls.pb(instance), - use_integers_for_enums=use_integers_for_enums, - including_default_value_fields=including_default_value_fields, - preserving_proto_field_name=preserving_proto_field_name, - sort_keys=sort_keys, - indent=indent, - float_precision=float_precision, + + print_fields = cls._normalize_print_fields_without_presence( + always_print_fields_with_no_presence, including_default_value_fields ) + if PROTOBUF_VERSION[0] in ("3", "4"): + return MessageToJson( + cls.pb(instance), + use_integers_for_enums=use_integers_for_enums, + including_default_value_fields=print_fields, + preserving_proto_field_name=preserving_proto_field_name, + sort_keys=sort_keys, + indent=indent, + float_precision=float_precision, + ) + else: + # The `including_default_value_fields` argument was removed from protobuf 5.x + # and replaced with `always_print_fields_with_no_presence` which very similar but has + # handles optional fields consistently by not affecting them. + # The old flag accidentally had inconsistent behavior between proto2 + # optional and proto3 optional fields. + return MessageToJson( + cls.pb(instance), + use_integers_for_enums=use_integers_for_enums, + always_print_fields_with_no_presence=print_fields, + preserving_proto_field_name=preserving_proto_field_name, + sort_keys=sort_keys, + indent=indent, + float_precision=float_precision, + ) + def from_json(cls, payload, *, ignore_unknown_fields=False) -> "Message": """Given a json string representing an instance, parse it into a message. @@ -434,39 +555,66 @@ def to_dict( *, use_integers_for_enums=True, preserving_proto_field_name=True, - including_default_value_fields=True, + including_default_value_fields=None, float_precision=None, + always_print_fields_with_no_presence=None, ) -> "Message": """Given a message instance, return its representation as a python dict. Args: instance: An instance of this message type, or something - compatible (accepted by the type's constructor). + compatible (accepted by the type's constructor). use_integers_for_enums (Optional(bool)): An option that determines whether enum values should be represented by strings (False) or integers (True). Default is True. preserving_proto_field_name (Optional(bool)): An option that determines whether field name representations preserve proto case (snake_case) or use lowerCamelCase. Default is True. - including_default_value_fields (Optional(bool)): An option that + including_default_value_fields (Optional(bool)): Deprecated. Use argument + `always_print_fields_with_no_presence` instead. An option that determines whether the default field values should be included in the results. - Default is True. + This value must match `always_print_fields_with_no_presence`, + if both arguments are explictly set. float_precision (Optional(int)): If set, use this to specify float field valid digits. Default is None. + always_print_fields_with_no_presence (Optional(bool)): If True, fields without + presence (implicit presence scalars, repeated fields, and map fields) will + always be serialized. Any field that supports presence is not affected by + this option (including singular message fields and oneof fields). This value + must match `including_default_value_fields`, if both arguments are explictly set. Returns: dict: A representation of the protocol buffer using pythonic data structures. Messages and map fields are represented as dicts, repeated fields are represented as lists. """ - return MessageToDict( - cls.pb(instance), - including_default_value_fields=including_default_value_fields, - preserving_proto_field_name=preserving_proto_field_name, - use_integers_for_enums=use_integers_for_enums, - float_precision=float_precision, + + print_fields = cls._normalize_print_fields_without_presence( + always_print_fields_with_no_presence, including_default_value_fields ) + if PROTOBUF_VERSION[0] in ("3", "4"): + return MessageToDict( + cls.pb(instance), + including_default_value_fields=print_fields, + preserving_proto_field_name=preserving_proto_field_name, + use_integers_for_enums=use_integers_for_enums, + float_precision=float_precision, + ) + else: + # The `including_default_value_fields` argument was removed from protobuf 5.x + # and replaced with `always_print_fields_with_no_presence` which very similar but has + # handles optional fields consistently by not affecting them. + # The old flag accidentally had inconsistent behavior between proto2 + # optional and proto3 optional fields. + return MessageToDict( + cls.pb(instance), + always_print_fields_with_no_presence=print_fields, + preserving_proto_field_name=preserving_proto_field_name, + use_integers_for_enums=use_integers_for_enums, + float_precision=float_precision, + ) + def copy_from(cls, instance, other): """Equivalent for protobuf.Message.CopyFrom diff --git a/proto/version.py b/proto/version.py index f11d15fb..dbdc76e0 100644 --- a/proto/version.py +++ b/proto/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. # -__version__ = "1.23.0" +__version__ = "1.24.0" diff --git a/pytest.ini b/pytest.ini index 985c2559..ba89114a 100644 --- a/pytest.ini +++ b/pytest.ini @@ -4,3 +4,5 @@ filterwarnings = error # Remove once https://github.com/protocolbuffers/protobuf/issues/12186 is fixed ignore:.*custom tp_new.*in Python 3.14:DeprecationWarning + # Remove once deprecated field `including_default_value_fields` is removed + ignore:.*The argument `including_default_value_fields` has been removed.*:DeprecationWarning diff --git a/setup.py b/setup.py index 796f0a91..e3a54892 100644 --- a/setup.py +++ b/setup.py @@ -42,13 +42,13 @@ long_description=README, platforms="Posix; MacOS X", include_package_data=True, - install_requires=("protobuf >= 3.19.0, <5.0.0dev",), + install_requires=("protobuf >=3.19.0, <6.0.0dev",), extras_require={ "testing": [ - "google-api-core[grpc] >= 1.31.5", + "google-api-core >= 1.31.5", ], }, - python_requires=">=3.6", + python_requires=">=3.7", classifiers=[ "Development Status :: 5 - Production/Stable", "Environment :: Console", @@ -56,7 +56,6 @@ "License :: OSI Approved :: Apache Software License", "Operating System :: POSIX", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", diff --git a/testing/constraints-3.13.txt b/testing/constraints-3.13.txt new file mode 100644 index 00000000..e69de29b diff --git a/testing/constraints-3.6.txt b/testing/constraints-3.6.txt deleted file mode 100644 index 849c8e7c..00000000 --- a/testing/constraints-3.6.txt +++ /dev/null @@ -1,9 +0,0 @@ -# This constraints file is used to check that lower bounds -# are correct in setup.py -# List *all* library dependencies and extras in this file. -# Pin the version to the lower bound. -# -# e.g., if setup.py has "foo >= 1.14.0, < 2.0.0dev", -# Then this file should have foo==1.14.0 -google-api-core==1.31.5 -protobuf==3.19.0 diff --git a/tests/conftest.py b/tests/conftest.py index 765326c6..9f2f5c95 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -41,9 +41,11 @@ def pytest_runtest_setup(item): item._mocks.append( mock.patch( - "google._upb._message.default_pool" - if has_upb() - else "google.protobuf.pyext._message.default_pool", + ( + "google._upb._message.default_pool" + if has_upb() + else "google.protobuf.pyext._message.default_pool" + ), pool, ) ) @@ -67,6 +69,14 @@ def pytest_runtest_setup(item): for name in dir(item.module): if name.endswith("_pb2") and not name.startswith("test_"): module = getattr(item.module, name) + + # Exclude `google.protobuf.descriptor_pb2` which causes error + # `RecursionError: maximum recursion depth exceeded while calling a Python object` + # when running the test suite and is not required for tests. + # See https://github.com/googleapis/proto-plus-python/issues/425 + if module.__package__ == "google.protobuf" and name == "descriptor_pb2": + continue + pool.AddSerializedFile(module.DESCRIPTOR.serialized_pb) fd = pool.FindFileByName(module.DESCRIPTOR.name) diff --git a/tests/test_json.py b/tests/test_json.py index e94e935a..ae3cf59e 100644 --- a/tests/test_json.py +++ b/tests/test_json.py @@ -121,10 +121,77 @@ class Squid(proto.Message): ) assert json1 == '{"name":"Steve"}' - json2 = Squid.to_json(s).replace(" ", "").replace("\n", "") - assert ( - json2 == '{"name":"Steve","massKg":0}' or json2 == '{"massKg":0,"name":"Steve"}' + json1 = ( + Squid.to_json(s, always_print_fields_with_no_presence=False) + .replace(" ", "") + .replace("\n", "") ) + assert json1 == '{"name":"Steve"}' + + json1 = ( + Squid.to_json( + s, + including_default_value_fields=False, + always_print_fields_with_no_presence=False, + ) + .replace(" ", "") + .replace("\n", "") + ) + assert json1 == '{"name":"Steve"}' + + with pytest.raises( + ValueError, + match="Arguments.*always_print_fields_with_no_presence.*including_default_value_fields.*must match", + ): + Squid.to_json( + s, + including_default_value_fields=True, + always_print_fields_with_no_presence=False, + ).replace(" ", "").replace("\n", "") + + with pytest.raises( + ValueError, + match="Arguments.*always_print_fields_with_no_presence.*including_default_value_fields.*must match", + ): + Squid.to_json( + s, + including_default_value_fields=False, + always_print_fields_with_no_presence=True, + ).replace(" ", "").replace("\n", "") + + json2 = ( + Squid.to_json( + s, + including_default_value_fields=True, + always_print_fields_with_no_presence=True, + ) + .replace(" ", "") + .replace("\n", "") + ) + assert json2 == '{"name":"Steve","massKg":0}' + + json2 = ( + Squid.to_json( + s, + including_default_value_fields=True, + ) + .replace(" ", "") + .replace("\n", "") + ) + assert json2 == '{"name":"Steve","massKg":0}' + + json2 = ( + Squid.to_json( + s, + always_print_fields_with_no_presence=True, + ) + .replace(" ", "") + .replace("\n", "") + ) + assert json2 == '{"name":"Steve","massKg":0}' + + json2 = Squid.to_json(s).replace(" ", "").replace("\n", "") + assert json2 == '{"name":"Steve","massKg":0}' s1 = Squid.from_json(json1) s2 = Squid.from_json(json2) diff --git a/tests/test_message.py b/tests/test_message.py index 983cde82..720995a9 100644 --- a/tests/test_message.py +++ b/tests/test_message.py @@ -267,8 +267,63 @@ class Color(proto.Enum): expected_dict = {"mass_kg": 20} assert s_dict_2 == expected_dict - new_s = Squid(s_dict) - assert new_s == s + s_dict_2 = Squid.to_dict(s_new_2, always_print_fields_with_no_presence=False) + expected_dict = {"mass_kg": 20} + assert s_dict_2 == expected_dict + + s_dict_2 = Squid.to_dict( + s_new_2, + including_default_value_fields=False, + always_print_fields_with_no_presence=False, + ) + expected_dict = {"mass_kg": 20} + assert s_dict_2 == expected_dict + + s_dict_2 = Squid.to_dict( + s_new_2, + including_default_value_fields=True, + ) + expected_dict = {"mass_kg": 20, "chromatophores": []} + assert s_dict_2 == expected_dict + + s_dict_2 = Squid.to_dict( + s_new_2, + always_print_fields_with_no_presence=True, + ) + expected_dict = {"mass_kg": 20, "chromatophores": []} + assert s_dict_2 == expected_dict + + s_dict_2 = Squid.to_dict( + s_new_2, + including_default_value_fields=True, + always_print_fields_with_no_presence=True, + ) + expected_dict = {"mass_kg": 20, "chromatophores": []} + assert s_dict_2 == expected_dict + + s_dict_2 = Squid.to_dict(s_new_2) + expected_dict = {"mass_kg": 20, "chromatophores": []} + assert s_dict_2 == expected_dict + + with pytest.raises( + ValueError, + match="Arguments.*always_print_fields_with_no_presence.*including_default_value_fields.*must match", + ): + s_dict_2 = Squid.to_dict( + s_new_2, + including_default_value_fields=True, + always_print_fields_with_no_presence=False, + ) + + with pytest.raises( + ValueError, + match="Arguments.*always_print_fields_with_no_presence.*including_default_value_fields.*must match", + ): + s_dict_2 = Squid.to_dict( + s_new_2, + including_default_value_fields=False, + always_print_fields_with_no_presence=True, + ) # TODO: https://github.com/googleapis/proto-plus-python/issues/390